summaryrefslogtreecommitdiffstats
path: root/keystone
diff options
context:
space:
mode:
Diffstat (limited to 'keystone')
-rw-r--r--keystone/assignment/backends/ldap.py2
-rw-r--r--keystone/assignment/core.py2
-rw-r--r--keystone/auth/controllers.py6
-rw-r--r--keystone/auth/core.py72
-rw-r--r--keystone/auth/plugins/oauth1.py80
-rw-r--r--keystone/auth/plugins/password.py6
-rw-r--r--keystone/auth/plugins/token.py11
-rw-r--r--keystone/catalog/backends/templated.py5
-rw-r--r--keystone/catalog/core.py2
-rw-r--r--keystone/clean.py20
-rw-r--r--keystone/cli.py4
-rw-r--r--keystone/common/cms.py2
-rw-r--r--keystone/common/config.py535
-rw-r--r--keystone/common/controller.py58
-rw-r--r--keystone/common/environment/__init__.py2
-rw-r--r--keystone/common/environment/eventlet_server.py13
-rw-r--r--keystone/common/ldap/core.py9
-rw-r--r--keystone/common/ldap/fakeldap.py20
-rw-r--r--keystone/common/openssl.py3
-rw-r--r--keystone/common/sql/core.py2
-rw-r--r--keystone/common/sql/legacy.py2
-rw-r--r--keystone/common/sql/migrate_repo/versions/032_username_length.py31
-rw-r--r--keystone/common/sql/nova.py2
-rw-r--r--keystone/common/utils.py3
-rw-r--r--keystone/common/wsgi.py19
-rw-r--r--keystone/config.py9
-rw-r--r--keystone/contrib/access/core.py3
-rw-r--r--keystone/contrib/oauth1/__init__.py17
-rw-r--r--keystone/contrib/oauth1/backends/__init__.py15
-rw-r--r--keystone/contrib/oauth1/backends/kvs.py222
-rw-r--r--keystone/contrib/oauth1/backends/sql.py284
-rw-r--r--keystone/contrib/oauth1/controllers.py377
-rw-r--r--keystone/contrib/oauth1/core.py272
-rw-r--r--keystone/contrib/oauth1/migrate_repo/__init__.py15
-rw-r--r--keystone/contrib/oauth1/migrate_repo/migrate.cfg25
-rw-r--r--keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py69
-rw-r--r--keystone/contrib/oauth1/migrate_repo/versions/__init__.py15
-rw-r--r--keystone/contrib/oauth1/routers.py129
-rw-r--r--keystone/contrib/stats/core.py2
-rw-r--r--keystone/contrib/user_crud/core.py4
-rw-r--r--keystone/controllers.py2
-rw-r--r--keystone/credential/core.py2
-rw-r--r--keystone/exception.py2
-rw-r--r--keystone/identity/backends/kvs.py3
-rw-r--r--keystone/identity/backends/ldap.py61
-rw-r--r--keystone/identity/backends/pam.py3
-rw-r--r--keystone/identity/backends/sql.py5
-rw-r--r--keystone/identity/controllers.py71
-rw-r--r--keystone/identity/core.py337
-rw-r--r--keystone/middleware/core.py3
-rw-r--r--keystone/middleware/s3_token.py2
-rw-r--r--keystone/policy/backends/rules.py2
-rw-r--r--keystone/service.py31
-rw-r--r--keystone/tests/_ldap_livetest.py11
-rw-r--r--keystone/tests/backend_multi_ldap_sql.conf35
-rw-r--r--keystone/tests/core.py16
-rw-r--r--keystone/tests/keystone.Default.conf14
-rw-r--r--keystone/tests/keystone.domain1.conf11
-rw-r--r--keystone/tests/keystone.domain2.conf13
-rw-r--r--keystone/tests/test_backend.py11
-rw-r--r--keystone/tests/test_backend_ldap.py330
-rw-r--r--keystone/tests/test_backend_sql.py2
-rw-r--r--keystone/tests/test_drivers.py5
-rw-r--r--keystone/tests/test_keystoneclient.py55
-rw-r--r--keystone/tests/test_overrides.conf3
-rw-r--r--keystone/tests/test_s3_token_middleware.py4
-rw-r--r--keystone/tests/test_sql_migrate_extensions.py63
-rw-r--r--keystone/tests/test_sql_upgrade.py40
-rw-r--r--keystone/tests/test_v3_auth.py61
-rw-r--r--keystone/tests/test_v3_oauth1.py574
-rw-r--r--keystone/token/backends/kvs.py30
-rw-r--r--keystone/token/backends/memcache.py14
-rw-r--r--keystone/token/backends/sql.py37
-rw-r--r--keystone/token/controllers.py2
-rw-r--r--keystone/token/core.py24
-rw-r--r--keystone/token/provider.py2
-rw-r--r--keystone/token/providers/pki.py2
-rw-r--r--keystone/token/providers/uuid.py31
-rw-r--r--keystone/trust/controllers.py2
-rw-r--r--keystone/trust/core.py2
80 files changed, 3711 insertions, 576 deletions
diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py
index 718d38c3..45ce6432 100644
--- a/keystone/assignment/backends/ldap.py
+++ b/keystone/assignment/backends/ldap.py
@@ -23,11 +23,11 @@ from keystone import assignment
from keystone import clean
from keystone.common import dependency
from keystone.common import ldap as common_ldap
-from keystone.common import logging
from keystone.common import models
from keystone import config
from keystone import exception
from keystone.identity.backends import ldap as ldap_identity
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py
index 0a2ee681..d78d3485 100644
--- a/keystone/assignment/core.py
+++ b/keystone/assignment/core.py
@@ -17,10 +17,10 @@
"""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
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py
index d1bd764f..9f6f1972 100644
--- a/keystone/auth/controllers.py
+++ b/keystone/auth/controllers.py
@@ -17,12 +17,12 @@
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
from keystone.openstack.common import importutils
+from keystone.openstack.common import log as logging
from keystone import token
from keystone import trust
@@ -285,6 +285,8 @@ class Auth(controller.V3Controller):
auth_info = AuthInfo(context, auth=auth)
auth_context = {'extras': {}, 'method_names': [], 'bind': {}}
self.authenticate(context, auth_info, auth_context)
+ if auth_context.get('access_token_id'):
+ auth_info.set_scope(None, auth_context['project_id'], None)
self._check_and_set_default_scoping(auth_info, auth_context)
(domain_id, project_id, trust) = auth_info.get_scope()
method_names = auth_info.get_method_names()
@@ -328,7 +330,7 @@ class Auth(controller.V3Controller):
def authenticate(self, context, auth_info, auth_context):
"""Authenticate user."""
- # user have been authenticated externally
+ # user has been authenticated externally
if 'REMOTE_USER' in context:
external = get_auth_method('external')
external.authenticate(context, auth_info, auth_context)
diff --git a/keystone/auth/core.py b/keystone/auth/core.py
index b7bdb7c6..26e7a470 100644
--- a/keystone/auth/core.py
+++ b/keystone/auth/core.py
@@ -35,46 +35,52 @@ class AuthMethodHandler(object):
by default. "method_names" is a list and "extras" is
a dictionary.
- If successful, plugin must set "user_id" in "auth_context".
- "method_name" is used to convey any additional authentication methods
- in case authentication is for re-scoping. For example,
- if the authentication is for re-scoping, plugin must append the
- previous method names into "method_names". Also, plugin may add
- any additional information into "extras". Anything in "extras"
- will be conveyed in the token's "extras" field. Here's an example of
- "auth_context" on successful authentication.
+ If successful, plugin must set ``user_id`` in ``auth_context``.
+ ``method_name`` is used to convey any additional authentication methods
+ in case authentication is for re-scoping. For example, if the
+ authentication is for re-scoping, plugin must append the previous
+ method names into ``method_names``. Also, plugin may add any additional
+ information into ``extras``. Anything in ``extras`` will be conveyed in
+ the token's ``extras`` attribute. Here's an example of ``auth_context``
+ on successful authentication::
- {"user_id": "abc123",
- "methods": ["password", "token"],
- "extras": {}}
+ {
+ "extras": {},
+ "methods": [
+ "password",
+ "token"
+ ],
+ "user_id": "abc123"
+ }
Plugins are invoked in the order in which they are specified in the
- "methods" attribute of the "identity" object.
- For example, with the following authentication request,
+ ``methods`` attribute of the ``identity`` object. For example,
+ ``custom-plugin`` is invoked before ``password``, which is invoked
+ before ``token`` in the following authentication request::
- {"auth": {
- "identity": {
- "methods": ["custom-plugin", "password", "token"],
- "token": {
- "id": "sdfafasdfsfasfasdfds"
- },
- "custom-plugin": {
- "custom-data": "sdfdfsfsfsdfsf"
- },
- "password": {
- "user": {
- "id": "s23sfad1",
- "password": "secrete"
+ {
+ "auth": {
+ "identity": {
+ "custom-plugin": {
+ "custom-data": "sdfdfsfsfsdfsf"
+ },
+ "methods": [
+ "custom-plugin",
+ "password",
+ "token"
+ ],
+ "password": {
+ "user": {
+ "id": "s23sfad1",
+ "password": "secrete"
+ }
+ },
+ "token": {
+ "id": "sdfafasdfsfasfasdfds"
+ }
}
}
}
- }}
-
- plugins will be invoked in this order:
-
- 1. custom-plugin
- 2. password
- 3. token
:returns: None if authentication is successful.
Authentication payload in the form of a dictionary for the
diff --git a/keystone/auth/plugins/oauth1.py b/keystone/auth/plugins/oauth1.py
new file mode 100644
index 00000000..ffebd365
--- /dev/null
+++ b/keystone/auth/plugins/oauth1.py
@@ -0,0 +1,80 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystone import auth
+from keystone.common import dependency
+from keystone.common import logging
+from keystone.contrib import oauth1
+from keystone.contrib.oauth1 import core as oauth
+from keystone import exception
+from keystone.openstack.common import timeutils
+
+
+METHOD_NAME = 'oauth1'
+LOG = logging.getLogger(__name__)
+
+
+@dependency.requires('oauth_api')
+class OAuth(auth.AuthMethodHandler):
+ def __init__(self):
+ self.oauth_api = oauth1.Manager()
+
+ def authenticate(self, context, auth_info, auth_context):
+ """Turn a signed request with an access key into a keystone token."""
+ headers = context['headers']
+ oauth_headers = oauth.get_oauth_headers(headers)
+ consumer_id = oauth_headers.get('oauth_consumer_key')
+ access_token_id = oauth_headers.get('oauth_token')
+
+ if not access_token_id:
+ raise exception.ValidationError(
+ attribute='oauth_token', target='request')
+
+ acc_token = self.oauth_api.get_access_token(access_token_id)
+ consumer = self.oauth_api._get_consumer(consumer_id)
+
+ expires_at = acc_token['expires_at']
+ if expires_at:
+ now = timeutils.utcnow()
+ expires = timeutils.normalize_time(
+ timeutils.parse_isotime(expires_at))
+ if now > expires:
+ raise exception.Unauthorized(_('Access token is expired'))
+
+ consumer_obj = oauth1.Consumer(key=consumer['id'],
+ secret=consumer['secret'])
+ acc_token_obj = oauth1.Token(key=acc_token['id'],
+ secret=acc_token['access_secret'])
+
+ url = oauth.rebuild_url(context['path'])
+ oauth_request = oauth1.Request.from_request(
+ http_method='POST',
+ http_url=url,
+ headers=context['headers'],
+ query_string=context['query_string'])
+ oauth_server = oauth1.Server()
+ oauth_server.add_signature_method(oauth1.SignatureMethod_HMAC_SHA1())
+ params = oauth_server.verify_request(oauth_request,
+ consumer_obj,
+ token=acc_token_obj)
+
+ if len(params) != 0:
+ msg = _('There should not be any non-oauth parameters')
+ raise exception.Unauthorized(message=msg)
+
+ auth_context['user_id'] = acc_token['authorizing_user_id']
+ auth_context['access_token_id'] = access_token_id
+ auth_context['project_id'] = acc_token['project_id']
diff --git a/keystone/auth/plugins/password.py b/keystone/auth/plugins/password.py
index f3cfeba8..b069f4d9 100644
--- a/keystone/auth/plugins/password.py
+++ b/keystone/auth/plugins/password.py
@@ -15,9 +15,9 @@
# under the License.
from keystone import auth
-from keystone.common import logging
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
METHOD_NAME = 'password'
@@ -94,6 +94,7 @@ class UserAuthInfo(object):
self._assert_user_is_enabled(user_ref)
self.user_ref = user_ref
self.user_id = user_ref['id']
+ self.domain_id = domain_ref['id']
class Password(auth.AuthMethodHandler):
@@ -106,7 +107,8 @@ class Password(auth.AuthMethodHandler):
try:
self.identity_api.authenticate(
user_id=user_info.user_id,
- password=user_info.password)
+ password=user_info.password,
+ domain_scope=user_info.domain_id)
except AssertionError:
# authentication failed because of invalid username or password
msg = _('Invalid username or password')
diff --git a/keystone/auth/plugins/token.py b/keystone/auth/plugins/token.py
index 720eccac..bc7cb1ba 100644
--- a/keystone/auth/plugins/token.py
+++ b/keystone/auth/plugins/token.py
@@ -15,9 +15,9 @@
# under the License.
from keystone import auth
-from keystone.common import logging
from keystone.common import wsgi
from keystone import exception
+from keystone.openstack.common import log as logging
from keystone import token
@@ -37,6 +37,12 @@ class Token(auth.AuthMethodHandler):
target=METHOD_NAME)
token_id = auth_payload['id']
token_ref = self.token_api.get_token(token_id)
+ if ('OS-TRUST:trust' in token_ref['token_data']['token'] or
+ 'trust' in token_ref['token_data']['token']):
+ raise exception.Forbidden()
+ if 'OS-OAUTH1' in token_ref['token_data']['token']:
+ raise exception.Forbidden()
+
wsgi.validate_token_bind(context, token_ref)
user_context.setdefault(
'user_id', token_ref['token_data']['token']['user']['id'])
@@ -48,9 +54,6 @@ class Token(auth.AuthMethodHandler):
token_ref['token_data']['token']['extras'])
user_context['method_names'].extend(
token_ref['token_data']['token']['methods'])
- 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)
raise exception.Unauthorized(e)
diff --git a/keystone/catalog/backends/templated.py b/keystone/catalog/backends/templated.py
index a96902d3..db99110b 100644
--- a/keystone/catalog/backends/templated.py
+++ b/keystone/catalog/backends/templated.py
@@ -18,16 +18,13 @@ import os.path
from keystone.catalog.backends import kvs
from keystone.catalog import core
-from keystone.common import logging
from keystone import config
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = config.CONF
-config.register_str('template_file',
- default='default_catalog.templates',
- group='catalog')
def parse_templates(template_lines):
diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py
index b8a081ac..61b7e8ac 100644
--- a/keystone/catalog/core.py
+++ b/keystone/catalog/core.py
@@ -18,10 +18,10 @@
"""Main entry point into the Catalog service."""
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/clean.py b/keystone/clean.py
index c1d01ec8..cb6c69c0 100644
--- a/keystone/clean.py
+++ b/keystone/clean.py
@@ -23,18 +23,23 @@ def check_length(property_name, value, min_length=1, max_length=64):
msg = _("%s cannot be empty.") % property_name
else:
msg = (_("%(property_name)s cannot be less than "
- "%(min_length)s characters.")) % locals()
+ "%(min_length)s characters.") % dict(
+ property_name=property_name, min_length=min_length))
raise exception.ValidationError(msg)
if len(value) > max_length:
msg = (_("%(property_name)s should not be greater than "
- "%(max_length)s characters.")) % locals()
+ "%(max_length)s characters.") % dict(
+ property_name=property_name, max_length=max_length))
+
raise exception.ValidationError(msg)
def check_type(property_name, value, expected_type, display_expected_type):
if not isinstance(value, expected_type):
- msg = _("%(property_name)s is not a "
- "%(display_expected_type)s") % locals()
+ msg = (_("%(property_name)s is not a "
+ "%(display_expected_type)s") % dict(
+ property_name=property_name,
+ display_expected_type=display_expected_type))
raise exception.ValidationError(msg)
@@ -44,10 +49,11 @@ def check_enabled(property_name, enabled):
return bool(enabled)
-def check_name(property_name, name):
+def check_name(property_name, name, min_length=1, max_length=64):
check_type('%s name' % property_name, name, basestring, 'str or unicode')
name = name.strip()
- check_length('%s name' % property_name, name)
+ check_length('%s name' % property_name, name,
+ min_length=min_length, max_length=max_length)
return name
@@ -64,7 +70,7 @@ def project_enabled(enabled):
def user_name(name):
- return check_name('User', name)
+ return check_name('User', name, max_length=255)
def user_enabled(enabled):
diff --git a/keystone/cli.py b/keystone/cli.py
index 18c095ce..6575f2e9 100644
--- a/keystone/cli.py
+++ b/keystone/cli.py
@@ -79,7 +79,7 @@ class DbSync(BaseApp):
package = importutils.import_module(package_name)
repo_path = os.path.abspath(os.path.dirname(package.__file__))
except ImportError:
- print _("This extension does not provide migrations.")
+ print(_("This extension does not provide migrations."))
exit(0)
try:
# Register the repo with the version control API
@@ -115,7 +115,7 @@ class DbVersion(BaseApp):
repo_path = os.path.abspath(os.path.dirname(package.__file__))
print(migration.db_version(repo_path))
except ImportError:
- print _("This extension does not provide migrations.")
+ print(_("This extension does not provide migrations."))
exit(1)
else:
print(migration.db_version())
diff --git a/keystone/common/cms.py b/keystone/common/cms.py
index 6ec740f8..09a98cdc 100644
--- a/keystone/common/cms.py
+++ b/keystone/common/cms.py
@@ -1,7 +1,7 @@
import hashlib
from keystone.common import environment
-from keystone.common import logging
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/config.py b/keystone/common/config.py
index 5a961d4a..34ab0988 100644
--- a/keystone/common/config.py
+++ b/keystone/common/config.py
@@ -24,6 +24,223 @@ _DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
_DEFAULT_AUTH_METHODS = ['external', 'password', 'token']
+FILE_OPTIONS = {
+ '': [
+ cfg.StrOpt('admin_token', secret=True, default='ADMIN'),
+ cfg.StrOpt('bind_host', default='0.0.0.0'),
+ cfg.IntOpt('compute_port', default=8774),
+ cfg.IntOpt('admin_port', default=35357),
+ cfg.IntOpt('public_port', default=5000),
+ cfg.StrOpt('public_endpoint',
+ default='http://localhost:%(public_port)s/'),
+ cfg.StrOpt('admin_endpoint',
+ default='http://localhost:%(admin_port)s/'),
+ cfg.StrOpt('onready'),
+ cfg.StrOpt('auth_admin_prefix', default=''),
+ cfg.StrOpt('policy_file', default='policy.json'),
+ cfg.StrOpt('policy_default_rule', default=None),
+ # default max request size is 112k
+ cfg.IntOpt('max_request_body_size', default=114688),
+ cfg.IntOpt('max_param_size', default=64),
+ # we allow tokens to be a bit larger to accommodate PKI
+ cfg.IntOpt('max_token_size', default=8192),
+ cfg.StrOpt('member_role_id',
+ default='9fe2ff9ee4384b1894a90878d3e92bab'),
+ cfg.StrOpt('member_role_name', default='_member_'),
+ cfg.IntOpt('crypt_strength', default=40000)],
+ 'identity': [
+ cfg.StrOpt('default_domain_id', default='default'),
+ cfg.BoolOpt('domain_specific_drivers_enabled',
+ default=False),
+ cfg.StrOpt('domain_config_dir',
+ default='/etc/keystone/domains'),
+ cfg.StrOpt('driver',
+ default=('keystone.identity.backends'
+ '.sql.Identity')),
+ cfg.IntOpt('max_password_length', default=4096)],
+ 'trust': [
+ cfg.BoolOpt('enabled', default=True),
+ cfg.StrOpt('driver',
+ default='keystone.trust.backends.sql.Trust')],
+ 'os_inherit': [
+ cfg.BoolOpt('enabled', default=False)],
+ 'token': [
+ cfg.ListOpt('bind', default=[]),
+ cfg.StrOpt('enforce_token_bind', default='permissive'),
+ cfg.IntOpt('expiration', default=86400),
+ cfg.StrOpt('provider', default=None),
+ cfg.StrOpt('driver',
+ default='keystone.token.backends.sql.Token')],
+ 'ssl': [
+ cfg.BoolOpt('enable', default=False),
+ cfg.StrOpt('certfile',
+ default="/etc/keystone/ssl/certs/keystone.pem"),
+ cfg.StrOpt('keyfile',
+ default="/etc/keystone/ssl/private/keystonekey.pem"),
+ cfg.StrOpt('ca_certs',
+ default="/etc/keystone/ssl/certs/ca.pem"),
+ cfg.StrOpt('ca_key',
+ default="/etc/keystone/ssl/certs/cakey.pem"),
+ cfg.BoolOpt('cert_required', default=False),
+ cfg.IntOpt('key_size', default=1024),
+ cfg.IntOpt('valid_days', default=3650),
+ cfg.StrOpt('ca_password', default=None),
+ cfg.StrOpt('cert_subject',
+ default='/C=US/ST=Unset/L=Unset/O=Unset/CN=localhost')],
+ 'signing': [
+ cfg.StrOpt('token_format', default=None),
+ cfg.StrOpt('certfile',
+ default="/etc/keystone/ssl/certs/signing_cert.pem"),
+ cfg.StrOpt('keyfile',
+ default="/etc/keystone/ssl/private/signing_key.pem"),
+ cfg.StrOpt('ca_certs',
+ default="/etc/keystone/ssl/certs/ca.pem"),
+ cfg.StrOpt('ca_key',
+ default="/etc/keystone/ssl/certs/cakey.pem"),
+ cfg.IntOpt('key_size', default=2048),
+ cfg.IntOpt('valid_days', default=3650),
+ cfg.StrOpt('ca_password', default=None),
+ cfg.StrOpt('cert_subject',
+ default=('/C=US/ST=Unset/L=Unset/O=Unset/'
+ 'CN=www.example.com'))],
+ 'sql': [
+ cfg.StrOpt('connection', secret=True,
+ default='sqlite:///keystone.db'),
+ cfg.IntOpt('idle_timeout', default=200)],
+ 'assignment': [
+ # assignment has no default for backward compatibility reasons.
+ # If assignment driver is not specified, the identity driver chooses
+ # the backend
+ cfg.StrOpt('driver', default=None)],
+ 'credential': [
+ cfg.StrOpt('driver',
+ default=('keystone.credential.backends'
+ '.sql.Credential'))],
+ 'oauth1': [
+ cfg.StrOpt('driver',
+ default='keystone.contrib.oauth1.backends.sql.OAuth1'),
+ cfg.IntOpt('request_token_duration', default=28800),
+ cfg.IntOpt('access_token_duration', default=86400)],
+ 'policy': [
+ cfg.StrOpt('driver',
+ default='keystone.policy.backends.sql.Policy')],
+ 'ec2': [
+ cfg.StrOpt('driver',
+ default='keystone.contrib.ec2.backends.kvs.Ec2')],
+ 'stats': [
+ cfg.StrOpt('driver',
+ default=('keystone.contrib.stats.backends'
+ '.kvs.Stats'))],
+ 'ldap': [
+ cfg.StrOpt('url', default='ldap://localhost'),
+ cfg.StrOpt('user', default=None),
+ cfg.StrOpt('password', secret=True, default=None),
+ cfg.StrOpt('suffix', default='cn=example,cn=com'),
+ cfg.BoolOpt('use_dumb_member', default=False),
+ cfg.StrOpt('dumb_member', default='cn=dumb,dc=nonexistent'),
+ cfg.BoolOpt('allow_subtree_delete', default=False),
+ cfg.StrOpt('query_scope', default='one'),
+ cfg.IntOpt('page_size', default=0),
+ cfg.StrOpt('alias_dereferencing', default='default'),
+
+ cfg.StrOpt('user_tree_dn', default=None),
+ cfg.StrOpt('user_filter', default=None),
+ cfg.StrOpt('user_objectclass', default='inetOrgPerson'),
+ cfg.StrOpt('user_id_attribute', default='cn'),
+ cfg.StrOpt('user_name_attribute', default='sn'),
+ cfg.StrOpt('user_mail_attribute', default='email'),
+ cfg.StrOpt('user_pass_attribute', default='userPassword'),
+ cfg.StrOpt('user_enabled_attribute', default='enabled'),
+ cfg.StrOpt('user_domain_id_attribute',
+ default='businessCategory'),
+ cfg.IntOpt('user_enabled_mask', default=0),
+ cfg.StrOpt('user_enabled_default', default='True'),
+ cfg.ListOpt('user_attribute_ignore',
+ default='tenant_id,tenants'),
+ cfg.BoolOpt('user_allow_create', default=True),
+ cfg.BoolOpt('user_allow_update', default=True),
+ cfg.BoolOpt('user_allow_delete', default=True),
+ cfg.BoolOpt('user_enabled_emulation', default=False),
+ cfg.StrOpt('user_enabled_emulation_dn', default=None),
+ cfg.ListOpt('user_additional_attribute_mapping',
+ default=None),
+
+ cfg.StrOpt('tenant_tree_dn', default=None),
+ cfg.StrOpt('tenant_filter', default=None),
+ cfg.StrOpt('tenant_objectclass', default='groupOfNames'),
+ cfg.StrOpt('tenant_id_attribute', default='cn'),
+ cfg.StrOpt('tenant_member_attribute', default='member'),
+ cfg.StrOpt('tenant_name_attribute', default='ou'),
+ cfg.StrOpt('tenant_desc_attribute', default='description'),
+ cfg.StrOpt('tenant_enabled_attribute', default='enabled'),
+ cfg.StrOpt('tenant_domain_id_attribute',
+ default='businessCategory'),
+ cfg.ListOpt('tenant_attribute_ignore', default=''),
+ cfg.BoolOpt('tenant_allow_create', default=True),
+ cfg.BoolOpt('tenant_allow_update', default=True),
+ cfg.BoolOpt('tenant_allow_delete', default=True),
+ cfg.BoolOpt('tenant_enabled_emulation', default=False),
+ cfg.StrOpt('tenant_enabled_emulation_dn', default=None),
+ cfg.ListOpt('tenant_additional_attribute_mapping',
+ default=None),
+
+ cfg.StrOpt('role_tree_dn', default=None),
+ cfg.StrOpt('role_filter', default=None),
+ cfg.StrOpt('role_objectclass', default='organizationalRole'),
+ cfg.StrOpt('role_id_attribute', default='cn'),
+ cfg.StrOpt('role_name_attribute', default='ou'),
+ cfg.StrOpt('role_member_attribute', default='roleOccupant'),
+ cfg.ListOpt('role_attribute_ignore', default=''),
+ cfg.BoolOpt('role_allow_create', default=True),
+ cfg.BoolOpt('role_allow_update', default=True),
+ cfg.BoolOpt('role_allow_delete', default=True),
+ cfg.ListOpt('role_additional_attribute_mapping',
+ default=None),
+
+ cfg.StrOpt('group_tree_dn', default=None),
+ cfg.StrOpt('group_filter', default=None),
+ cfg.StrOpt('group_objectclass', default='groupOfNames'),
+ cfg.StrOpt('group_id_attribute', default='cn'),
+ cfg.StrOpt('group_name_attribute', default='ou'),
+ cfg.StrOpt('group_member_attribute', default='member'),
+ cfg.StrOpt('group_desc_attribute', default='description'),
+ cfg.StrOpt('group_domain_id_attribute',
+ default='businessCategory'),
+ cfg.ListOpt('group_attribute_ignore', default=''),
+ cfg.BoolOpt('group_allow_create', default=True),
+ cfg.BoolOpt('group_allow_update', default=True),
+ cfg.BoolOpt('group_allow_delete', default=True),
+ cfg.ListOpt('group_additional_attribute_mapping',
+ default=None),
+
+ cfg.StrOpt('tls_cacertfile', default=None),
+ cfg.StrOpt('tls_cacertdir', default=None),
+ cfg.BoolOpt('use_tls', default=False),
+ cfg.StrOpt('tls_req_cert', default='demand')],
+ 'pam': [
+ cfg.StrOpt('userid', default=None),
+ cfg.StrOpt('password', default=None)],
+ 'auth': [
+ cfg.ListOpt('methods', default=_DEFAULT_AUTH_METHODS),
+ cfg.StrOpt('password',
+ default='keystone.auth.plugins.token.Token'),
+ cfg.StrOpt('token',
+ default='keystone.auth.plugins.password.Password'),
+ #deals with REMOTE_USER authentication
+ cfg.StrOpt('external',
+ default='keystone.auth.plugins.external.ExternalDefault')],
+ 'paste_deploy': [
+ cfg.StrOpt('config_file', default=None)],
+ 'memcache': [
+ cfg.StrOpt('servers', default='localhost:11211'),
+ cfg.IntOpt('max_compare_and_set_retry', default=16)],
+ 'catalog': [
+ cfg.StrOpt('template_file',
+ default='default_catalog.templates'),
+ cfg.StrOpt('driver',
+ default='keystone.catalog.backends.sql.Catalog')]}
+
+
CONF = cfg.CONF
@@ -40,297 +257,35 @@ def setup_logging(conf, product_name='keystone'):
logging.setup(product_name)
-def setup_authentication():
+def setup_authentication(conf=None):
# register any non-default auth methods here (used by extensions, etc)
- for method_name in CONF.auth.methods:
+ if conf is None:
+ conf = CONF
+ for method_name in conf.auth.methods:
if method_name not in _DEFAULT_AUTH_METHODS:
- register_str(method_name, group="auth")
-
-
-def register_str(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_opt(cfg.StrOpt(*args, **kw), group=group)
-
-
-def register_cli_str(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_cli_opt(cfg.StrOpt(*args, **kw), group=group)
-
-
-def register_list(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_opt(cfg.ListOpt(*args, **kw), group=group)
-
-
-def register_cli_list(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_cli_opt(cfg.ListOpt(*args, **kw), group=group)
-
-
-def register_bool(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_opt(cfg.BoolOpt(*args, **kw), group=group)
-
-
-def register_cli_bool(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_cli_opt(cfg.BoolOpt(*args, **kw), group=group)
-
-
-def register_int(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_opt(cfg.IntOpt(*args, **kw), group=group)
-
-
-def register_cli_int(*args, **kw):
- conf = kw.pop('conf', CONF)
- group = kw.pop('group', None)
- return conf.register_cli_opt(cfg.IntOpt(*args, **kw), group=group)
-
-
-def configure():
- register_cli_bool('standard-threads', default=False,
- help='Do not monkey-patch threading system modules.')
+ conf.register_opt(cfg.StrOpt(method_name), group='auth')
+
+
+def configure(conf=None):
+ if conf is None:
+ conf = CONF
+
+ conf.register_cli_opt(
+ cfg.BoolOpt('standard-threads', default=False,
+ help='Do not monkey-patch threading system modules.'))
+ conf.register_cli_opt(
+ cfg.StrOpt('pydev-debug-host', default=None,
+ help='Host to connect to for remote debugger.'))
+ conf.register_cli_opt(
+ cfg.IntOpt('pydev-debug-port', default=None,
+ help='Port to connect to for remote debugger.'))
+
+ for section in FILE_OPTIONS:
+ for option in FILE_OPTIONS[section]:
+ if section:
+ conf.register_opt(option, group=section)
+ else:
+ conf.register_opt(option)
- register_cli_str('pydev-debug-host', default=None,
- help='Host to connect to for remote debugger.')
- register_cli_int('pydev-debug-port', default=None,
- help='Port to connect to for remote debugger.')
-
- register_str('admin_token', secret=True, default='ADMIN')
- register_str('bind_host', default='0.0.0.0')
- register_int('compute_port', default=8774)
- register_int('admin_port', default=35357)
- register_int('public_port', default=5000)
- register_str(
- 'public_endpoint', default='http://localhost:%(public_port)s/')
- register_str('admin_endpoint', default='http://localhost:%(admin_port)s/')
- register_str('onready')
- register_str('auth_admin_prefix', default='')
- register_str('policy_file', default='policy.json')
- register_str('policy_default_rule', default=None)
- # default max request size is 112k
- register_int('max_request_body_size', default=114688)
- register_int('max_param_size', default=64)
- # we allow tokens to be a bit larger to accommodate PKI
- register_int('max_token_size', default=8192)
- register_str(
- 'member_role_id', default='9fe2ff9ee4384b1894a90878d3e92bab')
- register_str('member_role_name', default='_member_')
-
- # identity
- register_str('default_domain_id', group='identity', default='default')
- register_int('max_password_length', group='identity', default=4096)
-
- # 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',
- default="/etc/keystone/ssl/certs/keystone.pem")
- register_str('keyfile', group='ssl',
- default="/etc/keystone/ssl/private/keystonekey.pem")
- register_str('ca_certs', group='ssl',
- default="/etc/keystone/ssl/certs/ca.pem")
- register_str('ca_key', group='ssl',
- default="/etc/keystone/ssl/certs/cakey.pem")
- register_bool('cert_required', group='ssl', default=False)
- register_int('key_size', group='ssl', default=1024)
- register_int('valid_days', group='ssl', default=3650)
- register_str('ca_password', group='ssl', default=None)
- register_str('cert_subject', group='ssl',
- default='/C=US/ST=Unset/L=Unset/O=Unset/CN=localhost')
-
- # signing
- register_str(
- 'token_format', group='signing', default=None)
- register_str(
- 'certfile',
- group='signing',
- default="/etc/keystone/ssl/certs/signing_cert.pem")
- register_str(
- 'keyfile',
- group='signing',
- default="/etc/keystone/ssl/private/signing_key.pem")
- register_str(
- 'ca_certs',
- group='signing',
- default="/etc/keystone/ssl/certs/ca.pem")
- register_str('ca_key', group='signing',
- default="/etc/keystone/ssl/certs/cakey.pem")
- register_int('key_size', group='signing', default=2048)
- register_int('valid_days', group='signing', default=3650)
- register_str('ca_password', group='signing', default=None)
- register_str('cert_subject', group='signing',
- default='/C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com')
-
- # sql
- register_str('connection', group='sql', secret=True,
- 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',
- default='keystone.catalog.backends.sql.Catalog')
- register_str(
- 'driver',
- group='identity',
- default='keystone.identity.backends.sql.Identity')
- register_str(
- 'driver',
- group='credential',
- default='keystone.credential.backends.sql.Credential')
- register_str(
- 'driver',
- group='policy',
- default='keystone.policy.backends.sql.Policy')
- register_str(
- 'driver', group='token', default='keystone.token.backends.sql.Token')
- register_str(
- 'driver', group='trust', default='keystone.trust.backends.sql.Trust')
- register_str(
- 'driver', group='ec2', default='keystone.contrib.ec2.backends.kvs.Ec2')
- register_str(
- 'driver',
- group='stats',
- default='keystone.contrib.stats.backends.kvs.Stats')
-
- # ldap
- register_str('url', group='ldap', default='ldap://localhost')
- register_str('user', group='ldap', default=None)
- register_str('password', group='ldap', secret=True, default=None)
- register_str('suffix', group='ldap', default='cn=example,cn=com')
- register_bool('use_dumb_member', group='ldap', default=False)
- register_str('dumb_member', group='ldap', default='cn=dumb,dc=nonexistent')
- register_bool('allow_subtree_delete', group='ldap', default=False)
- register_str('query_scope', group='ldap', default='one')
- register_int('page_size', group='ldap', default=0)
- register_str('alias_dereferencing', group='ldap', default='default')
-
- register_str('user_tree_dn', group='ldap', default=None)
- register_str('user_filter', group='ldap', default=None)
- register_str('user_objectclass', group='ldap', default='inetOrgPerson')
- register_str('user_id_attribute', group='ldap', default='cn')
- register_str('user_name_attribute', group='ldap', default='sn')
- register_str('user_mail_attribute', group='ldap', default='email')
- register_str('user_pass_attribute', group='ldap', default='userPassword')
- register_str('user_enabled_attribute', group='ldap', default='enabled')
- register_str(
- 'user_domain_id_attribute', group='ldap', default='businessCategory')
- register_int('user_enabled_mask', group='ldap', default=0)
- register_str('user_enabled_default', group='ldap', default='True')
- register_list(
- 'user_attribute_ignore', group='ldap', default='tenant_id,tenants')
- register_bool('user_allow_create', group='ldap', default=True)
- register_bool('user_allow_update', group='ldap', default=True)
- register_bool('user_allow_delete', group='ldap', default=True)
- register_bool('user_enabled_emulation', group='ldap', default=False)
- register_str('user_enabled_emulation_dn', group='ldap', default=None)
- register_list(
- 'user_additional_attribute_mapping', group='ldap', default=None)
-
- register_str('tenant_tree_dn', group='ldap', default=None)
- register_str('tenant_filter', group='ldap', default=None)
- register_str('tenant_objectclass', group='ldap', default='groupOfNames')
- register_str('tenant_id_attribute', group='ldap', default='cn')
- register_str('tenant_member_attribute', group='ldap', default='member')
- register_str('tenant_name_attribute', group='ldap', default='ou')
- register_str('tenant_desc_attribute', group='ldap', default='description')
- register_str('tenant_enabled_attribute', group='ldap', default='enabled')
- register_str(
- 'tenant_domain_id_attribute', group='ldap', default='businessCategory')
- register_list('tenant_attribute_ignore', group='ldap', default='')
- register_bool('tenant_allow_create', group='ldap', default=True)
- register_bool('tenant_allow_update', group='ldap', default=True)
- register_bool('tenant_allow_delete', group='ldap', default=True)
- register_bool('tenant_enabled_emulation', group='ldap', default=False)
- register_str('tenant_enabled_emulation_dn', group='ldap', default=None)
- register_list(
- 'tenant_additional_attribute_mapping', group='ldap', default=None)
-
- register_str('role_tree_dn', group='ldap', default=None)
- register_str('role_filter', group='ldap', default=None)
- register_str(
- 'role_objectclass', group='ldap', default='organizationalRole')
- register_str('role_id_attribute', group='ldap', default='cn')
- register_str('role_name_attribute', group='ldap', default='ou')
- register_str('role_member_attribute', group='ldap', default='roleOccupant')
- register_list('role_attribute_ignore', group='ldap', default='')
- register_bool('role_allow_create', group='ldap', default=True)
- register_bool('role_allow_update', group='ldap', default=True)
- register_bool('role_allow_delete', group='ldap', default=True)
- register_list(
- 'role_additional_attribute_mapping', group='ldap', default=None)
-
- register_str('group_tree_dn', group='ldap', default=None)
- register_str('group_filter', group='ldap', default=None)
- register_str('group_objectclass', group='ldap', default='groupOfNames')
- register_str('group_id_attribute', group='ldap', default='cn')
- register_str('group_name_attribute', group='ldap', default='ou')
- register_str('group_member_attribute', group='ldap', default='member')
- register_str('group_desc_attribute', group='ldap', default='description')
- register_str(
- 'group_domain_id_attribute', group='ldap', default='businessCategory')
- register_list('group_attribute_ignore', group='ldap', default='')
- register_bool('group_allow_create', group='ldap', default=True)
- register_bool('group_allow_update', group='ldap', default=True)
- register_bool('group_allow_delete', group='ldap', default=True)
- register_list(
- 'group_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)
- register_str('tls_req_cert', group='ldap', default='demand')
-
- # pam
- register_str('userid', group='pam', default=None)
- register_str('password', group='pam', default=None)
-
- # default authentication methods
- register_list('methods', group='auth', default=_DEFAULT_AUTH_METHODS)
- register_str(
- 'password', group='auth', default='keystone.auth.plugins.token.Token')
- 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:
- register_str(method_name, group='auth')
-
- # PasteDeploy config file
- register_str('config_file', group='paste_deploy', default=None)
-
- # token provider
- register_str(
- 'provider',
- group='token',
- default=None)
+ setup_authentication(conf)
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
index affc34de..90818fb4 100644
--- a/keystone/common/controller.py
+++ b/keystone/common/controller.py
@@ -3,11 +3,10 @@ import functools
import uuid
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.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = config.CONF
@@ -169,6 +168,10 @@ class V2Controller(wsgi.Application):
self._delete_tokens_for_trust(trust['trustee_user_id'],
trust['id'])
+ def _delete_tokens_for_project(self, project_id):
+ for user_ref in self.identity_api.get_project_users(project_id):
+ self._delete_tokens_for_user(user_ref['id'], project_id=project_id)
+
def _require_attribute(self, ref, attr):
"""Ensures the reference contains the specified attribute."""
if ref.get(attr) is None or ref.get(attr) == '':
@@ -300,34 +303,35 @@ class V3Controller(V2Controller):
ref['id'] = uuid.uuid4().hex
return ref
+ def _get_domain_id_for_request(self, context):
+ """Get the domain_id for a v3 call."""
+
+ if context['is_admin']:
+ return DEFAULT_DOMAIN_ID
+
+ # Fish the domain_id out of the token
+ #
+ # We could make this more efficient by loading the domain_id
+ # into the context in the wrapper function above (since
+ # this version of normalize_domain will only be called inside
+ # a v3 protected call). However, this optimization is probably not
+ # worth the duplication of state
+ try:
+ token_ref = self.token_api.get_token(
+ token_id=context['token_id'])
+ except exception.TokenNotFound:
+ LOG.warning(_('Invalid token in _get_domain_id_for_request'))
+ raise exception.Unauthorized()
+
+ if 'domain' in token_ref:
+ return token_ref['domain']['id']
+ else:
+ return DEFAULT_DOMAIN_ID
+
def _normalize_domain_id(self, context, ref):
"""Fill in domain_id if not specified in a v3 call."""
-
if 'domain_id' not in ref:
- if context['is_admin']:
- ref['domain_id'] = DEFAULT_DOMAIN_ID
- else:
- # Fish the domain_id out of the token
- #
- # We could make this more efficient by loading the domain_id
- # into the context in the wrapper function above (since
- # this version of normalize_domain will only be called inside
- # a v3 protected call). However, given that we only use this
- # for creating entities, this optimization is probably not
- # worth the duplication of state
- try:
- token_ref = self.token_api.get_token(
- token_id=context['token_id'])
- except exception.TokenNotFound:
- LOG.warning(_('Invalid token in normalize_domain_id'))
- raise exception.Unauthorized()
-
- if 'domain' in token_ref:
- ref['domain_id'] = token_ref['domain']['id']
- else:
- # FIXME(henry-nash) Revisit this once v3 token scoping
- # across domains has been hashed out
- ref['domain_id'] = DEFAULT_DOMAIN_ID
+ ref['domain_id'] = self._get_domain_id_for_request(context)
return ref
def _filter_domain_id(self, ref):
diff --git a/keystone/common/environment/__init__.py b/keystone/common/environment/__init__.py
index 2993536a..7ec82002 100644
--- a/keystone/common/environment/__init__.py
+++ b/keystone/common/environment/__init__.py
@@ -2,7 +2,7 @@ import functools
import os
from keystone.common import config
-from keystone.common import logging
+from keystone.openstack.common import log as logging
CONF = config.CONF
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/environment/eventlet_server.py b/keystone/common/environment/eventlet_server.py
index 18987d26..874c4831 100644
--- a/keystone/common/environment/eventlet_server.py
+++ b/keystone/common/environment/eventlet_server.py
@@ -26,8 +26,7 @@ import eventlet
import eventlet.wsgi
import greenlet
-from keystone.common import logging
-from keystone.common import wsgi
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
@@ -48,10 +47,10 @@ class Server(object):
def start(self, key=None, backlog=128):
"""Run a WSGI server with the given application."""
- LOG.debug(_('Starting %(arg0)s on %(host)s:%(port)s') %
- {'arg0': sys.argv[0],
- 'host': self.host,
- 'port': self.port})
+ LOG.info(_('Starting %(arg0)s on %(host)s:%(port)s') %
+ {'arg0': sys.argv[0],
+ 'host': self.host,
+ 'port': self.port})
# TODO(dims): eventlet's green dns/socket module does not actually
# support IPv6 in getaddrinfo(). We need to get around this in the
@@ -108,7 +107,7 @@ class Server(object):
log = logging.getLogger('eventlet.wsgi.server')
try:
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
- log=wsgi.WritableLogger(log))
+ log=logging.WritableLogger(log))
except Exception:
LOG.exception(_('Server error'))
raise
diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py
index 39ea78de..48e4121f 100644
--- a/keystone/common/ldap/core.py
+++ b/keystone/common/ldap/core.py
@@ -20,9 +20,8 @@ import ldap
from ldap import filter as ldap_filter
from keystone.common.ldap import fakeldap
-from keystone.common import logging
from keystone import exception
-
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
@@ -509,7 +508,7 @@ class LdapWrapper(object):
def add_s(self, dn, attrs):
ldap_attrs = [(kind, [py2ldap(x) for x in safe_iter(values)])
for kind, values in attrs]
- if LOG.isEnabledFor(logging.DEBUG):
+ if LOG.isEnabledFor(LOG.debug):
sane_attrs = [(kind, values
if kind != 'userPassword'
else ['****'])
@@ -519,7 +518,7 @@ class LdapWrapper(object):
return self.conn.add_s(dn, ldap_attrs)
def search_s(self, dn, scope, query, attrlist=None):
- if LOG.isEnabledFor(logging.DEBUG):
+ if LOG.isEnabledFor(LOG.debug):
LOG.debug(_(
'LDAP search: dn=%(dn)s, scope=%(scope)s, query=%(query)s, '
'attrs=%(attrlist)s') % {
@@ -586,7 +585,7 @@ class LdapWrapper(object):
else [py2ldap(x) for x in safe_iter(values)]))
for op, kind, values in modlist]
- if LOG.isEnabledFor(logging.DEBUG):
+ if LOG.isEnabledFor(LOG.debug):
sane_modlist = [(op, kind, (values if kind != 'userPassword'
else ['****']))
for op, kind, values in ldap_modlist]
diff --git a/keystone/common/ldap/fakeldap.py b/keystone/common/ldap/fakeldap.py
index f6c95895..e4458874 100644
--- a/keystone/common/ldap/fakeldap.py
+++ b/keystone/common/ldap/fakeldap.py
@@ -29,8 +29,8 @@ import shelve
import ldap
-from keystone.common import logging
from keystone.common import utils
+from keystone.openstack.common import log as logging
SCOPE_NAMES = {
@@ -41,8 +41,6 @@ SCOPE_NAMES = {
LOG = logging.getLogger(__name__)
-#Only enable a lower level than WARN if you are actively debugging
-LOG.level = logging.WARN
def _match_query(query, attrs):
@@ -125,18 +123,14 @@ server_fail = False
class FakeShelve(dict):
- @classmethod
- def get_instance(cls):
- try:
- return cls.__instance
- except AttributeError:
- cls.__instance = cls()
- return cls.__instance
def sync(self):
pass
+FakeShelves = {}
+
+
class FakeLdap(object):
"""Fake LDAP connection."""
@@ -144,8 +138,10 @@ class FakeLdap(object):
def __init__(self, url):
LOG.debug(_('FakeLdap initialize url=%s'), url)
- if url == 'fake://memory':
- self.db = FakeShelve.get_instance()
+ if url.startswith('fake://memory'):
+ if url not in FakeShelves:
+ FakeShelves[url] = FakeShelve()
+ self.db = FakeShelves[url]
else:
self.db = shelve.open(url[7:])
diff --git a/keystone/common/openssl.py b/keystone/common/openssl.py
index 90484505..280815ae 100644
--- a/keystone/common/openssl.py
+++ b/keystone/common/openssl.py
@@ -19,9 +19,8 @@ import os
import stat
from keystone.common import environment
-from keystone.common import logging
from keystone import config
-
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = config.CONF
diff --git a/keystone/common/sql/core.py b/keystone/common/sql/core.py
index 67863588..fdb45c74 100644
--- a/keystone/common/sql/core.py
+++ b/keystone/common/sql/core.py
@@ -26,10 +26,10 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
import sqlalchemy.pool
from sqlalchemy import types as sql_types
-from keystone.common import logging
from keystone import config
from keystone import exception
from keystone.openstack.common import jsonutils
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/sql/legacy.py b/keystone/common/sql/legacy.py
index c8adc900..d88e5a46 100644
--- a/keystone/common/sql/legacy.py
+++ b/keystone/common/sql/legacy.py
@@ -21,10 +21,10 @@ 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
from keystone.identity.backends import sql as identity_sql
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/sql/migrate_repo/versions/032_username_length.py b/keystone/common/sql/migrate_repo/versions/032_username_length.py
new file mode 100644
index 00000000..636ebd75
--- /dev/null
+++ b/keystone/common/sql/migrate_repo/versions/032_username_length.py
@@ -0,0 +1,31 @@
+import sqlalchemy as sql
+from sqlalchemy.orm import sessionmaker
+
+
+def upgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+ user_table = sql.Table('user', meta, autoload=True)
+ user_table.c.name.alter(type=sql.String(255))
+
+
+def downgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+ user_table = sql.Table('user', meta, autoload=True)
+ if migrate_engine.name != 'mysql':
+ # NOTE(aloga): sqlite does not enforce length on the
+ # VARCHAR types: http://www.sqlite.org/faq.html#q9
+ # postgresql and DB2 do not truncate.
+ maker = sessionmaker(bind=migrate_engine)
+ session = maker()
+ for user in session.query(user_table).all():
+ values = {'name': user.name[:64]}
+ update = (user_table.update().
+ where(user_table.c.id == user.id).
+ values(values))
+ migrate_engine.execute(update)
+
+ session.commit()
+ session.close()
+ user_table.c.name.alter(type=sql.String(64))
diff --git a/keystone/common/sql/nova.py b/keystone/common/sql/nova.py
index fd8d2481..c7abfb81 100644
--- a/keystone/common/sql/nova.py
+++ b/keystone/common/sql/nova.py
@@ -19,10 +19,10 @@
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 import identity
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/utils.py b/keystone/common/utils.py
index 9966ee67..27968efc 100644
--- a/keystone/common/utils.py
+++ b/keystone/common/utils.py
@@ -27,12 +27,11 @@ import passlib.hash
from keystone.common import config
from keystone.common import environment
-from keystone.common import logging
from keystone import exception
+from keystone.openstack.common import log as logging
CONF = config.CONF
-config.register_int('crypt_strength', default=40000)
LOG = logging.getLogger(__name__)
diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py
index ae199d74..646bb4c4 100644
--- a/keystone/common/wsgi.py
+++ b/keystone/common/wsgi.py
@@ -27,12 +27,12 @@ import webob.dec
import webob.exc
from keystone.common import config
-from keystone.common import logging
from keystone.common import utils
from keystone import exception
from keystone.openstack.common import gettextutils
from keystone.openstack.common import importutils
from keystone.openstack.common import jsonutils
+from keystone.openstack.common import log as logging
CONF = config.CONF
@@ -123,17 +123,6 @@ def validate_token_bind(context, token_ref):
raise exception.Unauthorized()
-class WritableLogger(object):
- """A thin wrapper that responds to `write` and logs."""
-
- def __init__(self, logger, level=logging.DEBUG):
- self.logger = logger
- self.level = level
-
- def write(self, msg):
- self.logger.log(self.level, msg)
-
-
class Request(webob.Request):
def best_match_language(self):
"""Determines the best available locale from the Accept-Language
@@ -407,7 +396,7 @@ class Debug(Middleware):
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- if LOG.isEnabledFor(logging.DEBUG):
+ if LOG.isEnabledFor(LOG.debug):
LOG.debug('%s %s %s', ('*' * 20), 'REQUEST ENVIRON', ('*' * 20))
for key, value in req.environ.items():
LOG.debug('%s = %s', key, mask_password(value,
@@ -419,7 +408,7 @@ class Debug(Middleware):
LOG.debug('')
resp = req.get_response(self.application)
- if LOG.isEnabledFor(logging.DEBUG):
+ if LOG.isEnabledFor(LOG.debug):
LOG.debug('%s %s %s', ('*' * 20), 'RESPONSE HEADERS', ('*' * 20))
for (key, value) in resp.headers.iteritems():
LOG.debug('%s = %s', key, value)
@@ -468,7 +457,7 @@ class Router(object):
# if we're only running in debug, bump routes' internal logging up a
# notch, as it's very spammy
if CONF.debug:
- logging.getLogger('routes.middleware').setLevel(logging.INFO)
+ logging.getLogger('routes.middleware')
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
diff --git a/keystone/config.py b/keystone/config.py
index 28f1cf2c..c4a43b47 100644
--- a/keystone/config.py
+++ b/keystone/config.py
@@ -25,15 +25,8 @@ config.configure()
CONF = config.CONF
setup_logging = config.setup_logging
-register_str = config.register_str
-register_cli_str = config.register_cli_str
-register_list = config.register_list
-register_cli_list = config.register_cli_list
-register_bool = config.register_bool
-register_cli_bool = config.register_cli_bool
-register_int = config.register_int
-register_cli_int = config.register_cli_int
setup_authentication = config.setup_authentication
+configure = config.configure
def find_paste_config():
diff --git a/keystone/contrib/access/core.py b/keystone/contrib/access/core.py
index f0221200..fbe09a24 100644
--- a/keystone/contrib/access/core.py
+++ b/keystone/contrib/access/core.py
@@ -14,12 +14,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import webob
import webob.dec
-from keystone.common import logging
from keystone.common import wsgi
from keystone import config
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
diff --git a/keystone/contrib/oauth1/__init__.py b/keystone/contrib/oauth1/__init__.py
new file mode 100644
index 00000000..fdb8dc4b
--- /dev/null
+++ b/keystone/contrib/oauth1/__init__.py
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystone.contrib.oauth1.core import * # flake8: noqa
diff --git a/keystone/contrib/oauth1/backends/__init__.py b/keystone/contrib/oauth1/backends/__init__.py
new file mode 100644
index 00000000..3f393b26
--- /dev/null
+++ b/keystone/contrib/oauth1/backends/__init__.py
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
diff --git a/keystone/contrib/oauth1/backends/kvs.py b/keystone/contrib/oauth1/backends/kvs.py
new file mode 100644
index 00000000..09e31741
--- /dev/null
+++ b/keystone/contrib/oauth1/backends/kvs.py
@@ -0,0 +1,222 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+import random
+import uuid
+
+from keystone.common import kvs
+from keystone.common import logging
+from keystone.contrib.oauth1 import core
+from keystone import exception
+from keystone.openstack.common import timeutils
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OAuth1(kvs.Base):
+ """kvs backend for oauth is deprecated.
+ Deprecated in Havana and will be removed in Icehouse, as this backend
+ is not production grade.
+ """
+
+ def __init__(self, *args, **kw):
+ super(OAuth1, self).__init__(*args, **kw)
+ LOG.warn(_("kvs token backend is DEPRECATED. Use "
+ "keystone.contrib.oauth1.sql instead."))
+
+ def _get_consumer(self, consumer_id):
+ return self.db.get('consumer-%s' % consumer_id)
+
+ def get_consumer(self, consumer_id):
+ consumer_ref = self.db.get('consumer-%s' % consumer_id)
+ return core.filter_consumer(consumer_ref)
+
+ def create_consumer(self, consumer):
+ consumer_id = consumer['id']
+ consumer['secret'] = uuid.uuid4().hex
+ if not consumer.get('description'):
+ consumer['description'] = None
+ self.db.set('consumer-%s' % consumer_id, consumer)
+ consumer_list = set(self.db.get('consumer_list', []))
+ consumer_list.add(consumer_id)
+ self.db.set('consumer_list', list(consumer_list))
+ return consumer
+
+ def _delete_consumer(self, consumer_id):
+ # call get to make sure it exists
+ self.db.get('consumer-%s' % consumer_id)
+ self.db.delete('consumer-%s' % consumer_id)
+ consumer_list = set(self.db.get('consumer_list', []))
+ consumer_list.remove(consumer_id)
+ self.db.set('consumer_list', list(consumer_list))
+
+ def _delete_request_tokens(self, consumer_id):
+ consumer_requests = set(self.db.get('consumer-%s-requests' %
+ consumer_id, []))
+ for token in consumer_requests:
+ self.db.get('request_token-%s' % token)
+ self.db.delete('request_token-%s' % token)
+
+ if len(consumer_requests) > 0:
+ self.db.delete('consumer-%s-requests' % consumer_id)
+
+ def _delete_access_tokens(self, consumer_id):
+ consumer_accesses = set(self.db.get('consumer-%s-accesses' %
+ consumer_id, []))
+ for token in consumer_accesses:
+ access_token = self.db.get('access_token-%s' % token)
+ self.db.delete('access_token-%s' % token)
+
+ # kind of a hack, but I needed to update the auth_list
+ user_id = access_token['authorizing_user_id']
+ user_auth_list = set(self.db.get('auth_list-%s' % user_id, []))
+ user_auth_list.remove(token)
+ self.db.set('auth_list-%s' % user_id, list(user_auth_list))
+
+ if len(consumer_accesses) > 0:
+ self.db.delete('consumer-%s-accesses' % consumer_id)
+
+ def delete_consumer(self, consumer_id):
+ self._delete_consumer(consumer_id)
+ self._delete_request_tokens(consumer_id)
+ self._delete_access_tokens(consumer_id)
+
+ def list_consumers(self):
+ consumer_ids = self.db.get('consumer_list', [])
+ return [self.get_consumer(x) for x in consumer_ids]
+
+ def update_consumer(self, consumer_id, consumer):
+ # call get to make sure it exists
+ old_consumer_ref = self.db.get('consumer-%s' % consumer_id)
+ new_consumer_ref = old_consumer_ref.copy()
+ new_consumer_ref['description'] = consumer['description']
+ new_consumer_ref['id'] = consumer_id
+ self.db.set('consumer-%s' % consumer_id, new_consumer_ref)
+ return new_consumer_ref
+
+ def create_request_token(self, consumer_id, roles,
+ project_id, token_duration):
+ expiry_date = None
+ if token_duration:
+ now = timeutils.utcnow()
+ future = now + datetime.timedelta(seconds=token_duration)
+ expiry_date = timeutils.isotime(future, subsecond=True)
+
+ ref = {}
+ request_token_id = uuid.uuid4().hex
+ ref['id'] = request_token_id
+ ref['request_secret'] = uuid.uuid4().hex
+ ref['verifier'] = None
+ ref['authorizing_user_id'] = None
+ ref['requested_project_id'] = project_id
+ ref['requested_roles'] = roles
+ ref['consumer_id'] = consumer_id
+ ref['expires_at'] = expiry_date
+ self.db.set('request_token-%s' % request_token_id, ref)
+
+ # add req token to the list that containers the consumers req tokens
+ consumer_requests = set(self.db.get('consumer-%s-requests' %
+ consumer_id, []))
+ consumer_requests.add(request_token_id)
+ self.db.set('consumer-%s-requests' %
+ consumer_id, list(consumer_requests))
+ return ref
+
+ def get_request_token(self, request_token_id):
+ return self.db.get('request_token-%s' % request_token_id)
+
+ def authorize_request_token(self, request_token_id, user_id):
+ request_token = self.db.get('request_token-%s' % request_token_id)
+ request_token['authorizing_user_id'] = user_id
+ request_token['verifier'] = str(random.randint(1000, 9999))
+ self.db.set('request_token-%s' % request_token_id, request_token)
+ return request_token
+
+ def create_access_token(self, request_id, token_duration):
+ request_token = self.db.get('request_token-%s' % request_id)
+
+ expiry_date = None
+ if token_duration:
+ now = timeutils.utcnow()
+ future = now + datetime.timedelta(seconds=token_duration)
+ expiry_date = timeutils.isotime(future, subsecond=True)
+
+ ref = {}
+ access_token_id = uuid.uuid4().hex
+ ref['id'] = access_token_id
+ ref['access_secret'] = uuid.uuid4().hex
+ ref['authorizing_user_id'] = request_token['authorizing_user_id']
+ ref['project_id'] = request_token['requested_project_id']
+ ref['requested_roles'] = request_token['requested_roles']
+ ref['consumer_id'] = request_token['consumer_id']
+ ref['expires_at'] = expiry_date
+ self.db.set('access_token-%s' % access_token_id, ref)
+
+ #add access token id to user authorizations list too
+ user_id = request_token['authorizing_user_id']
+ user_auth_list = set(self.db.get('auth_list-%s' % user_id, []))
+ user_auth_list.add(access_token_id)
+ self.db.set('auth_list-%s' % user_id, list(user_auth_list))
+
+ #delete request token from table, it has been exchanged
+ self.db.get('request_token-%s' % request_id)
+ self.db.delete('request_token-%s' % request_id)
+
+ #add access token to the list that containers the consumers acc tokens
+ consumer_id = request_token['consumer_id']
+ consumer_accesses = set(self.db.get('consumer-%s-accesses' %
+ consumer_id, []))
+ consumer_accesses.add(access_token_id)
+ self.db.set('consumer-%s-accesses' %
+ consumer_id, list(consumer_accesses))
+
+ # remove the used up request token id from consumer req list
+ consumer_requests = set(self.db.get('consumer-%s-requests' %
+ consumer_id, []))
+ consumer_requests.remove(request_id)
+ self.db.set('consumer-%s-requests' %
+ consumer_id, list(consumer_requests))
+
+ return ref
+
+ def get_access_token(self, access_token_id):
+ return self.db.get('access_token-%s' % access_token_id)
+
+ def list_access_tokens(self, user_id):
+ user_auth_list = self.db.get('auth_list-%s' % user_id, [])
+ return [self.get_access_token(x) for x in user_auth_list]
+
+ def delete_access_token(self, user_id, access_token_id):
+ access_token = self.get_access_token(access_token_id)
+ consumer_id = access_token['consumer_id']
+ if access_token['authorizing_user_id'] != user_id:
+ raise exception.Unauthorized(_('User IDs do not match'))
+ self.db.get('access_token-%s' % access_token_id)
+ self.db.delete('access_token-%s' % access_token_id)
+
+ # remove access token id from user authz list
+ user_auth_list = set(self.db.get('auth_list-%s' % user_id, []))
+ user_auth_list.remove(access_token_id)
+ self.db.set('auth_list-%s' % user_id, list(user_auth_list))
+
+ # remove this token id from the consumer access list
+ consumer_accesses = set(self.db.get('consumer-%s-accesses' %
+ consumer_id, []))
+ consumer_accesses.remove(access_token_id)
+ self.db.set('consumer-%s-accesses' %
+ consumer_id, list(consumer_accesses))
diff --git a/keystone/contrib/oauth1/backends/sql.py b/keystone/contrib/oauth1/backends/sql.py
new file mode 100644
index 00000000..9dc0665c
--- /dev/null
+++ b/keystone/contrib/oauth1/backends/sql.py
@@ -0,0 +1,284 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+import random
+import uuid
+
+from keystone.common import sql
+from keystone.common.sql import migration
+from keystone.contrib.oauth1 import core
+from keystone import exception
+from keystone.openstack.common import timeutils
+
+
+class Consumer(sql.ModelBase, sql.DictBase):
+ __tablename__ = 'consumer'
+ attributes = ['id', 'description', 'secret']
+ id = sql.Column(sql.String(64), primary_key=True, nullable=False)
+ description = sql.Column(sql.String(64), nullable=False)
+ secret = sql.Column(sql.String(64), nullable=False)
+ extra = sql.Column(sql.JsonBlob(), nullable=False)
+
+
+class RequestToken(sql.ModelBase, sql.DictBase):
+ __tablename__ = 'request_token'
+ attributes = ['id', 'request_secret',
+ 'verifier', 'authorizing_user_id', 'requested_project_id',
+ 'requested_roles', 'consumer_id', 'expires_at']
+ id = sql.Column(sql.String(64), primary_key=True, nullable=False)
+ request_secret = sql.Column(sql.String(64), nullable=False)
+ verifier = sql.Column(sql.String(64), nullable=True)
+ authorizing_user_id = sql.Column(sql.String(64), nullable=True)
+ requested_project_id = sql.Column(sql.String(64), nullable=False)
+ requested_roles = sql.Column(sql.Text(), nullable=False)
+ consumer_id = sql.Column(sql.String(64), nullable=False, index=True)
+ expires_at = sql.Column(sql.String(64), nullable=True)
+
+ @classmethod
+ def from_dict(cls, user_dict):
+ return cls(**user_dict)
+
+ def to_dict(self):
+ return dict(self.iteritems())
+
+
+class AccessToken(sql.ModelBase, sql.DictBase):
+ __tablename__ = 'access_token'
+ attributes = ['id', 'access_secret', 'authorizing_user_id',
+ 'project_id', 'requested_roles', 'consumer_id',
+ 'expires_at']
+ id = sql.Column(sql.String(64), primary_key=True, nullable=False)
+ access_secret = sql.Column(sql.String(64), nullable=False)
+ authorizing_user_id = sql.Column(sql.String(64), nullable=False,
+ index=True)
+ project_id = sql.Column(sql.String(64), nullable=False)
+ requested_roles = sql.Column(sql.Text(), nullable=False)
+ consumer_id = sql.Column(sql.String(64), nullable=False)
+ expires_at = sql.Column(sql.String(64), nullable=True)
+
+ @classmethod
+ def from_dict(cls, user_dict):
+ return cls(**user_dict)
+
+ def to_dict(self):
+ return dict(self.iteritems())
+
+
+class OAuth1(sql.Base):
+ def db_sync(self):
+ migration.db_sync()
+
+ def _get_consumer(self, consumer_id):
+ session = self.get_session()
+ consumer_ref = session.query(Consumer).get(consumer_id)
+ if consumer_ref is None:
+ raise exception.NotFound(_('Consumer not found'))
+ return consumer_ref
+
+ def get_consumer(self, consumer_id):
+ session = self.get_session()
+ consumer_ref = session.query(Consumer).get(consumer_id)
+ if consumer_ref is None:
+ raise exception.NotFound(_('Consumer not found'))
+ return core.filter_consumer(consumer_ref.to_dict())
+
+ def create_consumer(self, consumer):
+ consumer['secret'] = uuid.uuid4().hex
+ if not consumer.get('description'):
+ consumer['description'] = None
+ session = self.get_session()
+ with session.begin():
+ consumer_ref = Consumer.from_dict(consumer)
+ session.add(consumer_ref)
+ session.flush()
+ return consumer_ref.to_dict()
+
+ def _delete_consumer(self, session, consumer_id):
+ consumer_ref = self._get_consumer(session, consumer_id)
+ q = session.query(Consumer)
+ q = q.filter_by(id=consumer_id)
+ q.delete(False)
+ session.delete(consumer_ref)
+
+ def _delete_request_tokens(self, session, consumer_id):
+ q = session.query(RequestToken)
+ req_tokens = q.filter_by(consumer_id=consumer_id)
+ req_tokens_list = set([x.id for x in req_tokens])
+ for token_id in req_tokens_list:
+ token_ref = self._get_request_token(session, token_id)
+ q = session.query(RequestToken)
+ q = q.filter_by(id=token_id)
+ q.delete(False)
+ session.delete(token_ref)
+
+ def _delete_access_tokens(self, session, consumer_id):
+ q = session.query(AccessToken)
+ acc_tokens = q.filter_by(consumer_id=consumer_id)
+ acc_tokens_list = set([x.id for x in acc_tokens])
+ for token_id in acc_tokens_list:
+ token_ref = self._get_access_token(session, token_id)
+ q = session.query(AccessToken)
+ q = q.filter_by(id=token_id)
+ q.delete(False)
+ session.delete(token_ref)
+
+ def delete_consumer(self, consumer_id):
+ session = self.get_session()
+ with session.begin():
+ self._delete_consumer(session, consumer_id)
+ self._delete_request_tokens(session, consumer_id)
+ self._delete_access_tokens(session, consumer_id)
+ session.flush()
+
+ def list_consumers(self):
+ session = self.get_session()
+ cons = session.query(Consumer)
+ return [core.filter_consumer(x.to_dict()) for x in cons]
+
+ def update_consumer(self, consumer_id, consumer):
+ session = self.get_session()
+ with session.begin():
+ consumer_ref = self._get_consumer(consumer_id)
+ old_consumer_dict = consumer_ref.to_dict()
+ old_consumer_dict.update(consumer)
+ new_consumer = Consumer.from_dict(old_consumer_dict)
+ for attr in Consumer.attributes:
+ if (attr != 'id' or attr != 'secret'):
+ setattr(consumer_ref,
+ attr,
+ getattr(new_consumer, attr))
+ consumer_ref.extra = new_consumer.extra
+ session.flush()
+ return core.filter_consumer(consumer_ref.to_dict())
+
+ def create_request_token(self, consumer_id, roles,
+ project_id, token_duration):
+ expiry_date = None
+ if token_duration:
+ now = timeutils.utcnow()
+ future = now + datetime.timedelta(seconds=token_duration)
+ expiry_date = timeutils.isotime(future, subsecond=True)
+
+ ref = {}
+ request_token_id = uuid.uuid4().hex
+ ref['id'] = request_token_id
+ ref['request_secret'] = uuid.uuid4().hex
+ ref['verifier'] = None
+ ref['authorizing_user_id'] = None
+ ref['requested_project_id'] = project_id
+ ref['requested_roles'] = roles
+ ref['consumer_id'] = consumer_id
+ ref['expires_at'] = expiry_date
+ session = self.get_session()
+ with session.begin():
+ token_ref = RequestToken.from_dict(ref)
+ session.add(token_ref)
+ session.flush()
+ return token_ref.to_dict()
+
+ def _get_request_token(self, session, request_token_id):
+ token_ref = session.query(RequestToken).get(request_token_id)
+ if token_ref is None:
+ raise exception.NotFound(_('Request token not found'))
+ return token_ref
+
+ def get_request_token(self, request_token_id):
+ session = self.get_session()
+ token_ref = self._get_request_token(session, request_token_id)
+ return token_ref.to_dict()
+
+ def authorize_request_token(self, request_token_id, user_id):
+ session = self.get_session()
+ with session.begin():
+ token_ref = self._get_request_token(session, request_token_id)
+ token_dict = token_ref.to_dict()
+ token_dict['authorizing_user_id'] = user_id
+ token_dict['verifier'] = str(random.randint(1000, 9999))
+
+ new_token = RequestToken.from_dict(token_dict)
+ for attr in RequestToken.attributes:
+ if (attr == 'authorizing_user_id' or attr == 'verifier'):
+ setattr(token_ref, attr, getattr(new_token, attr))
+
+ session.flush()
+ return token_ref.to_dict()
+
+ def create_access_token(self, request_token_id, token_duration):
+ session = self.get_session()
+ with session.begin():
+ req_token_ref = self._get_request_token(session, request_token_id)
+ token_dict = req_token_ref.to_dict()
+
+ expiry_date = None
+ if token_duration:
+ now = timeutils.utcnow()
+ future = now + datetime.timedelta(seconds=token_duration)
+ expiry_date = timeutils.isotime(future, subsecond=True)
+
+ # add Access Token
+ ref = {}
+ access_token_id = uuid.uuid4().hex
+ ref['id'] = access_token_id
+ ref['access_secret'] = uuid.uuid4().hex
+ ref['authorizing_user_id'] = token_dict['authorizing_user_id']
+ ref['project_id'] = token_dict['requested_project_id']
+ ref['requested_roles'] = token_dict['requested_roles']
+ ref['consumer_id'] = token_dict['consumer_id']
+ ref['expires_at'] = expiry_date
+ token_ref = AccessToken.from_dict(ref)
+ session.add(token_ref)
+
+ # remove request token, it's been used
+ q = session.query(RequestToken)
+ q = q.filter_by(id=request_token_id)
+ q.delete(False)
+ session.delete(req_token_ref)
+
+ session.flush()
+ return token_ref.to_dict()
+
+ def _get_access_token(self, session, access_token_id):
+ token_ref = session.query(AccessToken).get(access_token_id)
+ if token_ref is None:
+ raise exception.NotFound(_('Access token not found'))
+ return token_ref
+
+ def get_access_token(self, access_token_id):
+ session = self.get_session()
+ token_ref = self._get_access_token(session, access_token_id)
+ return token_ref.to_dict()
+
+ def list_access_tokens(self, user_id):
+ session = self.get_session()
+ q = session.query(AccessToken)
+ user_auths = q.filter_by(authorizing_user_id=user_id)
+ return [core.filter_token(x.to_dict()) for x in user_auths]
+
+ def delete_access_token(self, user_id, access_token_id):
+ session = self.get_session()
+ with session.begin():
+ token_ref = self._get_access_token(session, access_token_id)
+ token_dict = token_ref.to_dict()
+ if token_dict['authorizing_user_id'] != user_id:
+ raise exception.Unauthorized(_('User IDs do not match'))
+
+ q = session.query(AccessToken)
+ q = q.filter_by(id=access_token_id)
+ q.delete(False)
+
+ session.delete(token_ref)
+ session.flush()
diff --git a/keystone/contrib/oauth1/controllers.py b/keystone/contrib/oauth1/controllers.py
new file mode 100644
index 00000000..69522e0c
--- /dev/null
+++ b/keystone/contrib/oauth1/controllers.py
@@ -0,0 +1,377 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Extensions supporting OAuth1."""
+
+from keystone.common import controller
+from keystone.common import dependency
+from keystone.common import wsgi
+from keystone import config
+from keystone.contrib.oauth1 import core as oauth1
+from keystone import exception
+from keystone.openstack.common import jsonutils
+from keystone.openstack.common import timeutils
+
+
+CONF = config.CONF
+
+
+@dependency.requires('oauth_api', 'token_api')
+class ConsumerCrudV3(controller.V3Controller):
+ collection_name = 'consumers'
+ member_name = 'consumer'
+
+ def create_consumer(self, context, consumer):
+ ref = self._assign_unique_id(self._normalize_dict(consumer))
+ consumer_ref = self.oauth_api.create_consumer(ref)
+ return ConsumerCrudV3.wrap_member(context, consumer_ref)
+
+ def update_consumer(self, context, consumer_id, consumer):
+ self._require_matching_id(consumer_id, consumer)
+ ref = self._normalize_dict(consumer)
+ self._validate_consumer_ref(consumer)
+ ref = self.oauth_api.update_consumer(consumer_id, consumer)
+ return ConsumerCrudV3.wrap_member(context, ref)
+
+ def list_consumers(self, context):
+ ref = self.oauth_api.list_consumers()
+ return ConsumerCrudV3.wrap_collection(context, ref)
+
+ def get_consumer(self, context, consumer_id):
+ ref = self.oauth_api.get_consumer(consumer_id)
+ return ConsumerCrudV3.wrap_member(context, ref)
+
+ def delete_consumer(self, context, consumer_id):
+ user_token_ref = self.token_api.get_token(context['token_id'])
+ user_id = user_token_ref['user'].get('id')
+ self.token_api.delete_tokens(user_id, consumer_id=consumer_id)
+ self.oauth_api.delete_consumer(consumer_id)
+
+ def _validate_consumer_ref(self, consumer):
+ if 'secret' in consumer:
+ msg = _('Cannot change consumer secret')
+ raise exception.ValidationError(message=msg)
+
+
+@dependency.requires('oauth_api')
+class AccessTokenCrudV3(controller.V3Controller):
+ collection_name = 'access_tokens'
+ member_name = 'access_token'
+
+ def get_access_token(self, context, user_id, access_token_id):
+ access_token = self.oauth_api.get_access_token(access_token_id)
+ if access_token['authorizing_user_id'] != user_id:
+ raise exception.NotFound()
+ access_token = self._format_token_entity(access_token)
+ return AccessTokenCrudV3.wrap_member(context, access_token)
+
+ def list_access_tokens(self, context, user_id):
+ refs = self.oauth_api.list_access_tokens(user_id)
+ formatted_refs = ([self._format_token_entity(x) for x in refs])
+ return AccessTokenCrudV3.wrap_collection(context, formatted_refs)
+
+ def delete_access_token(self, context, user_id, access_token_id):
+ access_token = self.oauth_api.get_access_token(access_token_id)
+ consumer_id = access_token['consumer_id']
+ self.token_api.delete_tokens(user_id, consumer_id=consumer_id)
+ return self.oauth_api.delete_access_token(
+ user_id, access_token_id)
+
+ def _format_token_entity(self, entity):
+
+ formatted_entity = entity.copy()
+ access_token_id = formatted_entity['id']
+ user_id = ""
+ if 'requested_roles' in entity:
+ formatted_entity.pop('requested_roles')
+ if 'access_secret' in entity:
+ formatted_entity.pop('access_secret')
+ if 'authorizing_user_id' in entity:
+ user_id = formatted_entity['authorizing_user_id']
+
+ url = ('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(access_token_id)s'
+ '/roles' % {'user_id': user_id,
+ 'access_token_id': access_token_id})
+
+ formatted_entity.setdefault('links', {})
+ formatted_entity['links']['roles'] = (self.base_url(url))
+
+ return formatted_entity
+
+
+@dependency.requires('oauth_api')
+class AccessTokenRolesV3(controller.V3Controller):
+ collection_name = 'roles'
+ member_name = 'role'
+
+ def list_access_token_roles(self, context, user_id, access_token_id):
+ access_token = self.oauth_api.get_access_token(access_token_id)
+ if access_token['authorizing_user_id'] != user_id:
+ raise exception.NotFound()
+ roles = access_token['requested_roles']
+ roles_refs = jsonutils.loads(roles)
+ formatted_refs = ([self._format_role_entity(x) for x in roles_refs])
+ return AccessTokenRolesV3.wrap_collection(context, formatted_refs)
+
+ def get_access_token_role(self, context, user_id,
+ access_token_id, role_id):
+ access_token = self.oauth_api.get_access_token(access_token_id)
+ if access_token['authorizing_user_id'] != user_id:
+ raise exception.Unauthorized(_('User IDs do not match'))
+ roles = access_token['requested_roles']
+ roles_dict = jsonutils.loads(roles)
+ for role in roles_dict:
+ if role['id'] == role_id:
+ role = self._format_role_entity(role)
+ return AccessTokenRolesV3.wrap_member(context, role)
+ raise exception.RoleNotFound(_('Could not find role'))
+
+ def _format_role_entity(self, entity):
+
+ formatted_entity = entity.copy()
+ if 'description' in entity:
+ formatted_entity.pop('description')
+ if 'enabled' in entity:
+ formatted_entity.pop('enabled')
+ return formatted_entity
+
+
+@dependency.requires('oauth_api', 'token_api', 'identity_api',
+ 'token_provider_api', 'assignment_api')
+class OAuthControllerV3(controller.V3Controller):
+ collection_name = 'not_used'
+ member_name = 'not_used'
+
+ def create_request_token(self, context):
+ headers = context['headers']
+ oauth_headers = oauth1.get_oauth_headers(headers)
+ consumer_id = oauth_headers.get('oauth_consumer_key')
+ requested_role_ids = headers.get('Requested-Role-Ids')
+ requested_project_id = headers.get('Requested-Project-Id')
+ if not consumer_id:
+ raise exception.ValidationError(
+ attribute='oauth_consumer_key', target='request')
+ if not requested_role_ids:
+ raise exception.ValidationError(
+ attribute='requested_role_ids', target='request')
+ if not requested_project_id:
+ raise exception.ValidationError(
+ attribute='requested_project_id', target='request')
+
+ req_role_ids = requested_role_ids.split(',')
+ consumer_ref = self.oauth_api._get_consumer(consumer_id)
+ consumer = oauth1.Consumer(key=consumer_ref['id'],
+ secret=consumer_ref['secret'])
+
+ url = oauth1.rebuild_url(context['path'])
+ oauth_request = oauth1.Request.from_request(
+ http_method='POST',
+ http_url=url,
+ headers=context['headers'],
+ query_string=context['query_string'],
+ parameters={'requested_role_ids': requested_role_ids,
+ 'requested_project_id': requested_project_id})
+ oauth_server = oauth1.Server()
+ oauth_server.add_signature_method(oauth1.SignatureMethod_HMAC_SHA1())
+ params = oauth_server.verify_request(oauth_request,
+ consumer,
+ token=None)
+
+ project_params = params['requested_project_id']
+ if project_params != requested_project_id:
+ msg = _('Non-oauth parameter - project, do not match')
+ raise exception.Unauthorized(message=msg)
+
+ roles_params = params['requested_role_ids']
+ roles_params_list = roles_params.split(',')
+ if roles_params_list != req_role_ids:
+ msg = _('Non-oauth parameter - roles, do not match')
+ raise exception.Unauthorized(message=msg)
+
+ req_role_list = list()
+ all_roles = self.identity_api.list_roles()
+ for role in all_roles:
+ for req_role in req_role_ids:
+ if role['id'] == req_role:
+ req_role_list.append(role)
+
+ if len(req_role_list) == 0:
+ msg = _('could not find matching roles for provided role ids')
+ raise exception.Unauthorized(message=msg)
+
+ json_roles = jsonutils.dumps(req_role_list)
+ request_token_duration = CONF.oauth1.request_token_duration
+ token_ref = self.oauth_api.create_request_token(consumer_id,
+ json_roles,
+ requested_project_id,
+ request_token_duration)
+
+ result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
+ % {'key': token_ref['id'],
+ 'secret': token_ref['request_secret']})
+
+ if CONF.oauth1.request_token_duration:
+ expiry_bit = '&oauth_expires_at=%s' % token_ref['expires_at']
+ result += expiry_bit
+
+ headers = [('Content-Type', 'application/x-www-urlformencoded')]
+ response = wsgi.render_response(result,
+ status=(201, 'Created'),
+ headers=headers)
+
+ return response
+
+ def create_access_token(self, context):
+ headers = context['headers']
+ oauth_headers = oauth1.get_oauth_headers(headers)
+ consumer_id = oauth_headers.get('oauth_consumer_key')
+ request_token_id = oauth_headers.get('oauth_token')
+ oauth_verifier = oauth_headers.get('oauth_verifier')
+
+ if not consumer_id:
+ raise exception.ValidationError(
+ attribute='oauth_consumer_key', target='request')
+ if not request_token_id:
+ raise exception.ValidationError(
+ attribute='oauth_token', target='request')
+ if not oauth_verifier:
+ raise exception.ValidationError(
+ attribute='oauth_verifier', target='request')
+
+ consumer = self.oauth_api._get_consumer(consumer_id)
+ req_token = self.oauth_api.get_request_token(
+ request_token_id)
+
+ expires_at = req_token['expires_at']
+ if expires_at:
+ now = timeutils.utcnow()
+ expires = timeutils.normalize_time(
+ timeutils.parse_isotime(expires_at))
+ if now > expires:
+ raise exception.Unauthorized(_('Request token is expired'))
+
+ consumer_obj = oauth1.Consumer(key=consumer['id'],
+ secret=consumer['secret'])
+ req_token_obj = oauth1.Token(key=req_token['id'],
+ secret=req_token['request_secret'])
+ req_token_obj.set_verifier(oauth_verifier)
+
+ url = oauth1.rebuild_url(context['path'])
+ oauth_request = oauth1.Request.from_request(
+ http_method='POST',
+ http_url=url,
+ headers=context['headers'],
+ query_string=context['query_string'])
+ oauth_server = oauth1.Server()
+ oauth_server.add_signature_method(oauth1.SignatureMethod_HMAC_SHA1())
+ params = oauth_server.verify_request(oauth_request,
+ consumer_obj,
+ token=req_token_obj)
+
+ if len(params) != 0:
+ msg = _('There should not be any non-oauth parameters')
+ raise exception.Unauthorized(message=msg)
+
+ if req_token['consumer_id'] != consumer_id:
+ msg = _('provided consumer key does not match stored consumer key')
+ raise exception.Unauthorized(message=msg)
+
+ if req_token['verifier'] != oauth_verifier:
+ msg = _('provided verifier does not match stored verifier')
+ raise exception.Unauthorized(message=msg)
+
+ if req_token['id'] != request_token_id:
+ msg = _('provided request key does not match stored request key')
+ raise exception.Unauthorized(message=msg)
+
+ if not req_token.get('authorizing_user_id'):
+ msg = _('Request Token does not have an authorizing user id')
+ raise exception.Unauthorized(message=msg)
+
+ access_token_duration = CONF.oauth1.access_token_duration
+ token_ref = self.oauth_api.create_access_token(request_token_id,
+ access_token_duration)
+
+ result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
+ % {'key': token_ref['id'],
+ 'secret': token_ref['access_secret']})
+
+ if CONF.oauth1.access_token_duration:
+ expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at'])
+ result += expiry_bit
+
+ headers = [('Content-Type', 'application/x-www-urlformencoded')]
+ response = wsgi.render_response(result,
+ status=(201, 'Created'),
+ headers=headers)
+
+ return response
+
+ def authorize(self, context, request_token_id):
+ """An authenticated user is going to authorize a request token.
+
+ As a security precaution, the requested roles must match those in
+ the request token. Because this is in a CLI-only world at the moment,
+ there is not another easy way to make sure the user knows which roles
+ are being requested before authorizing.
+ """
+
+ req_token = self.oauth_api.get_request_token(request_token_id)
+
+ expires_at = req_token['expires_at']
+ if expires_at:
+ now = timeutils.utcnow()
+ expires = timeutils.normalize_time(
+ timeutils.parse_isotime(expires_at))
+ if now > expires:
+ raise exception.Unauthorized(_('Request token is expired'))
+
+ req_roles = req_token['requested_roles']
+ req_roles_list = jsonutils.loads(req_roles)
+
+ req_set = set()
+ for x in req_roles_list:
+ req_set.add(x['id'])
+
+ # verify the authorizing user has the roles
+ user_token = self.token_api.get_token(token_id=context['token_id'])
+ credentials = user_token['metadata'].copy()
+ user_roles = credentials.get('roles')
+ user_id = user_token['user'].get('id')
+ cred_set = set(user_roles)
+
+ if not cred_set.issuperset(req_set):
+ msg = _('authorizing user does not have role required')
+ raise exception.Unauthorized(message=msg)
+
+ # verify the user has the project too
+ req_project_id = req_token['requested_project_id']
+ user_projects = self.assignment_api.list_user_projects(user_id)
+ found = False
+ for user_project in user_projects:
+ if user_project['id'] == req_project_id:
+ found = True
+ break
+ if not found:
+ msg = _("User is not a member of the requested project")
+ raise exception.Unauthorized(message=msg)
+
+ # finally authorize the token
+ authed_token = self.oauth_api.authorize_request_token(
+ request_token_id, user_id)
+
+ to_return = {'token': {'oauth_verifier': authed_token['verifier']}}
+ return to_return
diff --git a/keystone/contrib/oauth1/core.py b/keystone/contrib/oauth1/core.py
new file mode 100644
index 00000000..eb4bf959
--- /dev/null
+++ b/keystone/contrib/oauth1/core.py
@@ -0,0 +1,272 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Extensions supporting OAuth1."""
+
+from __future__ import absolute_import
+
+import oauth2 as oauth
+
+from keystone.common import dependency
+from keystone.common import extension
+from keystone.common import manager
+from keystone import config
+from keystone import exception
+
+
+Consumer = oauth.Consumer
+Request = oauth.Request
+Server = oauth.Server
+SignatureMethod = oauth.SignatureMethod
+SignatureMethod_HMAC_SHA1 = oauth.SignatureMethod_HMAC_SHA1
+SignatureMethod_PLAINTEXT = oauth.SignatureMethod_PLAINTEXT
+Token = oauth.Token
+Client = oauth.Client
+
+
+CONF = config.CONF
+
+
+EXTENSION_DATA = {
+ 'name': 'OpenStack OAUTH1 API',
+ 'namespace': 'http://docs.openstack.org/identity/api/ext/'
+ 'OS-OAUTH1/v1.0',
+ 'alias': 'OS-OAUTH1',
+ 'updated': '2013-07-07T12:00:0-00:00',
+ 'description': 'OpenStack OAuth 1.0a Delegated Auth Mechanism.',
+ 'links': [
+ {
+ 'rel': 'describedby',
+ # TODO(dolph): link needs to be revised after
+ # bug 928059 merges
+ '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)
+
+
+def filter_consumer(consumer_ref):
+ """Filter out private items in a consumer dict.
+
+ 'secret' is never returned.
+
+ :returns: consumer_ref
+
+ """
+ if consumer_ref:
+ consumer_ref = consumer_ref.copy()
+ consumer_ref.pop('secret', None)
+ return consumer_ref
+
+
+def filter_token(access_token_ref):
+ """Filter out private items in an access token dict.
+
+ 'access_secret' is never returned.
+
+ :returns: access_token_ref
+
+ """
+ if access_token_ref:
+ access_token_ref = access_token_ref.copy()
+ access_token_ref.pop('access_secret', None)
+ return access_token_ref
+
+
+def rebuild_url(path):
+ endpoint = CONF.public_endpoint % CONF
+
+ # allow a missing trailing slash in the config
+ if endpoint[-1] != '/':
+ endpoint += '/'
+
+ url = endpoint + 'v3'
+ return url + path
+
+
+def get_oauth_headers(headers):
+ parameters = {}
+
+ # The incoming headers variable is your usual heading from context
+ # In an OAuth signed req, where the oauth variables are in the header,
+ # they with the key 'Authorization'.
+
+ if headers and 'Authorization' in headers:
+ # A typical value for Authorization is seen below
+ # 'OAuth realm="", oauth_body_hash="2jm%3D", oauth_nonce="14475435"
+ # along with other oauth variables, the 'OAuth ' part is trimmed
+ # to split the rest of the headers.
+
+ auth_header = headers['Authorization']
+ # Check that the authorization header is OAuth.
+ if auth_header[:6] == 'OAuth ':
+ auth_header = auth_header[6:]
+ # Get the parameters from the header.
+ header_params = oauth.Request._split_header(auth_header)
+ parameters.update(header_params)
+ return parameters
+
+
+@dependency.provider('oauth_api')
+class Manager(manager.Manager):
+ """Default pivot point for the OAuth1 backend.
+
+ See :mod:`keystone.common.manager.Manager` for more details on how this
+ dynamically calls the backend.
+
+ """
+
+ def __init__(self):
+ super(Manager, self).__init__(CONF.oauth1.driver)
+
+
+class Driver(object):
+ """Interface description for an OAuth1 driver."""
+
+ def create_consumer(self, consumer_ref):
+ """Create consumer.
+
+ :param consumer_ref: consumer ref with consumer name
+ :type consumer_ref: dict
+ :returns: consumer_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def update_consumer(self, consumer_id, consumer_ref):
+ """Update consumer.
+
+ :param consumer_id: id of consumer to update
+ :type consumer_ref: string
+ :param consumer_ref: new consumer ref with consumer name
+ :type consumer_ref: dict
+ :returns: consumer_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def list_consumers(self):
+ """List consumers.
+
+ returns: list of consumers
+
+ """
+ raise exception.NotImplemented()
+
+ def get_consumer(self, consumer_id):
+ """Get consumer.
+
+ :param consumer_id: id of consumer to get
+ :type consumer_ref: string
+ :returns: consumer_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_consumer(self, consumer_id):
+ """Delete consumer.
+
+ :param consumer_id: id of consumer to get
+ :type consumer_ref: string
+ :returns: None.
+
+ """
+ raise exception.NotImplemented()
+
+ def list_access_tokens(self, user_id):
+ """List access tokens.
+
+ :param user_id: search for access tokens authorized by given user id
+ :type user_id: string
+ returns: list of access tokens the user has authorized
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_access_token(self, user_id, access_token_id):
+ """Delete access token.
+
+ :param user_id: authorizing user id
+ :type user_id: string
+ :param access_token_id: access token to delete
+ :type access_token_id: string
+ returns: None
+
+ """
+ raise exception.NotImplemented()
+
+ def create_request_token(self, consumer_id, requested_roles,
+ requested_project, request_token_duration):
+ """Create request token.
+
+ :param consumer_id: the id of the consumer
+ :type consumer_id: string
+ :param requested_roles: requested roles
+ :type requested_roles: string
+ :param requested_project_id: requested project id
+ :type requested_project_id: string
+ :param request_token_duration: duration of request token
+ :type request_token_duration: string
+ returns: request_token_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def get_request_token(self, request_token_id):
+ """Get request token.
+
+ :param request_token_id: the id of the request token
+ :type request_token_id: string
+ returns: request_token_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def get_access_token(self, access_token_id):
+ """Get access token.
+
+ :param access_token_id: the id of the access token
+ :type access_token_id: string
+ returns: access_token_ref
+
+ """
+ raise exception.NotImplemented()
+
+ def authorize_request_token(self, request_id, user_id):
+ """Authorize request token.
+
+ :param request_id: the id of the request token, to be authorized
+ :type request_id: string
+ :param user_id: the id of the authorizing user
+ :type user_id: string
+ returns: verifier
+
+ """
+ raise exception.NotImplemented()
+
+ def create_access_token(self, request_id, access_token_duration):
+ """Create access token.
+
+ :param request_id: the id of the request token, to be deleted
+ :type request_id: string
+ :param access_token_duration: duration of an access token
+ :type access_token_duration: string
+ returns: access_token_ref
+
+ """
+ raise exception.NotImplemented()
diff --git a/keystone/contrib/oauth1/migrate_repo/__init__.py b/keystone/contrib/oauth1/migrate_repo/__init__.py
new file mode 100644
index 00000000..3f393b26
--- /dev/null
+++ b/keystone/contrib/oauth1/migrate_repo/__init__.py
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
diff --git a/keystone/contrib/oauth1/migrate_repo/migrate.cfg b/keystone/contrib/oauth1/migrate_repo/migrate.cfg
new file mode 100644
index 00000000..97ca7810
--- /dev/null
+++ b/keystone/contrib/oauth1/migrate_repo/migrate.cfg
@@ -0,0 +1,25 @@
+[db_settings]
+# Used to identify which repository this database is versioned under.
+# You can use the name of your project.
+repository_id=oauth1
+
+# The name of the database table used to track the schema version.
+# This name shouldn't already be used by your project.
+# If this is changed once a database is under version control, you'll need to
+# change the table name in each database too.
+version_table=migrate_version
+
+# When committing a change script, Migrate will attempt to generate the
+# sql for all supported databases; normally, if one of them fails - probably
+# because you don't have that database installed - it is ignored and the
+# commit continues, perhaps ending successfully.
+# Databases in this list MUST compile successfully during a commit, or the
+# entire commit will fail. List the databases your application will actually
+# be using to ensure your updates to that database work properly.
+# This must be a list; example: ['postgres','sqlite']
+required_dbs=[]
+
+# When creating new change scripts, Migrate will stamp the new script with
+# a version number. By default this is latest_version + 1. You can set this
+# to 'true' to tell Migrate to use the UTC timestamp instead.
+use_timestamp_numbering=False
diff --git a/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py b/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py
new file mode 100644
index 00000000..d3ed9033
--- /dev/null
+++ b/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import sqlalchemy as sql
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine; bind
+ # migrate_engine to your metadata
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+
+ consumer_table = sql.Table(
+ 'consumer',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True, nullable=False),
+ sql.Column('description', sql.String(64), nullable=False),
+ sql.Column('secret', sql.String(64), nullable=False),
+ sql.Column('extra', sql.Text(), nullable=False))
+ consumer_table.create(migrate_engine, checkfirst=True)
+
+ request_token_table = sql.Table(
+ 'request_token',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True, nullable=False),
+ sql.Column('request_secret', sql.String(64), nullable=False),
+ sql.Column('verifier', sql.String(64), nullable=True),
+ sql.Column('authorizing_user_id', sql.String(64), nullable=True),
+ sql.Column('requested_project_id', sql.String(64), nullable=False),
+ sql.Column('requested_roles', sql.Text(), nullable=False),
+ sql.Column('consumer_id', sql.String(64), nullable=False, index=True),
+ sql.Column('expires_at', sql.String(64), nullable=True))
+ request_token_table.create(migrate_engine, checkfirst=True)
+
+ access_token_table = sql.Table(
+ 'access_token',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True, nullable=False),
+ sql.Column('access_secret', sql.String(64), nullable=False),
+ sql.Column('authorizing_user_id', sql.String(64),
+ nullable=False, index=True),
+ sql.Column('project_id', sql.String(64), nullable=False),
+ sql.Column('requested_roles', sql.Text(), nullable=False),
+ sql.Column('consumer_id', sql.String(64), nullable=False),
+ sql.Column('expires_at', sql.String(64), nullable=True))
+ access_token_table.create(migrate_engine, checkfirst=True)
+
+
+def downgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+ # Operations to reverse the above upgrade go here.
+ tables = ['consumer', 'request_token', 'access_token']
+ for table_name in tables:
+ table = sql.Table(table_name, meta, autoload=True)
+ table.drop()
diff --git a/keystone/contrib/oauth1/migrate_repo/versions/__init__.py b/keystone/contrib/oauth1/migrate_repo/versions/__init__.py
new file mode 100644
index 00000000..3f393b26
--- /dev/null
+++ b/keystone/contrib/oauth1/migrate_repo/versions/__init__.py
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
diff --git a/keystone/contrib/oauth1/routers.py b/keystone/contrib/oauth1/routers.py
new file mode 100644
index 00000000..0d9123b1
--- /dev/null
+++ b/keystone/contrib/oauth1/routers.py
@@ -0,0 +1,129 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystone.common import wsgi
+from keystone.contrib.oauth1 import controllers
+
+
+class OAuth1Extension(wsgi.ExtensionRouter):
+ """API Endpoints for the OAuth1 extension.
+
+ The goal of this extension is to allow third-party service providers
+ to acquire tokens with a limited subset of a user's roles for acting
+ on behalf of that user. This is done using an oauth-similar flow and
+ api.
+
+ The API looks like:
+
+ # Basic admin-only consumer crud
+ POST /OS-OAUTH1/consumers
+ GET /OS-OAUTH1/consumers
+ PATCH /OS-OAUTH1/consumers/$consumer_id
+ GET /OS-OAUTH1/consumers/$consumer_id
+ DELETE /OS-OAUTH1/consumers/$consumer_id
+
+ # User access token crud
+ GET /users/$user_id/OS-OAUTH1/access_tokens
+ GET /users/$user_id/OS-OAUTH1/access_tokens/$access_token_id
+ GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles
+ GET /users/{user_id}/OS-OAUTH1/access_tokens
+ /{access_token_id}/roles/{role_id}
+ DELETE /users/$user_id/OS-OAUTH1/access_tokens/$access_token_id
+
+ # OAuth interfaces
+ POST /OS-OAUTH1/request_token # create a request token
+ PUT /OS-OAUTH1/authorize # authorize a request token
+ POST /OS-OAUTH1/access_token # create an access token
+
+ """
+
+ def add_routes(self, mapper):
+ consumer_controller = controllers.ConsumerCrudV3()
+ access_token_controller = controllers.AccessTokenCrudV3()
+ access_token_roles_controller = controllers.AccessTokenRolesV3()
+ oauth_controller = controllers.OAuthControllerV3()
+
+ # basic admin-only consumer crud
+ mapper.connect(
+ '/OS-OAUTH1/consumers',
+ controller=consumer_controller,
+ action='create_consumer',
+ conditions=dict(method=['POST']))
+ mapper.connect(
+ '/OS-OAUTH1/consumers/{consumer_id}',
+ controller=consumer_controller,
+ action='get_consumer',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/OS-OAUTH1/consumers/{consumer_id}',
+ controller=consumer_controller,
+ action='update_consumer',
+ conditions=dict(method=['PATCH']))
+ mapper.connect(
+ '/OS-OAUTH1/consumers/{consumer_id}',
+ controller=consumer_controller,
+ action='delete_consumer',
+ conditions=dict(method=['DELETE']))
+ mapper.connect(
+ '/OS-OAUTH1/consumers',
+ controller=consumer_controller,
+ action='list_consumers',
+ conditions=dict(method=['GET']))
+
+ # user accesss token crud
+ mapper.connect(
+ '/users/{user_id}/OS-OAUTH1/access_tokens',
+ controller=access_token_controller,
+ action='list_access_tokens',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
+ controller=access_token_controller,
+ action='get_access_token',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
+ controller=access_token_controller,
+ action='delete_access_token',
+ conditions=dict(method=['DELETE']))
+ mapper.connect(
+ '/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles',
+ controller=access_token_roles_controller,
+ action='list_access_token_roles',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/users/{user_id}/OS-OAUTH1/access_tokens/'
+ '{access_token_id}/roles/{role_id}',
+ controller=access_token_roles_controller,
+ action='get_access_token_role',
+ conditions=dict(method=['GET']))
+
+ # oauth flow calls
+ mapper.connect(
+ '/OS-OAUTH1/request_token',
+ controller=oauth_controller,
+ action='create_request_token',
+ conditions=dict(method=['POST']))
+ mapper.connect(
+ '/OS-OAUTH1/access_token',
+ controller=oauth_controller,
+ action='create_access_token',
+ conditions=dict(method=['POST']))
+ mapper.connect(
+ '/OS-OAUTH1/authorize/{request_token_id}',
+ controller=oauth_controller,
+ action='authorize',
+ conditions=dict(method=['PUT']))
diff --git a/keystone/contrib/stats/core.py b/keystone/contrib/stats/core.py
index 1d7b2cdf..9e6538db 100644
--- a/keystone/contrib/stats/core.py
+++ b/keystone/contrib/stats/core.py
@@ -15,12 +15,12 @@
# under the License.
from keystone.common import extension
-from keystone.common import logging
from keystone.common import manager
from keystone.common import wsgi
from keystone import config
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
from keystone import policy
from keystone import token
diff --git a/keystone/contrib/user_crud/core.py b/keystone/contrib/user_crud/core.py
index f9f09b89..2129af40 100644
--- a/keystone/contrib/user_crud/core.py
+++ b/keystone/contrib/user_crud/core.py
@@ -18,10 +18,10 @@ import copy
import uuid
from keystone.common import extension
-from keystone.common import logging
from keystone.common import wsgi
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
@@ -82,7 +82,7 @@ class UserController(identity.controllers.User):
new_token_ref = copy.copy(token_ref)
new_token_ref['id'] = token_id
self.token_api.create_token(token_id, new_token_ref)
- logging.debug('TOKEN_REF %s', new_token_ref)
+ LOG.debug('TOKEN_REF %s', new_token_ref)
return {'access': {'token': new_token_ref}}
diff --git a/keystone/controllers.py b/keystone/controllers.py
index 8ffa073a..be3c57fa 100644
--- a/keystone/controllers.py
+++ b/keystone/controllers.py
@@ -15,10 +15,10 @@
# under the License.
from keystone.common import extension
-from keystone.common import logging
from keystone.common import wsgi
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/keystone/credential/core.py b/keystone/credential/core.py
index a8921ba0..97cfc1c1 100644
--- a/keystone/credential/core.py
+++ b/keystone/credential/core.py
@@ -17,10 +17,10 @@
"""Main entry point into the Credentials service."""
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/exception.py b/keystone/exception.py
index 5e1defba..c0edc263 100644
--- a/keystone/exception.py
+++ b/keystone/exception.py
@@ -15,8 +15,8 @@
# under the License.
from keystone.common import config
-from keystone.common import logging
from keystone.openstack.common.gettextutils import _ # noqa
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py
index 0323d3d0..bcfb777b 100644
--- a/keystone/identity/backends/kvs.py
+++ b/keystone/identity/backends/kvs.py
@@ -27,6 +27,9 @@ class Identity(kvs.Base, identity.Driver):
def default_assignment_driver(self):
return "keystone.assignment.backends.kvs.Assignment"
+ def is_domain_aware(self):
+ return True
+
# Public interface
def authenticate(self, user_id, password):
user_ref = None
diff --git a/keystone/identity/backends/ldap.py b/keystone/identity/backends/ldap.py
index a359c63f..5898da1f 100644
--- a/keystone/identity/backends/ldap.py
+++ b/keystone/identity/backends/ldap.py
@@ -21,12 +21,12 @@ import ldap
from keystone import clean
from keystone.common import dependency
from keystone.common import ldap as common_ldap
-from keystone.common import logging
from keystone.common import models
from keystone.common import utils
from keystone import config
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
CONF = config.CONF
@@ -41,14 +41,19 @@ DEFAULT_DOMAIN = {
@dependency.requires('assignment_api')
class Identity(identity.Driver):
- def __init__(self):
+ def __init__(self, conf=None):
super(Identity, self).__init__()
- self.user = UserApi(CONF)
- self.group = GroupApi(CONF)
+ if conf is None:
+ conf = CONF
+ self.user = UserApi(conf)
+ self.group = GroupApi(conf)
def default_assignment_driver(self):
return "keystone.assignment.backends.ldap.Assignment"
+ def is_domain_aware(self):
+ return False
+
# Identity interface
def create_project(self, project_id, project):
@@ -68,37 +73,31 @@ class Identity(identity.Driver):
raise AssertionError('Invalid user / password')
except Exception:
raise AssertionError('Invalid user / password')
- return self.assignment_api._set_default_domain(
- identity.filter_user(user_ref))
+ return 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.assignment_api._set_default_domain(ref)
+ return identity.filter_user(self._get_user(user_id))
def list_users(self):
- return (self.assignment_api._set_default_domain
- (self.user.get_all_filtered()))
+ return self.user.get_all_filtered()
def get_user_by_name(self, user_name, domain_id):
- self.assignment_api._validate_default_domain_id(domain_id)
- ref = identity.filter_user(self.user.get_by_name(user_name))
- return self.assignment_api._set_default_domain(ref)
+ # domain_id will already have been handled in the Manager layer,
+ # parameter left in so this matches the Driver specification
+ return identity.filter_user(self.user.get_by_name(user_name))
# CRUD
def create_user(self, user_id, user):
- user = self.assignment_api._validate_default_domain(user)
user_ref = self.user.create(user)
tenant_id = user.get('tenant_id')
if tenant_id is not None:
self.assignment_api.add_user_to_project(tenant_id, user_id)
- return (self.assignment_api._set_default_domain
- (identity.filter_user(user_ref)))
+ return identity.filter_user(user_ref)
def update_user(self, user_id, user):
- user = self.assignment_api._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)
@@ -121,8 +120,7 @@ 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.assignment_api._set_default_domain
- (self.user.get_filtered(user_id)))
+ return self.user.get_filtered(user_id)
def delete_user(self, user_id):
self.assignment_api.delete_user(user_id)
@@ -138,21 +136,16 @@ class Identity(identity.Driver):
self.user.delete(user_id)
def create_group(self, group_id, group):
- group = self.assignment_api._validate_default_domain(group)
group['name'] = clean.group_name(group['name'])
- return self.assignment_api._set_default_domain(
- self.group.create(group))
+ return self.group.create(group)
def get_group(self, group_id):
- return self.assignment_api._set_default_domain(
- self.group.get(group_id))
+ return self.group.get(group_id)
def update_group(self, group_id, group):
- group = self.assignment_api._validate_default_domain(group)
if 'name' in group:
group['name'] = clean.group_name(group['name'])
- return (self.assignment_api._set_default_domain
- (self.group.update(group_id, group)))
+ return self.group.update(group_id, group)
def delete_group(self, group_id):
return self.group.delete(group_id)
@@ -172,11 +165,10 @@ 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.assignment_api._set_default_domain
- (self.group.list_user_groups(user_dn)))
+ return self.group.list_user_groups(user_dn)
def list_groups(self):
- return self.assignment_api._set_default_domain(self.group.get_all())
+ return self.group.get_all()
def list_users_in_group(self, group_id):
self.get_group(group_id)
@@ -190,7 +182,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.assignment_api._set_default_domain(users)
+ return users
def check_user_in_group(self, user_id, group_id):
self.get_user(user_id)
@@ -228,14 +220,15 @@ class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):
def _ldap_res_to_model(self, res):
obj = super(UserApi, self)._ldap_res_to_model(res)
if self.enabled_mask != 0:
- obj['enabled_nomask'] = obj['enabled']
- obj['enabled'] = ((obj['enabled'] & self.enabled_mask) !=
+ enabled = int(obj.get('enabled', self.enabled_default))
+ obj['enabled_nomask'] = enabled
+ obj['enabled'] = ((enabled & self.enabled_mask) !=
self.enabled_mask)
return obj
def mask_enabled_attribute(self, values):
value = values['enabled']
- values.setdefault('enabled_nomask', self.enabled_default)
+ values.setdefault('enabled_nomask', int(self.enabled_default))
if value != ((values['enabled_nomask'] & self.enabled_mask) !=
self.enabled_mask):
values['enabled_nomask'] ^= self.enabled_mask
diff --git a/keystone/identity/backends/pam.py b/keystone/identity/backends/pam.py
index 2a6ee621..a5459694 100644
--- a/keystone/identity/backends/pam.py
+++ b/keystone/identity/backends/pam.py
@@ -58,6 +58,9 @@ class PamIdentity(identity.Driver):
Tenant is always the same as User, root user has admin role.
"""
+ def is_domain_aware(self):
+ return False
+
def authenticate(self, user_id, password):
auth = pam.authenticate if pam else PAM_authenticate
if not auth(user_id, password):
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index bff41106..84026a58 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -26,7 +26,7 @@ class User(sql.ModelBase, sql.DictBase):
__tablename__ = 'user'
attributes = ['id', 'name', 'domain_id', 'password', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(64), nullable=False)
+ name = sql.Column(sql.String(255), nullable=False)
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
nullable=False)
password = sql.Column(sql.String(128))
@@ -85,6 +85,9 @@ class Identity(sql.Base, identity.Driver):
"""
return utils.check_password(password, user_ref.password)
+ def is_domain_aware(self):
+ return True
+
# Identity interface
def authenticate(self, user_id, password):
session = self.get_session()
diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
index 7ca1f8bf..281e3f1b 100644
--- a/keystone/identity/controllers.py
+++ b/keystone/identity/controllers.py
@@ -22,10 +22,9 @@ import urlparse
import uuid
from keystone.common import controller
-from keystone.common import logging
from keystone import config
from keystone import exception
-
+from keystone.openstack.common import log as logging
CONF = config.CONF
DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
@@ -109,12 +108,20 @@ class Tenant(controller.V2Controller):
# be specifying that
clean_tenant = tenant.copy()
clean_tenant.pop('domain_id', None)
+
+ # If the project has been disabled (or enabled=False) we are
+ # deleting the tokens for that project.
+ if not tenant.get('enabled', True):
+ self._delete_tokens_for_project(tenant_id)
+
tenant_ref = self.identity_api.update_project(
tenant_id, clean_tenant)
return {'tenant': tenant_ref}
def delete_project(self, context, tenant_id):
self.assert_admin(context)
+ # Delete all tokens belonging to the users for that project
+ self._delete_tokens_for_project(tenant_id)
self.identity_api.delete_project(tenant_id)
def get_project_users(self, context, tenant_id, **kw):
@@ -572,6 +579,10 @@ class ProjectV3(controller.V3Controller):
def update_project(self, context, project_id, project):
self._require_matching_id(project_id, project)
+ # The project was disabled so we delete the tokens
+ if not project.get('enabled', True):
+ self._delete_tokens_for_project(project_id)
+
ref = self.identity_api.update_project(project_id, project)
return ProjectV3.wrap_member(context, ref)
@@ -580,6 +591,10 @@ class ProjectV3(controller.V3Controller):
for cred in self.credential_api.list_credentials():
if cred['project_id'] == project_id:
self.credential_api.delete_credential(cred['id'])
+
+ # Delete all tokens belonging to the users for that project
+ self._delete_tokens_for_project(project_id)
+
# Finally delete the project itself - the backend is
# responsible for deleting any role assignments related
# to this project
@@ -605,23 +620,30 @@ class UserV3(controller.V3Controller):
@controller.filterprotected('domain_id', 'email', 'enabled', 'name')
def list_users(self, context, filters):
- refs = self.identity_api.list_users()
+ refs = self.identity_api.list_users(
+ domain_scope=self._get_domain_id_for_request(context))
return UserV3.wrap_collection(context, refs, filters)
@controller.filterprotected('domain_id', 'email', 'enabled', 'name')
def list_users_in_group(self, context, filters, group_id):
- refs = self.identity_api.list_users_in_group(group_id)
+ refs = self.identity_api.list_users_in_group(
+ group_id,
+ domain_scope=self._get_domain_id_for_request(context))
return UserV3.wrap_collection(context, refs, filters)
@controller.protected
def get_user(self, context, user_id):
- ref = self.identity_api.get_user(user_id)
+ ref = self.identity_api.get_user(
+ user_id,
+ domain_scope=self._get_domain_id_for_request(context))
return UserV3.wrap_member(context, ref)
@controller.protected
def update_user(self, context, user_id, user):
self._require_matching_id(user_id, user)
- ref = self.identity_api.update_user(user_id, user)
+ ref = self.identity_api.update_user(
+ user_id, user,
+ domain_scope=self._get_domain_id_for_request(context))
if user.get('password') or not user.get('enabled', True):
# revoke all tokens owned by this user
@@ -631,18 +653,24 @@ class UserV3(controller.V3Controller):
@controller.protected
def add_user_to_group(self, context, user_id, group_id):
- self.identity_api.add_user_to_group(user_id, group_id)
+ self.identity_api.add_user_to_group(
+ user_id, group_id,
+ domain_scope=self._get_domain_id_for_request(context))
# Delete any tokens so that group membership can have an
# immediate effect
self._delete_tokens_for_user(user_id)
@controller.protected
def check_user_in_group(self, context, user_id, group_id):
- return self.identity_api.check_user_in_group(user_id, group_id)
+ return self.identity_api.check_user_in_group(
+ user_id, group_id,
+ domain_scope=self._get_domain_id_for_request(context))
@controller.protected
def remove_user_from_group(self, context, user_id, group_id):
- self.identity_api.remove_user_from_group(user_id, group_id)
+ self.identity_api.remove_user_from_group(
+ user_id, group_id,
+ domain_scope=self._get_domain_id_for_request(context))
self._delete_tokens_for_user(user_id)
def _delete_user(self, context, user_id):
@@ -652,11 +680,13 @@ class UserV3(controller.V3Controller):
self.credential_api.delete_credential(cred['id'])
# Make sure any tokens are marked as deleted
+ domain_id = self._get_domain_id_for_request(context)
self._delete_tokens_for_user(user_id)
# Finally delete the user itself - the backend is
# responsible for deleting any role assignments related
# to this user
- return self.identity_api.delete_user(user_id)
+ return self.identity_api.delete_user(
+ user_id, domain_scope=domain_id)
@controller.protected
def delete_user(self, context, user_id):
@@ -678,24 +708,31 @@ class GroupV3(controller.V3Controller):
@controller.filterprotected('domain_id', 'name')
def list_groups(self, context, filters):
- refs = self.identity_api.list_groups()
+ refs = self.identity_api.list_groups(
+ domain_scope=self._get_domain_id_for_request(context))
return GroupV3.wrap_collection(context, refs, filters)
@controller.filterprotected('name')
def list_groups_for_user(self, context, filters, user_id):
- refs = self.identity_api.list_groups_for_user(user_id)
+ refs = self.identity_api.list_groups_for_user(
+ user_id,
+ domain_scope=self._get_domain_id_for_request(context))
return GroupV3.wrap_collection(context, refs, filters)
@controller.protected
def get_group(self, context, group_id):
- ref = self.identity_api.get_group(group_id)
+ ref = self.identity_api.get_group(
+ group_id,
+ domain_scope=self._get_domain_id_for_request(context))
return GroupV3.wrap_member(context, ref)
@controller.protected
def update_group(self, context, group_id, group):
self._require_matching_id(group_id, group)
- ref = self.identity_api.update_group(group_id, group)
+ ref = self.identity_api.update_group(
+ group_id, group,
+ domain_scope=self._get_domain_id_for_request(context))
return GroupV3.wrap_member(context, ref)
def _delete_group(self, context, group_id):
@@ -705,8 +742,10 @@ class GroupV3(controller.V3Controller):
# deletion, so that we can remove these tokens after we know
# the group deletion succeeded.
- user_refs = self.identity_api.list_users_in_group(group_id)
- self.identity_api.delete_group(group_id)
+ domain_id = self._get_domain_id_for_request(context)
+ user_refs = self.identity_api.list_users_in_group(
+ group_id, domain_scope=domain_id)
+ self.identity_api.delete_group(group_id, domain_scope=domain_id)
for user in user_refs:
self._delete_tokens_for_user(user['id'])
diff --git a/keystone/identity/core.py b/keystone/identity/core.py
index b2b3eaf0..7d5882e3 100644
--- a/keystone/identity/core.py
+++ b/keystone/identity/core.py
@@ -16,12 +16,18 @@
"""Main entry point into the Identity service."""
+import functools
+import os
+
+from oslo.config import cfg
+
from keystone import clean
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import importutils
+from keystone.openstack.common import log as logging
CONF = config.CONF
@@ -51,6 +57,121 @@ def filter_user(user_ref):
return user_ref
+class DomainConfigs(dict):
+ """Discover, store and provide access to domain specifc configs.
+
+ The setup_domain_drives() call will be made via the wrapper from
+ the first call to any driver function handled by this manager. This
+ setup call it will scan the domain config directory for files of the form
+
+ keystone.<domain_name>.conf
+
+ For each file, the domain_name will be turned into a domain_id and then
+ this class will:
+ - Create a new config structure, adding in the specific additional options
+ defined in this config file
+ - Initialise a new instance of the required driver with this new config.
+
+ """
+ configured = False
+ driver = None
+
+ def _load_driver(self, assignment_api, domain_id):
+ domain_config = self[domain_id]
+ domain_config['driver'] = (
+ importutils.import_object(
+ domain_config['cfg'].identity.driver, domain_config['cfg']))
+ domain_config['driver'].assignment_api = assignment_api
+
+ def _load_config(self, assignment_api, file_list, domain_name):
+ try:
+ domain_ref = assignment_api.get_domain_by_name(domain_name)
+ except exception.DomainNotFound:
+ msg = (_('Invalid domain name (%s) found in config file name')
+ % domain_name)
+ LOG.warning(msg)
+
+ if domain_ref:
+ # Create a new entry in the domain config dict, which contains
+ # a new instance of both the conf environment and driver using
+ # options defined in this set of config files. Later, when we
+ # service calls via this Manager, we'll index via this domain
+ # config dict to make sure we call the right driver
+ domain = domain_ref['id']
+ self[domain] = {}
+ self[domain]['cfg'] = cfg.ConfigOpts()
+ config.configure(conf=self[domain]['cfg'])
+ self[domain]['cfg'](args=[], project='keystone',
+ default_config_files=file_list)
+ self._load_driver(assignment_api, domain)
+
+ def setup_domain_drivers(self, standard_driver, assignment_api):
+ # This is called by the api call wrapper
+ self.configured = True
+ self.driver = standard_driver
+
+ conf_dir = CONF.identity.domain_config_dir
+ if not os.path.exists(conf_dir):
+ msg = _('Unable to locate domain config directory: %s') % conf_dir
+ LOG.warning(msg)
+ return
+
+ for r, d, f in os.walk(conf_dir):
+ for file in f:
+ if file.startswith('keystone.') and file.endswith('.conf'):
+ names = file.split('.')
+ if len(names) == 3:
+ self._load_config(assignment_api,
+ [os.path.join(r, file)],
+ names[1])
+ else:
+ msg = (_('Ignoring file (%s) while scanning domain '
+ 'config directory') % file)
+ LOG.debug(msg)
+
+ def get_domain_driver(self, domain_id):
+ if domain_id in self:
+ return self[domain_id]['driver']
+
+ def get_domain_conf(self, domain_id):
+ if domain_id in self:
+ return self[domain_id]['cfg']
+
+ def reload_domain_driver(self, assignment_api, domain_id):
+ # Only used to support unit tests that want to set
+ # new config values. This should only be called once
+ # the domains have been configured, since it relies on
+ # the fact that the configuration files have already been
+ # read.
+ if self.configured:
+ if domain_id in self:
+ self._load_driver(assignment_api, domain_id)
+ else:
+ # The standard driver
+ self.driver = self.driver()
+ self.driver.assignment_api = assignment_api
+
+
+def domains_configured(f):
+ """Wraps API calls to lazy load domain configs after init.
+
+ This is required since the assignment manager needs to be initialized
+ before this manager, and yet this manager's init wants to be
+ able to make assignment calls (to build the domain configs). So
+ instead, we check if the domains have been initialized on entry
+ to each call, and if requires load them,
+
+ """
+ @functools.wraps(f)
+ def wrapper(self, *args, **kwargs):
+ if (not self.domain_configs.configured and
+ CONF.identity.domain_specific_drivers_enabled):
+ self.domain_configs.setup_domain_drivers(
+ self.driver, self.assignment_api)
+ return f(self, *args, **kwargs)
+ return wrapper
+
+
@dependency.provider('identity_api')
@dependency.requires('assignment_api')
class Manager(manager.Manager):
@@ -59,30 +180,228 @@ class Manager(manager.Manager):
See :mod:`keystone.common.manager.Manager` for more details on how this
dynamically calls the backend.
+ This class also handles the support of domain specific backends, by using
+ the DomainConfigs class. The setup call for DomainConfigs is called
+ from with the @domains_configured wrapper in a lazy loading fashion
+ to get around the fact that we can't satisfy the assignment api it needs
+ from within our __init__() function since the assignment driver is not
+ itself yet intitalized.
+
+ Each of the identity calls are pre-processed here to choose, based on
+ domain, which of the drivers should be called. The non-domain-specific
+ driver is still in place, and is used if there is no specific driver for
+ the domain in question.
+
"""
def __init__(self):
super(Manager, self).__init__(CONF.identity.driver)
-
+ self.domain_configs = DomainConfigs()
+
+ # Domain ID normalization methods
+
+ def _set_domain_id(self, ref, domain_id):
+ if isinstance(ref, dict):
+ ref = ref.copy()
+ ref['domain_id'] = domain_id
+ return ref
+ elif isinstance(ref, list):
+ return [self._set_domain_id(x, domain_id) for x in ref]
+ else:
+ raise ValueError(_('Expected dict or list: %s') % type(ref))
+
+ def _clear_domain_id(self, ref):
+ # Clear the domain_id, and then check to ensure that if this
+ # was not the default domain, it is being handled by its own
+ # backend driver.
+ ref = ref.copy()
+ domain_id = ref.pop('domain_id', CONF.identity.default_domain_id)
+ if (domain_id != CONF.identity.default_domain_id and
+ domain_id not in self.domain_configs):
+ raise exception.DomainNotFound(domain_id=domain_id)
+ return ref
+
+ def _normalize_scope(self, domain_scope):
+ if domain_scope is None:
+ return CONF.identity.default_domain_id
+ else:
+ return domain_scope
+
+ def _select_identity_driver(self, domain_id):
+ driver = self.domain_configs.get_domain_driver(domain_id)
+ if driver:
+ return driver
+ else:
+ return self.driver
+
+ def _get_domain_conf(self, domain_id):
+ conf = self.domain_configs.get_domain_conf(domain_id)
+ if conf:
+ return conf
+ else:
+ return CONF
+
+ def _get_domain_id_and_driver(self, domain_scope):
+ domain_id = self._normalize_scope(domain_scope)
+ driver = self._select_identity_driver(domain_id)
+ return (domain_id, driver)
+
+ # The actual driver calls - these are pre/post processed here as
+ # part of the Manager layer to make sure we:
+ #
+ # - select the right driver for this domain
+ # - clear/set domain_ids for drivers that do not support domains
+
+ @domains_configured
+ def authenticate(self, user_id, password, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ ref = driver.authenticate(user_id, password)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
def create_user(self, user_id, user_ref):
user = user_ref.copy()
user['name'] = clean.user_name(user['name'])
user.setdefault('enabled', True)
user['enabled'] = clean.user_enabled(user['enabled'])
- return self.driver.create_user(user_id, user)
- def update_user(self, user_id, user_ref):
+ # For creating a user, the domain is in the object itself
+ domain_id = user_ref['domain_id']
+ driver = self._select_identity_driver(domain_id)
+ if not driver.is_domain_aware():
+ user = self._clear_domain_id(user)
+ ref = driver.create_user(user_id, user)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def get_user(self, user_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ ref = driver.get_user(user_id)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def get_user_by_name(self, user_name, domain_id):
+ driver = self._select_identity_driver(domain_id)
+ ref = driver.get_user_by_name(user_name, domain_id)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def list_users(self, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ user_list = driver.list_users()
+ if not driver.is_domain_aware():
+ user_list = self._set_domain_id(user_list, domain_id)
+ return user_list
+
+ @domains_configured
+ def update_user(self, user_id, user_ref, domain_scope=None):
user = user_ref.copy()
if 'name' in user:
user['name'] = clean.user_name(user['name'])
if 'enabled' in user:
user['enabled'] = clean.user_enabled(user['enabled'])
- return self.driver.update_user(user_id, user)
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ if not driver.is_domain_aware():
+ user = self._clear_domain_id(user)
+ ref = driver.update_user(user_id, user)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def delete_user(self, user_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ driver.delete_user(user_id)
+
+ @domains_configured
def create_group(self, group_id, group_ref):
group = group_ref.copy()
group.setdefault('description', '')
- return self.driver.create_group(group_id, group)
+
+ # For creating a group, the domain is in the object itself
+ domain_id = group_ref['domain_id']
+ driver = self._select_identity_driver(domain_id)
+ if not driver.is_domain_aware():
+ group = self._clear_domain_id(group)
+ ref = driver.create_group(group_id, group)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def get_group(self, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ ref = driver.get_group(group_id)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def update_group(self, group_id, group, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ if not driver.is_domain_aware():
+ group = self._clear_domain_id(group)
+ ref = driver.update_group(group_id, group)
+ if not driver.is_domain_aware():
+ ref = self._set_domain_id(ref, domain_id)
+ return ref
+
+ @domains_configured
+ def delete_group(self, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ driver.delete_group(group_id)
+
+ @domains_configured
+ def add_user_to_group(self, user_id, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ driver.add_user_to_group(user_id, group_id)
+
+ @domains_configured
+ def remove_user_from_group(self, user_id, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ driver.remove_user_from_group(user_id, group_id)
+
+ @domains_configured
+ def list_groups_for_user(self, user_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ group_list = driver.list_groups_for_user(user_id)
+ if not driver.is_domain_aware():
+ group_list = self._set_domain_id(group_list, domain_id)
+ return group_list
+
+ @domains_configured
+ def list_groups(self, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ group_list = driver.list_groups()
+ if not driver.is_domain_aware():
+ group_list = self._set_domain_id(group_list, domain_id)
+ return group_list
+
+ @domains_configured
+ def list_users_in_group(self, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ user_list = driver.list_users_in_group(group_id)
+ if not driver.is_domain_aware():
+ user_list = self._set_domain_id(user_list, domain_id)
+ return user_list
+
+ @domains_configured
+ def check_user_in_group(self, user_id, group_id, domain_scope=None):
+ domain_id, driver = self._get_domain_id_and_driver(domain_scope)
+ return driver.check_user_in_group(user_id, group_id)
+
+ # TODO(henry-nash, ayoung) The following cross calls to the assignment
+ # API should be removed, with the controller and tests making the correct
+ # calls direct to assignment.
def create_project(self, tenant_id, tenant_ref):
tenant = tenant_ref.copy()
@@ -358,6 +677,8 @@ class Driver(object):
"""
raise exception.NotImplemented()
- #end of identity
+ def is_domain_aware(self):
+ """Indicates if Driver supports domains."""
+ raise exception.NotImplemented()
- # Assignments
+ #end of identity
diff --git a/keystone/middleware/core.py b/keystone/middleware/core.py
index 863ef948..92b179c3 100644
--- a/keystone/middleware/core.py
+++ b/keystone/middleware/core.py
@@ -17,13 +17,12 @@
import webob.dec
from keystone.common import config
-from keystone.common import logging
from keystone.common import serializer
from keystone.common import utils
from keystone.common import wsgi
from keystone import exception
from keystone.openstack.common import jsonutils
-
+from keystone.openstack.common import log as logging
CONF = config.CONF
LOG = logging.getLogger(__name__)
diff --git a/keystone/middleware/s3_token.py b/keystone/middleware/s3_token.py
index b346893b..39678591 100644
--- a/keystone/middleware/s3_token.py
+++ b/keystone/middleware/s3_token.py
@@ -37,8 +37,8 @@ import httplib
import urllib
import webob
-from keystone.common import logging
from keystone.openstack.common import jsonutils
+from keystone.openstack.common import log as logging
PROTOCOL_NAME = 'S3 Token Authentication'
diff --git a/keystone/policy/backends/rules.py b/keystone/policy/backends/rules.py
index 63110e69..31a26d88 100644
--- a/keystone/policy/backends/rules.py
+++ b/keystone/policy/backends/rules.py
@@ -19,10 +19,10 @@
import os.path
-from keystone.common import logging
from keystone.common import utils
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
from keystone.openstack.common import policy as common_policy
from keystone import policy
diff --git a/keystone/service.py b/keystone/service.py
index ce64aba8..e3633865 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -14,19 +14,21 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
import routes
from keystone import assignment
from keystone import auth
from keystone import catalog
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import wsgi
from keystone import config
from keystone.contrib import ec2
+from keystone.contrib import oauth1
from keystone import controllers
from keystone import credential
from keystone import identity
+from keystone.openstack.common import log as logging
from keystone import policy
from keystone import routers
from keystone import token
@@ -48,6 +50,7 @@ DRIVERS = dict(
credentials_api=credential.Manager(),
ec2_api=ec2.Manager(),
identity_api=_IDENTITY_API,
+ oauth1_api=oauth1.Manager(),
policy_api=policy.Manager(),
token_api=token.Manager(),
trust_api=trust.Manager(),
@@ -56,7 +59,23 @@ DRIVERS = dict(
dependency.resolve_future_dependencies()
-@logging.fail_gracefully
+def fail_gracefully(f):
+ """Logs exceptions and aborts."""
+ @functools.wraps(f)
+ def wrapper(*args, **kw):
+ try:
+ return f(*args, **kw)
+ except Exception as e:
+ LOG.debug(e, exc_info=True)
+
+ # exception message is printed to all logs
+ LOG.critical(e)
+
+ exit(1)
+ return wrapper
+
+
+@fail_gracefully
def public_app_factory(global_conf, **local_conf):
controllers.register_version('v2.0')
conf = global_conf.copy()
@@ -68,7 +87,7 @@ def public_app_factory(global_conf, **local_conf):
routers.Extension(False)])
-@logging.fail_gracefully
+@fail_gracefully
def admin_app_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
@@ -79,7 +98,7 @@ def admin_app_factory(global_conf, **local_conf):
routers.Extension()])
-@logging.fail_gracefully
+@fail_gracefully
def public_version_app_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
@@ -87,7 +106,7 @@ def public_version_app_factory(global_conf, **local_conf):
[routers.Versions('public')])
-@logging.fail_gracefully
+@fail_gracefully
def admin_version_app_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
@@ -95,7 +114,7 @@ def admin_version_app_factory(global_conf, **local_conf):
[routers.Versions('admin')])
-@logging.fail_gracefully
+@fail_gracefully
def v3_app_factory(global_conf, **local_conf):
controllers.register_version('v3')
conf = global_conf.copy()
diff --git a/keystone/tests/_ldap_livetest.py b/keystone/tests/_ldap_livetest.py
index 59da4e66..4562ccb6 100644
--- a/keystone/tests/_ldap_livetest.py
+++ b/keystone/tests/_ldap_livetest.py
@@ -87,9 +87,6 @@ class LiveLDAPIdentity(test_backend_ldap.LDAPIdentity):
def tearDown(self):
test.TestCase.tearDown(self)
- def test_user_enable_attribute_mask(self):
- self.skipTest('Test is for Active Directory Only')
-
def test_ldap_dereferencing(self):
alt_users_ldif = {'objectclass': ['top', 'organizationalUnit'],
'ou': 'alt_users'}
@@ -158,3 +155,11 @@ class LiveLDAPIdentity(test_backend_ldap.LDAPIdentity):
alias_dereferencing=deref)
self.assertEqual(ldap.DEREF_SEARCHING,
ldap_wrapper.conn.get_option(ldap.OPT_DEREF))
+
+ def test_user_enable_attribute_mask(self):
+ CONF.ldap.user_enabled_emulation = False
+ CONF.ldap.user_enabled_attribute = 'employeeType'
+ super(LiveLDAPIdentity, self).test_user_enable_attribute_mask()
+
+ def test_create_unicode_user_name(self):
+ self.skipTest('Addressed by bug #1172106')
diff --git a/keystone/tests/backend_multi_ldap_sql.conf b/keystone/tests/backend_multi_ldap_sql.conf
new file mode 100644
index 00000000..59cff761
--- /dev/null
+++ b/keystone/tests/backend_multi_ldap_sql.conf
@@ -0,0 +1,35 @@
+[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
+
+[identity]
+# common identity backend is SQL, domain specific configs will
+# set their backends to ldap
+driver = keystone.identity.backends.sql.Identity
+# The test setup will set this to True, to allow easier creation
+# of initial domain data
+# domain_specific_drivers_enabled = True
+
+[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/keystone/tests/core.py b/keystone/tests/core.py
index 21dc61dc..cba6cbf8 100644
--- a/keystone/tests/core.py
+++ b/keystone/tests/core.py
@@ -40,15 +40,16 @@ from keystone import assignment
from keystone import catalog
from keystone.common import dependency
from keystone.common import kvs
-from keystone.common import logging
from keystone.common import sql
from keystone.common import utils
from keystone.common import wsgi
from keystone import config
from keystone.contrib import ec2
+from keystone.contrib import oauth1
from keystone import credential
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
from keystone import policy
from keystone import token
@@ -68,9 +69,6 @@ CONF = config.CONF
cd = os.chdir
-logging.getLogger('routes.middleware').level = logging.WARN
-
-
def rootdir(*p):
return os.path.join(ROOTDIR, *p)
@@ -271,7 +269,7 @@ class TestCase(NoModule, unittest.TestCase):
# assignment manager gets the default assignment driver from the
# identity driver.
for manager in [identity, assignment, catalog, credential, ec2, policy,
- token, token_provider, trust]:
+ token, token_provider, trust, oauth1]:
# manager.__name__ is like keystone.xxx[.yyy],
# converted to xxx[_yyy]
manager_name = ('%s_api' %
@@ -295,9 +293,11 @@ class TestCase(NoModule, unittest.TestCase):
for domain in fixtures.DOMAINS:
try:
rv = self.identity_api.create_domain(domain['id'], domain)
- except (exception.Conflict, exception.NotImplemented):
- pass
- setattr(self, 'domain_%s' % domain['id'], domain)
+ except exception.Conflict:
+ rv = self.identity_api.get_domain(domain['id'])
+ except exception.NotImplemented:
+ rv = domain
+ setattr(self, 'domain_%s' % domain['id'], rv)
for tenant in fixtures.TENANTS:
try:
diff --git a/keystone/tests/keystone.Default.conf b/keystone/tests/keystone.Default.conf
new file mode 100644
index 00000000..7049afed
--- /dev/null
+++ b/keystone/tests/keystone.Default.conf
@@ -0,0 +1,14 @@
+# The domain-specific configuration file for the default domain for
+# use with unit tests.
+#
+# The domain_name of the default domain is 'Default', hence the
+# strange mix of upper/lower case in the file name.
+
+[ldap]
+url = fake://memory
+user = cn=Admin
+password = password
+suffix = cn=example,cn=com
+
+[identity]
+driver = keystone.identity.backends.ldap.Identity \ No newline at end of file
diff --git a/keystone/tests/keystone.domain1.conf b/keystone/tests/keystone.domain1.conf
new file mode 100644
index 00000000..6b7e2488
--- /dev/null
+++ b/keystone/tests/keystone.domain1.conf
@@ -0,0 +1,11 @@
+# The domain-specific configuration file for the test domain
+# 'domain1' for use with unit tests.
+
+[ldap]
+url = fake://memory1
+user = cn=Admin
+password = password
+suffix = cn=example,cn=com
+
+[identity]
+driver = keystone.identity.backends.ldap.Identity \ No newline at end of file
diff --git a/keystone/tests/keystone.domain2.conf b/keystone/tests/keystone.domain2.conf
new file mode 100644
index 00000000..0ed68eb9
--- /dev/null
+++ b/keystone/tests/keystone.domain2.conf
@@ -0,0 +1,13 @@
+# The domain-specific configuration file for the test domain
+# 'domain2' for use with unit tests.
+
+[ldap]
+url = fake://memory
+user = cn=Admin
+password = password
+suffix = cn=myroot,cn=com
+group_tree_dn = ou=UserGroups,dc=myroot,dc=org
+user_tree_dn = ou=Users,dc=myroot,dc=org
+
+[identity]
+driver = keystone.identity.backends.ldap.Identity \ No newline at end of file
diff --git a/keystone/tests/test_backend.py b/keystone/tests/test_backend.py
index 8622b10d..8013deec 100644
--- a/keystone/tests/test_backend.py
+++ b/keystone/tests/test_backend.py
@@ -105,7 +105,9 @@ class IdentityTests(object):
self.assertIn(CONF.member_role_id, role_list)
def test_password_hashed(self):
- user_ref = self.identity_api._get_user(self.user_foo['id'])
+ driver = self.identity_api._select_identity_driver(
+ self.user_foo['domain_id'])
+ user_ref = driver._get_user(self.user_foo['id'])
self.assertNotEqual(user_ref['password'], self.user_foo['password'])
def test_create_unicode_user_name(self):
@@ -1521,7 +1523,8 @@ class IdentityTests(object):
self.assertRaises(exception.UserNotFound,
self.identity_api.update_user,
user_id,
- {'id': user_id})
+ {'id': user_id,
+ 'domain_id': DEFAULT_DOMAIN_ID})
def test_delete_user_with_project_association(self):
user = {'id': uuid.uuid4().hex,
@@ -1628,7 +1631,7 @@ class IdentityTests(object):
tenant)
def test_create_user_long_name_fails(self):
- user = {'id': 'fake1', 'name': 'a' * 65,
+ user = {'id': 'fake1', 'name': 'a' * 256,
'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
@@ -1701,7 +1704,7 @@ class IdentityTests(object):
user = {'id': 'fake1', 'name': 'fake1',
'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_user('fake1', user)
- user['name'] = 'a' * 65
+ user['name'] = 'a' * 256
self.assertRaises(exception.ValidationError,
self.identity_api.update_user,
'fake1',
diff --git a/keystone/tests/test_backend_ldap.py b/keystone/tests/test_backend_ldap.py
index 9c1c98d5..23379712 100644
--- a/keystone/tests/test_backend_ldap.py
+++ b/keystone/tests/test_backend_ldap.py
@@ -17,6 +17,8 @@
import uuid
+import ldap
+
from keystone import assignment
from keystone.common.ldap import fakeldap
from keystone.common import sql
@@ -38,8 +40,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
return self.identity_api.get_domain(CONF.identity.default_domain_id)
def clear_database(self):
- db = fakeldap.FakeShelve().get_instance()
- db.clear()
+ for shelf in fakeldap.FakeShelves:
+ fakeldap.FakeShelves[shelf].clear()
+
+ def reload_backends(self, domain_id):
+ # Only one backend unless we are using separate domain backends
+ self.load_backends()
+
+ def get_config(self, domain_id):
+ # Only one conf structure unless we are using separate domain backends
+ return CONF
def _set_config(self):
self.config([test.etcdir('keystone.conf.sample'),
@@ -57,6 +67,7 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
user = {'id': 'fake1',
'name': 'fake1',
'password': 'fakepass1',
+ 'domain_id': CONF.identity.default_domain_id,
'tenants': ['bar']}
self.identity_api.create_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
@@ -71,14 +82,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
'fake1')
def test_configurable_forbidden_user_actions(self):
- CONF.ldap.user_allow_create = False
- CONF.ldap.user_allow_update = False
- CONF.ldap.user_allow_delete = False
- self.load_backends()
+ conf = self.get_config(CONF.identity.default_domain_id)
+ conf.ldap.user_allow_create = False
+ conf.ldap.user_allow_update = False
+ conf.ldap.user_allow_delete = False
+ self.reload_backends(CONF.identity.default_domain_id)
user = {'id': 'fake1',
'name': 'fake1',
'password': 'fakepass1',
+ 'domain_id': CONF.identity.default_domain_id,
'tenants': ['bar']}
self.assertRaises(exception.ForbiddenAction,
self.identity_api.create_user,
@@ -100,8 +113,9 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
self.user_foo.pop('password')
self.assertDictEqual(user_ref, self.user_foo)
- CONF.ldap.user_filter = '(CN=DOES_NOT_MATCH)'
- self.load_backends()
+ conf = self.get_config(user_ref['domain_id'])
+ conf.ldap.user_filter = '(CN=DOES_NOT_MATCH)'
+ self.reload_backends(user_ref['domain_id'])
self.assertRaises(exception.UserNotFound,
self.identity_api.get_user,
self.user_foo['id'])
@@ -205,18 +219,21 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
# Create a group
group_id = None
- group = dict(name=uuid.uuid4().hex)
+ group = dict(name=uuid.uuid4().hex,
+ domain_id=CONF.identity.default_domain_id)
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 = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex,
+ domain_id=CONF.identity.default_domain_id)
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 = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex,
+ domain_id=CONF.identity.default_domain_id)
user_2_id = self.identity_api.create_user(user_id, user)['id']
self.identity_api.add_user_to_group(user_2_id, group_id)
@@ -224,7 +241,9 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
# 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)
+ driver = self.identity_api._select_identity_driver(
+ user['domain_id'])
+ driver.user.delete(user_2_id)
# List group users and verify only user 1.
res = self.identity_api.list_users_in_group(group_id)
@@ -249,13 +268,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
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
+ driver = self.identity_api._select_identity_driver(
+ user['domain_id'])
+ driver.user.LDAP_USER = None
+ driver.user.LDAP_PASSWORD = None
self.assertRaises(AssertionError,
self.identity_api.authenticate,
user_id=user['id'],
- password=None)
+ password=None,
+ domain_scope=user['domain_id'])
# (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
@@ -454,24 +476,56 @@ class LDAPIdentity(test.TestCase, BaseLDAPIdentity):
self.assertNotIn('name', role_ref)
def test_user_enable_attribute_mask(self):
- CONF.ldap.user_enabled_attribute = 'enabled'
CONF.ldap.user_enabled_mask = 2
- CONF.ldap.user_enabled_default = 512
+ CONF.ldap.user_enabled_default = '512'
self.clear_database()
- user = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
- self.identity_api.create_user('fake1', user)
+ self.load_backends()
+ self.load_fixtures(default_fixtures)
+
+ ldap_ = self.identity_api.driver.user.get_connection()
+
+ def get_enabled_vals():
+ user_dn = self.identity_api.driver.user._id_to_dn_string('fake1')
+ enabled_attr_name = CONF.ldap.user_enabled_attribute
+
+ res = ldap_.search_s(user_dn,
+ ldap.SCOPE_BASE,
+ query='(sn=fake1)')
+ return res[0][1][enabled_attr_name]
+
+ user = {'id': 'fake1', 'name': 'fake1', 'enabled': True,
+ 'domain_id': CONF.identity.default_domain_id}
+
+ user_ref = self.identity_api.create_user('fake1', user)
+
+ self.assertEqual(user_ref['enabled'], 512)
+ # TODO(blk-u): 512 seems wrong, should it be True?
+
+ enabled_vals = get_enabled_vals()
+ self.assertEqual(enabled_vals, [512])
+
user_ref = self.identity_api.get_user('fake1')
- self.assertEqual(user_ref['enabled'], True)
+ self.assertIs(user_ref['enabled'], True)
user['enabled'] = False
- self.identity_api.update_user('fake1', user)
+ user_ref = self.identity_api.update_user('fake1', user)
+ self.assertIs(user_ref['enabled'], False)
+
+ enabled_vals = get_enabled_vals()
+ self.assertEqual(enabled_vals, [514])
+
user_ref = self.identity_api.get_user('fake1')
- self.assertEqual(user_ref['enabled'], False)
+ self.assertIs(user_ref['enabled'], False)
user['enabled'] = True
- self.identity_api.update_user('fake1', user)
+ user_ref = self.identity_api.update_user('fake1', user)
+ self.assertIs(user_ref['enabled'], True)
+
+ enabled_vals = get_enabled_vals()
+ self.assertEqual(enabled_vals, [512])
+
user_ref = self.identity_api.get_user('fake1')
- self.assertEqual(user_ref['enabled'], True)
+ self.assertIs(user_ref['enabled'], True)
def test_user_api_get_connection_no_user_password(self):
"""Don't bind in case the user and password are blank."""
@@ -510,6 +564,7 @@ class LDAPIdentity(test.TestCase, BaseLDAPIdentity):
'id': 'extra_attributes',
'name': 'EXTRA_ATTRIBUTES',
'password': 'extra',
+ 'domain_id': CONF.identity.default_domain_id
}
self.identity_api.create_user(user['id'], user)
dn, attrs = self.identity_api.driver.user._ldap_get(user['id'])
@@ -743,3 +798,230 @@ class LdapIdentitySqlAssignment(sql.Base, test.TestCase, BaseLDAPIdentity):
def test_role_filter(self):
self.skipTest(
'N/A: Not part of SQL backend')
+
+
+class MultiLDAPandSQLIdentity(sql.Base, test.TestCase, BaseLDAPIdentity):
+ """Class to test common SQL plus individual LDAP backends.
+
+ We define a set of domains and domain-specific backends:
+
+ - A separate LDAP backend for the default domain
+ - A separate LDAP backend for domain1
+ - domain2 shares the same LDAP as domain1, but uses a different
+ tree attach point
+ - An SQL backend for all other domains (which will include domain3
+ and domain4)
+
+ Normally one would expect that the default domain would be handled as
+ part of the "other domains" - however the above provides better
+ test coverage since most of the existing backend tests use the default
+ domain.
+
+ """
+ def setUp(self):
+ super(MultiLDAPandSQLIdentity, self).setUp()
+
+ self._set_config()
+ self.load_backends()
+ self.engine = self.get_engine()
+ sql.ModelBase.metadata.create_all(bind=self.engine)
+ self._setup_domain_test_data()
+
+ # All initial domain data setup complete, time to switch on support
+ # for separate backends per domain.
+
+ self.orig_config_domains_enabled = (
+ config.CONF.identity.domain_specific_drivers_enabled)
+ self.opt_in_group('identity', domain_specific_drivers_enabled=True)
+ self.orig_config_dir = (
+ config.CONF.identity.domain_config_dir)
+ self.opt_in_group('identity', domain_config_dir=test.TESTSDIR)
+ self._set_domain_configs()
+ self.clear_database()
+ self.load_fixtures(default_fixtures)
+
+ def tearDown(self):
+ super(MultiLDAPandSQLIdentity, self).tearDown()
+ self.opt_in_group(
+ 'identity',
+ domain_config_dir=self.orig_config_dir)
+ self.opt_in_group(
+ 'identity',
+ domain_specific_drivers_enabled=self.orig_config_domains_enabled)
+ sql.ModelBase.metadata.drop_all(bind=self.engine)
+ self.engine.dispose()
+ sql.set_global_engine(None)
+
+ def _set_config(self):
+ self.config([test.etcdir('keystone.conf.sample'),
+ test.testsdir('test_overrides.conf'),
+ test.testsdir('backend_multi_ldap_sql.conf')])
+
+ def _setup_domain_test_data(self):
+
+ def create_domain(domain):
+ try:
+ ref = self.assignment_api.create_domain(
+ domain['id'], domain)
+ except exception.Conflict:
+ ref = (
+ self.assignment_api.get_domain_by_name(domain['name']))
+ return ref
+
+ self.domain_default = create_domain(assignment.DEFAULT_DOMAIN)
+ self.domain1 = create_domain(
+ {'id': uuid.uuid4().hex, 'name': 'domain1'})
+ self.domain2 = create_domain(
+ {'id': uuid.uuid4().hex, 'name': 'domain2'})
+ self.domain3 = create_domain(
+ {'id': uuid.uuid4().hex, 'name': 'domain3'})
+ self.domain4 = create_domain(
+ {'id': uuid.uuid4().hex, 'name': 'domain4'})
+
+ def _set_domain_configs(self):
+ # We need to load the domain configs explicitly to ensure the
+ # test overrides are included.
+ self.identity_api.domain_configs._load_config(
+ self.identity_api.assignment_api,
+ [test.etcdir('keystone.conf.sample'),
+ test.testsdir('test_overrides.conf'),
+ test.testsdir('backend_multi_ldap_sql.conf'),
+ test.testsdir('keystone.Default.conf')],
+ 'Default')
+ self.identity_api.domain_configs._load_config(
+ self.identity_api.assignment_api,
+ [test.etcdir('keystone.conf.sample'),
+ test.testsdir('test_overrides.conf'),
+ test.testsdir('backend_multi_ldap_sql.conf'),
+ test.testsdir('keystone.domain1.conf')],
+ 'domain1')
+ self.identity_api.domain_configs._load_config(
+ self.identity_api.assignment_api,
+ [test.etcdir('keystone.conf.sample'),
+ test.testsdir('test_overrides.conf'),
+ test.testsdir('backend_multi_ldap_sql.conf'),
+ test.testsdir('keystone.domain2.conf')],
+ 'domain2')
+
+ def reload_backends(self, domain_id):
+ # Just reload the driver for this domain - which will pickup
+ # any updated cfg
+ self.identity_api.domain_configs.reload_domain_driver(
+ self.identity_api.assignment_api, domain_id)
+
+ def get_config(self, domain_id):
+ # Get the config for this domain, will return CONF
+ # if no specific config defined for this domain
+ return self.identity_api.domain_configs.get_domain_conf(domain_id)
+
+ def test_list_domains(self):
+ self.skipTest(
+ 'N/A: Not relevant for multi ldap testing')
+
+ def test_domain_segregation(self):
+ """Test that separate configs have segregated the domain.
+
+ Test Plan:
+ - Create a user in each of the domains
+ - Make sure that you can only find a given user in its
+ relevant domain
+ - Make sure that for a backend that supports multiple domains
+ you can get the users via any of the domain scopes
+
+ """
+ def create_user(domain_id):
+ user = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'domain_id': domain_id,
+ 'password': uuid.uuid4().hex,
+ 'enabled': True}
+ self.identity_api.create_user(user['id'], user)
+ return user
+
+ userd = create_user(CONF.identity.default_domain_id)
+ user1 = create_user(self.domain1['id'])
+ user2 = create_user(self.domain2['id'])
+ user3 = create_user(self.domain3['id'])
+ user4 = create_user(self.domain4['id'])
+
+ # Now check that I can read user1 with the appropriate domain
+ # scope, but won't find it if the wrong scope is used
+
+ ref = self.identity_api.get_user(
+ userd['id'], domain_scope=CONF.identity.default_domain_id)
+ del userd['password']
+ self.assertDictEqual(ref, userd)
+ self.assertRaises(exception.UserNotFound,
+ self.identity_api.get_user,
+ userd['id'],
+ domain_scope=self.domain1['id'])
+ self.assertRaises(exception.UserNotFound,
+ self.identity_api.get_user,
+ userd['id'],
+ domain_scope=self.domain2['id'])
+ self.assertRaises(exception.UserNotFound,
+ self.identity_api.get_user,
+ userd['id'],
+ domain_scope=self.domain3['id'])
+ self.assertRaises(exception.UserNotFound,
+ self.identity_api.get_user,
+ userd['id'],
+ domain_scope=self.domain4['id'])
+
+ ref = self.identity_api.get_user(
+ user1['id'], domain_scope=self.domain1['id'])
+ del user1['password']
+ self.assertDictEqual(ref, user1)
+ ref = self.identity_api.get_user(
+ user2['id'], domain_scope=self.domain2['id'])
+ del user2['password']
+ self.assertDictEqual(ref, user2)
+
+ # Domains 3 and 4 share the same backend, so you should be
+ # able to see user3 and 4 from either
+
+ ref = self.identity_api.get_user(
+ user3['id'], domain_scope=self.domain3['id'])
+ del user3['password']
+ self.assertDictEqual(ref, user3)
+ ref = self.identity_api.get_user(
+ user4['id'], domain_scope=self.domain4['id'])
+ del user4['password']
+ self.assertDictEqual(ref, user4)
+ ref = self.identity_api.get_user(
+ user3['id'], domain_scope=self.domain4['id'])
+ self.assertDictEqual(ref, user3)
+ ref = self.identity_api.get_user(
+ user4['id'], domain_scope=self.domain3['id'])
+ self.assertDictEqual(ref, user4)
+
+ def test_scanning_of_config_dir(self):
+ """Test the Manager class scans the config directory.
+
+ The setup for the main tests above load the domain configs directly
+ so that the test overrides can be included. This test just makes sure
+ that the standard config directory scanning does pick up the relevant
+ domain config files.
+
+ """
+ # Confirm that config has drivers_enabled as True, which we will
+ # check has been set to False later in this test
+ self.assertTrue(config.CONF.identity.domain_specific_drivers_enabled)
+ self.load_backends()
+ # Execute any command to trigger the lazy loading of domain configs
+ self.identity_api.list_users(domain_scope=self.domain1['id'])
+ # ...and now check the domain configs have been set up
+ self.assertIn('default', self.identity_api.domain_configs)
+ self.assertIn(self.domain1['id'], self.identity_api.domain_configs)
+ self.assertIn(self.domain2['id'], self.identity_api.domain_configs)
+ self.assertNotIn(self.domain3['id'], self.identity_api.domain_configs)
+ self.assertNotIn(self.domain4['id'], self.identity_api.domain_configs)
+
+ # Finally check that a domain specific config contains items from both
+ # the primary config and the domain specific config
+ conf = self.identity_api.domain_configs.get_domain_conf(
+ self.domain1['id'])
+ # This should now be false, as is the default, since this is not
+ # set in the standard primary config file
+ self.assertFalse(conf.identity.domain_specific_drivers_enabled)
+ # ..and make sure a domain-specifc options is also set
+ self.assertEqual(conf.ldap.url, 'fake://memory1')
diff --git a/keystone/tests/test_backend_sql.py b/keystone/tests/test_backend_sql.py
index 773ae862..24159eb6 100644
--- a/keystone/tests/test_backend_sql.py
+++ b/keystone/tests/test_backend_sql.py
@@ -81,7 +81,7 @@ class SqlModels(SqlTests):
def test_user_model(self):
cols = (('id', sql.String, 64),
- ('name', sql.String, 64),
+ ('name', sql.String, 255),
('password', sql.String, 128),
('domain_id', sql.String, 64),
('enabled', sql.Boolean, None),
diff --git a/keystone/tests/test_drivers.py b/keystone/tests/test_drivers.py
index c83c1a89..888b365c 100644
--- a/keystone/tests/test_drivers.py
+++ b/keystone/tests/test_drivers.py
@@ -3,6 +3,7 @@ import unittest2 as unittest
from keystone import assignment
from keystone import catalog
+from keystone.contrib import oauth1
from keystone import exception
from keystone import identity
from keystone import policy
@@ -55,3 +56,7 @@ class TestDrivers(unittest.TestCase):
def test_token_driver_unimplemented(self):
interface = token.Driver()
self.assertInterfaceNotImplemented(interface)
+
+ def test_oauth1_driver_unimplemented(self):
+ interface = oauth1.Driver()
+ self.assertInterfaceNotImplemented(interface)
diff --git a/keystone/tests/test_keystoneclient.py b/keystone/tests/test_keystoneclient.py
index 7e59885d..ff2462f4 100644
--- a/keystone/tests/test_keystoneclient.py
+++ b/keystone/tests/test_keystoneclient.py
@@ -378,6 +378,46 @@ class KeystoneClientTests(object):
client.tokens.authenticate,
token=token_id)
+ def test_disable_tenant_invalidates_token(self):
+ from keystoneclient import exceptions as client_exceptions
+
+ admin_client = self.get_client(admin=True)
+ foo_client = self.get_client(self.user_foo)
+ tenant_bar = admin_client.tenants.get(self.tenant_bar['id'])
+
+ # Disable the tenant.
+ tenant_bar.update(enabled=False)
+
+ # Test that the token has been removed.
+ self.assertRaises(client_exceptions.Unauthorized,
+ foo_client.tokens.authenticate,
+ token=foo_client.auth_token)
+
+ # Test that the user access has been disabled.
+ self.assertRaises(client_exceptions.Unauthorized,
+ self.get_client,
+ self.user_foo)
+
+ def test_delete_tenant_invalidates_token(self):
+ from keystoneclient import exceptions as client_exceptions
+
+ admin_client = self.get_client(admin=True)
+ foo_client = self.get_client(self.user_foo)
+ tenant_bar = admin_client.tenants.get(self.tenant_bar['id'])
+
+ # Delete the tenant.
+ tenant_bar.delete()
+
+ # Test that the token has been removed.
+ self.assertRaises(client_exceptions.Unauthorized,
+ foo_client.tokens.authenticate,
+ token=foo_client.auth_token)
+
+ # Test that the user access has been disabled.
+ self.assertRaises(client_exceptions.Unauthorized,
+ self.get_client,
+ self.user_foo)
+
def test_disable_user_invalidates_token(self):
from keystoneclient import exceptions as client_exceptions
@@ -495,6 +535,15 @@ class KeystoneClientTests(object):
user = client.users.update_tenant(
user=user, tenant=self.tenant_bar['id'])
+ def test_user_create_no_string_password(self):
+ from keystoneclient import exceptions as client_exceptions
+ client = self.get_client(admin=True)
+ self.assertRaises(client_exceptions.BadRequest,
+ client.users.create,
+ name='test_user',
+ password=12345,
+ email=uuid.uuid4().hex)
+
def test_user_create_no_name(self):
from keystoneclient import exceptions as client_exceptions
client = self.get_client(admin=True)
@@ -1165,6 +1214,12 @@ class KcEssex3TestCase(CompatTestCase, KeystoneClientTests):
def test_policy_crud(self):
self.skipTest('N/A due to lack of endpoint CRUD')
+ def test_disable_tenant_invalidates_token(self):
+ self.skipTest('N/A')
+
+ def test_delete_tenant_invalidates_token(self):
+ self.skipTest('N/A')
+
class Kc11TestCase(CompatTestCase, KeystoneClientTests):
def get_checkout(self):
diff --git a/keystone/tests/test_overrides.conf b/keystone/tests/test_overrides.conf
index aac29f26..5cd522b2 100644
--- a/keystone/tests/test_overrides.conf
+++ b/keystone/tests/test_overrides.conf
@@ -14,6 +14,9 @@ driver = keystone.trust.backends.kvs.Trust
[token]
driver = keystone.token.backends.kvs.Token
+[oauth1]
+driver = keystone.contrib.oauth1.backends.kvs.OAuth1
+
[signing]
certfile = ../../examples/pki/certs/signing_cert.pem
keyfile = ../../examples/pki/private/signing_key.pem
diff --git a/keystone/tests/test_s3_token_middleware.py b/keystone/tests/test_s3_token_middleware.py
index ec31f2ac..2d561c10 100644
--- a/keystone/tests/test_s3_token_middleware.py
+++ b/keystone/tests/test_s3_token_middleware.py
@@ -225,9 +225,9 @@ class S3TokenMiddlewareTestUtil(unittest.TestCase):
def test_split_path_invalid_path(self):
try:
s3_token.split_path('o\nn e', 2)
- except ValueError, err:
+ except ValueError as err:
self.assertEquals(str(err), 'Invalid path: o%0An%20e')
try:
s3_token.split_path('o\nn e', 2, 3, True)
- except ValueError, err:
+ except ValueError as err:
self.assertEquals(str(err), 'Invalid path: o%0An%20e')
diff --git a/keystone/tests/test_sql_migrate_extensions.py b/keystone/tests/test_sql_migrate_extensions.py
index 4a529559..f9393cbe 100644
--- a/keystone/tests/test_sql_migrate_extensions.py
+++ b/keystone/tests/test_sql_migrate_extensions.py
@@ -27,6 +27,7 @@ To run these tests against a live database:
"""
from keystone.contrib import example
+from keystone.contrib import oauth1
import test_sql_upgrade
@@ -45,3 +46,65 @@ class SqlUpgradeExampleExtension(test_sql_upgrade.SqlMigrateBase):
self.assertTableColumns('example', ['id', 'type', 'extra'])
self.downgrade(0, repository=self.repo_path)
self.assertTableDoesNotExist('example')
+
+
+class SqlUpgradeOAuth1Extension(test_sql_upgrade.SqlMigrateBase):
+ def repo_package(self):
+ return oauth1
+
+ def test_upgrade(self):
+ self.assertTableDoesNotExist('consumer')
+ self.assertTableDoesNotExist('request_token')
+ self.assertTableDoesNotExist('access_token')
+ self.upgrade(1, repository=self.repo_path)
+ self.assertTableColumns('consumer',
+ ['id',
+ 'description',
+ 'secret',
+ 'extra'])
+ self.assertTableColumns('request_token',
+ ['id',
+ 'request_secret',
+ 'verifier',
+ 'authorizing_user_id',
+ 'requested_project_id',
+ 'requested_roles',
+ 'consumer_id',
+ 'expires_at'])
+ self.assertTableColumns('access_token',
+ ['id',
+ 'access_secret',
+ 'authorizing_user_id',
+ 'project_id',
+ 'requested_roles',
+ 'consumer_id',
+ 'expires_at'])
+
+ def test_downgrade(self):
+ self.upgrade(1, repository=self.repo_path)
+ self.assertTableColumns('consumer',
+ ['id',
+ 'description',
+ 'secret',
+ 'extra'])
+ self.assertTableColumns('request_token',
+ ['id',
+ 'request_secret',
+ 'verifier',
+ 'authorizing_user_id',
+ 'requested_project_id',
+ 'requested_roles',
+ 'consumer_id',
+ 'expires_at'])
+ self.assertTableColumns('access_token',
+ ['id',
+ 'access_secret',
+ 'authorizing_user_id',
+ 'project_id',
+ 'requested_roles',
+ 'consumer_id',
+ 'expires_at'])
+ self.downgrade(0, repository=self.repo_path)
+ self.assertTableDoesNotExist('consumer')
+ self.assertTableDoesNotExist('request_token')
+ self.assertTableDoesNotExist('access_token')
diff --git a/keystone/tests/test_sql_upgrade.py b/keystone/tests/test_sql_upgrade.py
index e904d6a7..0ee63433 100644
--- a/keystone/tests/test_sql_upgrade.py
+++ b/keystone/tests/test_sql_upgrade.py
@@ -556,6 +556,42 @@ class SqlUpgradeTests(SqlMigrateBase):
insert.execute(d)
session.commit()
+ def test_upgrade_31_to_32(self):
+ self.upgrade(32)
+
+ user_table = self.select_table("user")
+ self.assertEquals(user_table.c.name.type.length, 255)
+
+ def test_downgrade_32_to_31(self):
+ self.upgrade(32)
+ session = self.Session()
+ # NOTE(aloga): we need a different metadata object
+ user_table = sqlalchemy.Table('user',
+ sqlalchemy.MetaData(),
+ autoload=True,
+ autoload_with=self.engine)
+ user_id = uuid.uuid4().hex
+ ins = user_table.insert().values(
+ {'id': user_id,
+ 'name': 'a' * 255,
+ 'password': uuid.uuid4().hex,
+ 'enabled': True,
+ 'domain_id': DEFAULT_DOMAIN_ID,
+ 'extra': '{}'})
+ session.execute(ins)
+ session.commit()
+
+ self.downgrade(31)
+ # Check that username has been truncated
+ q = session.query(user_table.c.name)
+ q = q.filter(user_table.c.id == user_id)
+ r = q.one()
+ user_name = r[0]
+ self.assertEquals(len(user_name), 64)
+
+ user_table = self.select_table("user")
+ self.assertEquals(user_table.c.name.type.length, 64)
+
def test_downgrade_to_0(self):
self.upgrade(self.max_version)
@@ -1362,7 +1398,7 @@ class SqlUpgradeTests(SqlMigrateBase):
total = connection.execute("SELECT count(*) "
"from information_schema.TABLES "
"where TABLE_SCHEMA='%(database)s'" %
- locals())
+ dict(database=database))
self.assertTrue(total.scalar() > 0, "No tables found. Wrong schema?")
noninnodb = connection.execute("SELECT table_name "
@@ -1370,7 +1406,7 @@ class SqlUpgradeTests(SqlMigrateBase):
"where TABLE_SCHEMA='%(database)s' "
"and ENGINE!='InnoDB' "
"and TABLE_NAME!='migrate_version'" %
- locals())
+ dict(database=database))
names = [x[0] for x in noninnodb]
self.assertEqual(names, [],
"Non-InnoDB tables exist")
diff --git a/keystone/tests/test_v3_auth.py b/keystone/tests/test_v3_auth.py
index 43f87d98..1f4425ce 100644
--- a/keystone/tests/test_v3_auth.py
+++ b/keystone/tests/test_v3_auth.py
@@ -545,6 +545,67 @@ class TestTokenRevoking(test_v3.RestfulTestCase):
headers={'X-Subject-Token': token},
expected_status=204)
+ def test_disabling_project_revokes_token(self):
+ resp = self.post(
+ '/auth/tokens',
+ body=self.build_authentication_request(
+ user_id=self.user3['id'],
+ password=self.user3['password'],
+ project_id=self.projectA['id']))
+ token = resp.headers.get('X-Subject-Token')
+
+ # confirm token is valid
+ self.head('/auth/tokens',
+ headers={'X-Subject-Token': token},
+ expected_status=204)
+
+ # disable the project, which should invalidate the token
+ self.patch(
+ '/projects/%(project_id)s' % {'project_id': self.projectA['id']},
+ body={'project': {'enabled': False}})
+
+ # user should no longer have access to the project
+ self.head('/auth/tokens',
+ headers={'X-Subject-Token': token},
+ expected_status=401)
+ resp = self.post(
+ '/auth/tokens',
+ body=self.build_authentication_request(
+ user_id=self.user3['id'],
+ password=self.user3['password'],
+ project_id=self.projectA['id']),
+ expected_status=401)
+
+ def test_deleting_project_revokes_token(self):
+ resp = self.post(
+ '/auth/tokens',
+ body=self.build_authentication_request(
+ user_id=self.user3['id'],
+ password=self.user3['password'],
+ project_id=self.projectA['id']))
+ token = resp.headers.get('X-Subject-Token')
+
+ # confirm token is valid
+ self.head('/auth/tokens',
+ headers={'X-Subject-Token': token},
+ expected_status=204)
+
+ # delete the project, which should invalidate the token
+ self.delete(
+ '/projects/%(project_id)s' % {'project_id': self.projectA['id']})
+
+ # user should no longer have access to the project
+ self.head('/auth/tokens',
+ headers={'X-Subject-Token': token},
+ expected_status=401)
+ resp = self.post(
+ '/auth/tokens',
+ body=self.build_authentication_request(
+ user_id=self.user3['id'],
+ password=self.user3['password'],
+ project_id=self.projectA['id']),
+ expected_status=401)
+
def test_deleting_group_grant_revokes_tokens(self):
"""Test deleting a group grant revokes tokens.
diff --git a/keystone/tests/test_v3_oauth1.py b/keystone/tests/test_v3_oauth1.py
new file mode 100644
index 00000000..a0ae5fc6
--- /dev/null
+++ b/keystone/tests/test_v3_oauth1.py
@@ -0,0 +1,574 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import os
+import urlparse
+import uuid
+
+import webtest
+
+from keystone.common import cms
+from keystone import config
+from keystone.contrib import oauth1
+from keystone.contrib.oauth1 import controllers
+from keystone.tests import core
+
+import test_v3
+
+
+OAUTH_PASTE_FILE = 'v3_oauth1-paste.ini'
+CONF = config.CONF
+
+
+class OAuth1Tests(test_v3.RestfulTestCase):
+ def setUp(self):
+ super(OAuth1Tests, self).setUp()
+ self.controller = controllers.OAuthControllerV3()
+ self.base_url = CONF.public_endpoint % CONF + "v3"
+ self._generate_paste_config()
+ self.load_backends()
+ self.admin_app = webtest.TestApp(
+ self.loadapp('v3_oauth1', name='admin'))
+ self.public_app = webtest.TestApp(
+ self.loadapp('v3_oauth1', name='admin'))
+
+ def tearDown(self):
+ os.remove(OAUTH_PASTE_FILE)
+
+ def _generate_paste_config(self):
+ # Generate a file, based on keystone-paste.ini,
+ # that includes oauth_extension in the pipeline
+ old_pipeline = " ec2_extension "
+ new_pipeline = " oauth_extension ec2_extension "
+
+ with open(core.etcdir('keystone-paste.ini'), 'r') as f:
+ contents = f.read()
+ new_contents = contents.replace(old_pipeline, new_pipeline)
+ with open(OAUTH_PASTE_FILE, 'w') as f:
+ f.write(new_contents)
+
+ def _create_single_consumer(self):
+ ref = {'description': uuid.uuid4().hex}
+ resp = self.post(
+ '/OS-OAUTH1/consumers',
+ body={'consumer': ref})
+ return resp.result.get('consumer')
+
+ def _oauth_request(self, consumer, token=None, **kw):
+ return oauth1.Request.from_consumer_and_token(consumer=consumer,
+ token=token,
+ **kw)
+
+ def _create_request_token(self, consumer, role, project_id):
+ params = {'requested_role_ids': role,
+ 'requested_project_id': project_id}
+ headers = {'Content-Type': 'application/json'}
+ url = '/OS-OAUTH1/request_token'
+ oreq = self._oauth_request(
+ consumer=consumer,
+ http_url=self.base_url + url,
+ http_method='POST',
+ parameters=params)
+
+ hmac = oauth1.SignatureMethod_HMAC_SHA1()
+ oreq.sign_request(hmac, consumer, None)
+ headers.update(oreq.to_header())
+ headers.update(params)
+ return url, headers
+
+ def _create_access_token(self, consumer, token):
+ headers = {'Content-Type': 'application/json'}
+ url = '/OS-OAUTH1/access_token'
+ oreq = self._oauth_request(
+ consumer=consumer, token=token,
+ http_method='POST',
+ http_url=self.base_url + url)
+ hmac = oauth1.SignatureMethod_HMAC_SHA1()
+ oreq.sign_request(hmac, consumer, token)
+ headers.update(oreq.to_header())
+ return url, headers
+
+ def _get_oauth_token(self, consumer, token):
+ headers = {'Content-Type': 'application/json'}
+ body = {'auth': {'identity': {'methods': ['oauth1'], 'oauth1': {}}}}
+ url = '/auth/tokens'
+ oreq = self._oauth_request(
+ consumer=consumer, token=token,
+ http_method='POST',
+ http_url=self.base_url + url)
+ hmac = oauth1.SignatureMethod_HMAC_SHA1()
+ oreq.sign_request(hmac, consumer, token)
+ headers.update(oreq.to_header())
+ return url, headers, body
+
+ def _authorize_request_token(self, request_id):
+ return '/OS-OAUTH1/authorize/%s' % (request_id)
+
+
+class ConsumerCRUDTests(OAuth1Tests):
+
+ def test_consumer_create(self):
+ description = uuid.uuid4().hex
+ ref = {'description': description}
+ resp = self.post(
+ '/OS-OAUTH1/consumers',
+ body={'consumer': ref})
+ consumer = resp.result.get('consumer')
+ consumer_id = consumer.get('id')
+ self.assertEqual(consumer.get('description'), description)
+ self.assertIsNotNone(consumer_id)
+ self.assertIsNotNone(consumer.get('secret'))
+
+ def test_consumer_delete(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ resp = self.delete('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': consumer_id})
+ self.assertResponseStatus(resp, 204)
+
+ def test_consumer_get(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ resp = self.get('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': consumer_id})
+ self.assertTrue(resp.result.get('consumer').get('id'), consumer_id)
+
+ def test_consumer_list(self):
+ resp = self.get('/OS-OAUTH1/consumers')
+ entities = resp.result.get('consumers')
+ self.assertIsNotNone(entities)
+ self.assertValidListLinks(resp.result.get('links'))
+
+ def test_consumer_update(self):
+ consumer = self._create_single_consumer()
+ original_id = consumer.get('id')
+ original_description = consumer.get('description')
+ original_secret = consumer.get('secret')
+ update_description = original_description + "_new"
+
+ update_ref = {'description': update_description}
+ update_resp = self.patch('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': original_id},
+ body={'consumer': update_ref})
+ consumer = update_resp.result.get('consumer')
+ self.assertEqual(consumer.get('description'), update_description)
+ self.assertEqual(consumer.get('id'), original_id)
+ self.assertEqual(consumer.get('secret'), original_secret)
+
+ def test_consumer_update_bad_secret(self):
+ consumer = self._create_single_consumer()
+ original_id = consumer.get('id')
+ update_ref = copy.deepcopy(consumer)
+ update_ref['description'] = uuid.uuid4().hex
+ update_ref['secret'] = uuid.uuid4().hex
+ self.patch('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': original_id},
+ body={'consumer': update_ref},
+ expected_status=400)
+
+ def test_consumer_update_bad_id(self):
+ consumer = self._create_single_consumer()
+ original_id = consumer.get('id')
+ original_description = consumer.get('description')
+ update_description = original_description + "_new"
+
+ update_ref = copy.deepcopy(consumer)
+ update_ref['description'] = update_description
+ update_ref['id'] = update_description
+ self.patch('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': original_id},
+ body={'consumer': update_ref},
+ expected_status=400)
+
+ def test_consumer_create_no_description(self):
+ resp = self.post('/OS-OAUTH1/consumers', body={'consumer': {}})
+ consumer = resp.result.get('consumer')
+ consumer_id = consumer.get('id')
+ self.assertEqual(consumer.get('description'), None)
+ self.assertIsNotNone(consumer_id)
+ self.assertIsNotNone(consumer.get('secret'))
+
+ def test_consumer_get_bad_id(self):
+ self.get('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': uuid.uuid4().hex},
+ expected_status=404)
+
+
+class OAuthFlowTests(OAuth1Tests):
+
+ def test_oauth_flow(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ self.consumer = oauth1.Consumer(consumer_id, consumer_secret)
+ self.assertIsNotNone(self.consumer.key)
+
+ url, headers = self._create_request_token(self.consumer,
+ self.role_id,
+ self.project_id)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ request_key = credentials.get('oauth_token')[0]
+ request_secret = credentials.get('oauth_token_secret')[0]
+ self.request_token = oauth1.Token(request_key, request_secret)
+ self.assertIsNotNone(self.request_token.key)
+
+ url = self._authorize_request_token(request_key)
+ resp = self.put(url, expected_status=200)
+ self.verifier = resp.result['token']['oauth_verifier']
+
+ self.request_token.set_verifier(self.verifier)
+ url, headers = self._create_access_token(self.consumer,
+ self.request_token)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ access_key = credentials.get('oauth_token')[0]
+ access_secret = credentials.get('oauth_token_secret')[0]
+ self.access_token = oauth1.Token(access_key, access_secret)
+ self.assertIsNotNone(self.access_token.key)
+
+ url, headers, body = self._get_oauth_token(self.consumer,
+ self.access_token)
+ content = self.post(url, headers=headers, body=body)
+ self.keystone_token_id = content.headers.get('X-Subject-Token')
+ self.keystone_token = content.result.get('token')
+ self.assertIsNotNone(self.keystone_token_id)
+
+
+class AccessTokenCRUDTests(OAuthFlowTests):
+ def test_delete_access_token_dne(self):
+ self.delete('/users/%(user)s/OS-OAUTH1/access_tokens/%(auth)s'
+ % {'user': self.user_id,
+ 'auth': uuid.uuid4().hex},
+ expected_status=404)
+
+ def test_list_no_access_tokens(self):
+ resp = self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens'
+ % {'user_id': self.user_id})
+ entities = resp.result.get('access_tokens')
+ self.assertTrue(len(entities) == 0)
+ self.assertValidListLinks(resp.result.get('links'))
+
+ def test_get_single_access_token(self):
+ self.test_oauth_flow()
+ resp = self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(key)s'
+ % {'user_id': self.user_id,
+ 'key': self.access_token.key})
+ entity = resp.result.get('access_token')
+ self.assertTrue(entity['id'], self.access_token.key)
+ self.assertTrue(entity['consumer_id'], self.consumer.key)
+
+ def test_get_access_token_dne(self):
+ self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(key)s'
+ % {'user_id': self.user_id,
+ 'key': uuid.uuid4().hex},
+ expected_status=404)
+
+ def test_list_all_roles_in_access_token(self):
+ self.test_oauth_flow()
+ resp = self.get('/users/%(id)s/OS-OAUTH1/access_tokens/%(key)s/roles'
+ % {'id': self.user_id,
+ 'key': self.access_token.key})
+ entities = resp.result.get('roles')
+ self.assertTrue(len(entities) > 0)
+ self.assertValidListLinks(resp.result.get('links'))
+
+ def test_get_role_in_access_token(self):
+ self.test_oauth_flow()
+ url = ('/users/%(id)s/OS-OAUTH1/access_tokens/%(key)s/roles/%(role)s'
+ % {'id': self.user_id, 'key': self.access_token.key,
+ 'role': self.role_id})
+ resp = self.get(url)
+ entity = resp.result.get('role')
+ self.assertTrue(entity['id'], self.role_id)
+
+ def test_get_role_in_access_token_dne(self):
+ self.test_oauth_flow()
+ url = ('/users/%(id)s/OS-OAUTH1/access_tokens/%(key)s/roles/%(role)s'
+ % {'id': self.user_id, 'key': self.access_token.key,
+ 'role': uuid.uuid4().hex})
+ self.get(url, expected_status=404)
+
+ def test_list_and_delete_access_tokens(self):
+ self.test_oauth_flow()
+ # List access_tokens should be > 0
+ resp = self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens'
+ % {'user_id': self.user_id})
+ entities = resp.result.get('access_tokens')
+ self.assertTrue(len(entities) > 0)
+ self.assertValidListLinks(resp.result.get('links'))
+
+ # Delete access_token
+ resp = self.delete('/users/%(user)s/OS-OAUTH1/access_tokens/%(auth)s'
+ % {'user': self.user_id,
+ 'auth': self.access_token.key})
+ self.assertResponseStatus(resp, 204)
+
+ # List access_token should be 0
+ resp = self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens'
+ % {'user_id': self.user_id})
+ entities = resp.result.get('access_tokens')
+ self.assertTrue(len(entities) == 0)
+ self.assertValidListLinks(resp.result.get('links'))
+
+
+class AuthTokenTests(OAuthFlowTests):
+
+ def test_keystone_token_is_valid(self):
+ self.test_oauth_flow()
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ r = self.get('/auth/tokens', headers=headers)
+ self.assertValidTokenResponse(r, self.user)
+
+ # now verify the oauth section
+ oauth_section = r.result['token']['OS-OAUTH1']
+ self.assertEquals(oauth_section['access_token_id'],
+ self.access_token.key)
+ self.assertEquals(oauth_section['consumer_id'], self.consumer.key)
+
+ def test_delete_access_token_also_revokes_token(self):
+ self.test_oauth_flow()
+
+ # Delete access token
+ resp = self.delete('/users/%(user)s/OS-OAUTH1/access_tokens/%(auth)s'
+ % {'user': self.user_id,
+ 'auth': self.access_token.key})
+ self.assertResponseStatus(resp, 204)
+
+ # Check Keystone Token no longer exists
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ self.get('/auth/tokens', headers=headers,
+ expected_status=401)
+
+ def test_deleting_consumer_also_deletes_tokens(self):
+ self.test_oauth_flow()
+
+ # Delete consumer
+ consumer_id = self.consumer.key
+ resp = self.delete('/OS-OAUTH1/consumers/%(consumer_id)s'
+ % {'consumer_id': consumer_id})
+ self.assertResponseStatus(resp, 204)
+
+ # List access_token should be 0
+ resp = self.get('/users/%(user_id)s/OS-OAUTH1/access_tokens'
+ % {'user_id': self.user_id})
+ entities = resp.result.get('access_tokens')
+ self.assertEqual(len(entities), 0)
+
+ # Check Keystone Token no longer exists
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ self.head('/auth/tokens', headers=headers,
+ expected_status=401)
+
+ def test_change_user_password_also_deletes_tokens(self):
+ self.test_oauth_flow()
+
+ # delegated keystone token exists
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ r = self.get('/auth/tokens', headers=headers)
+ self.assertValidTokenResponse(r, self.user)
+
+ user = {'password': uuid.uuid4().hex}
+ r = self.patch('/users/%(user_id)s' % {
+ 'user_id': self.user['id']},
+ body={'user': user})
+
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ self.admin_request(path='/auth/tokens', headers=headers,
+ method='GET', expected_status=404)
+
+ def test_deleting_project_also_invalidates_tokens(self):
+ self.test_oauth_flow()
+
+ # delegated keystone token exists
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ r = self.get('/auth/tokens', headers=headers)
+ self.assertValidTokenResponse(r, self.user)
+
+ r = self.delete('/projects/%(project_id)s' % {
+ 'project_id': self.project_id})
+
+ headers = {'X-Subject-Token': self.keystone_token_id,
+ 'X-Auth-Token': self.keystone_token_id}
+ self.admin_request(path='/auth/tokens', headers=headers,
+ method='GET', expected_status=404)
+
+ def test_token_chaining_is_not_allowed(self):
+ self.test_oauth_flow()
+
+ #attempt to re-authenticate (token chain) with the given token
+ path = '/v3/auth/tokens/'
+ auth_data = self.build_authentication_request(
+ token=self.keystone_token_id)
+
+ self.admin_request(
+ path=path,
+ body=auth_data,
+ token=self.keystone_token_id,
+ method='POST',
+ expected_status=403)
+
+ def test_list_keystone_tokens_by_consumer(self):
+ self.test_oauth_flow()
+ tokens = self.token_api.list_tokens(self.user_id,
+ consumer_id=self.consumer.key)
+ keystone_token_uuid = cms.cms_hash_token(self.keystone_token_id)
+ self.assertTrue(len(tokens) > 0)
+ self.assertTrue(keystone_token_uuid in tokens)
+
+
+class MaliciousOAuth1Tests(OAuth1Tests):
+
+ def test_bad_consumer_secret(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer = oauth1.Consumer(consumer_id, "bad_secret")
+ url, headers = self._create_request_token(consumer,
+ self.role_id,
+ self.project_id)
+ self.post(url, headers=headers, expected_status=500)
+
+ def test_bad_request_token_key(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ consumer = oauth1.Consumer(consumer_id, consumer_secret)
+ url, headers = self._create_request_token(consumer,
+ self.role_id,
+ self.project_id)
+ self.post(url, headers=headers)
+ url = self._authorize_request_token("bad_key")
+ self.put(url, expected_status=404)
+
+ def test_bad_verifier(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ consumer = oauth1.Consumer(consumer_id, consumer_secret)
+
+ url, headers = self._create_request_token(consumer,
+ self.role_id,
+ self.project_id)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ request_key = credentials.get('oauth_token')[0]
+ request_secret = credentials.get('oauth_token_secret')[0]
+ request_token = oauth1.Token(request_key, request_secret)
+
+ url = self._authorize_request_token(request_key)
+ resp = self.put(url, expected_status=200)
+ verifier = resp.result['token']['oauth_verifier']
+ self.assertIsNotNone(verifier)
+
+ request_token.set_verifier("bad verifier")
+ url, headers = self._create_access_token(consumer,
+ request_token)
+ self.post(url, headers=headers, expected_status=401)
+
+ def test_bad_requested_roles(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ consumer = oauth1.Consumer(consumer_id, consumer_secret)
+
+ url, headers = self._create_request_token(consumer,
+ "bad_role",
+ self.project_id)
+ self.post(url, headers=headers, expected_status=401)
+
+ def test_bad_authorizing_roles(self):
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ consumer = oauth1.Consumer(consumer_id, consumer_secret)
+
+ url, headers = self._create_request_token(consumer,
+ self.role_id,
+ self.project_id)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ request_key = credentials.get('oauth_token')[0]
+
+ self.identity_api.remove_role_from_user_and_project(self.user_id,
+ self.project_id,
+ self.role_id)
+ url = self._authorize_request_token(request_key)
+ self.admin_request(path=url, method='PUT', expected_status=404)
+
+ def test_expired_authorizing_request_token(self):
+ CONF.oauth1.request_token_duration = -1
+
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ self.consumer = oauth1.Consumer(consumer_id, consumer_secret)
+ self.assertIsNotNone(self.consumer.key)
+
+ url, headers = self._create_request_token(self.consumer,
+ self.role_id,
+ self.project_id)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ request_key = credentials.get('oauth_token')[0]
+ request_secret = credentials.get('oauth_token_secret')[0]
+ self.request_token = oauth1.Token(request_key, request_secret)
+ self.assertIsNotNone(self.request_token.key)
+
+ url = self._authorize_request_token(request_key)
+ self.put(url, expected_status=401)
+
+ def test_expired_creating_keystone_token(self):
+ CONF.oauth1.access_token_duration = -1
+ consumer = self._create_single_consumer()
+ consumer_id = consumer.get('id')
+ consumer_secret = consumer.get('secret')
+ self.consumer = oauth1.Consumer(consumer_id, consumer_secret)
+ self.assertIsNotNone(self.consumer.key)
+
+ url, headers = self._create_request_token(self.consumer,
+ self.role_id,
+ self.project_id)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ request_key = credentials.get('oauth_token')[0]
+ request_secret = credentials.get('oauth_token_secret')[0]
+ self.request_token = oauth1.Token(request_key, request_secret)
+ self.assertIsNotNone(self.request_token.key)
+
+ url = self._authorize_request_token(request_key)
+ resp = self.put(url, expected_status=200)
+ self.verifier = resp.result['token']['oauth_verifier']
+
+ self.request_token.set_verifier(self.verifier)
+ url, headers = self._create_access_token(self.consumer,
+ self.request_token)
+ content = self.post(url, headers=headers)
+ credentials = urlparse.parse_qs(content.result)
+ access_key = credentials.get('oauth_token')[0]
+ access_secret = credentials.get('oauth_token_secret')[0]
+ self.access_token = oauth1.Token(access_key, access_secret)
+ self.assertIsNotNone(self.access_token.key)
+
+ url, headers, body = self._get_oauth_token(self.consumer,
+ self.access_token)
+ self.post(url, headers=headers, body=body, expected_status=401)
diff --git a/keystone/token/backends/kvs.py b/keystone/token/backends/kvs.py
index c3c3e769..b2c6ed30 100644
--- a/keystone/token/backends/kvs.py
+++ b/keystone/token/backends/kvs.py
@@ -17,8 +17,8 @@
import copy
from keystone.common import kvs
-from keystone.common import logging
from keystone import exception
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
from keystone import token
@@ -90,6 +90,29 @@ class Token(kvs.Base, token.Driver):
tokens.append(token.split('-', 1)[1])
return tokens
+ def _consumer_matches(self, consumer_id, token_ref_dict):
+ if consumer_id is None:
+ return True
+ else:
+ if 'token_data' in token_ref_dict:
+ token_data = token_ref_dict.get('token_data')
+ if 'token' in token_data:
+ token = token_data.get('token')
+ oauth = token.get('OS-OAUTH1')
+ if oauth and oauth.get('consumer_id') == consumer_id:
+ return True
+ return False
+
+ def _list_tokens_for_consumer(self, consumer_id):
+ tokens = []
+ now = timeutils.utcnow()
+ for token, ref in self.db.items():
+ if not token.startswith('token-') or self.is_expired(now, ref):
+ continue
+ if self._consumer_matches(consumer_id, ref):
+ tokens.append(token.split('-', 1)[1])
+ return tokens
+
def _list_tokens_for_user(self, user_id, tenant_id=None):
def user_matches(user_id, ref):
return ref.get('user') and ref['user'].get('id') == user_id
@@ -110,9 +133,12 @@ class Token(kvs.Base, token.Driver):
tokens.append(token.split('-', 1)[1])
return tokens
- def list_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
if trust_id:
return self._list_tokens_for_trust(trust_id)
+ if consumer_id:
+ return self._list_tokens_for_consumer(consumer_id)
else:
return self._list_tokens_for_user(user_id, tenant_id)
diff --git a/keystone/token/backends/memcache.py b/keystone/token/backends/memcache.py
index 06e89d60..b80d01bc 100644
--- a/keystone/token/backends/memcache.py
+++ b/keystone/token/backends/memcache.py
@@ -19,18 +19,16 @@ import copy
import memcache
-from keystone.common import logging
from keystone.common import utils
from keystone import config
from keystone import exception
from keystone.openstack.common import jsonutils
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
from keystone import token
CONF = config.CONF
-config.register_str('servers', group='memcache', default='localhost:11211')
-config.register_int('max_compare_and_set_retry', group='memcache', default=16)
LOG = logging.getLogger(__name__)
@@ -180,7 +178,8 @@ class Token(token.Driver):
self._add_to_revocation_list(data)
return result
- def list_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
tokens = []
user_key = self._prefix_user_id(user_id)
user_record = self.client.get(user_key) or ""
@@ -201,6 +200,13 @@ class Token(token.Driver):
continue
if trust != trust_id:
continue
+ if consumer_id is not None:
+ try:
+ oauth = token_ref['token_data']['token']['OS-OAUTH1']
+ if oauth.get('consumer_id') != consumer_id:
+ continue
+ except KeyError:
+ continue
tokens.append(token_id)
return tokens
diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py
index 82eab651..5d24fb4f 100644
--- a/keystone/token/backends/sql.py
+++ b/keystone/token/backends/sql.py
@@ -78,7 +78,8 @@ class Token(sql.Base, token.Driver):
token_ref.valid = False
session.flush()
- def delete_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def delete_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
"""Deletes all tokens in one session
The user_id will be ignored if the trust_id is specified. user_id
@@ -103,6 +104,11 @@ class Token(sql.Base, token.Driver):
token_ref_dict = token_ref.to_dict()
if not self._tenant_matches(tenant_id, token_ref_dict):
continue
+ if consumer_id:
+ token_ref_dict = token_ref.to_dict()
+ if not self._consumer_matches(consumer_id, token_ref_dict):
+ continue
+
token_ref.valid = False
session.flush()
@@ -112,6 +118,13 @@ class Token(sql.Base, token.Driver):
(token_ref_dict.get('tenant') and
token_ref_dict['tenant'].get('id') == tenant_id))
+ def _consumer_matches(self, consumer_id, token_ref_dict):
+ if consumer_id is None:
+ return True
+ else:
+ oauth = token_ref_dict['token_data']['token'].get('OS-OAUTH1', {})
+ return oauth and oauth['consumer_id'] == consumer_id
+
def _list_tokens_for_trust(self, trust_id):
session = self.get_session()
tokens = []
@@ -141,9 +154,29 @@ class Token(sql.Base, token.Driver):
tokens.append(token_ref['id'])
return tokens
- def list_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def _list_tokens_for_consumer(self, user_id, consumer_id):
+ tokens = []
+ session = self.get_session()
+ with session.begin():
+ now = timeutils.utcnow()
+ query = session.query(TokenModel)
+ query = query.filter(TokenModel.expires > now)
+ query = query.filter(TokenModel.user_id == user_id)
+ token_references = query.filter_by(valid=True)
+
+ for token_ref in token_references:
+ token_ref_dict = token_ref.to_dict()
+ if self._consumer_matches(consumer_id, token_ref_dict):
+ tokens.append(token_ref_dict['id'])
+ session.flush()
+ return tokens
+
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
if trust_id:
return self._list_tokens_for_trust(trust_id)
+ if consumer_id:
+ return self._list_tokens_for_consumer(user_id, consumer_id)
else:
return self._list_tokens_for_user(user_id, tenant_id)
diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py
index 91514493..954ff8e8 100644
--- a/keystone/token/controllers.py
+++ b/keystone/token/controllers.py
@@ -3,10 +3,10 @@ import json
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.openstack.common import log as logging
from keystone.openstack.common import timeutils
from keystone.token import core
from keystone.token import provider as token_provider
diff --git a/keystone/token/core.py b/keystone/token/core.py
index bc27b80d..7eadbe63 100644
--- a/keystone/token/core.py
+++ b/keystone/token/core.py
@@ -21,15 +21,15 @@ import datetime
from keystone.common import cms
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
CONF = config.CONF
-config.register_int('expiration', group='token', default=86400)
+
LOG = logging.getLogger(__name__)
@@ -174,41 +174,51 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def delete_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def delete_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
"""Deletes tokens by user.
If the tenant_id is not None, only delete the tokens by user id under
the specified tenant.
If the trust_id is not None, it will be used to query tokens and the
user_id will be ignored.
+ If the consumer_id is not None, only delete the tokens by consumer id
+ that match the specified consumer id
:param user_id: identity of user
:type user_id: string
:param tenant_id: identity of the tenant
:type tenant_id: string
- :param trust_id: identified of the trust
+ :param trust_id: identity of the trust
:type trust_id: string
+ :param consumer_id: identity of the consumer
+ :type consumer_id: string
:returns: None.
:raises: keystone.exception.TokenNotFound
"""
token_list = self.list_tokens(user_id,
tenant_id=tenant_id,
- trust_id=trust_id)
+ trust_id=trust_id,
+ consumer_id=consumer_id)
+
for token in token_list:
try:
self.delete_token(token)
except exception.NotFound:
pass
- def list_tokens(self, user_id, tenant_id=None, trust_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None,
+ consumer_id=None):
"""Returns a list of current token_id's for a user
:param user_id: identity of the user
:type user_id: string
:param tenant_id: identity of the tenant
:type tenant_id: string
- :param trust_id: identified of the trust
+ :param trust_id: identity of the trust
:type trust_id: string
+ :param consumer_id: identity of the consumer
+ :type consumer_id: string
:returns: list of token_id's
"""
diff --git a/keystone/token/provider.py b/keystone/token/provider.py
index 2864be6f..f2acb0e1 100644
--- a/keystone/token/provider.py
+++ b/keystone/token/provider.py
@@ -18,10 +18,10 @@
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
CONF = config.CONF
diff --git a/keystone/token/providers/pki.py b/keystone/token/providers/pki.py
index 81abe5d4..64dde473 100644
--- a/keystone/token/providers/pki.py
+++ b/keystone/token/providers/pki.py
@@ -20,9 +20,9 @@ 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.openstack.common import log as logging
from keystone.token.providers import uuid
diff --git a/keystone/token/providers/uuid.py b/keystone/token/providers/uuid.py
index acfa9372..612df999 100644
--- a/keystone/token/providers/uuid.py
+++ b/keystone/token/providers/uuid.py
@@ -18,6 +18,7 @@
from __future__ import absolute_import
+import json
import sys
import uuid
@@ -206,12 +207,23 @@ class V3TokenDataHelper(object):
'domain': self._get_filtered_domain(user_ref['domain_id'])}
token_data['user'] = filtered_user
+ def _populate_oauth_section(self, token_data, access_token):
+ if access_token:
+ access_token_id = access_token['id']
+ consumer_id = access_token['consumer_id']
+ token_data['OS-OAUTH1'] = ({'access_token_id': access_token_id,
+ 'consumer_id': consumer_id})
+
def _populate_roles(self, token_data, user_id, domain_id, project_id,
- trust):
+ trust, access_token):
if 'roles' in token_data:
# no need to repopulate roles
return
+ if access_token:
+ token_data['roles'] = json.loads(access_token['requested_roles'])
+ return
+
if CONF.trust.enabled and trust:
token_user_id = trust['trustor_user_id']
token_project_id = trust['project_id']
@@ -288,7 +300,7 @@ class V3TokenDataHelper(object):
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):
+ bind=None, access_token=None):
token_data = {'methods': method_names,
'extras': extras}
@@ -307,15 +319,17 @@ class V3TokenDataHelper(object):
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_roles(token_data, user_id, domain_id, project_id, trust,
+ access_token)
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)
+ self._populate_oauth_section(token_data, access_token)
return {'token': token_data}
-@dependency.requires('token_api', 'identity_api', 'catalog_api')
+@dependency.requires('token_api', 'identity_api', 'catalog_api', 'oauth_api')
class Provider(token.provider.Provider):
def __init__(self, *args, **kwargs):
super(Provider, self).__init__(*args, **kwargs)
@@ -380,6 +394,12 @@ class Provider(token.provider.Provider):
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'])
+
+ access_token = None
+ if 'oauth1' in method_names:
+ access_token_id = auth_context['access_token_id']
+ access_token = self.oauth_api.get_access_token(access_token_id)
+
token_data = self.v3_token_data_helper.get_token_data(
user_id,
method_names,
@@ -389,7 +409,8 @@ class Provider(token.provider.Provider):
expires=expires_at,
trust=trust,
bind=auth_context.get('bind') if auth_context else None,
- include_catalog=include_catalog)
+ include_catalog=include_catalog,
+ access_token=access_token)
token_id = self._get_token_id(token_data)
try:
diff --git a/keystone/trust/controllers.py b/keystone/trust/controllers.py
index 7a94fe29..3d8df459 100644
--- a/keystone/trust/controllers.py
+++ b/keystone/trust/controllers.py
@@ -2,10 +2,10 @@ import uuid
from keystone.common import controller
from keystone.common import dependency
-from keystone.common import logging
from keystone import config
from keystone import exception
from keystone import identity
+from keystone.openstack.common import log as logging
from keystone.openstack.common import timeutils
diff --git a/keystone/trust/core.py b/keystone/trust/core.py
index 5c4fc90f..e4ff74de 100644
--- a/keystone/trust/core.py
+++ b/keystone/trust/core.py
@@ -17,10 +17,10 @@
"""Main entry point into the Identity service."""
from keystone.common import dependency
-from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
+from keystone.openstack.common import log as logging
CONF = config.CONF