summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/keystone.conf.sample3
-rw-r--r--etc/policy.json20
-rw-r--r--keystone/auth/controllers.py111
-rw-r--r--keystone/auth/core.py28
-rw-r--r--keystone/auth/methods/token.py18
-rw-r--r--keystone/auth/token_factory.py130
-rw-r--r--keystone/cli.py1
-rw-r--r--keystone/common/controller.py86
-rw-r--r--keystone/common/ldap/core.py104
-rw-r--r--keystone/common/models.py15
-rw-r--r--keystone/common/serializer.py34
-rw-r--r--keystone/common/sql/migrate_repo/versions/018_add_trust_tables.py68
-rw-r--r--keystone/common/wsgi.py2
-rw-r--r--keystone/config.py9
-rw-r--r--keystone/exception.py4
-rw-r--r--keystone/identity/backends/ldap/core.py33
-rw-r--r--keystone/identity/backends/sql.py17
-rw-r--r--keystone/identity/controllers.py35
-rw-r--r--keystone/locale/keystone.pot194
-rw-r--r--keystone/middleware/core.py3
-rw-r--r--keystone/openstack/common/timeutils.py67
-rw-r--r--keystone/service.py6
-rw-r--r--keystone/test.py2
-rw-r--r--keystone/token/backends/kvs.py16
-rw-r--r--keystone/token/backends/memcache.py11
-rw-r--r--keystone/token/backends/sql.py20
-rw-r--r--keystone/token/controllers.py71
-rw-r--r--keystone/token/core.py6
-rw-r--r--keystone/trust/__init__.py19
-rw-r--r--keystone/trust/backends/__init__.py0
-rw-r--r--keystone/trust/backends/kvs.py92
-rw-r--r--keystone/trust/backends/sql.py123
-rw-r--r--keystone/trust/controllers.py244
-rw-r--r--keystone/trust/core.py63
-rw-r--r--keystone/trust/routers.py58
-rwxr-xr-xrun_tests.sh18
-rw-r--r--tests/__init__.py41
-rw-r--r--tests/backend_sql.conf3
-rw-r--r--tests/default_fixtures.py7
-rw-r--r--tests/test_auth.py339
-rw-r--r--tests/test_auth_plugin.py8
-rw-r--r--tests/test_backend.py88
-rw-r--r--tests/test_backend_kvs.py10
-rw-r--r--tests/test_backend_sql.py8
-rw-r--r--tests/test_content_types.py25
-rw-r--r--tests/test_overrides.conf3
-rw-r--r--tests/test_serializer.py11
-rw-r--r--tests/test_sql_upgrade.py12
-rw-r--r--tests/test_v3.py527
-rw-r--r--tests/test_v3_auth.py434
-rw-r--r--tests/test_v3_catalog.py46
-rw-r--r--tests/test_v3_identity.py139
-rw-r--r--tests/test_v3_policy.py24
-rw-r--r--tests/test_v3_protection.py31
-rw-r--r--tests/test_v3_trust.py286
-rw-r--r--tools/pip-requires2
56 files changed, 3010 insertions, 765 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample
index 8c49f68e..808cff75 100644
--- a/etc/keystone.conf.sample
+++ b/etc/keystone.conf.sample
@@ -137,6 +137,7 @@
# use_dumb_member = False
# allow_subtree_delete = False
# dumb_member = cn=dumb,dc=example,dc=com
+# page_size = 0
# The LDAP scope for queries, this can be either 'one'
# (onelevel/singleLevel) or 'sub' (subtree/wholeSubtree)
@@ -145,6 +146,7 @@
# user_tree_dn = ou=Users,dc=example,dc=com
# user_filter =
# user_objectclass = inetOrgPerson
+# user_domain_id_attribute = businessCategory
# user_id_attribute = cn
# user_name_attribute = sn
# user_mail_attribute = email
@@ -162,6 +164,7 @@
# tenant_tree_dn = ou=Groups,dc=example,dc=com
# tenant_filter =
# tenant_objectclass = groupOfNames
+# tenant_domain_id_attribute = businessCategory
# tenant_id_attribute = cn
# tenant_member_attribute = member
# tenant_name_attribute = ou
diff --git a/etc/policy.json b/etc/policy.json
index a0e77fc2..89365e5e 100644
--- a/etc/policy.json
+++ b/etc/policy.json
@@ -1,5 +1,9 @@
{
"admin_required": [["role:admin"], ["is_admin:1"]],
+ "owner" : [["user_id:%(user_id)s"]],
+ "admin_or_owner": [["rule:admin_required"], ["rule:owner"]],
+
+ "default": [["rule:admin_required"]],
"identity:get_service": [["rule:admin_required"]],
"identity:list_services": [["rule:admin_required"]],
@@ -21,8 +25,9 @@
"identity:get_project": [["rule:admin_required"]],
"identity:list_projects": [["rule:admin_required"]],
- "identity:list_user_projects": [["rule:admin_required"], ["user_id:%(user_id)s"]],
- "identity:create_project": [["rule:admin_required"]],
+ "identity:list_user_projects": [["rule:admin_required"],
+ ["user_id:%(user_id)s"]],
+ "identity:create_project": [["rule:admin_or_owner"]],
"identity:update_project": [["rule:admin_required"]],
"identity:delete_project": [["rule:admin_required"]],
@@ -68,5 +73,14 @@
"identity:check_token": [["rule:admin_required"]],
"identity:validate_token": [["rule:admin_required"]],
"identity:revocation_list": [["rule:admin_required"]],
- "identity:revoke_token": [["rule:admin_required"], ["user_id:%(user_id)s"]]
+ "identity:revoke_token": [["rule:admin_required"],
+ ["user_id:%(user_id)s"]],
+
+ "identity:create_trust": [["user_id:%(trust.trustor_user_id)s"]],
+ "identity:get_trust": [["rule:admin_or_owner"]],
+ "identity:list_trusts": [["@"]],
+ "identity:list_roles_for_trust": [["@"]],
+ "identity:check_role_for_trust": [["@"]],
+ "identity:get_role_for_trust": [["@"]],
+ "identity:delete_trust": [["@"]]
}
diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py
index d2eaa234..96f3dc0c 100644
--- a/keystone/auth/controllers.py
+++ b/keystone/auth/controllers.py
@@ -24,6 +24,7 @@ from keystone import config
from keystone import exception
from keystone import identity
from keystone import token
+from keystone import trust
from keystone.openstack.common import importutils
@@ -63,13 +64,15 @@ class AuthInfo(object):
def __init__(self, context, auth=None):
self.identity_api = identity.Manager()
+ self.trust_api = trust.Manager()
self.context = context
self.auth = auth
- self._scope_data = (None, None)
- # self._scope_data is (domain_id, project_id)
- # project scope: (None, project_id)
- # domain scope: (domain_id, None)
- # unscoped: (None, None)
+ self._scope_data = (None, None, None)
+ # self._scope_data is (domain_id, project_id, trust_ref)
+ # project scope: (None, project_id, None)
+ # domain scope: (domain_id, None, None)
+ # trust scope: (None, None, trust_id)
+ # unscoped: (None, None, None)
self._validate_and_normalize_auth_data()
def _assert_project_is_enabled(self, project_ref):
@@ -136,6 +139,16 @@ class AuthInfo(object):
self._assert_project_is_enabled(project_ref)
return project_ref
+ def _lookup_trust(self, trust_info):
+ trust_id = trust_info.get('id')
+ if not trust_id:
+ raise exception.ValidationError(attribute='trust_id',
+ target='trust')
+ trust = self.trust_api.get_trust(self.context, trust_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+ return trust
+
def lookup_user(self, user_info):
user_id = user_info.get('id')
user_name = user_info.get('name')
@@ -165,37 +178,40 @@ class AuthInfo(object):
""" Validate and normalize scope data """
if 'scope' not in self.auth:
return
-
- # if scoped, only to a project or domain, but not both
- if ('project' not in self.auth['scope'] and
- 'domain' not in self.auth['scope']):
- # neither domain or project provided
- raise exception.ValidationError(attribute='project or domain',
- target='scope')
- if ('project' in self.auth['scope'] and
- 'domain' in self.auth['scope']):
- # both domain and project provided
- raise exception.ValidationError(attribute='project or domain',
- target='scope')
+ if sum(['project' in self.auth['scope'],
+ 'domain' in self.auth['scope'],
+ 'trust' in self.auth['scope']]) != 1:
+ raise exception.ValidationError(
+ attribute='project, domain, or trust',
+ target='scope')
if 'project' in self.auth['scope']:
project_ref = self._lookup_project(self.auth['scope']['project'])
- self._scope_data = (None, project_ref['id'])
- else:
+ self._scope_data = (None, project_ref['id'], None)
+ elif 'domain' in self.auth['scope']:
domain_ref = self._lookup_domain(self.auth['scope']['domain'])
- self._scope_data = (domain_ref['id'], None)
+ self._scope_data = (domain_ref['id'], None, None)
+ elif 'trust' in self.auth['scope']:
+ trust_ref = self._lookup_trust(self.auth['scope']['trust'])
+ #TODO ayoung when trusts support domain, Fill in domain data here
+ if 'project_id' in trust_ref:
+ project_ref = self._lookup_project(
+ {'id': trust_ref['project_id']})
+ self._scope_data = (None, project_ref['id'], trust_ref)
+ else:
+ self._scope_data = (None, None, trust_ref)
def _validate_auth_methods(self):
# make sure auth methods are provided
- if 'methods' not in self.auth['authentication']:
+ if 'methods' not in self.auth['identity']:
raise exception.ValidationError(attribute='methods',
- target='authentication')
+ target='identity')
# make sure all the method data/payload are provided
for method_name in self.get_method_names():
- if method_name not in self.auth['authentication']:
+ if method_name not in self.auth['identity']:
raise exception.ValidationError(attribute=method_name,
- target='authentication')
+ target='identity')
# make sure auth method is supported
for method_name in self.get_method_names():
@@ -213,12 +229,12 @@ class AuthInfo(object):
self._validate_and_normalize_scope_data()
def get_method_names(self):
- """ Returns the authentication method names.
+ """ Returns the identity method names.
:returns: list of auth method names
"""
- return self.auth['authentication']['methods']
+ return self.auth['identity']['methods']
def get_method_data(self, method):
""" Get the auth method payload.
@@ -226,30 +242,41 @@ class AuthInfo(object):
:returns: auth method payload
"""
- if method not in self.auth['authentication']['methods']:
+ if method not in self.auth['identity']['methods']:
raise exception.ValidationError(attribute=method_name,
- target='authentication')
- return self.auth['authentication'][method]
+ target='identity')
+ return self.auth['identity'][method]
def get_scope(self):
""" Get scope information.
Verify and return the scoping information.
- :returns: (domain_id, project_id). If scope to a project,
- (None, project_id) will be returned. If scope to a domain,
- (domain_id, None) will be returned. If unscope,
- (None, None) will be returned.
+ :returns: (domain_id, project_id, trust_ref).
+ If scope to a project, (None, project_id, None)
+ will be returned.
+ If scoped to a domain, (domain_id, None,None)
+ will be returned.
+ If scoped to a trust, (None, project_id, trust_ref),
+ Will be returned, where the project_id comes from the
+ trust definition.
+ If unscoped, (None, None, None) will be returned.
"""
return self._scope_data
- def set_scope(self, domain_id=None, project_id=None):
+ def set_scope(self, domain_id=None, project_id=None, trust=None):
""" Set scope information. """
if domain_id and project_id:
msg = _('Scoping to both domain and project is not allowed')
raise ValueError(msg)
- self._scope_data = (domain_id, project_id)
+ if domain_id and trust:
+ msg = _('Scoping to both domain and trust is not allowed')
+ raise ValueError(msg)
+ if project_id and trust:
+ msg = _('Scoping to both project and trust is not allowed')
+ raise ValueError(msg)
+ self._scope_data = (domain_id, project_id, trust)
class Auth(controller.V3Controller):
@@ -257,13 +284,9 @@ class Auth(controller.V3Controller):
super(Auth, self).__init__(*args, **kw)
self.token_controllers_ref = token.controllers.Auth()
- def authenticate_for_token(self, context, authentication, scope=None):
+ def authenticate_for_token(self, context, auth=None):
""" Authenticate user and issue a token. """
try:
- auth = None
- auth = {'authentication': authentication}
- if scope:
- auth['scope'] = scope
auth_info = AuthInfo(context, auth=auth)
auth_context = {'extras': {}, 'method_names': []}
self.authenticate(context, auth_info, auth_context)
@@ -282,8 +305,10 @@ class Auth(controller.V3Controller):
raise exception.Unauthorized(e)
def _check_and_set_default_scoping(self, context, auth_info, auth_context):
- (domain_id, project_id) = auth_info.get_scope()
- if domain_id or project_id:
+ (domain_id, project_id, trust) = auth_info.get_scope()
+ if trust:
+ project_id = trust['project_id']
+ if domain_id or project_id or trust:
# scope is specified
return
@@ -306,7 +331,7 @@ class Auth(controller.V3Controller):
# requiring domain_id to do user lookup now. Try to get
# the user_id from auth_info for now, assuming external auth
# has check to make sure user is the same as the one specify
- # in "authentication".
+ # in "identity".
if 'password' in auth_info.get_method_names():
user_info = auth_info.get_method_data('password')
user_ref = auth_info.lookup_user(user_info['user'])
diff --git a/keystone/auth/core.py b/keystone/auth/core.py
index 40f7d040..da70c43c 100644
--- a/keystone/auth/core.py
+++ b/keystone/auth/core.py
@@ -49,21 +49,23 @@ class AuthMethodHandler(object):
"extras": {}}
Plugins are invoked in the order in which they are specified in the
- "methods" attribute of the "authentication" request body.
+ "methods" attribute of the "identity" object.
For example, with the following authentication request,
- {"authentication": {
- "methods": ["custom-plugin", "password", "token"],
- "token": {
- "id": "sdfafasdfsfasfasdfds"
- },
- "custom-plugin": {
- "custom-data": "sdfdfsfsfsdfsf"
- },
- "password": {
- "user": {
- "id": "s23sfad1",
- "password": "secrete"
+ {"auth": {
+ "identity": {
+ "methods": ["custom-plugin", "password", "token"],
+ "token": {
+ "id": "sdfafasdfsfasfasdfds"
+ },
+ "custom-plugin": {
+ "custom-data": "sdfdfsfsfsdfsf"
+ },
+ "password": {
+ "user": {
+ "id": "s23sfad1",
+ "password": "secrete"
+ }
}
}
}}
diff --git a/keystone/auth/methods/token.py b/keystone/auth/methods/token.py
index 72006130..bb7b8d58 100644
--- a/keystone/auth/methods/token.py
+++ b/keystone/auth/methods/token.py
@@ -38,12 +38,18 @@ class Token(auth.AuthMethodHandler):
target=METHOD_NAME)
token_id = auth_payload['id']
token_ref = self.token_api.get_token(context, token_id)
- user_context.setdefault('user_id',
- token_ref['token_data']['user']['id'])
- user_context.setdefault('expires',
- token_ref['expires'])
- user_context['extras'].update(token_ref['token_data']['extras'])
- user_context['method_names'] += token_ref['token_data']['methods']
+ user_context.setdefault(
+ 'user_id', token_ref['token_data']['token']['user']['id'])
+ # to support Grizzly-3 to Grizzly-RC1 transition
+ expires_at = token_ref['token_data']['token'].get(
+ 'expires_at', token_ref['token_data']['token'].get('expires'))
+ user_context.setdefault('expires_at', expires_at)
+ user_context['extras'].update(
+ token_ref['token_data']['token']['extras'])
+ user_context['method_names'].extend(
+ token_ref['token_data']['token']['methods'])
+ if 'trust' in token_ref['token_data']:
+ raise exception.Forbidden(e)
except AssertionError as e:
LOG.error(e)
raise exception.Unauthorized(e)
diff --git a/keystone/auth/token_factory.py b/keystone/auth/token_factory.py
index 03d4ed74..8460aec6 100644
--- a/keystone/auth/token_factory.py
+++ b/keystone/auth/token_factory.py
@@ -28,6 +28,7 @@ from keystone import config
from keystone import exception
from keystone import identity
from keystone import token as token_module
+from keystone import trust
from keystone.openstack.common import jsonutils
from keystone.openstack.common import timeutils
@@ -42,6 +43,7 @@ class TokenDataHelper(object):
def __init__(self, context):
self.identity_api = identity.Manager()
self.catalog_api = catalog.Manager()
+ self.trust_api = trust.Manager()
self.context = context
def _get_filtered_domain(self, domain_id):
@@ -100,51 +102,100 @@ class TokenDataHelper(object):
roles = self._get_project_roles_for_user(user_id, project_id)
return roles
- def _populate_user(self, token_data, user_id, domain_id, project_id):
+ def _populate_user(self, token_data, user_id, domain_id, project_id,
+ trust):
user_ref = self.identity_api.get_user(self.context,
user_id)
+ if trust:
+ trustor_user_ref = (self.identity_api.get_user(self.context,
+ trust['trustor_user_id']))
+ if not trustor_user_ref['enabled']:
+ raise exception.Forbidden()
+ if trust['impersonation']:
+ user_ref = trustor_user_ref
+ token_data['trust'] = (
+ {
+ 'id': trust['id'],
+ 'trustor_user': {'id': trust['trustor_user_id']},
+ 'trustee_user': {'id': trust['trustee_user_id']},
+ 'impersonation': trust['impersonation']
+ })
filtered_user = {
'id': user_ref['id'],
'name': user_ref['name'],
'domain': self._get_filtered_domain(user_ref['domain_id'])}
token_data['user'] = filtered_user
- def _populate_roles(self, token_data, user_id, domain_id, project_id):
- if domain_id or project_id:
- roles = self._get_roles_for_user(user_id, domain_id, project_id)
- # we only care about id and name
+ def _populate_roles(self, token_data, user_id, domain_id, project_id,
+ trust):
+ if trust:
+ token_user_id = trust['trustor_user_id']
+ token_project_id = trust['project_id']
+ #trusts do not support domains yet
+ token_domain_id = None
+ else:
+ token_user_id = user_id
+ token_project_id = project_id
+ token_domain_id = domain_id
+
+ if token_domain_id or token_project_id:
+ roles = self._get_roles_for_user(token_user_id,
+ token_domain_id,
+ token_project_id)
filtered_roles = []
- for role in roles:
- filtered_roles.append({'id': role['id'], 'name': role['name']})
+ if trust:
+ for trust_role in trust['roles']:
+ match_roles = [x for x in roles
+ if x['id'] == trust_role['id']]
+ if match_roles:
+ filtered_roles.append(match_roles[0])
+ else:
+ raise exception.Forbidden()
+ else:
+ for role in roles:
+ filtered_roles.append({'id': role['id'],
+ 'name': role['name']})
token_data['roles'] = filtered_roles
def _populate_service_catalog(self, token_data, user_id,
- domain_id, project_id):
+ domain_id, project_id, trust):
+ if trust:
+ user_id = trust['trustor_user_id']
if project_id or domain_id:
- service_catalog = self.catalog_api.get_v3_catalog(
- self.context, user_id, project_id)
+ try:
+ service_catalog = self.catalog_api.get_v3_catalog(
+ self.context, user_id, project_id)
+ #TODO KVS backend needs a sample implementation
+ except exception.NotImplemented:
+ service_catalog = {}
# TODO(gyee): v3 service catalog is not quite completed yet
+ #TODO Enforce Endpoints for trust
token_data['catalog'] = service_catalog
- def _populate_token(self, token_data, expires=None):
+ def _populate_token(self, token_data, expires=None, trust=None):
if not expires:
expires = token_module.default_expire_time()
- if not isinstance(expires, unicode):
- expires = timeutils.isotime(expires)
- token_data['expires'] = expires
- token_data['issued_at'] = timeutils.strtime()
+ if not isinstance(expires, basestring):
+ expires = timeutils.isotime(expires, subsecond=True)
+ token_data['expires_at'] = expires
+ token_data['issued_at'] = timeutils.isotime(subsecond=True)
def get_token_data(self, user_id, method_names, extras,
- domain_id=None, project_id=None, expires=None):
+ domain_id=None, project_id=None, expires=None,
+ trust=None):
token_data = {'methods': method_names,
'extras': extras}
+ if trust:
+ if user_id != trust['trustee_user_id']:
+ raise exception.Forbidden()
+
self._populate_scope(token_data, domain_id, project_id)
- self._populate_user(token_data, user_id, domain_id, project_id)
- self._populate_roles(token_data, user_id, domain_id, project_id)
+ self._populate_user(token_data, user_id, domain_id, project_id, trust)
+ self._populate_roles(token_data, user_id, domain_id, project_id, trust)
self._populate_service_catalog(token_data, user_id, domain_id,
- project_id)
- self._populate_token(token_data, expires)
- return token_data
+ project_id, trust)
+ self._populate_token(token_data, expires, trust)
+ return {'token': token_data}
def recreate_token_data(context, token_data=None, expires=None,
@@ -161,12 +212,17 @@ def recreate_token_data(context, token_data=None, expires=None,
methods = ['password', 'token']
extras = {}
if token_data:
+ # peel the outer layer so its easier to operate
+ token_data = token_data['token']
domain_id = (token_data['domain']['id'] if 'domain' in token_data
else None)
project_id = (token_data['project']['id'] if 'project' in token_data
else None)
if not new_expires:
- new_expires = token_data['expires']
+ # support Grizzly-3 to Grizzly-RC1 transition
+ # tokens issued in G3 has 'expires' instead of 'expires_at'
+ new_expires = token_data.get('expires_at',
+ token_data.get('expires'))
user_id = token_data['user']['id']
methods = token_data['methods']
extras = token_data['extras']
@@ -184,16 +240,18 @@ def recreate_token_data(context, token_data=None, expires=None,
def create_token(context, auth_context, auth_info):
token_data_helper = TokenDataHelper(context)
- (domain_id, project_id) = auth_info.get_scope()
+ (domain_id, project_id, trust) = auth_info.get_scope()
method_names = list(set(auth_info.get_method_names() +
auth_context.get('method_names', [])))
- token_data = token_data_helper.get_token_data(auth_context['user_id'],
- method_names,
- auth_context['extras'],
- domain_id,
- project_id,
- auth_context.get('expires',
- None))
+ token_data = token_data_helper.get_token_data(
+ auth_context['user_id'],
+ method_names,
+ auth_context['extras'],
+ domain_id,
+ project_id,
+ auth_context.get('expires_at', None),
+ trust)
+
if CONF.signing.token_format == 'UUID':
token_id = uuid.uuid4().hex
elif CONF.signing.token_format == 'PKI':
@@ -207,20 +265,20 @@ def create_token(context, auth_context, auth_info):
CONF.signing.token_format)
token_api = token_module.Manager()
try:
- expiry = token_data['expires']
+ expiry = token_data['token']['expires_at']
if isinstance(expiry, basestring):
- expiry = timeutils.parse_isotime(expiry)
+ expiry = timeutils.normalize_time(timeutils.parse_isotime(expiry))
role_ids = []
- if 'project' in token_data:
+ if 'project' in token_data['token']:
# project-scoped token, fill in the v2 token data
# all we care are the role IDs
- role_ids = [role['id'] for role in token_data['roles']]
+ role_ids = [role['id'] for role in token_data['token']['roles']]
metadata_ref = {'roles': role_ids}
data = dict(key=token_id,
id=token_id,
expires=expiry,
- user=token_data['user'],
- tenant=token_data.get('project'),
+ user=token_data['token']['user'],
+ tenant=token_data['token'].get('project'),
metadata=metadata_ref,
token_data=token_data)
token_api.create_token(context, token_id, data)
diff --git a/keystone/cli.py b/keystone/cli.py
index e33e8188..6410d7b8 100644
--- a/keystone/cli.py
+++ b/keystone/cli.py
@@ -169,4 +169,5 @@ def main(argv=None, config_files=None):
project='keystone',
usage='%(prog)s [' + '|'.join([cmd.name for cmd in CMDS]) + ']',
default_config_files=config_files)
+ config.setup_logging(CONF)
CONF.command.cmd_class.main()
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
index 6377692e..f4f3c79d 100644
--- a/keystone/common/controller.py
+++ b/keystone/common/controller.py
@@ -1,3 +1,4 @@
+import collections
import functools
import uuid
@@ -27,46 +28,78 @@ def _build_policy_check_credentials(self, action, context, kwargs):
raise exception.Unauthorized()
creds = {}
- token_data = token_ref['token_data']
+ if 'token_data' in token_ref:
+ #V3 Tokens
+ token_data = token_ref['token_data']['token']
+ try:
+ creds['user_id'] = token_data['user']['id']
+ except AttributeError:
+ LOG.warning(_('RBAC: Invalid user'))
+ raise exception.Unauthorized()
+
+ if 'project' in token_data:
+ creds['project_id'] = token_data['project']['id']
+ else:
+ LOG.debug(_('RBAC: Proceeding without project'))
- try:
- creds['user_id'] = token_data['user']['id']
- except AttributeError:
- LOG.warning(_('RBAC: Invalid user'))
- raise exception.Unauthorized()
+ if 'domain' in token_data:
+ creds['domain_id'] = token_data['domain']['id']
- if 'project' in token_data:
- creds['project_id'] = token_data['project']['id']
+ if 'roles' in token_data:
+ creds['roles'] = []
+ for role in token_data['roles']:
+ creds['roles'].append(role['name'])
else:
- LOG.debug(_('RBAC: Proceeding without project'))
+ #v2 Tokens
+ creds = token_ref.get('metadata', {}).copy()
+ try:
+ creds['user_id'] = token_ref['user'].get('id')
+ except AttributeError:
+ LOG.warning(_('RBAC: Invalid user'))
+ raise exception.Unauthorized()
+ try:
+ creds['project_id'] = token_ref['tenant'].get('id')
+ except AttributeError:
+ LOG.debug(_('RBAC: Proceeding without tenant'))
+ # NOTE(vish): this is pretty inefficient
+ creds['roles'] = [self.identity_api.get_role(context, role)['name']
+ for role in creds.get('roles', [])]
+
+ return creds
- if 'domain' in token_data:
- creds['domain_id'] = token_data['domain']['id']
- if 'roles' in token_data:
- creds['roles'] = []
- for role in token_data['roles']:
- creds['roles'].append(role['name'])
+def flatten(d, parent_key=''):
+ """Flatten a nested dictionary
- return creds
+ Converts a dictionary with nested values to a single level flat
+ dictionary, with dotted notation for each key.
+
+ """
+ items = []
+ for k, v in d.items():
+ new_key = parent_key + '.' + k if parent_key else k
+ if isinstance(v, collections.MutableMapping):
+ items.extend(flatten(v, new_key).items())
+ else:
+ items.append((new_key, v))
+ return dict(items)
def protected(f):
"""Wraps API calls with role based access controls (RBAC)."""
-
@functools.wraps(f)
def wrapper(self, context, **kwargs):
- if not context['is_admin']:
+ if 'is_admin' in context and context['is_admin']:
+ LOG.warning(_('RBAC: Bypassing authorization'))
+ else:
action = 'identity:%s' % f.__name__
creds = _build_policy_check_credentials(self, action,
context, kwargs)
# Simply use the passed kwargs as the target dict, which
# would typically include the prime key of a get/update/delete
# call.
- self.policy_api.enforce(context, creds, action, kwargs)
+ self.policy_api.enforce(context, creds, action, flatten(kwargs))
LOG.debug(_('RBAC: Authorization granted'))
- else:
- LOG.warning(_('RBAC: Bypassing authorization'))
return f(self, context, **kwargs)
return wrapper
@@ -89,11 +122,6 @@ def filterprotected(*filters):
# parameter) and would typically include the prime key
# of a get/update/delete call
#
- # TODO(henry-nash) do we need to put the whole object
- # in, which is part of kwargs? I kept this in as it was part
- # of the previous implementation, but without a specific key
- # reference in the target I don't see how it can be used.
-
# First any query filter parameters
target = dict()
if len(filters) > 0:
@@ -109,7 +137,8 @@ def filterprotected(*filters):
for key in kwargs:
target[key] = kwargs[key]
- self.policy_api.enforce(context, creds, action, target)
+ self.policy_api.enforce(context, creds, action,
+ flatten(target))
LOG.debug(_('RBAC: Authorization granted'))
else:
@@ -119,7 +148,8 @@ def filterprotected(*filters):
return _filterprotected
-@dependency.requires('identity_api', 'policy_api', 'token_api')
+@dependency.requires('identity_api', 'policy_api', 'token_api',
+ 'trust_api', 'catalog_api')
class V2Controller(wsgi.Application):
"""Base controller class for Identity API v2."""
diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py
index f0a5cac6..25f6a8fe 100644
--- a/keystone/common/ldap/core.py
+++ b/keystone/common/ldap/core.py
@@ -88,6 +88,7 @@ class BaseLdap(object):
self.LDAP_USER = conf.ldap.user
self.LDAP_PASSWORD = conf.ldap.password
self.LDAP_SCOPE = ldap_scope(conf.ldap.query_scope)
+ self.page_size = conf.ldap.page_size
if self.options_name is not None:
self.suffix = conf.ldap.suffix
@@ -128,7 +129,8 @@ class BaseLdap(object):
if self.LDAP_URL.startswith('fake://'):
conn = fakeldap.FakeLdap(self.LDAP_URL)
else:
- conn = LdapWrapper(self.LDAP_URL)
+ conn = LdapWrapper(self.LDAP_URL,
+ self.page_size)
if user is None:
user = self.LDAP_USER
@@ -270,51 +272,6 @@ class BaseLdap(object):
return [self._ldap_res_to_model(x)
for x in self._ldap_get_all(filter)]
- def get_page(self, marker, limit):
- return self._get_page(marker, limit, self.get_all())
-
- def get_page_markers(self, marker, limit):
- return self._get_page_markers(marker, limit, self.get_all())
-
- @staticmethod
- def _get_page(marker, limit, lst, key=lambda x: x.id):
- lst.sort(key=key)
- if not marker:
- return lst[:limit]
- else:
- return [x for x in lst if key(x) > marker][:limit]
-
- @staticmethod
- def _get_page_markers(marker, limit, lst, key=lambda x: x.id):
- if len(lst) < limit:
- return (None, None)
-
- lst.sort(key=key)
- if marker is None:
- if len(lst) <= limit + 1:
- nxt = None
- else:
- nxt = key(lst[limit])
- return (None, nxt)
-
- i = 0
- for i, item in enumerate(lst):
- k = key(item)
- if k >= marker:
- break
-
- if i <= limit:
- prv = None
- else:
- prv = key(lst[i - limit])
-
- if i + limit >= len(lst) - 1:
- nxt = None
- else:
- nxt = key(lst[i + limit])
-
- return (prv, nxt)
-
def update(self, id, values, old_obj=None):
if not self.allow_update:
action = _('LDAP %s update') % self.options_name
@@ -361,9 +318,10 @@ class BaseLdap(object):
class LdapWrapper(object):
- def __init__(self, url):
+ def __init__(self, url, page_size):
LOG.debug(_("LDAP init: url=%s"), url)
self.conn = ldap.initialize(url)
+ self.page_size = page_size
def simple_bind_s(self, user, password):
LOG.debug(_("LDAP bind: dn=%s"), user)
@@ -387,15 +345,59 @@ class LdapWrapper(object):
scope,
query,
attrlist)
- res = self.conn.search_s(dn, scope, query, attrlist)
+ if self.page_size:
+ res = self.paged_search_s(dn, scope, query, attrlist)
+ else:
+ res = self.conn.search_s(dn, scope, query, attrlist)
o = []
for dn, attrs in res:
o.append((dn, dict((kind, [ldap2py(x) for x in values])
for kind, values in attrs.iteritems())))
-
return o
+ def paged_search_s(self, dn, scope, query, attrlist=None):
+ res = []
+ lc = ldap.controls.SimplePagedResultsControl(
+ controlType=ldap.LDAP_CONTROL_PAGE_OID,
+ criticality=True,
+ controlValue=(self.page_size, ''))
+ msgid = self.conn.search_ext(dn,
+ scope,
+ query,
+ attrlist,
+ serverctrls=[lc])
+ # Endless loop request pages on ldap server until it has no data
+ while True:
+ # Request to the ldap server a page with 'page_size' entries
+ rtype, rdata, rmsgid, serverctrls = self.conn.result3(msgid)
+ # Receive the data
+ res.extend(rdata)
+ pctrls = [c for c in serverctrls
+ if c.controlType == ldap.LDAP_CONTROL_PAGE_OID]
+ if pctrls:
+ # LDAP server supports pagination
+ est, cookie = pctrls[0].controlValue
+ if cookie:
+ # There is more data still on the server
+ # so we request another page
+ lc.controlValue = (self.page_size, cookie)
+ msgid = self.conn.search_ext(dn,
+ scope,
+ query,
+ attrlist,
+ serverctrls=[lc])
+ else:
+ # Exit condition no more data on server
+ break
+ else:
+ LOG.warning(_('LDAP Server does not support paging.'
+ 'Disable paging in keystone.conf to'
+ 'avoid this message'))
+ self._disable_paging()
+ break
+ return res
+
def modify_s(self, dn, modlist):
ldap_modlist = [
(op, kind, (None if values is None
@@ -418,6 +420,10 @@ class LdapWrapper(object):
LOG.debug(_("LDAP delete_ext: dn=%s, serverctrls=%s"), dn, serverctrls)
return self.conn.delete_ext_s(dn, serverctrls)
+ def _disable_paging(self):
+ # Disable the pagination from now on
+ self.page_size = 0
+
class EnabledEmuMixIn(BaseLdap):
"""Emulates boolean 'enabled' attribute if turned on.
diff --git a/keystone/common/models.py b/keystone/common/models.py
index f572d382..86a327c2 100644
--- a/keystone/common/models.py
+++ b/keystone/common/models.py
@@ -42,6 +42,7 @@ class Token(Model):
user
tenant
metadata
+ trust_id
"""
required_keys = ('id', 'expires')
@@ -147,3 +148,17 @@ class Role(Model):
required_keys = ('id', 'name')
optional_keys = tuple()
+
+
+class Trust(Model):
+ """Trust object.
+
+ Required keys:
+ id
+ trustor_user_id
+ trustee_user_id
+ project_id
+ """
+
+ required_keys = ('id', 'trustor_user_id', 'trustee_user_id', 'project_id')
+ optional_keys = tuple('expires_at')
diff --git a/keystone/common/serializer.py b/keystone/common/serializer.py
index 2a33ee70..91a16be4 100644
--- a/keystone/common/serializer.py
+++ b/keystone/common/serializer.py
@@ -120,10 +120,28 @@ class XmlDeserializer(object):
# current spec does not have attributes on an element with text
values = values or text or {}
+ decoded_tag = XmlDeserializer._tag_name(element.tag, namespace)
+ list_item_tag = None
+ if decoded_tag[-1] == 's' and len(values) == 0:
+ # FIXME(gyee): special-case lists for now unti we
+ # figure out how to properly handle them.
+ # If any key ends with an 's', we are assuming it is a list.
+ # List element have no attributes.
+ values = list(values)
+ if decoded_tag == 'policies':
+ list_item_tag = 'policy'
+ else:
+ list_item_tag = decoded_tag[:-1]
for child in [self.walk_element(x) for x in element
if not isinstance(x, ENTITY_TYPE)]:
- values = dict(values.items() + child.items())
+ if list_item_tag:
+ # FIXME(gyee): special-case lists for now unti we
+ # figure out how to properly handle them.
+ # If any key ends with an 's', we are assuming it is a list.
+ values.append(child[list_item_tag])
+ else:
+ values = dict(values.items() + child.items())
return {XmlDeserializer._tag_name(element.tag, namespace): values}
@@ -173,7 +191,7 @@ class XmlSerializer(object):
container = etree.Element(k)
element.append(container)
name = k[:-1]
- elif k == 'serviceCatalog':
+ elif k == 'serviceCatalog' or k == 'catalog':
# xsd compliance: <serviceCatalog> contains <service>s
container = etree.Element(k)
element.append(container)
@@ -184,7 +202,13 @@ class XmlSerializer(object):
# unnecessary in XML
name = element.tag[:-1]
elif k[-1] == 's':
- name = k[:-1]
+ container = etree.Element(k)
+ element.append(container)
+ if k == 'policies':
+ # need to special-case policies since policie is not a word
+ name = 'policy'
+ else:
+ name = k[:-1]
else:
name = k
@@ -226,6 +250,8 @@ class XmlSerializer(object):
self._populate_sequence(element, value)
elif isinstance(value, dict):
self._populate_tree(element, value)
+ elif isinstance(value, basestring):
+ element.text = unicode(value)
def _populate_sequence(self, element, l):
"""Populates an etree with a sequence of elements, given a list."""
@@ -233,6 +259,8 @@ class XmlSerializer(object):
name = element.tag
if element.tag[-1] == 's':
name = element.tag[:-1]
+ if name == 'policie':
+ name = 'policy'
for item in l:
child = etree.Element(name)
diff --git a/keystone/common/sql/migrate_repo/versions/018_add_trust_tables.py b/keystone/common/sql/migrate_repo/versions/018_add_trust_tables.py
new file mode 100644
index 00000000..77c42ba1
--- /dev/null
+++ b/keystone/common/sql/migrate_repo/versions/018_add_trust_tables.py
@@ -0,0 +1,68 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import migrate
+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
+
+ user_table = sql.Table('user', meta, autoload=True)
+ role_table = sql.Table('role', meta, autoload=True)
+ tenant_table = sql.Table('project', meta, autoload=True)
+
+ trust_table = sql.Table(
+ 'trust',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('trustor_user_id',
+ sql.String(64),
+ unique=False,
+ nullable=False,),
+ sql.Column('trustee_user_id',
+ sql.String(64),
+ unique=False,
+ nullable=False),
+ sql.Column('project_id', sql.String(64),
+ unique=False,
+ nullable=True),
+ sql.Column("impersonation", sql.types.Boolean, nullable=False),
+ sql.Column("deleted_at", sql.types.DateTime, nullable=True),
+ sql.Column("expires_at", sql.types.DateTime, nullable=True),
+ sql.Column('extra', sql.Text()))
+ trust_table.create(migrate_engine, checkfirst=True)
+
+ trust_role_table = sql.Table(
+ 'trust_role',
+ meta,
+ sql.Column('trust_id', sql.String(64), primary_key=True,
+ nullable=False),
+ sql.Column('role_id', sql.String(64), primary_key=True,
+ nullable=False))
+ trust_role_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.
+ for table_name in ['trust_role', 'trust']:
+ table = sql.Table(table_name, meta, autoload=True)
+ table.drop()
diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py
index a515eefe..108cc0ca 100644
--- a/keystone/common/wsgi.py
+++ b/keystone/common/wsgi.py
@@ -553,5 +553,5 @@ def render_exception(error):
'message': str(error)
}}
if isinstance(error, exception.AuthPluginException):
- body['authentication'] = error.authentication
+ body['error']['identity'] = error.authentication
return render_response(status=(error.code, error.title), body=body)
diff --git a/keystone/config.py b/keystone/config.py
index a96073c6..4c319778 100644
--- a/keystone/config.py
+++ b/keystone/config.py
@@ -235,6 +235,8 @@ register_str('driver', group='policy',
default='keystone.policy.backends.sql.Policy')
register_str('driver', group='token',
default='keystone.token.backends.kvs.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',
@@ -250,6 +252,7 @@ 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('user_tree_dn', group='ldap', default=None)
register_str('user_filter', group='ldap', default=None)
@@ -259,7 +262,8 @@ 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='domain_id')
+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',
@@ -278,7 +282,8 @@ 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='domain_id')
+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)
diff --git a/keystone/exception.py b/keystone/exception.py
index 017db27f..09c43585 100644
--- a/keystone/exception.py
+++ b/keystone/exception.py
@@ -190,6 +190,10 @@ class GroupNotFound(NotFound):
"""Could not find group: %(group_id)s"""
+class TrustNotFound(NotFound):
+ """Could not find trust: %(trust_id)s"""
+
+
class Conflict(Error):
"""Conflict occurred attempting to store %(type)s.
diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py
index 53a7d977..078a1deb 100644
--- a/keystone/identity/backends/ldap/core.py
+++ b/keystone/identity/backends/ldap/core.py
@@ -464,22 +464,6 @@ class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap, ApiShimMixin):
return self.role_api.add_user(values.role_id, values.user_id,
values.tenant_id)
- def users_get_page(self, marker, limit):
- return self.get_page(marker, limit)
-
- def users_get_page_markers(self, marker, limit):
- return self.get_page_markers(marker, limit)
-
- def users_get_by_project_get_page(self, tenant_id, role_id, marker, limit):
- return self._get_page(marker,
- limit,
- self.project_api.get_users(tenant_id, role_id))
-
- def users_get_by_project_get_page_markers(self, tenant_id, role_id,
- marker, limit):
- return self._get_page_markers(
- marker, limit, self.project_api.get_users(tenant_id, role_id))
-
def check_password(self, user_id, password):
user = self.get(user_id)
return utils.check_password(password, user.password)
@@ -553,15 +537,6 @@ class ProjectApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap,
projects.append(self.get(project_id))
return projects
- def list_for_user_get_page(self, user, marker, limit):
- return self._get_page(marker,
- limit,
- self.get_user_projects(user['id']))
-
- def list_for_user_get_page_markers(self, user, marker, limit):
- return self._get_page_markers(
- marker, limit, self.get_user_projects(user['id']))
-
def is_empty(self, id):
tenant = self._ldap_get(id)
members = tenant[1].get(self.member_attribute, [])
@@ -851,14 +826,6 @@ class RoleApi(common_ldap.BaseLdap, ApiShimMixin):
tenant_id=tenant_id))
return res
- def get_by_service_get_page(self, service_id, marker, limit):
- all_roles = self.get_by_service(service_id)
- return self._get_page(marker, limit, all_roles)
-
- def get_by_service_get_page_markers(self, service_id, marker, limit):
- all_roles = self.get_by_service(service_id)
- return self._get_page_markers(marker, limit, all_roles)
-
def roles_delete_subtree_by_project(self, tenant_id):
conn = self.get_connection()
query = '(objectClass=%s)' % self.object_class
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index e6f07c2e..f36002ce 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -14,8 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import functools
-
from keystone import clean
from keystone import config
from keystone.common import sql
@@ -195,18 +193,7 @@ class Identity(sql.Base, identity.Driver):
# FIXME(gyee): this should really be
# get_roles_for_user_and_project() after the dusts settle
if tenant_id not in self.get_projects_for_user(user_id):
- # get_roles_for_user_and_project() returns a set
- roles = []
- try:
- roles = self.get_roles_for_user_and_project(user_id,
- tenant_id)
- except:
- # FIXME(gyee): we should never get into this situation
- # after user project role migration is completed
- pass
- if not roles:
- raise AssertionError('Invalid tenant')
-
+ raise AssertionError('Invalid project')
try:
tenant_ref = self.get_project(tenant_id)
metadata_ref = self.get_metadata(user_id, tenant_id)
@@ -215,7 +202,6 @@ class Identity(sql.Base, identity.Driver):
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
-
return (identity.filter_user(user_ref), tenant_ref, metadata_ref)
def get_project(self, tenant_id):
@@ -622,6 +608,7 @@ class Identity(sql.Base, identity.Driver):
raise exception.DomainNotFound(domain_id=domain_id)
return ref.to_dict()
+ @sql.handle_conflicts(type='domain')
def get_domain_by_name(self, domain_name):
session = self.get_session()
try:
diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
index e5f30e56..e8472987 100644
--- a/keystone/identity/controllers.py
+++ b/keystone/identity/controllers.py
@@ -161,6 +161,24 @@ class Tenant(controller.V2Controller):
return o
+def delete_tokens_for_user(token_api, trust_api, context, user_id, user):
+ try:
+ #First delete tokens that could get other tokens.
+ for token_id in token_api.list_tokens(context, user_id):
+ token_api.delete_token(context, token_id)
+ #now delete trust tokens
+ for trust_id in (trust_api.list_trusts_for_trustee(context, user_id)):
+ token_list = token_api.list_tokens(context, userid,
+ trust_id=trust_id)
+ for token in token_list:
+ token_api.delete_token(context, token)
+ except exception.NotImplemented:
+ # The users status has been changed but tokens remain valid for
+ # backends that can't list tokens for users
+ LOG.warning('User %s status has changed, but existing tokens '
+ 'remain valid' % user_id)
+
+
class User(controller.V2Controller):
def get_user(self, context, user_id):
self.assert_admin(context)
@@ -215,16 +233,12 @@ class User(controller.V2Controller):
self.assert_admin(context)
user_ref = self.identity_api.update_user(context, user_id, user)
- # If the password was changed or the user was disabled we clear tokens
if user.get('password') or not user.get('enabled', True):
- try:
- for token_id in self.token_api.list_tokens(context, user_id):
- self.token_api.delete_token(context, token_id)
- except exception.NotImplemented:
- # The users status has been changed but tokens remain valid for
- # backends that can't list tokens for users
- LOG.warning('User %s status has changed, but existing tokens '
- 'remain valid' % user_id)
+ # If the password was changed or the user was disabled we clear tokens
+ delete_tokens_for_user(self.token_api, self.trust_api,
+ context,
+ user_id,
+ user)
return {'user': self._filter_domain_id(user_ref)}
def delete_user(self, context, user_id):
@@ -329,7 +343,8 @@ class Role(controller.V2Controller):
context, user_id, tenant_id, role_id)
roles = self.identity_api.get_roles_for_user_and_project(
context, user_id, tenant_id)
- self.token_api.revoke_tokens(context, user_id, tenant_id)
+ delete_tokens_for_user(self.token_api, self.trust_api, context,
+ user_id, tenant_id)
# COMPAT(diablo): CRUD extension
def get_role_refs(self, context, user_id):
diff --git a/keystone/locale/keystone.pot b/keystone/locale/keystone.pot
index e8d0e04e..92d56792 100644
--- a/keystone/locale/keystone.pot
+++ b/keystone/locale/keystone.pot
@@ -6,9 +6,10 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: keystone 2013.1\n"
+"Project-Id-Version: keystone "
+"jenkins.keystone.propose.translation.update.130\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2013-02-05 00:01+0000\n"
+"POT-Creation-Date: 2013-03-04 00:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -37,20 +38,56 @@ msgstr ""
msgid "%(property_name)s is not a%(display_expected_type)s"
msgstr ""
-#: keystone/config.py:44
+#: keystone/config.py:93
#, python-format
msgid "Unable to locate specified logging config file: %s"
msgstr ""
-#: keystone/config.py:62
+#: keystone/config.py:111
msgid "Invalid syslog facility"
msgstr ""
-#: keystone/test.py:106
+#: keystone/test.py:107
#, python-format
msgid "Failed to checkout %s"
msgstr ""
+#: keystone/auth/controllers.py:78
+#, python-format
+msgid "Project is disabled: %s"
+msgstr ""
+
+#: keystone/auth/controllers.py:84 keystone/auth/methods/password.py:40
+#, python-format
+msgid "Domain is disabled: %s"
+msgstr ""
+
+#: keystone/auth/controllers.py:90 keystone/auth/methods/password.py:46
+#, python-format
+msgid "User is disabled: %s"
+msgstr ""
+
+#: keystone/auth/controllers.py:250
+msgid "Scoping to both domain and project is not allowed"
+msgstr ""
+
+#: keystone/auth/controllers.py:311
+#, python-format
+msgid "Unable to lookup user %s"
+msgstr ""
+
+#: keystone/auth/controllers.py:341
+msgid "User not found"
+msgstr ""
+
+#: keystone/auth/token_factory.py:75
+msgid "User have no access to project"
+msgstr ""
+
+#: keystone/auth/token_factory.py:90
+msgid "User have no access to domain"
+msgstr ""
+
#: keystone/catalog/core.py:38
#, python-format
msgid "Malformed endpoint %(url)s - unknown key %(keyerror)s"
@@ -90,29 +127,42 @@ msgstr ""
msgid "Signing error: %s"
msgstr ""
-#: keystone/common/controller.py:21
+#: keystone/common/controller.py:19
#, python-format
msgid "RBAC: Authorizing %s(%s)"
msgstr ""
-#: keystone/common/controller.py:29
+#: keystone/common/controller.py:27
msgid "RBAC: Invalid token"
msgstr ""
-#: keystone/common/controller.py:37
+#: keystone/common/controller.py:37 keystone/common/controller.py:58
msgid "RBAC: Invalid user"
msgstr ""
#: keystone/common/controller.py:43
+msgid "RBAC: Proceeding without project"
+msgstr ""
+
+#: keystone/common/controller.py:63
msgid "RBAC: Proceeding without tenant"
msgstr ""
-#: keystone/common/controller.py:51
+#: keystone/common/controller.py:93 keystone/common/controller.py:145
+msgid "RBAC: Bypassing authorization"
+msgstr ""
+
+#: keystone/common/controller.py:102 keystone/common/controller.py:143
msgid "RBAC: Authorization granted"
msgstr ""
-#: keystone/common/controller.py:53
-msgid "RBAC: Bypassing authorization"
+#: keystone/common/controller.py:132
+#, python-format
+msgid "RBAC: Adding query filter params (%s)"
+msgstr ""
+
+#: keystone/common/controller.py:280
+msgid "Invalid token in normalize_domain_id"
msgstr ""
#: keystone/common/utils.py:93
@@ -144,182 +194,188 @@ msgstr ""
msgid "base64 encoded digest: %s"
msgstr ""
-#: keystone/common/wsgi.py:74
+#: keystone/common/wsgi.py:76
#, python-format
msgid "Starting %(arg0)s on %(host)s:%(port)s"
msgstr ""
-#: keystone/common/wsgi.py:209
+#: keystone/common/wsgi.py:211
#, python-format
msgid "arg_dict: %s"
msgstr ""
-#: keystone/common/wsgi.py:230
+#: keystone/common/wsgi.py:233
#, python-format
msgid "Authorization failed. %s from %s"
msgstr ""
-#: keystone/common/wsgi.py:443
+#: keystone/common/wsgi.py:454
msgid "The resource could not be found."
msgstr ""
-#: keystone/common/ldap/core.py:171
+#: keystone/common/ldap/core.py:68
+#, python-format
+msgid "Invalid LDAP scope: %s. Choose one of: "
+msgstr ""
+
+#: keystone/common/ldap/core.py:197 keystone/identity/backends/kvs.py:588
+#: keystone/identity/backends/kvs.py:616
#, python-format
msgid "Duplicate name, %s."
msgstr ""
-#: keystone/common/ldap/core.py:181
+#: keystone/common/ldap/core.py:207 keystone/identity/backends/kvs.py:581
#, python-format
msgid "Duplicate ID, %s."
msgstr ""
-#: keystone/common/ldap/core.py:186
+#: keystone/common/ldap/core.py:212
#, python-format
msgid "LDAP %s create"
msgstr ""
-#: keystone/common/ldap/core.py:292
+#: keystone/common/ldap/core.py:320
#, python-format
msgid "LDAP %s update"
msgstr ""
-#: keystone/common/ldap/core.py:319
+#: keystone/common/ldap/core.py:348
#, python-format
msgid "LDAP %s delete"
msgstr ""
-#: keystone/common/ldap/core.py:336
+#: keystone/common/ldap/core.py:365
#, python-format
msgid "LDAP init: url=%s"
msgstr ""
-#: keystone/common/ldap/core.py:340
+#: keystone/common/ldap/core.py:369
#, python-format
msgid "LDAP bind: dn=%s"
msgstr ""
-#: keystone/common/ldap/core.py:351
+#: keystone/common/ldap/core.py:380
#, python-format
msgid "LDAP add: dn=%s, attrs=%s"
msgstr ""
-#: keystone/common/ldap/core.py:356
+#: keystone/common/ldap/core.py:385
#, python-format
-msgid "LDAP search: dn=%s, scope=%s, query=%s"
+msgid "LDAP search: dn=%s, scope=%s, query=%s, attrs=%s"
msgstr ""
-#: keystone/common/ldap/core.py:379
+#: keystone/common/ldap/core.py:409
#, python-format
msgid "LDAP modify: dn=%s, modlist=%s"
msgstr ""
-#: keystone/common/ldap/core.py:384
+#: keystone/common/ldap/core.py:414
#, python-format
msgid "LDAP delete: dn=%s"
msgstr ""
-#: keystone/common/ldap/core.py:388
+#: keystone/common/ldap/core.py:418
#, python-format
msgid "LDAP delete_ext: dn=%s, serverctrls=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:148
+#: keystone/common/ldap/fakeldap.py:146
#, python-format
msgid "FakeLdap initialize url=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:158
+#: keystone/common/ldap/fakeldap.py:156
#, python-format
msgid "FakeLdap bind dn=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:165
+#: keystone/common/ldap/fakeldap.py:163
#, python-format
msgid "FakeLdap bind fail: dn=%s not found"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:172
+#: keystone/common/ldap/fakeldap.py:170
#, python-format
msgid "FakeLdap bind fail: password for dn=%s not found"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:177
+#: keystone/common/ldap/fakeldap.py:175
#, python-format
msgid "FakeLdap bind fail: password for dn=%s does not match"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:192
+#: keystone/common/ldap/fakeldap.py:190
#, python-format
msgid "FakeLdap add item: dn=%s, attrs=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:194
+#: keystone/common/ldap/fakeldap.py:192
#, python-format
msgid "FakeLdap add item failed: dn=%s is already in store."
msgstr ""
-#: keystone/common/ldap/fakeldap.py:208 keystone/common/ldap/fakeldap.py:222
+#: keystone/common/ldap/fakeldap.py:206 keystone/common/ldap/fakeldap.py:220
#, python-format
msgid "FakeLdap delete item: dn=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:212 keystone/common/ldap/fakeldap.py:226
+#: keystone/common/ldap/fakeldap.py:210 keystone/common/ldap/fakeldap.py:224
#, python-format
msgid "FakeLdap delete item failed: dn=%s not found."
msgstr ""
-#: keystone/common/ldap/fakeldap.py:241
+#: keystone/common/ldap/fakeldap.py:239
#, python-format
msgid "FakeLdap modify item: dn=%s attrs=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:245
+#: keystone/common/ldap/fakeldap.py:243
#, python-format
msgid "FakeLdap modify item failed: dn=%s not found."
msgstr ""
-#: keystone/common/ldap/fakeldap.py:262
+#: keystone/common/ldap/fakeldap.py:260
#, python-format
msgid "FakeLdap modify item failed: item has no attribute \"%s\" to delete"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:273
+#: keystone/common/ldap/fakeldap.py:271
#, python-format
msgid ""
"FakeLdap modify item failed: item has no attribute \"%s\" with value "
"\"%s\" to delete"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:278
+#: keystone/common/ldap/fakeldap.py:276
#, python-format
msgid "FakeLdap modify item failed: unknown command %s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:280
+#: keystone/common/ldap/fakeldap.py:278
#, python-format
msgid "modify_s action %s not implemented"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:298
+#: keystone/common/ldap/fakeldap.py:296
#, python-format
msgid "FakeLdap search at dn=%s scope=%s query=%s"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:304
+#: keystone/common/ldap/fakeldap.py:302
msgid "FakeLdap search fail: dn not found for SCOPE_BASE"
msgstr ""
-#: keystone/common/ldap/fakeldap.py:318
+#: keystone/common/ldap/fakeldap.py:316
#, python-format
msgid "Search scope %s not implemented."
msgstr ""
-#: keystone/common/sql/core.py:203
+#: keystone/common/sql/core.py:206
#, python-format
msgid "Got mysql server has gone away: %s"
msgstr ""
-#: keystone/common/sql/legacy.py:175
+#: keystone/common/sql/legacy.py:180
#, python-format
msgid "Cannot migrate EC2 credential: %s"
msgstr ""
@@ -328,55 +384,75 @@ msgstr ""
msgid "version should be an integer"
msgstr ""
-#: keystone/common/sql/nova.py:58
+#: keystone/common/sql/nova.py:62
#, python-format
msgid "Create tenant %s"
msgstr ""
-#: keystone/common/sql/nova.py:74
+#: keystone/common/sql/nova.py:79
#, python-format
msgid "Create user %s"
msgstr ""
-#: keystone/common/sql/nova.py:83
+#: keystone/common/sql/nova.py:88
#, python-format
msgid "Add user %s to tenant %s"
msgstr ""
-#: keystone/common/sql/nova.py:91
+#: keystone/common/sql/nova.py:96
#, python-format
msgid "Ignoring existing role %s"
msgstr ""
-#: keystone/common/sql/nova.py:98
+#: keystone/common/sql/nova.py:103
#, python-format
msgid "Create role %s"
msgstr ""
-#: keystone/common/sql/nova.py:108
+#: keystone/common/sql/nova.py:113
#, python-format
msgid "Assign role %s to user %s on tenant %s"
msgstr ""
-#: keystone/common/sql/nova.py:123
+#: keystone/common/sql/nova.py:128
#, python-format
msgid "Creating ec2 cred for user %s and tenant %s"
msgstr ""
-#: keystone/identity/backends/kvs.py:250 keystone/identity/backends/kvs.py:259
+#: keystone/identity/backends/kvs.py:254 keystone/identity/backends/kvs.py:263
msgid "User not found in group"
msgstr ""
-#: keystone/identity/backends/ldap/core.py:751
+#: keystone/identity/backends/sql.py:467
+#, python-format
+msgid "Cannot remove role that has not been granted, %s"
+msgstr ""
+
+#: keystone/identity/backends/ldap/core.py:730
#, python-format
msgid "Role %s not found"
msgstr ""
-#: keystone/identity/backends/ldap/core.py:1115
+#: keystone/identity/backends/ldap/core.py:968
msgid "Changing Name not supported by LDAP"
msgstr ""
-#: keystone/policy/backends/rules.py:95
+#: keystone/openstack/common/policy.py:394
+#, python-format
+msgid "Failed to understand rule %(rule)s"
+msgstr ""
+
+#: keystone/openstack/common/policy.py:404
+#, python-format
+msgid "No handler for matches of kind %s"
+msgstr ""
+
+#: keystone/openstack/common/policy.py:679
+#, python-format
+msgid "Failed to understand rule %(rule)r"
+msgstr ""
+
+#: keystone/policy/backends/rules.py:93
#, python-format
msgid "enforce %s: %s"
msgstr ""
diff --git a/keystone/middleware/core.py b/keystone/middleware/core.py
index d904e3c0..29a6832b 100644
--- a/keystone/middleware/core.py
+++ b/keystone/middleware/core.py
@@ -16,6 +16,7 @@
import webob.dec
+from keystone.common import logging
from keystone.common import serializer
from keystone.common import utils
from keystone.common import wsgi
@@ -25,6 +26,7 @@ from keystone.openstack.common import jsonutils
CONF = config.CONF
+LOG = logging.getLogger(__name__)
# Header used to transmit the auth token
@@ -158,6 +160,7 @@ class XmlBodyMiddleware(wsgi.Middleware):
body_obj = jsonutils.loads(response.body)
response.body = serializer.to_xml(body_obj)
except Exception:
+ LOG.exception('Serializer failed')
raise exception.Error(message=response.body)
return response
diff --git a/keystone/openstack/common/timeutils.py b/keystone/openstack/common/timeutils.py
index 86004391..8e40660f 100644
--- a/keystone/openstack/common/timeutils.py
+++ b/keystone/openstack/common/timeutils.py
@@ -25,18 +25,22 @@ import datetime
import iso8601
-TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
-PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
+# ISO 8601 extended time format with microseconds
+_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
+_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
+PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
-def isotime(at=None):
+def isotime(at=None, subsecond=False):
"""Stringify time in ISO 8601 format"""
if not at:
at = utcnow()
- str = at.strftime(TIME_FORMAT)
+ st = at.strftime(_ISO8601_TIME_FORMAT
+ if not subsecond
+ else _ISO8601_TIME_FORMAT_SUBSECOND)
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
- str += ('Z' if tz == 'UTC' else tz)
- return str
+ st += ('Z' if tz == 'UTC' else tz)
+ return st
def parse_isotime(timestr):
@@ -71,11 +75,15 @@ def normalize_time(timestamp):
def is_older_than(before, seconds):
"""Return True if before is older than seconds."""
+ if isinstance(before, basestring):
+ before = parse_strtime(before).replace(tzinfo=None)
return utcnow() - before > datetime.timedelta(seconds=seconds)
def is_newer_than(after, seconds):
"""Return True if after is newer than seconds."""
+ if isinstance(after, basestring):
+ after = parse_strtime(after).replace(tzinfo=None)
return after - utcnow() > datetime.timedelta(seconds=seconds)
@@ -87,22 +95,37 @@ def utcnow_ts():
def utcnow():
"""Overridable version of utils.utcnow."""
if utcnow.override_time:
- return utcnow.override_time
+ try:
+ return utcnow.override_time.pop(0)
+ except AttributeError:
+ return utcnow.override_time
return datetime.datetime.utcnow()
+def iso8601_from_timestamp(timestamp):
+ """Returns a iso8601 formated date from timestamp"""
+ return isotime(datetime.datetime.utcfromtimestamp(timestamp))
+
+
utcnow.override_time = None
def set_time_override(override_time=datetime.datetime.utcnow()):
- """Override utils.utcnow to return a constant time."""
+ """
+ Override utils.utcnow to return a constant time or a list thereof,
+ one at a time.
+ """
utcnow.override_time = override_time
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
assert(not utcnow.override_time is None)
- utcnow.override_time += timedelta
+ try:
+ for dt in utcnow.override_time:
+ dt += timedelta
+ except TypeError:
+ utcnow.override_time += timedelta
def advance_time_seconds(seconds):
@@ -135,3 +158,29 @@ def unmarshall_time(tyme):
minute=tyme['minute'],
second=tyme['second'],
microsecond=tyme['microsecond'])
+
+
+def delta_seconds(before, after):
+ """
+ Compute the difference in seconds between two date, time, or
+ datetime objects (as a float, to microsecond resolution).
+ """
+ delta = after - before
+ try:
+ return delta.total_seconds()
+ except AttributeError:
+ return ((delta.days * 24 * 3600) + delta.seconds +
+ float(delta.microseconds) / (10 ** 6))
+
+
+def is_soon(dt, window):
+ """
+ Determines if time is going to happen in the next window seconds.
+
+ :params dt: the time
+ :params window: minimum seconds to remain to consider the time not soon
+
+ :return: True if expiration is within the given duration
+ """
+ soon = (utcnow() + datetime.timedelta(seconds=window))
+ return normalize_time(dt) <= soon
diff --git a/keystone/service.py b/keystone/service.py
index e4cca53e..423aee06 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -25,6 +25,7 @@ from keystone import identity
from keystone import policy
from keystone import routers
from keystone import token
+from keystone import trust
LOG = logging.getLogger(__name__)
@@ -34,7 +35,8 @@ DRIVERS = dict(
ec2_api=ec2.Manager(),
identity_api=identity.Manager(),
policy_api=policy.Manager(),
- token_api=token.Manager())
+ token_api=token.Manager(),
+ trust_api=trust.Manager())
@logging.fail_gracefully
@@ -81,7 +83,7 @@ def v3_app_factory(global_conf, **local_conf):
conf.update(local_conf)
mapper = routes.Mapper()
v3routers = []
- for module in [auth, catalog, identity, policy]:
+ for module in [auth, catalog, identity, policy, trust]:
module.routers.append_v3_routers(mapper, v3routers)
# TODO(ayoung): put token routes here
return wsgi.ComposingRouter(mapper, v3routers)
diff --git a/keystone/test.py b/keystone/test.py
index 2972314f..7386f552 100644
--- a/keystone/test.py
+++ b/keystone/test.py
@@ -39,6 +39,7 @@ from keystone import exception
from keystone import identity
from keystone import policy
from keystone import token
+from keystone import trust
do_monkeypatch = not os.getenv('STANDARD_THREADS')
@@ -74,6 +75,7 @@ def initialize_drivers():
DRIVERS['identity_api'] = identity.Manager()
DRIVERS['policy_api'] = policy.Manager()
DRIVERS['token_api'] = token.Manager()
+ DRIVERS['trust_api'] = trust.Manager()
return DRIVERS
diff --git a/keystone/token/backends/kvs.py b/keystone/token/backends/kvs.py
index f12fe80d..0adf0579 100644
--- a/keystone/token/backends/kvs.py
+++ b/keystone/token/backends/kvs.py
@@ -31,7 +31,11 @@ class Token(kvs.Base, token.Driver):
ref = self.db.get('token-%s' % token_id)
except exception.NotFound:
raise exception.TokenNotFound(token_id=token_id)
- if ref['expires'] is None or ref['expires'] > timeutils.utcnow():
+ now = timeutils.utcnow()
+ expiry = ref['expires']
+ if expiry is None:
+ raise exception.TokenNotFound(token_id=token_id)
+ if expiry > now:
return copy.deepcopy(ref)
else:
raise exception.TokenNotFound(token_id=token_id)
@@ -39,8 +43,10 @@ class Token(kvs.Base, token.Driver):
def create_token(self, token_id, data):
token_id = token.unique_id(token_id)
data_copy = copy.deepcopy(data)
- if 'expires' not in data:
+ if not data_copy.get('expires'):
data_copy['expires'] = token.default_expire_time()
+ if 'trust_id' in data and data['trust_id'] is None:
+ data_copy.pop('trust_id')
self.db.set('token-%s' % token_id, data_copy)
return copy.deepcopy(data_copy)
@@ -53,7 +59,7 @@ class Token(kvs.Base, token.Driver):
except exception.NotFound:
raise exception.TokenNotFound(token_id=token_id)
- def list_tokens(self, user_id, tenant_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None):
tokens = []
now = timeutils.utcnow()
for token, ref in self.db.items():
@@ -72,6 +78,10 @@ class Token(kvs.Base, token.Driver):
continue
if tenant.get('id') != tenant_id:
continue
+ if trust_id is not None:
+ trust = ref.get('trust_id')
+ if not trust:
+ continue
tokens.append(token.split('-', 1)[1])
return tokens
diff --git a/keystone/token/backends/memcache.py b/keystone/token/backends/memcache.py
index efac16fd..b097ab5e 100644
--- a/keystone/token/backends/memcache.py
+++ b/keystone/token/backends/memcache.py
@@ -64,7 +64,7 @@ class Token(token.Driver):
def create_token(self, token_id, data):
data_copy = copy.deepcopy(data)
ptk = self._prefix_token_id(token.unique_id(token_id))
- if 'expires' not in data_copy:
+ if not data_copy.get('expires'):
data_copy['expires'] = token.default_expire_time()
kwargs = {}
if data_copy['expires'] is not None:
@@ -99,7 +99,7 @@ class Token(token.Driver):
self._add_to_revocation_list(data)
return result
- def list_tokens(self, user_id, tenant_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None):
tokens = []
user_key = self._prefix_user_id(user_id)
user_record = self.client.get(user_key) or ""
@@ -114,6 +114,13 @@ class Token(token.Driver):
continue
if tenant.get('id') != tenant_id:
continue
+ if trust_id is not None:
+ trust = token_ref.get('trust_id')
+ if not trust:
+ continue
+ if trust != trust_id:
+ continue
+
tokens.append(token_id)
return tokens
diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py
index 822b869e..62122ebe 100644
--- a/keystone/token/backends/sql.py
+++ b/keystone/token/backends/sql.py
@@ -43,16 +43,18 @@ class Token(sql.Base, token.Driver):
query = query.filter_by(id=token.unique_id(token_id), valid=True)
token_ref = query.first()
now = datetime.datetime.utcnow()
- if token_ref and (not token_ref.expires or now < token_ref.expires):
- return token_ref.to_dict()
- else:
+ if not token_ref:
raise exception.TokenNotFound(token_id=token_id)
+ if not token_ref.expires:
+ raise exception.TokenNotFound(token_id=token_id)
+ if now >= token_ref.expires:
+ raise exception.TokenNotFound(token_id=token_id)
+ return token_ref.to_dict()
def create_token(self, token_id, data):
data_copy = copy.deepcopy(data)
- if 'expires' not in data_copy:
+ if not data_copy.get('expires'):
data_copy['expires'] = token.default_expire_time()
-
token_ref = TokenModel.from_dict(data_copy)
token_ref.id = token.unique_id(token_id)
token_ref.valid = True
@@ -73,7 +75,7 @@ class Token(sql.Base, token.Driver):
token_ref.valid = False
session.flush()
- def list_tokens(self, user_id, tenant_id=None):
+ def list_tokens(self, user_id, tenant_id=None, trust_id=None):
session = self.get_session()
tokens = []
now = timeutils.utcnow()
@@ -93,6 +95,12 @@ class Token(sql.Base, token.Driver):
continue
if tenant.get('id') != tenant_id:
continue
+ if trust_id is not None:
+ token_trust_id = token_ref_dict.get('trust_id')
+ if not token_trust_id:
+ continue
+ if token_trust_id != trust_id:
+ continue
tokens.append(token_ref['id'])
return tokens
diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py
index d0538098..ade2af4f 100644
--- a/keystone/token/controllers.py
+++ b/keystone/token/controllers.py
@@ -21,7 +21,7 @@ class ExternalAuthNotApplicable(Exception):
pass
-@dependency.requires('catalog_api')
+@dependency.requires('catalog_api', 'trust_api', 'token_api')
class Auth(controller.V2Controller):
def ca_cert(self, context, auth=None):
ca_file = open(CONF.signing.ca_certs, 'r')
@@ -78,6 +78,7 @@ class Auth(controller.V2Controller):
context, auth)
user_ref, tenant_ref, metadata_ref, expiry = auth_info
+ trust_id = metadata_ref.get('trust_id')
user_ref = self._filter_domain_id(user_ref)
if tenant_ref:
tenant_ref = self._filter_domain_id(tenant_ref)
@@ -128,7 +129,8 @@ class Auth(controller.V2Controller):
expires=auth_token_data['expires'],
user=user_ref,
tenant=tenant_ref,
- metadata=metadata_ref))
+ metadata=metadata_ref,
+ trust_id=trust_id))
except Exception as e:
# an identical token may have been created already.
# if so, return the token_data as it is also identical
@@ -166,11 +168,43 @@ class Auth(controller.V2Controller):
except exception.NotFound as e:
raise exception.Unauthorized(e)
+ #A trust token cannot be used to get another token
+ if 'trust' in old_token_ref:
+ raise exception.Unauthorized()
+ if 'trust_id' in old_token_ref["metadata"]:
+ raise exception.Forbidden()
+
user_ref = old_token_ref['user']
user_id = user_ref['id']
+ if 'trust_id' in auth:
+ trust_ref = self.trust_api.get_trust(context, auth['trust_id'])
+ if trust_ref is None:
+ raise exception.Forbidden()
+ if user_id != trust_ref['trustee_user_id']:
+ raise exception.Forbidden()
+ if ('expires' in trust_ref) and (trust_ref['expires']):
+ expiry = trust_ref['expires']
+ if expiry < timeutils.parse_isotime(timeutils.isotime()):
+ raise exception.Forbidden()()
+ user_id = trust_ref['trustor_user_id']
+ trustor_user_ref = (self.identity_api.get_user(
+ context=context,
+ user_id=trust_ref['trustor_user_id']))
+ if not trustor_user_ref['enabled']:
+ raise exception.Forbidden()()
+ trustee_user_ref = self.identity_api.get_user(
+ context, trust_ref['trustee_user_id'])
+ if not trustee_user_ref['enabled']:
+ raise exception.Forbidden()()
+ if trust_ref['impersonation'] == 'True':
+ current_user_ref = trustor_user_ref
+ else:
+ current_user_ref = trustee_user_ref
- current_user_ref = self.identity_api.get_user(context=context,
- user_id=user_id)
+ else:
+ tenant_id = self._get_project_id_from_auth(context, auth)
+ current_user_ref = self.identity_api.get_user(context=context,
+ user_id=user_id)
tenant_id = self._get_project_id_from_auth(context, auth)
@@ -185,6 +219,28 @@ class Auth(controller.V2Controller):
context, user_id, tenant_id))
expiry = old_token_ref['expires']
+ if 'trust_id' in auth:
+ trust_id = auth['trust_id']
+ trust_roles = []
+ for role in trust_ref['roles']:
+ if not 'roles' in metadata_ref:
+ raise exception.Forbidden()()
+ if role['id'] in metadata_ref['roles']:
+ trust_roles.append(role['id'])
+ else:
+ raise exception.Forbidden()
+ if 'expiry' in trust_ref and trust_ref['expiry']:
+ trust_expiry = timeutils.parse_isotime(trust_ref['expiry'])
+ if trust_expiry < expiry:
+ expiry = trust_expiry
+ metadata_ref['roles'] = trust_roles
+ metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id']
+ metadata_ref['trust_id'] = trust_id
+
+ auth_token_data = self._get_auth_token_data(current_user_ref,
+ tenant_ref,
+ metadata_ref,
+ expiry)
return (current_user_ref, tenant_ref, metadata_ref, expiry)
def _authenticate_local(self, context, auth):
@@ -526,7 +582,12 @@ class Auth(controller.V2Controller):
else:
o['access']['metadata'] = {'is_admin': 0}
if 'roles' in metadata_ref:
- o['access']['metadata']['roles'] = metadata_ref['roles']
+ o['access']['metadata']['roles'] = metadata_ref['roles']
+ if 'trust_id' in metadata_ref:
+ o['access']['trust'] = {'trustee_user_id':
+ metadata_ref['trustee_user_id'],
+ 'id': metadata_ref['trust_id']
+ }
return o
@classmethod
diff --git a/keystone/token/core.py b/keystone/token/core.py
index 4737f539..37ecffbc 100644
--- a/keystone/token/core.py
+++ b/keystone/token/core.py
@@ -179,11 +179,15 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def list_tokens(self, user_id):
+ def list_tokens(self, user_id, tenant_id=None, trust_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
+ :type trust_id: string
:returns: list of token_id's
"""
diff --git a/keystone/trust/__init__.py b/keystone/trust/__init__.py
new file mode 100644
index 00000000..9c6a22f0
--- /dev/null
+++ b/keystone/trust/__init__.py
@@ -0,0 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystone.trust.core import Manager, Driver
+from keystone.trust import controllers
+from keystone.trust import routers
diff --git a/keystone/trust/backends/__init__.py b/keystone/trust/backends/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keystone/trust/backends/__init__.py
diff --git a/keystone/trust/backends/kvs.py b/keystone/trust/backends/kvs.py
new file mode 100644
index 00000000..ef528626
--- /dev/null
+++ b/keystone/trust/backends/kvs.py
@@ -0,0 +1,92 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+An in memory implementation of the trusts API.
+only to be used for testing purposes
+"""
+import copy
+import datetime
+
+
+from keystone.common import kvs
+from keystone.openstack.common import timeutils
+from keystone import exception
+from keystone import trust
+
+
+class Trust(kvs.Base, trust.Driver):
+ def create_trust(self, trust_id, trust, roles):
+ trust_ref = trust
+ trust_ref['id'] = trust_id
+ trust_ref['deleted'] = False
+ trust_ref['roles'] = roles
+ if (trust_ref.get('expires_at') and
+ trust_ref['expires_at'].tzinfo is not None):
+ trust_ref['expires_at'] = (timeutils.normalize_time
+ (trust_ref['expires_at']))
+
+ self.db.set('trust-%s' % trust_id, trust_ref)
+ trustee_user_id = trust_ref['trustee_user_id']
+ trustee_list = self.db.get('trustee-%s' % trustee_user_id, [])
+ trustee_list.append(trust_id)
+ self.db.set('trustee-%s' % trustee_user_id, trustee_list)
+ trustor_user_id = trust_ref['trustor_user_id']
+ trustor_list = self.db.get('trustor-%s' % trustor_user_id, [])
+ trustor_list.append(trust_id)
+ self.db.set('trustor-%s' % trustor_user_id, trustor_list)
+ return copy.deepcopy(trust_ref)
+
+ def _filter_trust(selfself, ref):
+ if ref['deleted']:
+ return None
+ if ref.get('expires_at') and timeutils.utcnow() > ref['expires_at']:
+ return None
+ ref = copy.deepcopy(ref)
+ return ref
+
+ def get_trust(self, trust_id):
+ try:
+ ref = self.db.get('trust-%s' % trust_id)
+ return self._filter_trust(ref)
+ except exception.NotFound:
+ return None
+
+ def delete_trust(self, trust_id):
+ try:
+ ref = self.db.get('trust-%s' % trust_id)
+ except exception.NotFound:
+ raise exception.TrustNotFound(token_id=token_id)
+ ref['deleted'] = True
+ self.db.set('trust-%s' % trust_id, ref)
+
+ def list_trusts(self):
+ trusts = []
+ for key, value in self.db.items():
+ if key.startswith("trust-") and not value['deleted']:
+ trusts.append(value)
+ return trusts
+
+ def list_trusts_for_trustee(self, trustee_user_id):
+ trusts = []
+ for trust in self.db.get('trustee-%s' % trustee_user_id, []):
+ trusts.append(self.get_trust(trust))
+ return trusts
+
+ def list_trusts_for_trustor(self, trustor_user_id):
+ trusts = []
+ for trust in self.db.get('trustor-%s' % trustor_user_id, []):
+ trusts.append(self.get_trust(trust))
+ return trusts
diff --git a/keystone/trust/backends/sql.py b/keystone/trust/backends/sql.py
new file mode 100644
index 00000000..dc3644e3
--- /dev/null
+++ b/keystone/trust/backends/sql.py
@@ -0,0 +1,123 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystone.common import sql
+from keystone import exception
+from keystone.openstack.common import timeutils
+from keystone import trust
+
+
+class TrustModel(sql.ModelBase, sql.DictBase):
+ __tablename__ = 'trust'
+ attributes = ['id', 'trustor_user_id', 'trustee_user_id',
+ 'project_id', 'impersonation', 'expires_at']
+ id = sql.Column(sql.String(64), primary_key=True)
+ #user id Of owner
+ trustor_user_id = sql.Column(sql.String(64), unique=False, nullable=False,)
+ #user_id of user allowed to consume this preauth
+ trustee_user_id = sql.Column(sql.String(64), unique=False, nullable=False)
+ project_id = sql.Column(sql.String(64), unique=False, nullable=True)
+ impersonation = sql.Column(sql.Boolean)
+ deleted_at = sql.Column(sql.DateTime)
+ expires_at = sql.Column(sql.DateTime)
+ extra = sql.Column(sql.JsonBlob())
+
+
+class TrustRole(sql.ModelBase):
+ __tablename__ = 'trust_role'
+ attributes = ['trust_id', 'role_id']
+ trust_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
+ role_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
+
+
+class Trust(sql.Base, trust.Driver):
+ @sql.handle_conflicts(type='trust')
+ def create_trust(self, trust_id, trust, roles):
+ session = self.get_session()
+ with session.begin():
+ ref = TrustModel.from_dict(trust)
+ ref['id'] = trust_id
+ if ref.get('expires_at') and ref['expires_at'].tzinfo is not None:
+ ref['expires_at'] = timeutils.normalize_time(ref['expires_at'])
+ session.add(ref)
+ added_roles = []
+ for role in roles:
+ trust_role = TrustRole()
+ trust_role.trust_id = trust_id
+ trust_role.role_id = role['id']
+ added_roles.append({'id': role['id']})
+ session.add(trust_role)
+ session.flush()
+ trust_dict = ref.to_dict()
+ trust_dict['roles'] = added_roles
+ return trust_dict
+
+ def _add_roles(self, trust_id, session, trust_dict):
+ roles = []
+ for role in session.query(TrustRole).filter_by(trust_id=trust_id):
+ roles.append({'id': role.role_id})
+ trust_dict['roles'] = roles
+
+ @sql.handle_conflicts(type='trust')
+ def get_trust(self, trust_id):
+ session = self.get_session()
+ ref = (session.query(TrustModel).
+ filter_by(deleted_at=None).
+ filter_by(id=trust_id).first())
+ if ref is None:
+ return None
+ if ref.expires_at is not None:
+ now = timeutils.utcnow()
+ if now > ref.expires_at:
+ return None
+ trust_dict = ref.to_dict()
+
+ self._add_roles(trust_id, session, trust_dict)
+ return trust_dict
+
+ @sql.handle_conflicts(type='trust')
+ def list_trusts(self):
+ session = self.get_session()
+ trusts = session.query(TrustModel).filter_by(deleted_at=None)
+ return [trust_ref.to_dict() for trust_ref in trusts]
+
+ @sql.handle_conflicts(type='trust')
+ def list_trusts_for_trustee(self, trustee_user_id):
+ session = self.get_session()
+ trusts = (session.query(TrustModel).
+ filter_by(deleted_at=None).
+ filter_by(trustee_user_id=trustee_user_id))
+ return [trust_ref.to_dict() for trust_ref in trusts]
+
+ @sql.handle_conflicts(type='trust')
+ def list_trusts_for_trustor(self, trustor_user_id):
+ session = self.get_session()
+ trusts = (session.query(TrustModel).
+ filter_by(deleted_at=None).
+ filter_by(trustor_user_id=trustor_user_id))
+ return [trust_ref.to_dict() for trust_ref in trusts]
+
+ @sql.handle_conflicts(type='trust')
+ def delete_trust(self, trust_id):
+ session = self.get_session()
+ with session.begin():
+ try:
+ trust_ref = (session.query(TrustModel).
+ filter_by(id=trust_id).one())
+ except sql.NotFound:
+ raise exception.TrustNotFound(trust_id=trust_id)
+ trust_ref.deleted_at = timeutils.utcnow()
+ session.flush()
diff --git a/keystone/trust/controllers.py b/keystone/trust/controllers.py
new file mode 100644
index 00000000..00183bc5
--- /dev/null
+++ b/keystone/trust/controllers.py
@@ -0,0 +1,244 @@
+import uuid
+import json
+
+from keystone import config
+from keystone import exception
+from keystone import identity
+from keystone.common import controller
+from keystone.common import dependency
+from keystone.common import logging
+from keystone import exception
+from keystone.openstack.common import timeutils
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+def _trustor_only(context, trust, user_id):
+ if user_id != trust.get('trustor_user_id'):
+ raise exception.Forbidden()
+
+
+def _admin_trustor_trustee_only(context, trust, user_id):
+ if (user_id != trust.get('trustor_user_id') and
+ user_id != trust.get('trustor_user_id') and
+ context['is_admin']):
+ raise exception.Forbidden()
+
+
+def _admin_trustor_only(context, trust, user_id):
+ if user_id != trust.get('trustor_user_id') and not context['is_admin']:
+ raise exception.Forbidden()
+
+
+@dependency.requires('identity_api', 'trust_api', 'token_api')
+class TrustV3(controller.V3Controller):
+ collection_name = "trusts"
+ member_name = "trust"
+
+ def _get_user_id(self, context):
+ if 'token_id' in context:
+ token_id = context['token_id']
+ token = self.token_api.get_token(context, token_id)
+ user_id = token['user']['id']
+ return user_id
+ return None
+
+ def get_trust(self, context, trust_id):
+ user_id = self._get_user_id(context)
+ trust = self.trust_api.get_trust(context, trust_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+ _admin_trustor_trustee_only(context, trust, user_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id=trust_id)
+ if (user_id != trust['trustor_user_id'] and
+ user_id != trust['trustee_user_id']):
+ raise exception.Forbidden()
+ self._fill_in_roles(context, trust,
+ self.identity_api.list_roles(context))
+ return TrustV3.wrap_member(context, trust)
+
+ def _fill_in_roles(self, context, trust, global_roles):
+ if trust.get('expires_at') is not None:
+ trust['expires_at'] = (timeutils.isotime
+ (trust['expires_at'],
+ subsecond=True))
+
+ if not 'roles' in trust:
+ trust['roles'] = []
+ trust_full_roles = []
+ for trust_role in trust['roles']:
+ if isinstance(trust_role, basestring):
+ trust_role = {'id': trust_role}
+ matching_roles = [x for x in global_roles
+ if x['id'] == trust_role['id']]
+ if matching_roles:
+ full_role = identity.controllers.RoleV3.wrap_member(
+ context, matching_roles[0])['role']
+ trust_full_roles.append(full_role)
+ trust['roles'] = trust_full_roles
+ trust['roles_links'] = {
+ 'self': (CONF.public_endpoint % CONF +
+ "trusts/%s/roles" % trust['id']),
+ 'next': None,
+ 'previous': None}
+
+ def _clean_role_list(self, context, trust, global_roles):
+ trust_roles = []
+ global_role_names = dict((r['name'], r)
+ for r in
+ global_roles)
+ for role in trust.get('roles', []):
+ if 'id' in role:
+ trust_roles.append({'id': role['id']})
+ elif 'name' in role:
+ rolename = role['name']
+ if rolename in global_role_names:
+ trust_roles.append({'id':
+ global_role_names[rolename]['id']})
+ else:
+ raise exception.RoleNotFound("role %s is not defined" %
+ rolename)
+ else:
+ raise exception.ValidationError(attribute='id or name',
+ target='roles')
+ return trust_roles
+
+ @controller.protected
+ def create_trust(self, context, trust=None):
+ """
+ The user creating the trust must be trustor
+ """
+
+ #TODO instead of raising ValidationError on the first problem,
+ #return a collection of all the problems.
+ if not trust:
+ raise exception.ValidationError(attribute='trust',
+ target='request')
+ try:
+ user_id = self._get_user_id(context)
+ _trustor_only(context, trust, user_id)
+ #confirm that the trustee exists
+ trustee_ref = self.identity_api.get_user(context,
+ trust['trustee_user_id'])
+ if not trustee_ref:
+ raise exception.UserNotFound(user_id=trust['trustee_user_id'])
+ global_roles = self.identity_api.list_roles(context)
+ clean_roles = self._clean_role_list(context, trust, global_roles)
+ if trust.get('project_id'):
+ user_roles = self.identity_api.get_roles_for_user_and_project(
+ context, user_id, trust['project_id'])
+ else:
+ user_roles = []
+ for trust_role in clean_roles:
+ matching_roles = [x for x in user_roles
+ if x == trust_role['id']]
+ if not matching_roles:
+ raise exception.RoleNotFound(role_id=trust_role['id'])
+ if trust.get('expires_at') is not None:
+ if not trust['expires_at'].endswith('Z'):
+ trust['expires_at'] += 'Z'
+ trust['expires_at'] = (timeutils.parse_isotime
+ (trust['expires_at']))
+ new_trust = self.trust_api.create_trust(
+ context=context,
+ trust_id=uuid.uuid4().hex,
+ trust=trust,
+ roles=clean_roles)
+ self._fill_in_roles(context,
+ new_trust,
+ global_roles)
+ return TrustV3.wrap_member(context, new_trust)
+ except KeyError as e:
+ raise exception.ValidationError(attribute=e.args[0],
+ target='trust')
+
+ @controller.protected
+ def list_trusts(self, context):
+ query = context['query_string']
+ trusts = []
+ if not query:
+ self.assert_admin(context)
+ trusts += self.trust_api.list_trusts(context)
+ if 'trustor_user_id' in query:
+ user_id = query['trustor_user_id']
+ calling_user_id = self._get_user_id(context)
+ if user_id != calling_user_id:
+ raise exception.Forbidden()
+ trusts += (self.trust_api.
+ list_trusts_for_trustor(context, user_id))
+ if 'trustee_user_id' in query:
+ user_id = query['trustee_user_id']
+ calling_user_id = self._get_user_id(context)
+ if user_id != calling_user_id:
+ raise exception.Forbidden()
+ trusts += (self.trust_api.
+ list_trusts_for_trustee(context, user_id))
+ global_roles = self.identity_api.list_roles(context)
+ for trust in trusts:
+ self._fill_in_roles(context, trust, global_roles)
+ return TrustV3.wrap_collection(context, trusts)
+
+ @controller.protected
+ def delete_trust(self, context, trust_id):
+ trust = self.trust_api.get_trust(context, trust_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+
+ user_id = self._get_user_id(context)
+ _admin_trustor_only(context, trust, user_id)
+ self.trust_api.delete_trust(context, trust_id)
+ userid = trust['trustor_user_id']
+ token_list = self.token_api.list_tokens(context,
+ userid,
+ trust_id=trust_id)
+ for token in token_list:
+ self.token_api.delete_token(context, token)
+
+ @controller.protected
+ def list_roles_for_trust(self, context, trust_id):
+ trust = self.get_trust(context, trust_id)['trust']
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+ user_id = self._get_user_id(context)
+ _admin_trustor_trustee_only(context, trust, user_id)
+ return {'roles': trust['roles'],
+ 'links': trust['roles_links']}
+
+ @controller.protected
+ def check_role_for_trust(self, context, trust_id, role_id):
+ """Checks if a role has been assigned to a trust."""
+ trust = self.trust_api.get_trust(context, trust_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+ user_id = self._get_user_id(context)
+ _admin_trustor_trustee_only(context, trust, user_id)
+ matching_roles = [x for x in trust['roles']
+ if x['id'] == role_id]
+ if not matching_roles:
+ raise exception.RoleNotFound(role_id=role_id)
+
+ @controller.protected
+ def get_role_for_trust(self, context, trust_id, role_id):
+ """Checks if a role has been assigned to a trust."""
+ trust = self.trust_api.get_trust(context, trust_id)
+ if not trust:
+ raise exception.TrustNotFound(trust_id)
+
+ user_id = self._get_user_id(context)
+ _admin_trustor_trustee_only(context, trust, user_id)
+ matching_roles = [x for x in trust['roles']
+ if x['id'] == role_id]
+ if not matching_roles:
+ raise exception.RoleNotFound(role_id=role_id)
+ global_roles = self.identity_api.list_roles(context)
+ matching_roles = [x for x in global_roles
+ if x['id'] == role_id]
+ if matching_roles:
+ full_role = (identity.controllers.
+ RoleV3.wrap_member(context, matching_roles[0]))
+ return full_role
+ else:
+ raise exception.RoleNotFound(role_id=role_id)
diff --git a/keystone/trust/core.py b/keystone/trust/core.py
new file mode 100644
index 00000000..a9a15ff7
--- /dev/null
+++ b/keystone/trust/core.py
@@ -0,0 +1,63 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Main entry point into the Identity service."""
+
+from keystone.common import dependency
+from keystone.common import logging
+from keystone.common import manager
+from keystone.common import wsgi
+from keystone import config
+from keystone import exception
+
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+@dependency.provider('trust_api')
+class Manager(manager.Manager):
+ """Default pivot point for the Trust 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.trust.driver)
+
+
+class Driver(object):
+ def create_trust(self, trust_id, trust, roles):
+ """Create a new trust.
+
+ :returns: a new trust
+ """
+ raise exception.NotImplemented()
+
+ def get_trust(self, trust_id):
+ raise exception.NotImplemented()
+
+ def list_trusts(self):
+ raise exception.NotImplemented()
+
+ def list_trusts_for_trustee(self, trustee):
+ raise exception.NotImplemented()
+
+ def list_trusts_for_trustor(self, trustor):
+ raise exception.NotImplemented()
diff --git a/keystone/trust/routers.py b/keystone/trust/routers.py
new file mode 100644
index 00000000..2ed35ed0
--- /dev/null
+++ b/keystone/trust/routers.py
@@ -0,0 +1,58 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""WSGI Routers for the Identity service."""
+from keystone.common import wsgi
+from keystone.trust import controllers
+from keystone.common import router
+
+
+def append_v3_routers(mapper, routers):
+ trust_controller = controllers.TrustV3()
+
+ mapper.connect('/trusts',
+ controller=trust_controller,
+ action='create_trust',
+ conditions=dict(method=['POST']))
+
+ mapper.connect('/trusts',
+ controller=trust_controller,
+ action='list_trusts',
+ conditions=dict(method=['GET']))
+
+ mapper.connect('/trusts/{trust_id}',
+ controller=trust_controller,
+ action='delete_trust',
+ conditions=dict(method=['DELETE']))
+
+ mapper.connect('/trusts/{trust_id}',
+ controller=trust_controller,
+ action='get_trust',
+ conditions=dict(method=['GET']))
+
+ mapper.connect('/trusts/{trust_id}/roles',
+ controller=trust_controller,
+ action='list_roles_for_trust',
+ conditions=dict(method=['GET']))
+
+ mapper.connect('/trusts/{trust_id}/roles/{role_id}',
+ controller=trust_controller,
+ action='check_role_for_trust',
+ conditions=dict(method=['HEAD']))
+
+ mapper.connect('/trusts/{trust_id}/roles/{role_id}',
+ controller=trust_controller,
+ action='get_role_for_trust',
+ conditions=dict(method=['GET']))
diff --git a/run_tests.sh b/run_tests.sh
index 915de147..bfa2dbcc 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -51,6 +51,7 @@ function process_option {
-f|--force) force=1;;
-u|--update) update=1;;
-p|--pep8) just_pep8=1;;
+ -8|--8) short_pep8=1;;
-P|--no-pep8) no_pep8=1;;
-c|--coverage) coverage=1;;
-xintegration) nokeystoneclient=1;;
@@ -71,6 +72,7 @@ noseargs=
noseopts=
wrapper=""
just_pep8=0
+short_pep8=0
no_pep8=0
coverage=0
nokeystoneclient=0
@@ -108,6 +110,14 @@ function run_tests {
}
function run_pep8 {
+ FLAGS=--show-pep8
+ echo $#
+ if [ $# -gt 0 ] && [ 'short' == ''$1 ]
+ then
+ FLAGS=''
+ fi
+
+
echo "Running pep8 ..."
# Opt-out files from pep8
ignore_scripts="*.pyc,*.pyo,*.sh,*.swp,*.rst"
@@ -116,7 +126,7 @@ function run_pep8 {
ignore="$ignore_scripts,$ignore_files,$ignore_dirs"
srcfiles="."
# Just run PEP8 in current environment
- ${wrapper} pep8 --repeat --show-pep8 --show-source \
+ ${wrapper} pep8 --repeat $FLAGS --show-source \
--exclude=${ignore} ${srcfiles} | tee pep8.txt
}
@@ -162,6 +172,12 @@ if [ $just_pep8 -eq 1 ]; then
exit
fi
+if [ $short_pep8 -eq 1 ]; then
+ run_pep8 short
+ exit
+fi
+
+
if [ $recreate_db -eq 1 ]; then
rm -f tests.sqlite
fi
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..54099681
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+import sys
+
+from keystone.common import utils
+
+
+RE_VERSION = re.compile(r'^OpenSSL 1\.\d\.\d')
+
+
+def setup_package():
+ check_dependencies()
+
+
+def check_dependencies():
+ check_openssl_version()
+
+
+def check_openssl_version():
+ openssl_version = utils.check_output(['openssl', 'version'])
+ openssl_version = openssl_version.strip()
+ match = RE_VERSION.match(openssl_version)
+ if not match:
+ raise AssertionError('Incorrect version of OpenSSL (%s),'
+ ' 1.0.0+ required.' % openssl_version)
diff --git a/tests/backend_sql.conf b/tests/backend_sql.conf
index e1027cc8..0baf610c 100644
--- a/tests/backend_sql.conf
+++ b/tests/backend_sql.conf
@@ -22,3 +22,6 @@ driver = keystone.catalog.backends.sql.Catalog
[policy]
driver = keystone.policy.backends.sql.Policy
+
+[trust]
+driver = keystone.trust.backends.sql.Trust
diff --git a/tests/default_fixtures.py b/tests/default_fixtures.py
index 4499be17..1141ddfd 100644
--- a/tests/default_fixtures.py
+++ b/tests/default_fixtures.py
@@ -109,5 +109,12 @@ ROLES = [
}, {
'id': 'other',
'name': 'Other',
+ }, {
+ 'id': 'browser',
+ 'name': 'Browser',
+ }, {
+ 'id': 'writer',
+ 'name': 'Writer',
}
+
]
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 7567d379..8c8feba0 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -12,23 +12,29 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+import datetime
import time
import uuid
-import default_fixtures
-
+from keystone import auth
from keystone import config
from keystone import exception
from keystone.openstack.common import timeutils
from keystone import test
from keystone import token
+from keystone import trust
+
+import default_fixtures
CONF = config.CONF
+TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
def _build_user_auth(token=None, user_id=None, username=None,
- password=None, tenant_id=None, tenant_name=None):
+ password=None, tenant_id=None, tenant_name=None,
+ trust_id=None):
"""Build auth dictionary.
It will create an auth dictionary based on all the arguments
@@ -49,6 +55,8 @@ def _build_user_auth(token=None, user_id=None, username=None,
auth_json['tenantName'] = tenant_name
if tenant_id is not None:
auth_json['tenantId'] = tenant_id
+ if trust_id is not None:
+ auth_json['trust_id'] = trust_id
return auth_json
@@ -60,7 +68,7 @@ class AuthTest(test.TestCase):
self.load_backends()
self.load_fixtures(default_fixtures)
- self.api = token.controllers.Auth()
+ self.controller = token.controllers.Auth()
def assertEqualTokens(self, a, b):
"""Assert that two tokens are equal.
@@ -92,76 +100,85 @@ class AuthBadRequests(AuthTest):
not applicable"""
self.assertRaises(
token.controllers.ExternalAuthNotApplicable,
- self.api._authenticate_external,
+ self.controller._authenticate_external,
{}, {})
def test_no_token_in_auth(self):
"""Verity that _authenticate_token() raises exception if no token"""
self.assertRaises(
exception.ValidationError,
- self.api._authenticate_token,
+ self.controller._authenticate_token,
None, {})
def test_no_credentials_in_auth(self):
"""Verity that _authenticate_local() raises exception if no creds"""
self.assertRaises(
exception.ValidationError,
- self.api._authenticate_local,
+ self.controller._authenticate_local,
None, {})
def test_authenticate_blank_request_body(self):
"""Verify sending empty json dict raises the right exception."""
- self.assertRaises(exception.ValidationError, self.api.authenticate,
+ self.assertRaises(exception.ValidationError,
+ self.controller.authenticate,
{}, {})
def test_authenticate_blank_auth(self):
"""Verify sending blank 'auth' raises the right exception."""
body_dict = _build_user_auth()
- self.assertRaises(exception.ValidationError, self.api.authenticate,
+ self.assertRaises(exception.ValidationError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_invalid_auth_content(self):
"""Verify sending invalid 'auth' raises the right exception."""
- self.assertRaises(exception.ValidationError, self.api.authenticate,
+ self.assertRaises(exception.ValidationError,
+ self.controller.authenticate,
{}, {'auth': 'abcd'})
def test_authenticate_user_id_too_large(self):
"""Verify sending large 'userId' raises the right exception."""
body_dict = _build_user_auth(user_id='0' * 65, username='FOO',
password='foo2')
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_username_too_large(self):
"""Verify sending large 'username' raises the right exception."""
body_dict = _build_user_auth(username='0' * 65, password='foo2')
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_tenant_id_too_large(self):
"""Verify sending large 'tenantId' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='foo2',
tenant_id='0' * 65)
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_tenant_name_too_large(self):
"""Verify sending large 'tenantName' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='foo2',
tenant_name='0' * 65)
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_token_too_large(self):
"""Verify sending large 'token' raises the right exception."""
body_dict = _build_user_auth(token={'id': '0' * 8193})
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_password_too_large(self):
"""Verify sending large 'password' raises the right exception."""
body_dict = _build_user_auth(username='FOO', password='0' * 8193)
- self.assertRaises(exception.ValidationSizeError, self.api.authenticate,
+ self.assertRaises(exception.ValidationSizeError,
+ self.controller.authenticate,
{}, body_dict)
@@ -173,7 +190,7 @@ class AuthWithToken(AuthTest):
"""Verify getting an unscoped token with password creds"""
body_dict = _build_user_auth(username='FOO',
password='foo2')
- unscoped_token = self.api.authenticate({}, body_dict)
+ unscoped_token = self.controller.authenticate({}, body_dict)
tenant = unscoped_token["access"]["token"].get("tenant", None)
self.assertEqual(tenant, None)
@@ -182,7 +199,7 @@ class AuthWithToken(AuthTest):
body_dict = _build_user_auth(token={"id": uuid.uuid4().hex})
self.assertRaises(
exception.Unauthorized,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_auth_bad_formatted_token(self):
@@ -190,7 +207,7 @@ class AuthWithToken(AuthTest):
body_dict = _build_user_auth(token={})
self.assertRaises(
exception.ValidationError,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_auth_unscoped_token_no_project(self):
@@ -198,11 +215,11 @@ class AuthWithToken(AuthTest):
body_dict = _build_user_auth(
username='FOO',
password='foo2')
- unscoped_token = self.api.authenticate({}, body_dict)
+ unscoped_token = self.controller.authenticate({}, body_dict)
body_dict = _build_user_auth(
token=unscoped_token["access"]["token"])
- unscoped_token_2 = self.api.authenticate({}, body_dict)
+ unscoped_token_2 = self.controller.authenticate({}, body_dict)
self.assertEqualTokens(unscoped_token, unscoped_token_2)
@@ -217,12 +234,12 @@ class AuthWithToken(AuthTest):
body_dict = _build_user_auth(
username='FOO',
password='foo2')
- unscoped_token = self.api.authenticate({}, body_dict)
+ unscoped_token = self.controller.authenticate({}, body_dict)
# Get a token on BAR tenant using the unscoped tenant
body_dict = _build_user_auth(
token=unscoped_token["access"]["token"],
tenant_name="BAR")
- scoped_token = self.api.authenticate({}, body_dict)
+ scoped_token = self.controller.authenticate({}, body_dict)
tenant = scoped_token["access"]["token"]["tenant"]
roles = scoped_token["access"]["metadata"]["roles"]
@@ -253,7 +270,7 @@ class AuthWithToken(AuthTest):
password='foo2',
tenant_name="BAR")
- scoped_token = self.api.authenticate({}, body_dict)
+ scoped_token = self.controller.authenticate({}, body_dict)
tenant = scoped_token["access"]["token"]["tenant"]
roles = scoped_token["access"]["metadata"]["roles"]
@@ -307,7 +324,7 @@ class AuthWithToken(AuthTest):
password=self.user_foo['password'],
tenant_name=project1['name'])
- scoped_token = self.api.authenticate({}, body_dict)
+ scoped_token = self.controller.authenticate({}, body_dict)
tenant = scoped_token["access"]["token"]["tenant"]
roles = scoped_token["access"]["metadata"]["roles"]
self.assertEquals(tenant["id"], project1['id'])
@@ -328,7 +345,7 @@ class AuthWithPasswordCredentials(AuthTest):
password=uuid.uuid4().hex)
self.assertRaises(
exception.Unauthorized,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_auth_valid_user_invalid_password(self):
@@ -338,7 +355,7 @@ class AuthWithPasswordCredentials(AuthTest):
password=uuid.uuid4().hex)
self.assertRaises(
exception.Unauthorized,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_auth_empty_password(self):
@@ -348,7 +365,7 @@ class AuthWithPasswordCredentials(AuthTest):
password="")
self.assertRaises(
exception.Unauthorized,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_auth_no_password(self):
@@ -356,21 +373,23 @@ class AuthWithPasswordCredentials(AuthTest):
body_dict = _build_user_auth(username="FOO")
self.assertRaises(
exception.ValidationError,
- self.api.authenticate,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_blank_password_credentials(self):
"""Verify sending empty json dict as passwordCredentials raises the
right exception."""
body_dict = {'passwordCredentials': {}, 'tenantName': 'demo'}
- self.assertRaises(exception.ValidationError, self.api.authenticate,
+ self.assertRaises(exception.ValidationError,
+ self.controller.authenticate,
{}, body_dict)
def test_authenticate_no_username(self):
"""Verify skipping username raises the right exception."""
body_dict = _build_user_auth(password="pass",
tenant_name="demo")
- self.assertRaises(exception.ValidationError, self.api.authenticate,
+ self.assertRaises(exception.ValidationError,
+ self.controller.authenticate,
{}, body_dict)
@@ -383,11 +402,11 @@ class AuthWithRemoteUser(AuthTest):
body_dict = _build_user_auth(
username='FOO',
password='foo2')
- local_token = self.api.authenticate(
+ local_token = self.controller.authenticate(
{}, body_dict)
body_dict = _build_user_auth()
- remote_token = self.api.authenticate(
+ remote_token = self.controller.authenticate(
{'REMOTE_USER': 'FOO'}, body_dict)
self.assertEqualTokens(local_token, remote_token)
@@ -396,7 +415,7 @@ class AuthWithRemoteUser(AuthTest):
"""Verify that external auth with invalid request fails"""
self.assertRaises(
exception.ValidationError,
- self.api.authenticate,
+ self.controller.authenticate,
{'REMOTE_USER': 'FOO'},
None)
@@ -406,12 +425,12 @@ class AuthWithRemoteUser(AuthTest):
username='FOO',
password='foo2',
tenant_name='BAR')
- local_token = self.api.authenticate(
+ local_token = self.controller.authenticate(
{}, body_dict)
body_dict = _build_user_auth(
tenant_name='BAR')
- remote_token = self.api.authenticate(
+ remote_token = self.controller.authenticate(
{'REMOTE_USER': 'FOO'}, body_dict)
self.assertEqualTokens(local_token, remote_token)
@@ -422,11 +441,11 @@ class AuthWithRemoteUser(AuthTest):
username='TWO',
password='two2',
tenant_name='BAZ')
- local_token = self.api.authenticate(
+ local_token = self.controller.authenticate(
{}, body_dict)
body_dict = _build_user_auth(tenant_name='BAZ')
- remote_token = self.api.authenticate(
+ remote_token = self.controller.authenticate(
{'REMOTE_USER': 'TWO'}, body_dict)
self.assertEqualTokens(local_token, remote_token)
@@ -436,15 +455,249 @@ class AuthWithRemoteUser(AuthTest):
body_dict = _build_user_auth(tenant_name="BAR")
self.assertRaises(
exception.Unauthorized,
- self.api.authenticate,
+ self.controller.authenticate,
{'REMOTE_USER': uuid.uuid4().hex},
body_dict)
+class AuthWithTrust(AuthTest):
+ def setUp(self):
+ super(AuthWithTrust, self).setUp()
+
+ trust.Manager()
+ self.trust_controller = trust.controllers.TrustV3()
+ self.auth_v3_controller = auth.controllers.Auth()
+ self.trustor = self.user_foo
+ self.trustee = self.user_two
+ self.assigned_roles = [self.role_member['id'],
+ self.role_browser['id']]
+ for assigned_role in self.assigned_roles:
+ self.identity_api.add_role_to_user_and_project(
+ self.trustor['id'], self.tenant_bar['id'], assigned_role)
+
+ self.sample_data = {'trustor_user_id': self.trustor['id'],
+ 'trustee_user_id': self.trustee['id'],
+ 'project_id': self.tenant_bar['id'],
+ 'impersonation': 'True',
+ 'roles': [{'id': self.role_browser['id']},
+ {'name': self.role_member['name']}]}
+ expires_at = timeutils.strtime(timeutils.utcnow() +
+ datetime.timedelta(minutes=10),
+ fmt=TIME_FORMAT)
+ self.create_trust(expires_at=expires_at)
+
+ def create_trust(self, expires_at=None, impersonation='True'):
+ username = self.trustor['name'],
+ password = 'foo2'
+ body_dict = _build_user_auth(username=username, password=password)
+ self.unscoped_token = self.controller.authenticate({}, body_dict)
+ context = {'token_id': self.unscoped_token['access']['token']['id']}
+ trust_data = copy.deepcopy(self.sample_data)
+ trust_data['expires_at'] = expires_at
+ trust_data['impersonation'] = impersonation
+
+ self.new_trust = (self.trust_controller.create_trust
+ (context, trust=trust_data)['trust'])
+
+ def build_v2_token_request(self, username, password):
+ body_dict = _build_user_auth(username=username, password=password)
+ self.unscoped_token = self.controller.authenticate({}, body_dict)
+ unscoped_token_id = self.unscoped_token['access']['token']['id']
+ request_body = _build_user_auth(token={'id': unscoped_token_id},
+ trust_id=self.new_trust['id'],
+ tenant_id=self.tenant_bar['id'])
+ return request_body
+
+ def test_create_trust_bad_data_fails(self):
+ context = {'token_id': self.unscoped_token['access']['token']['id']}
+ bad_sample_data = {'trustor_user_id': self.trustor['id']}
+
+ self.assertRaises(exception.ValidationError,
+ self.trust_controller.create_trust,
+ context, trust=bad_sample_data)
+
+ def test_create_trust_no_roles(self):
+ self.new_trust = None
+ self.sample_data['roles'] = []
+ self.create_trust()
+ self.assertEquals(self.new_trust['roles'], [])
+
+ def test_create_trust(self):
+ self.assertEquals(self.new_trust['trustor_user_id'],
+ self.trustor['id'])
+ self.assertEquals(self.new_trust['trustee_user_id'],
+ self.trustee['id'])
+ role_ids = [self.role_browser['id'], self.role_member['id']]
+ self.assertTrue(timeutils.parse_strtime(self.new_trust['expires_at'],
+ fmt=TIME_FORMAT))
+
+ for role in self.new_trust['roles']:
+ self.assertIn(role['id'], role_ids)
+
+ def test_get_trust(self):
+ context = {'token_id': self.unscoped_token['access']['token']['id']}
+ trust = self.trust_controller.get_trust(context,
+ self.new_trust['id'])['trust']
+ self.assertEquals(trust['trustor_user_id'],
+ self.trustor['id'])
+ self.assertEquals(trust['trustee_user_id'],
+ self.trustee['id'])
+ role_ids = [self.role_browser['id'], self.role_member['id']]
+ for role in self.new_trust['roles']:
+ self.assertIn(role['id'], role_ids)
+
+ def test_create_trust_no_impersonation(self):
+ self.create_trust(expires_at=None, impersonation='False')
+ self.assertEquals(self.new_trust['trustor_user_id'],
+ self.trustor['id'])
+ self.assertEquals(self.new_trust['trustee_user_id'],
+ self.trustee['id'])
+ self.assertEquals(self.new_trust['impersonation'],
+ 'False')
+ auth_response = self.fetch_v2_token_from_trust()
+ token_user = auth_response['access']['user']
+ self.assertEquals(token_user['id'],
+ self.new_trust['trustee_user_id'])
+
+ #TODO Endpoints
+
+ def test_token_from_trust_wrong_user_fails(self):
+ new_trust = self.create_trust()
+ request_body = self.build_v2_token_request('FOO', 'foo2')
+ self.assertRaises(
+ exception.Forbidden,
+ self.controller.authenticate, {}, request_body)
+
+ def fetch_v2_token_from_trust(self):
+ request_body = self.build_v2_token_request('TWO', 'two2')
+ auth_response = self.controller.authenticate({}, request_body)
+ return auth_response
+
+ def fetch_v3_token_from_trust(self):
+ self.identity_api.create_domain("default",
+ {"name": "default",
+ "id": "default"})
+ v3_password_data = {
+ 'identity': {
+ "methods": ["password"],
+ "password": {
+ "user": {
+ "id": self.trustee["id"],
+ "password": self.trustee["password"]}}
+ },
+ 'scope': {
+ 'project': {
+ 'id': self.tenant_baz['id']}}}
+ auth_response = (self.auth_v3_controller.authenticate_for_token
+ ({}, v3_password_data))
+ token = auth_response.headers['X-Subject-Token']
+
+ v3_req_with_trust = {
+ "identity": {
+ "methods": ["token"],
+ "token": {"id": token}},
+ "scope": {
+ "trust": {"id": self.new_trust['id']}}}
+ token_auth_response = (self.auth_v3_controller.authenticate_for_token
+ ({}, v3_req_with_trust))
+ return token_auth_response
+
+ def test_create_v3_token_from_trust(self):
+ auth_response = self.fetch_v3_token_from_trust()
+
+ trust_token_user = auth_response.json['token']['user']
+ self.assertEquals(trust_token_user['id'], self.trustor['id'])
+
+ trust_token_trust = auth_response.json['token']['trust']
+ self.assertEquals(trust_token_trust['id'], self.new_trust['id'])
+ self.assertEquals(trust_token_trust['trustor_user']['id'],
+ self.trustor['id'])
+ self.assertEquals(trust_token_trust['trustee_user']['id'],
+ self.trustee['id'])
+
+ trust_token_roles = auth_response.json['token']['roles']
+ self.assertEquals(len(trust_token_roles), 2)
+
+ def test_v3_trust_token_get_token_fails(self):
+ auth_response = self.fetch_v3_token_from_trust()
+ trust_token = auth_response.headers['X-Subject-Token']
+ v3_token_data = {
+ "methods": ["token"],
+ "token": {"id": trust_token}
+ }
+ self.assertRaises(
+ exception.Unauthorized,
+ self.auth_v3_controller.authenticate_for_token,
+ {}, v3_token_data)
+
+ def test_token_from_trust(self):
+ auth_response = self.fetch_v2_token_from_trust()
+
+ self.assertIsNotNone(auth_response)
+ self.assertEquals(len(auth_response['access']['metadata']['roles']),
+ 2,
+ "user_foo has three roles, but the token should"
+ " only get the two roles specified in the trust.")
+
+ def test_token_from_trust_cant_get_another_token(self):
+ auth_response = self.fetch_v2_token_from_trust()
+ trust_token_id = auth_response['access']['token']['id']
+ request_body = _build_user_auth(token={'id': trust_token_id},
+ tenant_id=self.tenant_bar['id'])
+ self.assertRaises(
+ exception.Forbidden,
+ self.controller.authenticate, {}, request_body)
+
+ def test_delete_trust_revokes_token(self):
+ context = {'token_id': self.unscoped_token['access']['token']['id']}
+ auth_response = self.fetch_v2_token_from_trust()
+ trust_id = self.new_trust['id']
+ trust_token_id = auth_response['access']['token']['id']
+ tokens = self.token_api.list_tokens(self.trustor['id'],
+ trust_id=trust_id)
+ self.assertEquals(len(tokens), 1)
+ self.trust_controller.delete_trust(context, trust_id=trust_id)
+ tokens = self.token_api.list_tokens(self.trustor['id'],
+ trust_id=trust_id)
+ self.assertEquals(len(tokens), 0)
+
+ def test_token_from_trust_with_no_role_fails(self):
+ for assigned_role in self.assigned_roles:
+ self.identity_api.remove_role_from_user_and_project(
+ self.trustor['id'], self.tenant_bar['id'], assigned_role)
+ request_body = self.build_v2_token_request('TWO', 'two2')
+ self.assertRaises(
+ exception.Forbidden,
+ self.controller.authenticate, {}, request_body)
+
+ def test_expired_trust_get_token_fails(self):
+ expiry = "1999-02-18T10:10:00Z"
+ self.create_trust(expiry)
+ request_body = self.build_v2_token_request('TWO', 'two2')
+ self.assertRaises(
+ exception.Forbidden,
+ self.controller.authenticate, {}, request_body)
+
+ def test_token_from_trust_with_wrong_role_fails(self):
+ self.identity_api.add_role_to_user_and_project(
+ self.trustor['id'],
+ self.tenant_bar['id'],
+ self.role_other['id'])
+ for assigned_role in self.assigned_roles:
+ self.identity_api.remove_role_from_user_and_project(
+ self.trustor['id'], self.tenant_bar['id'], assigned_role)
+
+ request_body = self.build_v2_token_request('TWO', 'two2')
+
+ self.assertRaises(
+ exception.Forbidden,
+ self.controller.authenticate, {}, request_body)
+
+
class TokenExpirationTest(AuthTest):
def _maintain_token_expiration(self):
"""Token expiration should be maintained after re-auth & validation."""
- r = self.api.authenticate(
+ r = self.controller.authenticate(
{},
auth={
'passwordCredentials': {
@@ -457,14 +710,14 @@ class TokenExpirationTest(AuthTest):
time.sleep(0.5)
- r = self.api.validate_token(
+ r = self.controller.validate_token(
dict(is_admin=True, query_string={}),
token_id=unscoped_token_id)
self.assertEqual(original_expiration, r['access']['token']['expires'])
time.sleep(0.5)
- r = self.api.authenticate(
+ r = self.controller.authenticate(
{},
auth={
'token': {
@@ -477,7 +730,7 @@ class TokenExpirationTest(AuthTest):
time.sleep(0.5)
- r = self.api.validate_token(
+ r = self.controller.validate_token(
dict(is_admin=True, query_string={}),
token_id=scoped_token_id)
self.assertEqual(original_expiration, r['access']['token']['expires'])
diff --git a/tests/test_auth_plugin.py b/tests/test_auth_plugin.py
index d35d5f23..fc44c53d 100644
--- a/tests/test_auth_plugin.py
+++ b/tests/test_auth_plugin.py
@@ -56,7 +56,7 @@ class TestAuthPlugin(test.TestCase):
method_name = uuid.uuid4().hex
auth_data = {'methods': [method_name]}
auth_data[method_name] = {'test': 'test'}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
self.assertRaises(exception.AuthMethodNotSupported,
auth.controllers.AuthInfo,
None,
@@ -66,7 +66,7 @@ class TestAuthPlugin(test.TestCase):
auth_data = {'methods': ['simple-challenge-response']}
auth_data['simple-challenge-response'] = {
'test': 'test'}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
try:
@@ -81,7 +81,7 @@ class TestAuthPlugin(test.TestCase):
auth_data = {'methods': ['simple-challenge-response']}
auth_data['simple-challenge-response'] = {
'response': EXPECTED_RESPONSE}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
self.api.authenticate({}, auth_info, auth_context)
@@ -91,7 +91,7 @@ class TestAuthPlugin(test.TestCase):
auth_data = {'methods': ['simple-challenge-response']}
auth_data['simple-challenge-response'] = {
'response': uuid.uuid4().hex}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
self.assertRaises(exception.Unauthorized,
diff --git a/tests/test_backend.py b/tests/test_backend.py
index 029901eb..1af0822c 100644
--- a/tests/test_backend.py
+++ b/tests/test_backend.py
@@ -29,6 +29,7 @@ from keystone import test
CONF = config.CONF
DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
+TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
class IdentityTests(object):
@@ -1888,12 +1889,14 @@ class TokenTests(object):
self.assertRaises(exception.TokenNotFound,
self.token_api.delete_token, token_id)
- def create_token_sample_data(self, tenant_id=None):
+ def create_token_sample_data(self, tenant_id=None, trust_id=None):
token_id = uuid.uuid4().hex
data = {'id': token_id, 'a': 'b',
'user': {'id': 'testuserid'}}
if tenant_id is not None:
data['tenant'] = {'id': tenant_id, 'name': tenant_id}
+ if trust_id is not None:
+ data['trust_id'] = trust_id
self.token_api.create_token(token_id, data)
return token_id
@@ -1936,6 +1939,13 @@ class TokenTests(object):
self.assertNotIn(token_id3, tokens)
self.assertIn(token_id4, tokens)
+ def test_token_list_trust(self):
+ trust_id = uuid.uuid4().hex
+ token_id5 = self.create_token_sample_data(trust_id=trust_id)
+ tokens = self.token_api.list_tokens('testuserid', trust_id=trust_id)
+ self.assertEquals(len(tokens), 1)
+ self.assertIn(token_id5, tokens)
+
def test_get_token_404(self):
self.assertRaises(exception.TokenNotFound,
self.token_api.get_token,
@@ -1965,7 +1975,7 @@ class TokenTests(object):
data = {'id': token_id, 'id_hash': token_id, 'a': 'b', 'expires': None,
'user': {'id': 'testuserid'}}
data_ref = self.token_api.create_token(token_id, data)
- self.assertDictEqual(data_ref, data)
+ self.assertIsNotNone(data_ref['expires'])
new_data_ref = self.token_api.get_token(token_id)
self.assertEqual(data_ref, new_data_ref)
@@ -2002,6 +2012,80 @@ class TokenTests(object):
for x in xrange(2)])
+class TrustTests(object):
+ def create_sample_trust(self, new_id):
+ self.trustor = self.user_foo
+ self.trustee = self.user_two
+ trust_data = (self.trust_api.create_trust
+ (new_id,
+ {'trustor_user_id': self.trustor['id'],
+ 'trustee_user_id': self.user_two['id'],
+ 'project_id': self.tenant_bar['id'],
+ 'expires_at': timeutils.
+ parse_isotime('2031-02-18T18:10:00Z'),
+ 'impersonation': True},
+ roles=[{"id": "member"},
+ {"id": "other"},
+ {"id": "browser"}]))
+ return trust_data
+
+ def test_delete_trust(self):
+ new_id = uuid.uuid4().hex
+ trust_data = self.create_sample_trust(new_id)
+ trust_id = trust_data['id']
+ self.assertIsNotNone(trust_data)
+ trust_data = self.trust_api.get_trust(trust_id)
+ self.assertEquals(new_id, trust_data['id'])
+ self.trust_api.delete_trust(trust_id)
+ self.assertIsNone(self.trust_api.get_trust(trust_id))
+
+ def test_get_trust(self):
+ new_id = uuid.uuid4().hex
+ trust_data = self.create_sample_trust(new_id)
+ trust_id = trust_data['id']
+ self.assertIsNotNone(trust_data)
+ trust_data = self.trust_api.get_trust(trust_id)
+ self.assertEquals(new_id, trust_data['id'])
+
+ def test_create_trust(self):
+ new_id = uuid.uuid4().hex
+ trust_data = self.create_sample_trust(new_id)
+
+ self.assertEquals(new_id, trust_data['id'])
+ self.assertEquals(self.trustee['id'], trust_data['trustee_user_id'])
+ self.assertEquals(self.trustor['id'], trust_data['trustor_user_id'])
+ self.assertTrue(timeutils.normalize_time(trust_data['expires_at']) >
+ timeutils.utcnow())
+
+ self.assertEquals([{'id':'member'},
+ {'id': 'other'},
+ {'id': 'browser'}], trust_data['roles'])
+
+ def test_list_trust_by_trustee(self):
+ for i in range(0, 3):
+ trust_data = self.create_sample_trust(uuid.uuid4().hex)
+ trusts = self.trust_api.list_trusts_for_trustee(self.trustee)
+ self.assertEqual(len(trusts), 3)
+ self.assertEqual(trusts[0]["trustee_user_id"], self.trustee['id'])
+ trusts = self.trust_api.list_trusts_for_trustee(self.trustor)
+ self.assertEqual(len(trusts), 0)
+
+ def test_list_trust_by_trustee(self):
+ for i in range(0, 3):
+ trust_data = self.create_sample_trust(uuid.uuid4().hex)
+ trusts = self.trust_api.list_trusts_for_trustor(self.trustor['id'])
+ self.assertEqual(len(trusts), 3)
+ self.assertEqual(trusts[0]["trustor_user_id"], self.trustor['id'])
+ trusts = self.trust_api.list_trusts_for_trustor(self.trustee['id'])
+ self.assertEqual(len(trusts), 0)
+
+ def test_list_trusts(self):
+ for i in range(0, 3):
+ trust_data = self.create_sample_trust(uuid.uuid4().hex)
+ trusts = self.trust_api.list_trusts()
+ self.assertEqual(len(trusts), 3)
+
+
class CommonHelperTests(test.TestCase):
def test_format_helper_raises_malformed_on_missing_key(self):
with self.assertRaises(exception.MalformedEndpoint):
diff --git a/tests/test_backend_kvs.py b/tests/test_backend_kvs.py
index e1d99d47..74b5e4eb 100644
--- a/tests/test_backend_kvs.py
+++ b/tests/test_backend_kvs.py
@@ -22,6 +22,7 @@ from keystone import exception
from keystone.identity.backends import kvs as identity_kvs
from keystone import test
from keystone.token.backends import kvs as token_kvs
+from keystone.trust.backends import kvs as trust_kvs
import default_fixtures
import test_backend
@@ -71,6 +72,15 @@ class KvsToken(test.TestCase, test_backend.TokenTests):
self.token_api = token_kvs.Token(db={})
+class KvsTrust(test.TestCase, test_backend.TrustTests):
+ def setUp(self):
+ super(KvsTrust, self).setUp()
+ self.trust_api = trust_kvs.Trust(db={})
+ self.identity_api = identity_kvs.Identity(db={})
+ self.catalog_api = catalog_kvs.Catalog(db={})
+ self.load_fixtures(default_fixtures)
+
+
class KvsCatalog(test.TestCase, test_backend.CatalogTests):
def setUp(self):
super(KvsCatalog, self).setUp()
diff --git a/tests/test_backend_sql.py b/tests/test_backend_sql.py
index a4be85e3..04310307 100644
--- a/tests/test_backend_sql.py
+++ b/tests/test_backend_sql.py
@@ -24,6 +24,8 @@ from keystone import identity
from keystone import policy
from keystone import test
from keystone import token
+from keystone import trust
+
import default_fixtures
import test_backend
@@ -43,6 +45,7 @@ class SqlTests(test.TestCase):
self.catalog_man = catalog.Manager()
self.identity_man = identity.Manager()
self.token_man = token.Manager()
+ self.trust_man = trust.Manager()
self.policy_man = policy.Manager()
# create shortcut references to each driver
@@ -50,6 +53,7 @@ class SqlTests(test.TestCase):
self.identity_api = self.identity_man.driver
self.token_api = self.token_man.driver
self.policy_api = self.policy_man.driver
+ self.trust_api = self.trust_man.driver
# populate the engine with tables & fixtures
self.load_fixtures(default_fixtures)
@@ -221,6 +225,10 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
self.assertEqual(arbitrary_value, ref['extra'][arbitrary_key])
+class SqlTrust(SqlTests, test_backend.TrustTests):
+ pass
+
+
class SqlToken(SqlTests, test_backend.TokenTests):
pass
diff --git a/tests/test_content_types.py b/tests/test_content_types.py
index 183974fd..e0b42db9 100644
--- a/tests/test_content_types.py
+++ b/tests/test_content_types.py
@@ -90,7 +90,7 @@ class RestfulTestCase(test.TestCase):
# Initialize headers dictionary
headers = {} if not headers else headers
- connection = httplib.HTTPConnection(host, port, timeout=10)
+ connection = httplib.HTTPConnection(host, port, timeout=100000)
# Perform the request
connection.request(method, path, body, headers)
@@ -173,15 +173,15 @@ class RestfulTestCase(test.TestCase):
if response.body is not None and response.body.strip():
# if a body is provided, a Content-Type is also expected
header = response.getheader('Content-Type', None)
- self.assertIn(self.content_type, header)
+ self.assertIn(content_type, header)
- if self.content_type == 'json':
+ if content_type == 'json':
response.body = jsonutils.loads(response.body)
- elif self.content_type == 'xml':
+ elif content_type == 'xml':
response.body = etree.fromstring(response.body)
def restful_request(self, method='GET', headers=None, body=None,
- token=None, **kwargs):
+ token=None, content_type=None, **kwargs):
"""Serializes/deserializes json/xml as request/response body.
.. WARNING::
@@ -196,13 +196,13 @@ class RestfulTestCase(test.TestCase):
if token is not None:
headers['X-Auth-Token'] = token
- body = self._to_content_type(body, headers)
+ body = self._to_content_type(body, headers, content_type)
# Perform the HTTP request/response
response = self.request(method=method, headers=headers, body=body,
**kwargs)
- self._from_content_type(response)
+ self._from_content_type(response, content_type)
# we can save some code & improve coverage by always doing this
if method != 'HEAD' and response.status >= 400:
@@ -742,8 +742,9 @@ class XmlTestCase(RestfulTestCase, CoreApiTests):
self.assertIsNotNone(extension.find(self._tag('description')))
self.assertTrue(extension.find(self._tag('description')).text)
- self.assertTrue(len(extension.findall(self._tag('link'))))
- for link in extension.findall(self._tag('link')):
+ links = extension.find(self._tag('links'))
+ self.assertTrue(len(links.findall(self._tag('link'))))
+ for link in links.findall(self._tag('link')):
self.assertValidExtensionLink(link)
def assertValidExtensionListResponse(self, r):
@@ -763,8 +764,10 @@ class XmlTestCase(RestfulTestCase, CoreApiTests):
def assertValidVersion(self, version):
super(XmlTestCase, self).assertValidVersion(version)
- self.assertTrue(len(version.findall(self._tag('link'))))
- for link in version.findall(self._tag('link')):
+ links = version.find(self._tag('links'))
+ self.assertIsNotNone(links)
+ self.assertTrue(len(links.findall(self._tag('link'))))
+ for link in links.findall(self._tag('link')):
self.assertIsNotNone(link.get('rel'))
self.assertIsNotNone(link.get('href'))
diff --git a/tests/test_overrides.conf b/tests/test_overrides.conf
index 48f5dd7f..0e41fd32 100644
--- a/tests/test_overrides.conf
+++ b/tests/test_overrides.conf
@@ -8,6 +8,9 @@ driver = keystone.identity.backends.kvs.Identity
driver = keystone.catalog.backends.templated.TemplatedCatalog
template_file = default_catalog.templates
+[trust]
+driver = keystone.trust.backends.kvs.Trust
+
[signing]
certfile = ../examples/pki/certs/signing_cert.pem
keyfile = ../examples/pki/private/signing_key.pem
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 288e5516..816bad45 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -145,6 +145,17 @@ class XmlSerializerTestCase(test.TestCase):
self.assertSerializeDeserialize(d, xml)
+ def test_policy_list(self):
+ d = {"policies": [{"id": "ab12cd"}]}
+
+ xml = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <policies xmlns="http://docs.openstack.org/identity/api/v2.0">
+ <policy id="ab12cd"/>
+ </policies>
+ """
+ self.assertEqualIgnoreWhitespace(serializer.to_xml(d), xml)
+
def test_values_list(self):
d = {
"objects": {
diff --git a/tests/test_sql_upgrade.py b/tests/test_sql_upgrade.py
index 85ea7580..003fb4ec 100644
--- a/tests/test_sql_upgrade.py
+++ b/tests/test_sql_upgrade.py
@@ -526,6 +526,18 @@ class SqlUpgradeTests(test.TestCase):
cmd = this_table.delete(id=project['id'])
self.engine.execute(cmd)
+ def test_upgrade_trusts(self):
+ self.assertEqual(self.schema.version, 0, "DB is at version 0")
+ self.upgrade(18)
+ self.assertTableColumns("trust",
+ ["id", "trustor_user_id",
+ "trustee_user_id",
+ "project_id", "impersonation",
+ "deleted_at",
+ "expires_at", "extra"])
+ self.assertTableColumns("trust_role",
+ ["trust_id", "role_id"])
+
def populate_user_table(self, with_pass_enab=False,
with_pass_enab_domain=False):
# Populate the appropriate fields in the user
diff --git a/tests/test_v3.py b/tests/test_v3.py
index ef1a1a6c..39807cec 100644
--- a/tests/test_v3.py
+++ b/tests/test_v3.py
@@ -1,23 +1,34 @@
+import datetime
import uuid
-from keystone.common.sql import util as sql_util
+from lxml import etree
+
from keystone import auth
-from keystone import test
+from keystone.common import serializer
+from keystone.common.sql import util as sql_util
from keystone import config
+from keystone.openstack.common import timeutils
+from keystone.policy.backends import rules
+from keystone import test
import test_content_types
CONF = config.CONF
+TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
+
class RestfulTestCase(test_content_types.RestfulTestCase):
def setUp(self):
+ rules.reset()
+
self.config([
test.etcdir('keystone.conf.sample'),
test.testsdir('test_overrides.conf'),
test.testsdir('backend_sql.conf'),
test.testsdir('backend_sql_disk.conf')])
+
sql_util.setup_test_database()
self.load_backends()
@@ -59,6 +70,9 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
sql_util.teardown_test_database()
# need to reset the plug-ins
auth.controllers.AUTH_METHODS = {}
+ #drop the policy rules
+ CONF.reset()
+ rules.reset()
def new_ref(self):
"""Populates a ref with attributes common to all API entities."""
@@ -122,27 +136,61 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
ref['type'] = uuid.uuid4().hex
return ref
+ def new_trust_ref(self, trustor_user_id, trustee_user_id, project_id=None,
+ impersonation=None, expires=None, role_ids=None,
+ role_names=None):
+ ref = self.new_ref()
+
+ ref['trustor_user_id'] = trustor_user_id
+ ref['trustee_user_id'] = trustee_user_id
+ ref['impersonation'] = impersonation or False
+ ref['project_id'] = project_id
+
+ if isinstance(expires, basestring):
+ ref['expires_at'] = expires
+ elif isinstance(expires, dict):
+ ref['expires_at'] = timeutils.strtime(
+ timeutils.utcnow() + datetime.timedelta(**expires),
+ fmt=TIME_FORMAT)
+ elif expires is None:
+ pass
+ else:
+ raise NotImplementedError('Unexpected value for "expires"')
+
+ role_ids = role_ids or []
+ role_names = role_names or []
+ if role_ids or role_names:
+ ref['roles'] = []
+ for role_id in role_ids:
+ ref['roles'].append({'id': role_id})
+ for role_name in role_names:
+ ref['roles'].append({'name': role_name})
+
+ return ref
+
def get_scoped_token(self):
"""Convenience method so that we can test authenticated requests."""
r = self.admin_request(
method='POST',
path='/v3/auth/tokens',
body={
- 'authentication': {
- 'methods': ['password'],
- 'password': {
- 'user': {
- 'name': self.user['name'],
- 'password': self.user['password'],
- 'domain': {
- 'id': self.user['domain_id']
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': self.user['name'],
+ 'password': self.user['password'],
+ 'domain': {
+ 'id': self.user['domain_id']
+ }
}
}
- }
- },
- 'scope': {
- 'project': {
- 'id': self.project['id'],
+ },
+ 'scope': {
+ 'project': {
+ 'id': self.project['id'],
+ }
}
}
})
@@ -191,11 +239,32 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
return self.v3_request(method='DELETE', path=path, **kwargs)
def assertValidErrorResponse(self, r):
- self.assertIsNotNone(r.body.get('error'))
- self.assertIsNotNone(r.body['error'].get('code'))
- self.assertIsNotNone(r.body['error'].get('title'))
- self.assertIsNotNone(r.body['error'].get('message'))
- self.assertEqual(r.body['error']['code'], r.status)
+ if r.getheader('Content-Type') == 'application/xml':
+ resp = serializer.from_xml(etree.tostring(r.body))
+ else:
+ resp = r.body
+ self.assertIsNotNone(resp.get('error'))
+ self.assertIsNotNone(resp['error'].get('code'))
+ self.assertIsNotNone(resp['error'].get('title'))
+ self.assertIsNotNone(resp['error'].get('message'))
+ self.assertEqual(int(resp['error']['code']), r.status)
+
+ def assertValidListLinks(self, links):
+ self.assertIsNotNone(links)
+ self.assertIsNotNone(links.get('self'))
+ self.assertIn(CONF.public_endpoint % CONF, links['self'])
+
+ self.assertIn('next', links)
+ if links['next'] is not None:
+ self.assertIn(
+ CONF.public_endpoint % CONF,
+ links['next'])
+
+ self.assertIn('previous', links)
+ if links['previous'] is not None:
+ self.assertIn(
+ CONF.public_endpoint % CONF,
+ links['previous'])
def assertValidListResponse(self, resp, key, entity_validator, ref=None,
expected_length=None):
@@ -215,11 +284,7 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
self.assertTrue(len(entities))
# collections should have relational links
- self.assertIsNotNone(resp.body.get('links'))
- self.assertIn('previous', resp.body['links'])
- self.assertIn('self', resp.body['links'])
- self.assertIn('next', resp.body['links'])
- self.assertIn(CONF.public_endpoint % CONF, resp.body['links']['self'])
+ self.assertValidListLinks(resp.body.get('links'))
for entity in entities:
self.assertIsNotNone(entity)
@@ -231,12 +296,13 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
entity_validator(entity, ref)
return entities
- def assertValidResponse(self, resp, key, entity_validator, ref):
+ def assertValidResponse(self, resp, key, entity_validator, *args,
+ **kwargs):
"""Make assertions common to all API responses."""
entity = resp.body.get(key)
self.assertIsNotNone(entity)
- self.assertValidEntity(entity, ref)
- entity_validator(entity, ref)
+ self.assertValidEntity(entity, *args, **kwargs)
+ entity_validator(entity, *args, **kwargs)
return entity
def assertValidEntity(self, entity, ref=None):
@@ -263,6 +329,411 @@ class RestfulTestCase(test_content_types.RestfulTestCase):
return entity
+ # auth validation
+
+ def assertValidISO8601ExtendedFormatDatetime(self, dt):
+ try:
+ return timeutils.parse_strtime(dt, fmt=TIME_FORMAT)
+ except Exception:
+ msg = '%s is not a valid ISO 8601 extended format date time.' % dt
+ raise AssertionError(msg)
+ self.assertTrue(isinstance(dt, datetime.datetime))
+
+ def assertValidTokenResponse(self, r, user=None):
+ self.assertTrue(r.getheader('X-Subject-Token'))
+ token = r.body
+ if r.getheader('Content-Type') == 'application/xml':
+ token = serializer.from_xml(etree.tostring(r.body))['token']
+ else:
+ token = r.body['token']
+
+ self.assertIsNotNone(token.get('expires_at'))
+ expires_at = self.assertValidISO8601ExtendedFormatDatetime(
+ token['expires_at'])
+ self.assertIsNotNone(token.get('issued_at'))
+ issued_at = self.assertValidISO8601ExtendedFormatDatetime(
+ token['issued_at'])
+ self.assertTrue(issued_at < expires_at)
+
+ self.assertIn('user', token)
+ self.assertIn('id', token['user'])
+ self.assertIn('name', token['user'])
+ self.assertIn('domain', token['user'])
+ self.assertIn('id', token['user']['domain'])
+
+ if user is not None:
+ self.assertEqual(user['id'], token['user']['id'])
+ self.assertEqual(user['name'], token['user']['name'])
+ self.assertEqual(user['domain_id'], token['user']['domain']['id'])
+
+ return token
+
+ def assertValidUnscopedTokenResponse(self, r, *args, **kwargs):
+ token = self.assertValidTokenResponse(r, *args, **kwargs)
+
+ self.assertNotIn('roles', token)
+ self.assertNotIn('catalog', token)
+ self.assertNotIn('project', token)
+ self.assertNotIn('domain', token)
+
+ return token
+
+ def assertValidScopedTokenResponse(self, r, *args, **kwargs):
+ token = self.assertValidTokenResponse(r, *args, **kwargs)
+
+ self.assertIn('catalog', token)
+ self.assertIn('roles', token)
+ self.assertTrue(token['roles'])
+ for role in token['roles']:
+ self.assertIn('id', role)
+ self.assertIn('name', role)
+
+ return token
+
+ def assertValidProjectScopedTokenResponse(self, r, *args, **kwargs):
+ token = self.assertValidScopedTokenResponse(r, *args, **kwargs)
+
+ self.assertIn('project', token)
+ self.assertIn('id', token['project'])
+ self.assertIn('name', token['project'])
+ self.assertIn('domain', token['project'])
+ self.assertIn('id', token['project']['domain'])
+ self.assertIn('name', token['project']['domain'])
+
+ self.assertEqual(self.role_id, token['roles'][0]['id'])
+
+ return token
+
+ def assertValidProjectTrustScopedTokenResponse(self, r, *args, **kwargs):
+ token = self.assertValidProjectScopedTokenResponse(r, *args, **kwargs)
+
+ self.assertIsNotNone(token.get('trust'))
+ self.assertIsNotNone(token['trust'].get('id'))
+ self.assertTrue(isinstance(token['trust'].get('impersonation'), bool))
+ self.assertIsNotNone(token['trust'].get('trustor_user'))
+ self.assertIsNotNone(token['trust'].get('trustee_user'))
+ self.assertIsNotNone(token['trust']['trustor_user'].get('id'))
+ self.assertIsNotNone(token['trust']['trustee_user'].get('id'))
+
+ def assertValidDomainScopedTokenResponse(self, r, *args, **kwargs):
+ token = self.assertValidScopedTokenResponse(r, *args, **kwargs)
+
+ self.assertIn('domain', token)
+ self.assertIn('id', token['domain'])
+ self.assertIn('name', token['domain'])
+
+ return token
+
+ def assertEqualTokens(self, a, b):
+ """Assert that two tokens are equal.
+
+ Compare two tokens except for their ids. This also truncates
+ the time in the comparison.
+ """
+ def normalize(token):
+ del token['token']['expires_at']
+ del token['token']['issued_at']
+ return token
+
+ a_expires_at = self.assertValidISO8601ExtendedFormatDatetime(
+ a['token']['expires_at'])
+ b_expires_at = self.assertValidISO8601ExtendedFormatDatetime(
+ b['token']['expires_at'])
+ self.assertCloseEnoughForGovernmentWork(a_expires_at, b_expires_at)
+
+ a_issued_at = self.assertValidISO8601ExtendedFormatDatetime(
+ a['token']['issued_at'])
+ b_issued_at = self.assertValidISO8601ExtendedFormatDatetime(
+ b['token']['issued_at'])
+ self.assertCloseEnoughForGovernmentWork(a_issued_at, b_issued_at)
+
+ return self.assertDictEqual(normalize(a), normalize(b))
+
+ # service validation
+
+ def assertValidServiceListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'services',
+ self.assertValidService,
+ *args,
+ **kwargs)
+
+ def assertValidServiceResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'service',
+ self.assertValidService,
+ *args,
+ **kwargs)
+
+ def assertValidService(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('type'))
+ if ref:
+ self.assertEqual(ref['type'], entity['type'])
+ return entity
+
+ # endpoint validation
+
+ def assertValidEndpointListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'endpoints',
+ self.assertValidEndpoint,
+ *args,
+ **kwargs)
+
+ def assertValidEndpointResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'endpoint',
+ self.assertValidEndpoint,
+ *args,
+ **kwargs)
+
+ def assertValidEndpoint(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('interface'))
+ self.assertIsNotNone(entity.get('service_id'))
+ if ref:
+ self.assertEqual(ref['interface'], entity['interface'])
+ self.assertEqual(ref['service_id'], entity['service_id'])
+ return entity
+
+ # domain validation
+
+ def assertValidDomainListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'domains',
+ self.assertValidDomain,
+ *args,
+ **kwargs)
+
+ def assertValidDomainResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'domain',
+ self.assertValidDomain,
+ *args,
+ **kwargs)
+
+ def assertValidDomain(self, entity, ref=None):
+ if ref:
+ pass
+ return entity
+
+ # project validation
+
+ def assertValidProjectListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'projects',
+ self.assertValidProject,
+ *args,
+ **kwargs)
+
+ def assertValidProjectResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'project',
+ self.assertValidProject,
+ *args,
+ **kwargs)
+
+ def assertValidProject(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('domain_id'))
+ if ref:
+ self.assertEqual(ref['domain_id'], entity['domain_id'])
+ return entity
+
+ # user validation
+
+ def assertValidUserListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'users',
+ self.assertValidUser,
+ *args,
+ **kwargs)
+
+ def assertValidUserResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'user',
+ self.assertValidUser,
+ *args,
+ **kwargs)
+
+ def assertValidUser(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('domain_id'))
+ self.assertIsNotNone(entity.get('email'))
+ self.assertIsNone(entity.get('password'))
+ if ref:
+ self.assertEqual(ref['domain_id'], entity['domain_id'])
+ self.assertEqual(ref['email'], entity['email'])
+ return entity
+
+ # group validation
+
+ def assertValidGroupListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'groups',
+ self.assertValidGroup,
+ *args,
+ **kwargs)
+
+ def assertValidGroupResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'group',
+ self.assertValidGroup,
+ *args,
+ **kwargs)
+
+ def assertValidGroup(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('name'))
+ if ref:
+ self.assertEqual(ref['name'], entity['name'])
+ return entity
+
+ # credential validation
+
+ def assertValidCredentialListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'credentials',
+ self.assertValidCredential,
+ *args,
+ **kwargs)
+
+ def assertValidCredentialResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'credential',
+ self.assertValidCredential,
+ *args,
+ **kwargs)
+
+ def assertValidCredential(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('user_id'))
+ self.assertIsNotNone(entity.get('blob'))
+ self.assertIsNotNone(entity.get('type'))
+ if ref:
+ self.assertEqual(ref['user_id'], entity['user_id'])
+ self.assertEqual(ref['blob'], entity['blob'])
+ self.assertEqual(ref['type'], entity['type'])
+ self.assertEqual(ref.get('project_id'), entity.get('project_id'))
+ return entity
+
+ # role validation
+
+ def assertValidRoleListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'roles',
+ self.assertValidRole,
+ *args,
+ **kwargs)
+
+ def assertValidRoleResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'role',
+ self.assertValidRole,
+ *args,
+ **kwargs)
+
+ def assertValidRole(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('name'))
+ if ref:
+ self.assertEqual(ref['name'], entity['name'])
+ return entity
+
+ # policy validation
+
+ def assertValidPolicyListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'policies',
+ self.assertValidPolicy,
+ *args,
+ **kwargs)
+
+ def assertValidPolicyResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'policy',
+ self.assertValidPolicy,
+ *args,
+ **kwargs)
+
+ def assertValidPolicy(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('blob'))
+ self.assertIsNotNone(entity.get('type'))
+ if ref:
+ self.assertEqual(ref['blob'], entity['blob'])
+ self.assertEqual(ref['type'], entity['type'])
+ return entity
+
+ # trust validation
+
+ def assertValidTrustListResponse(self, resp, *args, **kwargs):
+ return self.assertValidListResponse(
+ resp,
+ 'trusts',
+ self.assertValidTrust,
+ *args,
+ **kwargs)
+
+ def assertValidTrustResponse(self, resp, *args, **kwargs):
+ return self.assertValidResponse(
+ resp,
+ 'trust',
+ self.assertValidTrust,
+ *args,
+ **kwargs)
+
+ def assertValidTrust(self, entity, ref=None):
+ self.assertIsNotNone(entity.get('trustor_user_id'))
+ self.assertIsNotNone(entity.get('trustee_user_id'))
+
+ self.assertIn('expires_at', entity)
+ if entity['expires_at'] is not None:
+ self.assertValidISO8601ExtendedFormatDatetime(entity['expires_at'])
+
+ # always disallow project xor project_id (neither or both is allowed)
+ has_roles = bool(entity.get('roles'))
+ has_project = bool(entity.get('project_id'))
+ self.assertFalse(has_roles ^ has_project)
+
+ for role in entity['roles']:
+ self.assertIsNotNone(role)
+ self.assertValidEntity(role)
+ self.assertValidRole(role)
+
+ self.assertValidListLinks(entity.get('roles_links'))
+
+ # these were used during dev and shouldn't land in final impl
+ self.assertNotIn('role_ids', entity)
+ self.assertNotIn('role_names', entity)
+
+ if ref:
+ self.assertEqual(ref['trustor_user_id'], entity['trustor_user_id'])
+ self.assertEqual(ref['trustee_user_id'], entity['trustee_user_id'])
+ self.assertEqual(ref['project_id'], entity['project_id'])
+ if entity.get('expires_at') or ref.get('expires_at'):
+ entity_exp = self.assertValidISO8601ExtendedFormatDatetime(
+ entity['expires_at'])
+ ref_exp = self.assertValidISO8601ExtendedFormatDatetime(
+ ref['expires_at'])
+ self.assertCloseEnoughForGovernmentWork(entity_exp, ref_exp)
+ else:
+ self.assertEqual(ref.get('expires_at'),
+ entity.get('expires_at'))
+
+ return entity
+
class VersionTestCase(RestfulTestCase):
def test_get_version(self):
diff --git a/tests/test_v3_auth.py b/tests/test_v3_auth.py
index c7f78adf..40a4c846 100644
--- a/tests/test_v3_auth.py
+++ b/tests/test_v3_auth.py
@@ -14,10 +14,11 @@
import uuid
+import nose.exc
+
from keystone import auth
from keystone import config
from keystone import exception
-from keystone.openstack.common import timeutils
from keystone import test
import test_v3
@@ -28,7 +29,7 @@ CONF = config.CONF
def _build_auth_scope(project_id=None, project_name=None,
project_domain_id=None, project_domain_name=None,
- domain_id=None, domain_name=None):
+ domain_id=None, domain_name=None, trust_id=None):
scope_data = {}
if project_id or project_name:
scope_data['project'] = {}
@@ -49,6 +50,9 @@ def _build_auth_scope(project_id=None, project_name=None,
scope_data['domain']['id'] = domain_id
else:
scope_data['domain']['name'] = domain_name
+ if trust_id:
+ scope_data['trust'] = {}
+ scope_data['trust']['id'] = trust_id
return scope_data
@@ -76,116 +80,30 @@ def _build_token_auth(token):
def _build_authentication_request(token=None, user_id=None, username=None,
user_domain_id=None, user_domain_name=None,
- password=None, project_id=None,
- project_name=None, project_domain_id=None,
- project_domain_name=None,
- domain_id=None, domain_name=None):
+ password=None, **kwargs):
"""Build auth dictionary.
It will create an auth dictionary based on all the arguments
that it receives.
"""
auth_data = {}
- auth_data['authentication'] = {'methods': []}
+ auth_data['identity'] = {'methods': []}
if token:
- auth_data['authentication']['methods'].append('token')
- auth_data['authentication']['token'] = _build_token_auth(token)
+ auth_data['identity']['methods'].append('token')
+ auth_data['identity']['token'] = _build_token_auth(token)
if user_id or username:
- auth_data['authentication']['methods'].append('password')
- auth_data['authentication']['password'] = _build_password_auth(
+ auth_data['identity']['methods'].append('password')
+ auth_data['identity']['password'] = _build_password_auth(
user_id, username, user_domain_id, user_domain_name, password)
- if project_id or project_name or domain_id or domain_name:
- auth_data['scope'] = _build_auth_scope(project_id,
- project_name,
- project_domain_id,
- project_domain_name,
- domain_id,
- domain_name)
- return auth_data
-
-
-class AuthTest(test_v3.RestfulTestCase):
- def assertValidTokenResponse(self, r):
- self.assertTrue(r.getheader('X-Subject-Token'))
- token = r.body
-
- self.assertIn('expires', token)
- self.assertIn('user', token)
- self.assertEqual(self.user['id'], token['user']['id'])
- self.assertEqual(self.user['name'], token['user']['name'])
- self.assertEqual(self.user['domain_id'], token['user']['domain']['id'])
-
- return token
-
- def assertValidUnscopedTokenResponse(self, r):
- token = self.assertValidTokenResponse(r)
-
- self.assertNotIn('roles', token)
- self.assertNotIn('catalog', token)
- self.assertNotIn('project', token)
- self.assertNotIn('domain', token)
-
- return token
-
- def assertValidScopedTokenResponse(self, r):
- token = self.assertValidTokenResponse(r)
-
- self.assertIn('catalog', token)
- self.assertIn('roles', token)
- self.assertTrue(token['roles'])
- for role in token['roles']:
- self.assertIn('id', role)
- self.assertIn('name', role)
-
- return token
-
- def assertValidProjectScopedTokenResponse(self, r):
- token = self.assertValidScopedTokenResponse(r)
-
- self.assertIn('project', token)
- self.assertIn('id', token['project'])
- self.assertIn('name', token['project'])
- self.assertIn('domain', token['project'])
- self.assertIn('id', token['project']['domain'])
- self.assertIn('name', token['project']['domain'])
-
- self.assertEqual(self.role_id, token['roles'][0]['id'])
-
- return token
-
- def assertValidDomainScopedTokenResponse(self, r):
- token = self.assertValidScopedTokenResponse(r)
-
- self.assertIn('domain', token)
- self.assertIn('id', token['domain'])
- self.assertIn('name', token['domain'])
-
- return token
-
- def assertEqualTokens(self, a, b):
- """Assert that two tokens are equal.
-
- Compare two tokens except for their ids. This also truncates
- the time in the comparison.
- """
- def normalize(token):
- del token['expires']
- del token['issued_at']
- return token
-
- self.assertCloseEnoughForGovernmentWork(
- timeutils.parse_isotime(a['expires']),
- timeutils.parse_isotime(b['expires']))
- self.assertCloseEnoughForGovernmentWork(
- timeutils.parse_isotime(a['issued_at']),
- timeutils.parse_isotime(b['issued_at']))
- return self.assertDictEqual(normalize(a), normalize(b))
+ if kwargs:
+ auth_data['scope'] = _build_auth_scope(**kwargs)
+ return {'auth': auth_data}
class TestAuthInfo(test.TestCase):
def test_missing_auth_methods(self):
- auth_data = {'authentication': {}}
- auth_data['authentication']['token'] = {'id': uuid.uuid4().hex}
+ auth_data = {'identity': {}}
+ auth_data['identity']['token'] = {'id': uuid.uuid4().hex}
self.assertRaises(exception.ValidationError,
auth.controllers.AuthInfo,
None,
@@ -194,7 +112,7 @@ class TestAuthInfo(test.TestCase):
def test_unsupported_auth_method(self):
auth_data = {'methods': ['abc']}
auth_data['abc'] = {'test': 'test'}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
self.assertRaises(exception.AuthMethodNotSupported,
auth.controllers.AuthInfo,
None,
@@ -202,7 +120,7 @@ class TestAuthInfo(test.TestCase):
def test_missing_auth_method_data(self):
auth_data = {'methods': ['password']}
- auth_data = {'authentication': auth_data}
+ auth_data = {'identity': auth_data}
self.assertRaises(exception.ValidationError,
auth.controllers.AuthInfo,
None,
@@ -211,7 +129,7 @@ class TestAuthInfo(test.TestCase):
def test_project_name_no_domain(self):
auth_data = _build_authentication_request(username='test',
password='test',
- project_name='abc')
+ project_name='abc')['auth']
self.assertRaises(exception.ValidationError,
auth.controllers.AuthInfo,
None,
@@ -221,14 +139,14 @@ class TestAuthInfo(test.TestCase):
auth_data = _build_authentication_request(user_id='test',
password='test',
project_name='test',
- domain_name='test')
+ domain_name='test')['auth']
self.assertRaises(exception.ValidationError,
auth.controllers.AuthInfo,
None,
auth_data)
-class TestTokenAPIs(AuthTest):
+class TestTokenAPIs(test_v3.RestfulTestCase):
def setUp(self):
super(TestTokenAPIs, self).setUp()
auth_data = _build_authentication_request(
@@ -262,11 +180,13 @@ class TestTokenAPIs(AuthTest):
method='GET')
v2_token = resp.body
self.assertEqual(v2_token['access']['user']['id'],
- token_data['user']['id'])
- self.assertEqual(v2_token['access']['token']['expires'],
- token_data['expires'])
+ token_data['token']['user']['id'])
+ # v2 token time has not fraction of second precision so
+ # just need to make sure the non fraction part agrees
+ self.assertIn(v2_token['access']['token']['expires'][:-1],
+ token_data['token']['expires_at'])
self.assertEqual(v2_token['access']['user']['roles'][0]['id'],
- token_data['roles'][0]['id'])
+ token_data['token']['roles'][0]['id'])
def test_v3_v2_pki_token_intermix(self):
# FIXME(gyee): PKI tokens are not interchangeable because token
@@ -287,11 +207,13 @@ class TestTokenAPIs(AuthTest):
method='GET')
v2_token = resp.body
self.assertEqual(v2_token['access']['user']['id'],
- token_data['user']['id'])
- self.assertEqual(v2_token['access']['token']['expires'],
- token_data['expires'])
+ token_data['token']['user']['id'])
+ # v2 token time has not fraction of second precision so
+ # just need to make sure the non fraction part agrees
+ self.assertIn(v2_token['access']['token']['expires'][-1],
+ token_data['token']['expires_at'])
self.assertEqual(v2_token['access']['user']['roles'][0]['id'],
- token_data['roles'][0]['id'])
+ token_data['token']['roles'][0]['id'])
def test_v2_v3_uuid_token_intermix(self):
self.opt_in_group('signing', token_format='UUID')
@@ -312,11 +234,13 @@ class TestTokenAPIs(AuthTest):
resp = self.get('/auth/tokens', headers=headers)
token_data = resp.body
self.assertEqual(v2_token_data['access']['user']['id'],
- token_data['user']['id'])
- self.assertEqual(v2_token_data['access']['token']['expires'],
- token_data['expires'])
+ token_data['token']['user']['id'])
+ # v2 token time has not fraction of second precision so
+ # just need to make sure the non fraction part agrees
+ self.assertIn(v2_token_data['access']['token']['expires'][-1],
+ token_data['token']['expires_at'])
self.assertEqual(v2_token_data['access']['user']['roles'][0]['name'],
- token_data['roles'][0]['name'])
+ token_data['token']['roles'][0]['name'])
def test_v2_v3_pki_token_intermix(self):
self.opt_in_group('signing', token_format='PKI')
@@ -337,21 +261,23 @@ class TestTokenAPIs(AuthTest):
resp = self.get('/auth/tokens', headers=headers)
token_data = resp.body
self.assertEqual(v2_token_data['access']['user']['id'],
- token_data['user']['id'])
- self.assertEqual(v2_token_data['access']['token']['expires'],
- token_data['expires'])
+ token_data['token']['user']['id'])
+ # v2 token time has not fraction of second precision so
+ # just need to make sure the non fraction part agrees
+ self.assertIn(v2_token_data['access']['token']['expires'][-1],
+ token_data['token']['expires_at'])
self.assertEqual(v2_token_data['access']['user']['roles'][0]['name'],
- token_data['roles'][0]['name'])
+ token_data['token']['roles'][0]['name'])
def test_rescoping_token(self):
- expires = self.token_data['expires']
+ expires = self.token_data['token']['expires_at']
auth_data = _build_authentication_request(
token=self.token,
project_id=self.project_id)
r = self.post('/auth/tokens', body=auth_data)
self.assertValidProjectScopedTokenResponse(r)
# make sure expires stayed the same
- self.assertEqual(expires, r.body['expires'])
+ self.assertEqual(expires, r.body['token']['expires_at'])
def test_check_token(self):
self.head('/auth/tokens', headers=self.headers, expected_status=204)
@@ -370,7 +296,9 @@ class TestTokenAPIs(AuthTest):
self.assertIn('signed', r.body)
-class TestAuth(AuthTest):
+class TestAuthJSON(test_v3.RestfulTestCase):
+ content_type = 'json'
+
def test_unscoped_token_with_user_id(self):
auth_data = _build_authentication_request(
user_id=self.user['id'],
@@ -601,7 +529,7 @@ class TestAuth(AuthTest):
def test_remote_user(self):
auth_data = _build_authentication_request(
user_id=self.user['id'],
- password=self.user['password'])
+ password=self.user['password'])['auth']
api = auth.controllers.Auth()
context = {'REMOTE_USER': self.user['name']}
auth_info = auth.controllers.AuthInfo(None, auth_data)
@@ -612,7 +540,7 @@ class TestAuth(AuthTest):
def test_remote_user_no_domain(self):
auth_data = _build_authentication_request(
username=self.user['name'],
- password=self.user['password'])
+ password=self.user['password'])['auth']
api = auth.controllers.Auth()
context = {'REMOTE_USER': self.user['name']}
auth_info = auth.controllers.AuthInfo(None, auth_data)
@@ -622,3 +550,259 @@ class TestAuth(AuthTest):
context,
auth_info,
auth_context)
+
+
+class TestAuthXML(TestAuthJSON):
+ content_type = 'xml'
+
+
+class TestTrustAuth(test_v3.RestfulTestCase):
+ def setUp(self):
+ super(TestTrustAuth, self).setUp()
+
+ # create a trustee to delegate stuff to
+ self.trustee_user_id = uuid.uuid4().hex
+ self.trustee_user = self.new_user_ref(domain_id=self.domain_id)
+ self.trustee_user['id'] = self.trustee_user_id
+ self.identity_api.create_user(self.trustee_user_id, self.trustee_user)
+
+ def test_create_trust_400(self):
+ raise nose.exc.SkipTest('Blocked by bug 1133435')
+ self.post('/trusts', body={'trust': {}}, expected_status=400)
+
+ def test_create_unscoped_trust(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id)
+ del ref['id']
+ r = self.post('/trusts', body={'trust': ref})
+ self.assertValidTrustResponse(r, ref)
+
+ def test_trust_crud(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ role_ids=[self.role_id])
+ del ref['id']
+ r = self.post('/trusts', body={'trust': ref})
+ trust = self.assertValidTrustResponse(r, ref)
+
+ r = self.get(
+ '/trusts/%(trust_id)s' % {'trust_id': trust['id']},
+ expected_status=200)
+ self.assertValidTrustResponse(r, ref)
+
+ # validate roles on the trust
+ r = self.get(
+ '/trusts/%(trust_id)s/roles' % {
+ 'trust_id': trust['id']},
+ expected_status=200)
+ roles = self.assertValidRoleListResponse(r, self.role)
+ self.assertIn(self.role['id'], [x['id'] for x in roles])
+ self.head(
+ '/trusts/%(trust_id)s/roles/%(role_id)s' % {
+ 'trust_id': trust['id'],
+ 'role_id': self.role['id']},
+ expected_status=204)
+ r = self.get(
+ '/trusts/%(trust_id)s/roles/%(role_id)s' % {
+ 'trust_id': trust['id'],
+ 'role_id': self.role['id']},
+ expected_status=200)
+ self.assertValidRoleResponse(r, self.role)
+
+ r = self.get('/trusts', expected_status=200)
+ self.assertValidTrustListResponse(r, trust)
+
+ # trusts are immutable
+ self.patch(
+ '/trusts/%(trust_id)s' % {'trust_id': trust['id']},
+ body={'trust': ref},
+ expected_status=404)
+
+ self.delete(
+ '/trusts/%(trust_id)s' % {'trust_id': trust['id']},
+ expected_status=204)
+
+ self.get(
+ '/trusts/%(trust_id)s' % {'trust_id': trust['id']},
+ expected_status=404)
+
+ def test_create_trust_trustee_404(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=uuid.uuid4().hex)
+ del ref['id']
+ self.post('/trusts', body={'trust': ref}, expected_status=404)
+
+ def test_create_trust_trustor_trustee_backwards(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.trustee_user_id,
+ trustee_user_id=self.user_id)
+ del ref['id']
+ self.post('/trusts', body={'trust': ref}, expected_status=403)
+
+ def test_create_trust_project_404(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=uuid.uuid4().hex,
+ role_ids=[self.role_id])
+ del ref['id']
+ self.post('/trusts', body={'trust': ref}, expected_status=404)
+
+ def test_create_trust_role_id_404(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ role_ids=[uuid.uuid4().hex])
+ del ref['id']
+ self.post('/trusts', body={'trust': ref}, expected_status=404)
+
+ def test_create_trust_role_name_404(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ role_names=[uuid.uuid4().hex])
+ del ref['id']
+ self.post('/trusts', body={'trust': ref}, expected_status=404)
+
+ def test_create_expired_trust(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ expires=dict(seconds=-1),
+ role_ids=[self.role_id])
+ del ref['id']
+ r = self.post('/trusts', body={'trust': ref})
+ trust = self.assertValidTrustResponse(r, ref)
+
+ self.get('/trusts/%(trust_id)s' % {
+ 'trust_id': trust['id']},
+ expected_status=404)
+
+ auth_data = _build_authentication_request(
+ user_id=self.trustee_user['id'],
+ password=self.trustee_user['password'],
+ trust_id=trust['id'])
+ self.post('/auth/tokens', body=auth_data, expected_status=401)
+
+ def test_exercise_trust_scoped_token_without_impersonation(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ impersonation=False,
+ expires=dict(minutes=1),
+ role_ids=[self.role_id])
+ del ref['id']
+
+ r = self.post('/trusts', body={'trust': ref})
+ trust = self.assertValidTrustResponse(r)
+
+ auth_data = _build_authentication_request(
+ user_id=self.trustee_user['id'],
+ password=self.trustee_user['password'],
+ trust_id=trust['id'])
+ r = self.post('/auth/tokens', body=auth_data)
+ self.assertValidProjectTrustScopedTokenResponse(r, self.trustee_user)
+ self.assertEqual(r.body['token']['user']['id'],
+ self.trustee_user['id'])
+ self.assertEqual(r.body['token']['user']['name'],
+ self.trustee_user['name'])
+ self.assertEqual(r.body['token']['user']['domain']['id'],
+ self.domain['id'])
+ self.assertEqual(r.body['token']['user']['domain']['name'],
+ self.domain['name'])
+ self.assertEqual(r.body['token']['project']['id'], self.project['id'])
+ self.assertEqual(r.body['token']['project']['name'],
+ self.project['name'])
+
+ def test_exercise_trust_scoped_token_with_impersonation(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ impersonation=True,
+ expires=dict(minutes=1),
+ role_ids=[self.role_id])
+ del ref['id']
+
+ r = self.post('/trusts', body={'trust': ref})
+ trust = self.assertValidTrustResponse(r)
+
+ auth_data = _build_authentication_request(
+ user_id=self.trustee_user['id'],
+ password=self.trustee_user['password'],
+ trust_id=trust['id'])
+ r = self.post('/auth/tokens', body=auth_data)
+ self.assertValidProjectTrustScopedTokenResponse(r, self.user)
+ self.assertEqual(r.body['token']['user']['id'], self.user['id'])
+ self.assertEqual(r.body['token']['user']['name'], self.user['name'])
+ self.assertEqual(r.body['token']['user']['domain']['id'],
+ self.domain['id'])
+ self.assertEqual(r.body['token']['user']['domain']['name'],
+ self.domain['name'])
+ self.assertEqual(r.body['token']['project']['id'], self.project['id'])
+ self.assertEqual(r.body['token']['project']['name'],
+ self.project['name'])
+
+ def test_delete_trust(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ impersonation=False,
+ expires=dict(minutes=1),
+ role_ids=[self.role_id])
+ del ref['id']
+
+ r = self.post('/trusts', body={'trust': ref})
+
+ trust = self.assertValidTrustResponse(r, ref)
+
+ self.delete('/trusts/%(trust_id)s' % {
+ 'trust_id': trust['id']},
+ expected_status=204)
+
+ self.get('/trusts/%(trust_id)s' % {
+ 'trust_id': trust['id']},
+ expected_status=404)
+
+ self.get('/trusts/%(trust_id)s' % {
+ 'trust_id': trust['id']},
+ expected_status=404)
+
+ auth_data = _build_authentication_request(
+ user_id=self.trustee_user['id'],
+ password=self.trustee_user['password'],
+ trust_id=trust['id'])
+ self.post('/auth/tokens', body=auth_data, expected_status=401)
+
+ def test_list_trusts(self):
+ ref = self.new_trust_ref(
+ trustor_user_id=self.user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.project_id,
+ impersonation=False,
+ expires=dict(minutes=1),
+ role_ids=[self.role_id])
+ del ref['id']
+
+ for i in range(0, 3):
+ r = self.post('/trusts', body={'trust': ref})
+ trust = self.assertValidTrustResponse(r, ref)
+
+ r = self.get('/trusts?trustor_user_id=%s' %
+ self.user_id, expected_status=200)
+ trusts = r.body['trusts']
+ self.assertEqual(len(trusts), 3)
+
+ r = self.get('/trusts?trustee_user_id=%s' %
+ self.user_id, expected_status=200)
+ trusts = r.body['trusts']
+ self.assertEqual(len(trusts), 0)
diff --git a/tests/test_v3_catalog.py b/tests/test_v3_catalog.py
index 9f2f4f01..2d161db5 100644
--- a/tests/test_v3_catalog.py
+++ b/tests/test_v3_catalog.py
@@ -23,52 +23,6 @@ class CatalogTestCase(test_v3.RestfulTestCase):
self.endpoint_id,
self.endpoint.copy())
- # service validation
-
- def assertValidServiceListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'services',
- self.assertValidService,
- **kwargs)
-
- def assertValidServiceResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'service',
- self.assertValidService,
- ref)
-
- def assertValidService(self, entity, ref=None):
- self.assertIsNotNone(entity.get('type'))
- if ref:
- self.assertEqual(ref['type'], entity['type'])
- return entity
-
- # endpoint validation
-
- def assertValidEndpointListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'endpoints',
- self.assertValidEndpoint,
- **kwargs)
-
- def assertValidEndpointResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'endpoint',
- self.assertValidEndpoint,
- ref)
-
- def assertValidEndpoint(self, entity, ref=None):
- self.assertIsNotNone(entity.get('interface'))
- self.assertIsNotNone(entity.get('service_id'))
- if ref:
- self.assertEqual(ref['interface'], entity['interface'])
- self.assertEqual(ref['service_id'], entity['service_id'])
- return entity
-
# service crud tests
def test_create_service(self):
diff --git a/tests/test_v3_identity.py b/tests/test_v3_identity.py
index 47e50281..9ef487c3 100644
--- a/tests/test_v3_identity.py
+++ b/tests/test_v3_identity.py
@@ -40,145 +40,6 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.credential_id,
self.credential)
- # domain validation
-
- def assertValidDomainListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'domains',
- self.assertValidDomain,
- **kwargs)
-
- def assertValidDomainResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'domain',
- self.assertValidDomain,
- ref)
-
- def assertValidDomain(self, entity, ref=None):
- if ref:
- pass
- return entity
-
- # project validation
-
- def assertValidProjectListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'projects',
- self.assertValidProject,
- **kwargs)
-
- def assertValidProjectResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'project',
- self.assertValidProject,
- ref)
-
- def assertValidProject(self, entity, ref=None):
- self.assertIsNotNone(entity.get('domain_id'))
- if ref:
- self.assertEqual(ref['domain_id'], entity['domain_id'])
- return entity
-
- # user validation
-
- def assertValidUserListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'users',
- self.assertValidUser,
- **kwargs)
-
- def assertValidUserResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'user',
- self.assertValidUser,
- ref)
-
- def assertValidUser(self, entity, ref=None):
- self.assertIsNotNone(entity.get('domain_id'))
- self.assertIsNotNone(entity.get('email'))
- self.assertIsNone(entity.get('password'))
- if ref:
- self.assertEqual(ref['domain_id'], entity['domain_id'])
- self.assertEqual(ref['email'], entity['email'])
- return entity
-
- # group validation
-
- def assertValidGroupListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'groups',
- self.assertValidGroup,
- **kwargs)
-
- def assertValidGroupResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'group',
- self.assertValidGroup,
- ref)
-
- def assertValidGroup(self, entity, ref=None):
- self.assertIsNotNone(entity.get('name'))
- if ref:
- self.assertEqual(ref['name'], entity['name'])
- return entity
-
- # credential validation
-
- def assertValidCredentialListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'credentials',
- self.assertValidCredential,
- **kwargs)
-
- def assertValidCredentialResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'credential',
- self.assertValidCredential,
- ref)
-
- def assertValidCredential(self, entity, ref=None):
- self.assertIsNotNone(entity.get('user_id'))
- self.assertIsNotNone(entity.get('blob'))
- self.assertIsNotNone(entity.get('type'))
- if ref:
- self.assertEqual(ref['user_id'], entity['user_id'])
- self.assertEqual(ref['blob'], entity['blob'])
- self.assertEqual(ref['type'], entity['type'])
- self.assertEqual(ref.get('project_id'), entity.get('project_id'))
- return entity
-
- # role validation
-
- def assertValidRoleListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'roles',
- self.assertValidRole,
- **kwargs)
-
- def assertValidRoleResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'role',
- self.assertValidRole,
- ref)
-
- def assertValidRole(self, entity, ref=None):
- self.assertIsNotNone(entity.get('name'))
- if ref:
- self.assertEqual(ref['name'], entity['name'])
- return entity
-
# domain crud tests
def test_create_domain(self):
diff --git a/tests/test_v3_policy.py b/tests/test_v3_policy.py
index 811eb577..1af68b6e 100644
--- a/tests/test_v3_policy.py
+++ b/tests/test_v3_policy.py
@@ -15,30 +15,6 @@ class PolicyTestCase(test_v3.RestfulTestCase):
self.policy_id,
self.policy.copy())
- # policy validation
-
- def assertValidPolicyListResponse(self, resp, **kwargs):
- return self.assertValidListResponse(
- resp,
- 'policies',
- self.assertValidPolicy,
- **kwargs)
-
- def assertValidPolicyResponse(self, resp, ref):
- return self.assertValidResponse(
- resp,
- 'policy',
- self.assertValidPolicy,
- ref)
-
- def assertValidPolicy(self, entity, ref=None):
- self.assertIsNotNone(entity.get('blob'))
- self.assertIsNotNone(entity.get('type'))
- if ref:
- self.assertEqual(ref['blob'], entity['blob'])
- self.assertEqual(ref['type'], entity['type'])
- return entity
-
# policy crud tests
def test_create_policy(self):
diff --git a/tests/test_v3_protection.py b/tests/test_v3_protection.py
index bda73415..5b461693 100644
--- a/tests/test_v3_protection.py
+++ b/tests/test_v3_protection.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import tempfile
import uuid
@@ -82,13 +83,14 @@ class IdentityTestProtectedCase(test_v3.RestfulTestCase):
# A default auth request we can use - un-scoped user token
self.auth = {}
- self.auth['authentication'] = {'methods': []}
- self.auth['authentication']['methods'].append('password')
- self.auth['authentication']['password'] = {'user': {}}
- self.auth['authentication']['password']['user']['id'] = (
+ self.auth['identity'] = {'methods': []}
+ self.auth['identity']['methods'].append('password')
+ self.auth['identity']['password'] = {'user': {}}
+ self.auth['identity']['password']['user']['id'] = (
self.user1['id'])
- self.auth['authentication']['password']['user']['password'] = (
+ self.auth['identity']['password']['user']['password'] = (
self.user1['password'])
+ self.auth = {'auth': self.auth}
def tearDown(self):
super(IdentityTestProtectedCase, self).tearDown()
@@ -130,20 +132,25 @@ class IdentityTestProtectedCase(test_v3.RestfulTestCase):
def test_list_users_protected_by_domain(self):
"""GET /users?domain_id=mydomain (protected)"""
- raise nose.exc.SkipTest('Blocked by incomplete '
- 'domain scoping in v3/auth')
# Update policy to protect by domain, and then use a domain
# scoped token
new_policy = """{"identity:list_users": ["domain_id:%(domain_id)s"]}"""
with open(self.tmpfilename, "w") as policyfile:
policyfile.write(new_policy)
- self.auth['scope'] = {'domain': []}
- self.auth['domain']['id'] = self.domainA['id']
+ self.auth['auth']['scope'] = {'domain': {'id': self.domainA['id']}}
url_by_name = '/users?domain_id=%s' % self.user1['domain_id']
r = self.get(url_by_name, auth=self.auth)
# We should only get back one user, the one in DomainA
id_list = self._get_id_list_from_ref_list(r.body.get('users'))
- self.assertIn(self.user2['id'], id_list)
+ self.assertIn(self.user1['id'], id_list)
- # TODO (henry-nash) Add some more tests to cover the various likely
- # protection filters
+ def test_get_user_protected_match_id(self):
+ """GET /users/{id} (match payload)"""
+ # Tests the flattening of the payload
+ policy = {"identity:get_user": [["user_id:%(user_id)s"]]}
+ with open(self.tmpfilename, "w") as policyfile:
+ policyfile.write(json.dumps(policy))
+ url_by_name = '/users/%s' % self.user1['id']
+ r = self.get(url_by_name, auth=self.auth)
+ body = r.body
+ self.assertEquals(self.user1['id'], body['user']['id'])
diff --git a/tests/test_v3_trust.py b/tests/test_v3_trust.py
new file mode 100644
index 00000000..19d645d8
--- /dev/null
+++ b/tests/test_v3_trust.py
@@ -0,0 +1,286 @@
+import copy
+import uuid
+import test_v3
+import json
+
+from keystone import config
+from keystone.common.sql import util as sql_util
+from keystone import test
+
+import test_content_types
+
+
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
+
+
+class TrustTestCase(test_v3.RestfulTestCase):
+ def setUp(self):
+ super(TrustTestCase, self).setUp()
+ self.domain = None
+ self.password = 'freeipa4all'
+ self.auth_url = '/v2.0'
+ self.admin_url = '/v2.0'
+ self.admin_url_v3 = '/v3'
+ self.url_template = "%(auth_url)s/%(resource)s"
+ self.headers = {'Content-type': 'application/json'}
+ self.trustor = self.create_user()
+ self.trustee = self.create_user()
+ self.role_1 = self.create_role()
+ self.role_2 = self.create_role()
+ self.grant_role_to_user(self.trustor['id'],
+ self.role_1['id'],
+ self.get_project()['id'])
+ self.grant_role_to_user(self.trustor['id'],
+ self.role_2['id'],
+ self.get_project()['id'])
+
+ def v3_request(self, path, data):
+ r = self.request(method='POST',
+ path=path,
+ body=data,
+ headers=self.headers)
+ return r
+
+ def get_unscoped_token_response(self, username, password):
+ url = self.url_template % {'auth_url': self.admin_url,
+ 'resource': "tokens"}
+ data = self.get_unscoped_auth(username=username, password=password)
+ r = self.restful_request(method='POST',
+ port=self._public_port(),
+ path=url,
+ body=data,
+ headers=self.headers)
+ if 'access' in r.body:
+ return r.body['access']
+ raise Exception(r)
+
+ def get_scoped_token_response(self, username, password, project_name):
+ url = self.url_template % {'auth_url': self.admin_url,
+ 'resource': "tokens"}
+ data = self.get_scoped_auth(username, password, project_name)
+ r = self.restful_request(method='POST',
+ port=self._public_port(),
+ path=url,
+ body=data,
+ headers=self.headers)
+ if 'access' in r.body:
+ return r.body['access']
+ raise Exception(r)
+
+ def get_admin_token_data(self):
+ if not hasattr(self, 'admin_token_response'):
+ self.admin_token_response = self.get_scoped_token_response(
+ 'admin', 'freeipa4all', 'demo')
+ return self.admin_token_response
+
+ def get_admin_token_id(self):
+ return 'ADMIN'
+
+ def make_admin_post_request(self, resource, data):
+ return self.make_post_request(resource,
+ data,
+ self.get_admin_token_id())
+
+ def make_post_request(self, resource, data, token_id):
+ headers = copy.copy(self.headers)
+ headers["x-auth-token"] = token_id
+ url = self.url_template % {'auth_url': self.admin_url_v3,
+ 'resource': resource}
+ r = self.restful_request(method='POST',
+ path=url,
+ port=self._admin_port(),
+ body=data,
+ headers=headers)
+ return r
+
+ def make_v2_post_request(self, resource, data, token_id):
+ headers = copy.copy(self.headers)
+ headers["x-auth-token"] = token_id
+ url = self.url_template % {'auth_url': self.admin_url,
+ 'resource': resource}
+ r = self.restful_request(method='POST',
+ path=url,
+ port=self._admin_port(),
+ body=data,
+ headers=headers)
+ return r
+
+ def make_put_request(self, resource, data, token_id):
+ headers = copy.copy(self.headers)
+ headers["x-auth-token"] = self.get_admin_token_id()
+ url = self.url_template % {'auth_url': self.admin_url_v3,
+ 'resource': resource}
+ r = self.request(method='PUT',
+ path=url,
+ port=self._admin_port(),
+ body=json.dumps(data),
+ headers=headers)
+ return r
+
+ def create_domain(self):
+ domain = self.new_domain_ref()
+ resource = 'domains'
+ data = {'domain': domain}
+ r = self.make_admin_post_request(resource, data)
+ dom = r.body['domain']
+ self.domain = dom
+
+ def create_project(self):
+ project = self.new_project_ref(
+ domain_id=self.get_domain()['id'])
+ data = {'project': project}
+ r = self.make_admin_post_request('projects', data)
+ self.project = r.body['project']
+
+ def get_domain(self):
+ if not self.domain:
+ #once authenticate supports domains, use the following function
+# self.create_domain()
+ self.domain = {'id': DEFAULT_DOMAIN_ID}
+ return self.domain
+
+ def get_project(self):
+ if not hasattr(self, 'project'):
+ self.create_project()
+ return self.project
+
+ def create_user(self):
+ user_id = uuid.uuid4().hex
+ user = {'user': {'name': uuid.uuid4().hex,
+ 'password': self.password,
+ 'enabled': True,
+ 'domain_id': self.get_domain()['id'],
+ 'project_id': self.get_project()['id']}}
+ r = self.make_admin_post_request('users', user)
+ return r.body['user']
+
+ def create_role(self):
+ ref = self.new_role_ref()
+ body = {'role': ref}
+ r = self.make_admin_post_request('roles', body)
+ return r.body['role']
+
+ def grant_role_to_user(self, user_id, role_id, project_id):
+ """PUT /projects/{project_id}/users/{user_id}/roles/{role_id}"""
+ url_template = 'projects/%(project_id)s/users'\
+ '/%(user_id)s/roles/%(role_id)s'
+ url = url_template % {'project_id': project_id,
+ 'user_id': user_id,
+ 'role_id': role_id}
+ r = self.make_put_request(url, '', self.get_admin_token_id())
+ return r
+
+ def get_scoped_auth(self, username, password, project_name):
+ return {"auth":
+ {"passwordCredentials": {"username": username,
+ "password": password},
+ "projectName": project_name}}
+
+ def get_unscoped_auth(self, username, password):
+ return {"auth":
+ {"passwordCredentials": {"username": username,
+ "password": password}}}
+
+ def create_trust(self, impersonation=True):
+ trustor_token = self.get_scoped_token_response(
+ self.trustor['name'],
+ self.password,
+ self.get_project()['name'])
+ trustee_token = self.get_unscoped_token_response(self.trustee['name'],
+ self.password)
+ trust_request = {'trust':
+ {'trustor_user_id': self.trustor['id'],
+ 'trustee_user_id': self.trustee['id'],
+ 'project_id': self.get_project()['id'],
+ 'impersonation': impersonation,
+ 'description': 'described',
+ 'roles': []}}
+ trust_response = self.make_post_request('trusts', trust_request,
+ trustor_token['token']['id'])
+ return trust_response, trustee_token
+
+ def test_create_trust(self):
+ trust_response, trustee_token = self.create_trust()
+ trust_id = trust_response.body['trust']['id']
+ self.assertEquals(trust_response.body['trust']['description'],
+ 'described')
+ auth_data = {"auth": {"token": {'id': trustee_token['token']['id']},
+ "trust_id": trust_id}}
+ r = self.make_v2_post_request("tokens",
+ auth_data,
+ trustee_token['token']['id'])
+ trust_token = r.body
+ self.assertIsNotNone(trust_token['access']['token']['id'])
+ self.assertEquals(trust_token['access']['trust']['trustee_user_id'],
+ self.trustee['id'])
+ self.assertEquals(trust_token['access']['trust']['id'], trust_id)
+
+ def test_delete_trust(self):
+ trust_response, trustee_token = self.create_trust()
+ url = self.url_template % {'auth_url': self.admin_url_v3,
+ 'resource': "trusts/"}
+ url += trust_response.body['trust']['id']
+ trustor_token = self.get_scoped_token_response(
+ self.trustor['name'],
+ self.password,
+ self.get_project()['name'])
+
+ headers = copy.copy(self.headers)
+ headers["x-auth-token"] = trustor_token['token']['id']
+ response = self.request(method='DELETE',
+ path=url,
+ port=self._public_port(),
+ body="",
+ headers=headers)
+ self.assertIsNotNone(response)
+
+ def test_list_trusts(self):
+ trustor_token = self.get_scoped_token_response(
+ self.trustor['name'],
+ self.password,
+ self.get_project()['name'])
+
+ for i in range(0, 3):
+ trust_response, trustee_token = self.create_trust()
+ url = self.url_template % {'auth_url': self.admin_url_v3,
+ 'resource': "trusts"}
+ headers = copy.copy(self.headers)
+ headers["x-auth-token"] = self.get_admin_token_id()
+ trust_lists_response = self.restful_request(method='GET',
+ path=url,
+ port=self._public_port(),
+ body="",
+ headers=headers)
+ trusts = trust_lists_response.body['trusts']
+ self.assertEqual(len(trusts), 3)
+
+ trustee_url = url + "?trustee_user_id=" + self.trustee['id']
+ headers["x-auth-token"] = trustee_token['token']['id']
+ trust_lists_response = self.restful_request(
+ method='GET', path=trustee_url, port=self._public_port(),
+ body="", headers=headers)
+ trusts = trust_lists_response.body['trusts']
+ self.assertEqual(len(trusts), 3)
+
+ headers["x-auth-token"] = trustor_token['token']['id']
+
+ trust_lists_response = self.restful_request(
+ method='GET', path=trustee_url, port=self._public_port(),
+ body="", headers=headers, expected_status=403)
+
+ trustor_url = url + "?trustor_user_id=" + self.trustor['id']
+ headers["x-auth-token"] = trustor_token['token']['id']
+ trust_lists_response = self.restful_request(
+ method='GET',
+ path=trustor_url,
+ port=self._public_port(),
+ body="",
+ headers=headers)
+ trusts = trust_lists_response.body['trusts']
+ self.assertEqual(len(trusts), 3)
+
+ headers["x-auth-token"] = trustee_token['token']['id']
+ trust_lists_response = self.restful_request(
+ method='GET', path=trustor_url, port=self._public_port(),
+ body="", headers=headers, expected_status=403)
diff --git a/tools/pip-requires b/tools/pip-requires
index 688da1a7..f949c7fd 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -1,5 +1,5 @@
# keystone dependencies
-pam==0.1.4
+pam>=0.1.4
WebOb==1.2.3
eventlet
greenlet