diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py index b0b2d934b2..78f1c9e140 100644 --- a/keystone/catalog/controllers.py +++ b/keystone/catalog/controllers.py @@ -120,31 +120,34 @@ class Endpoint(controller.V2Controller): @dependency.requires('catalog_api') class ServiceV3(controller.V3Controller): + collection_name = 'services' + member_name = 'service' + @controller.protected def create_service(self, context, service): ref = self._assign_unique_id(self._normalize_dict(service)) self._require_attribute(ref, 'type') ref = self.catalog_api.create_service(context, ref['id'], ref) - return {'service': ref} + return ServiceV3.wrap_member(context, ref) @controller.protected def list_services(self, context): refs = self.catalog_api.list_services(context) refs = self._filter_by_attribute(context, refs, 'type') - return {'services': self._paginate(context, refs)} + return ServiceV3.wrap_collection(context, refs) @controller.protected def get_service(self, context, service_id): ref = self.catalog_api.get_service(context, service_id) - return {'service': ref} + return ServiceV3.wrap_member(context, ref) @controller.protected def update_service(self, context, service_id, service): self._require_matching_id(service_id, service) ref = self.catalog_api.update_service(context, service_id, service) - return {'service': ref} + return ServiceV3.wrap_member(context, ref) @controller.protected def delete_service(self, context, service_id): @@ -153,6 +156,9 @@ class ServiceV3(controller.V3Controller): @dependency.requires('catalog_api') class EndpointV3(controller.V3Controller): + collection_name = 'endpoints' + member_name = 'endpoint' + @controller.protected def create_endpoint(self, context, endpoint): ref = self._assign_unique_id(self._normalize_dict(endpoint)) @@ -161,19 +167,19 @@ class EndpointV3(controller.V3Controller): self.catalog_api.get_service(context, ref['service_id']) ref = self.catalog_api.create_endpoint(context, ref['id'], ref) - return {'endpoint': ref} + return EndpointV3.wrap_member(context, ref) @controller.protected def list_endpoints(self, context): refs = self.catalog_api.list_endpoints(context) refs = self._filter_by_attribute(context, refs, 'service_id') refs = self._filter_by_attribute(context, refs, 'interface') - return {'endpoints': self._paginate(context, refs)} + return EndpointV3.wrap_collection(context, refs) @controller.protected def get_endpoint(self, context, endpoint_id): ref = self.catalog_api.get_endpoint(context, endpoint_id) - return {'endpoint': ref} + return EndpointV3.wrap_member(context, ref) @controller.protected def update_endpoint(self, context, endpoint_id, endpoint): @@ -183,7 +189,7 @@ class EndpointV3(controller.V3Controller): self.catalog_api.get_service(context, endpoint['service_id']) ref = self.catalog_api.update_endpoint(context, endpoint_id, endpoint) - return {'endpoint': ref} + return EndpointV3.wrap_member(context, ref) @controller.protected def delete_endpoint(self, context, endpoint_id): diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 05d074e775..3112fc0c39 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -4,10 +4,12 @@ import uuid from keystone.common import dependency from keystone.common import logging from keystone.common import wsgi +from keystone import config from keystone import exception LOG = logging.getLogger(__name__) +CONF = config.CONF def protected(f): @@ -68,10 +70,63 @@ class V2Controller(wsgi.Application): class V3Controller(V2Controller): - """Base controller class for Identity API v3.""" + """Base controller class for Identity API v3. - def _paginate(self, context, refs): + Child classes should set the ``collection_name`` and ``member_name`` class + attributes, representing the collection of entities they are exposing to + the API. This is required for supporting self-referential links, + pagination, etc. + + """ + + collection_name = 'entities' + member_name = 'entity' + + @classmethod + def base_url(cls, path=None): + endpoint = CONF.public_endpoint % CONF + + # allow a missing trailing slash in the config + if endpoint[-1] != '/': + endpoint += '/' + + url = endpoint + 'v3' + + if path: + return url + path + else: + return url + '/' + cls.collection_name + + @classmethod + def _add_self_referential_link(cls, ref): + ref.setdefault('links', {}) + ref['links']['self'] = cls.base_url() + '/' + ref['id'] + + @classmethod + def wrap_member(cls, context, ref): + cls._add_self_referential_link(ref) + return {cls.member_name: ref} + + @classmethod + def wrap_collection(cls, context, refs): + refs = cls.paginate(context, refs) + + for ref in refs: + cls.wrap_member(context, ref) + + container = {cls.collection_name: refs} + container['links'] = { + 'next': None, + 'self': cls.base_url(path=context['path']), + 'previous': None} + return container + + @classmethod + def paginate(cls, context, refs): """Paginates a list of references by page & per_page query strings.""" + # FIXME(dolph): client needs to support pagination first + return refs + page = context['query_string'].get('page', 1) per_page = context['query_string'].get('per_page', 30) return refs[per_page * (page - 1):per_page * page] diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py index 92f6e1c12f..5abbc46859 100644 --- a/keystone/common/wsgi.py +++ b/keystone/common/wsgi.py @@ -211,6 +211,7 @@ class Application(BaseApplication): # allow middleware up the stack to provide context & params context = req.environ.get(CONTEXT_ENV, {}) context['query_string'] = dict(req.params.iteritems()) + context['path'] = req.environ['PATH_INFO'] params = req.environ.get(PARAMS_ENV, {}) if 'REMOTE_USER' in req.environ: context['REMOTE_USER'] = req.environ['REMOTE_USER'] diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index 8d35fd0cea..1b7180bbe4 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -391,21 +391,24 @@ class Role(controller.V2Controller): class DomainV3(controller.V3Controller): + collection_name = 'domains' + member_name = 'domain' + @controller.protected def create_domain(self, context, domain): ref = self._assign_unique_id(self._normalize_dict(domain)) ref = self.identity_api.create_domain(context, ref['id'], ref) - return {'domain': ref} + return DomainV3.wrap_member(context, ref) @controller.protected def list_domains(self, context): refs = self.identity_api.list_domains(context) - return {'domains': self._paginate(context, refs)} + return DomainV3.wrap_collection(context, refs) @controller.protected def get_domain(self, context, domain_id): ref = self.identity_api.get_domain(context, domain_id) - return {'domain': ref} + return DomainV3.wrap_member(context, ref) @controller.protected def update_domain(self, context, domain_id, domain): @@ -441,7 +444,7 @@ class DomainV3(controller.V3Controller): user_id=user['id'], tenant_id=project['id']) - return {'domain': ref} + return DomainV3.wrap_member(context, ref) @controller.protected def delete_domain(self, context, domain_id): @@ -455,33 +458,36 @@ class DomainV3(controller.V3Controller): class ProjectV3(controller.V3Controller): + collection_name = 'projects' + member_name = 'project' + @controller.protected def create_project(self, context, project): ref = self._assign_unique_id(self._normalize_dict(project)) ref = self.identity_api.create_project(context, ref['id'], ref) - return {'project': ref} + return ProjectV3.wrap_member(context, ref) @controller.protected def list_projects(self, context): refs = self.identity_api.list_projects(context) - return {'projects': self._paginate(context, refs)} + return ProjectV3.wrap_collection(context, refs) @controller.protected def list_user_projects(self, context, user_id): refs = self.identity_api.list_user_projects(context, user_id) - return {'projects': self._paginate(context, refs)} + return ProjectV3.wrap_collection(context, refs) @controller.protected def get_project(self, context, project_id): ref = self.identity_api.get_project(context, project_id) - return {'project': ref} + return ProjectV3.wrap_member(context, ref) @controller.protected def update_project(self, context, project_id, project): self._require_matching_id(project_id, project) ref = self.identity_api.update_project(context, project_id, project) - return {'project': ref} + return ProjectV3.wrap_member(context, ref) @controller.protected def delete_project(self, context, project_id): @@ -489,26 +495,29 @@ class ProjectV3(controller.V3Controller): class UserV3(controller.V3Controller): + collection_name = 'users' + member_name = 'user' + @controller.protected def create_user(self, context, user): ref = self._assign_unique_id(self._normalize_dict(user)) ref = self.identity_api.create_user(context, ref['id'], ref) - return {'user': ref} + return UserV3.wrap_member(context, ref) @controller.protected def list_users(self, context): refs = self.identity_api.list_users(context) - return {'users': self._paginate(context, refs)} + return UserV3.wrap_collection(context, refs) @controller.protected def list_users_in_group(self, context, group_id): refs = self.identity_api.list_users_in_group(context, group_id) - return {'users': self._paginate(context, refs)} + return UserV3.wrap_collection(context, refs) @controller.protected def get_user(self, context, user_id): ref = self.identity_api.get_user(context, user_id) - return {'user': ref} + return UserV3.wrap_member(context, ref) @controller.protected def update_user(self, context, user_id, user): @@ -522,7 +531,7 @@ class UserV3(controller.V3Controller): context, user_id=user['id']) - return {'user': ref} + return UserV3.wrap_member(context, ref) @controller.protected def add_user_to_group(self, context, user_id, group_id): @@ -545,33 +554,36 @@ class UserV3(controller.V3Controller): class GroupV3(controller.V3Controller): + collection_name = 'groups' + member_name = 'group' + @controller.protected def create_group(self, context, group): ref = self._assign_unique_id(self._normalize_dict(group)) ref = self.identity_api.create_group(context, ref['id'], ref) - return {'group': ref} + return GroupV3.wrap_member(context, ref) @controller.protected def list_groups(self, context): refs = self.identity_api.list_groups(context) - return {'groups': self._paginate(context, refs)} + return GroupV3.wrap_collection(context, refs) @controller.protected def list_groups_for_user(self, context, user_id): refs = self.identity_api.list_groups_for_user(context, user_id) - return {'groups': self._paginate(context, refs)} + return GroupV3.wrap_collection(context, refs) @controller.protected def get_group(self, context, group_id): ref = self.identity_api.get_group(context, group_id) - return {'group': ref} + return GroupV3.wrap_member(context, ref) @controller.protected def update_group(self, context, group_id, group): self._require_matching_id(group_id, group) ref = self.identity_api.update_group(context, group_id, group) - return {'group': ref} + return GroupV3.wrap_member(context, ref) @controller.protected def delete_group(self, context, group_id): @@ -579,21 +591,24 @@ class GroupV3(controller.V3Controller): class CredentialV3(controller.V3Controller): + collection_name = 'credentials' + member_name = 'credential' + @controller.protected def create_credential(self, context, credential): ref = self._assign_unique_id(self._normalize_dict(credential)) ref = self.identity_api.create_credential(context, ref['id'], ref) - return {'credential': ref} + return CredentialV3.wrap_member(context, ref) @controller.protected def list_credentials(self, context): refs = self.identity_api.list_credentials(context) - return {'credentials': self._paginate(context, refs)} + return CredentialV3.wrap_collection(context, refs) @controller.protected def get_credential(self, context, credential_id): ref = self.identity_api.get_credential(context, credential_id) - return {'credential': ref} + return CredentialV3.wrap_member(context, ref) @controller.protected def update_credential(self, context, credential_id, credential): @@ -603,7 +618,7 @@ class CredentialV3(controller.V3Controller): context, credential_id, credential) - return {'credential': ref} + return CredentialV3.wrap_member(context, ref) @controller.protected def delete_credential(self, context, credential_id): @@ -611,28 +626,31 @@ class CredentialV3(controller.V3Controller): class RoleV3(controller.V3Controller): + collection_name = 'roles' + member_name = 'role' + @controller.protected def create_role(self, context, role): ref = self._assign_unique_id(self._normalize_dict(role)) ref = self.identity_api.create_role(context, ref['id'], ref) - return {'role': ref} + return RoleV3.wrap_member(context, ref) @controller.protected def list_roles(self, context): refs = self.identity_api.list_roles(context) - return {'roles': self._paginate(context, refs)} + return RoleV3.wrap_collection(context, refs) @controller.protected def get_role(self, context, role_id): ref = self.identity_api.get_role(context, role_id) - return {'role': ref} + return RoleV3.wrap_member(context, ref) @controller.protected def update_role(self, context, role_id, role): self._require_matching_id(role_id, role) ref = self.identity_api.update_role(context, role_id, role) - return {'role': ref} + return RoleV3.wrap_member(context, ref) @controller.protected def delete_role(self, context, role_id): @@ -655,7 +673,7 @@ class RoleV3(controller.V3Controller): self._require_domain_xor_project(domain_id, project_id) self._require_user_xor_group(user_id, group_id) - return self.identity_api.create_grant( + self.identity_api.create_grant( context, role_id, user_id, group_id, domain_id, project_id) @controller.protected @@ -665,8 +683,9 @@ class RoleV3(controller.V3Controller): self._require_domain_xor_project(domain_id, project_id) self._require_user_xor_group(user_id, group_id) - return self.identity_api.list_grants( + refs = self.identity_api.list_grants( context, user_id, group_id, domain_id, project_id) + return RoleV3.wrap_collection(context, refs) @controller.protected def check_grant(self, context, role_id, user_id=None, group_id=None, diff --git a/keystone/policy/controllers.py b/keystone/policy/controllers.py index e3b9625283..ee2e4ee337 100644 --- a/keystone/policy/controllers.py +++ b/keystone/policy/controllers.py @@ -18,6 +18,9 @@ from keystone.common import controller class PolicyV3(controller.V3Controller): + collection_name = 'policies' + member_name = 'policy' + @controller.protected def create_policy(self, context, policy): ref = self._assign_unique_id(self._normalize_dict(policy)) @@ -25,23 +28,23 @@ class PolicyV3(controller.V3Controller): self._require_attribute(ref, 'type') ref = self.policy_api.create_policy(context, ref['id'], ref) - return {'policy': ref} + return PolicyV3.wrap_member(context, ref) @controller.protected def list_policies(self, context): refs = self.policy_api.list_policies(context) refs = self._filter_by_attribute(context, refs, 'type') - return {'policies': self._paginate(context, refs)} + return PolicyV3.wrap_collection(context, refs) @controller.protected def get_policy(self, context, policy_id): ref = self.policy_api.get_policy(context, policy_id) - return {'policy': ref} + return PolicyV3.wrap_member(context, ref) @controller.protected def update_policy(self, context, policy_id, policy): ref = self.policy_api.update_policy(context, policy_id, policy) - return {'policy': ref} + return PolicyV3.wrap_member(context, ref) @controller.protected def delete_policy(self, context, policy_id): diff --git a/tests/test_v3.py b/tests/test_v3.py index 958260dda5..28475e1440 100644 --- a/tests/test_v3.py +++ b/tests/test_v3.py @@ -2,11 +2,12 @@ import uuid from keystone.common.sql import util as sql_util from keystone import test +from keystone import config import test_content_types -BASE_URL = 'http://127.0.0.1:35357/v3' +CONF = config.CONF class RestfulTestCase(test_content_types.RestfulTestCase): @@ -133,7 +134,8 @@ class RestfulTestCase(test_content_types.RestfulTestCase): def delete(self, path, **kwargs): return self.v3_request(method='DELETE', path=path, **kwargs) - def assertValidListResponse(self, resp, key, entity_validator, ref=None): + def assertValidListResponse(self, resp, key, entity_validator, ref=None, + expected_length=None): """Make assertions common to all API list responses. If a reference is provided, it's ID will be searched for in the @@ -142,7 +144,20 @@ class RestfulTestCase(test_content_types.RestfulTestCase): """ entities = resp.body.get(key) self.assertIsNotNone(entities) - self.assertTrue(len(entities)) + + if expected_length is not None: + self.assertEqual(len(entities), expected_length) + elif ref is not None: + # we're at least expecting the ref + 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']) + for entity in entities: self.assertIsNotNone(entity) self.assertValidEntity(entity) @@ -173,10 +188,10 @@ class RestfulTestCase(test_content_types.RestfulTestCase): msg = '%s unnexpectedly None in %s' % (k, entity) self.assertIsNotNone(entity.get(k), msg) - # FIXME(dolph): need to test this in v3 - # self.assertIsNotNone(entity.get('link')) - # self.assertIsNotNone(entity['link'].get('href')) - # self.assertEquals(entity['link'].get('rel'), 'self') + self.assertIsNotNone(entity.get('links')) + self.assertIsNotNone(entity['links'].get('self')) + self.assertIn(CONF.public_endpoint % CONF, entity['links']['self']) + self.assertIn(entity['id'], entity['links']['self']) if ref: for k in keys: diff --git a/tests/test_v3_catalog.py b/tests/test_v3_catalog.py index 3a90170944..9f2f4f01a0 100644 --- a/tests/test_v3_catalog.py +++ b/tests/test_v3_catalog.py @@ -25,12 +25,12 @@ class CatalogTestCase(test_v3.RestfulTestCase): # service validation - def assertValidServiceListResponse(self, resp, ref): + def assertValidServiceListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'services', self.assertValidService, - ref) + **kwargs) def assertValidServiceResponse(self, resp, ref): return self.assertValidResponse( @@ -47,12 +47,12 @@ class CatalogTestCase(test_v3.RestfulTestCase): # endpoint validation - def assertValidEndpointListResponse(self, resp, ref): + def assertValidEndpointListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'endpoints', self.assertValidEndpoint, - ref) + **kwargs) def assertValidEndpointResponse(self, resp, ref): return self.assertValidResponse( @@ -82,7 +82,7 @@ class CatalogTestCase(test_v3.RestfulTestCase): def test_list_services(self): """GET /services""" r = self.get('/services') - self.assertValidServiceListResponse(r, self.service) + self.assertValidServiceListResponse(r, ref=self.service) def test_get_service(self): """GET /services/{service_id}""" @@ -109,7 +109,7 @@ class CatalogTestCase(test_v3.RestfulTestCase): def test_list_endpoints(self): """GET /endpoints""" r = self.get('/endpoints') - self.assertValidEndpointListResponse(r, self.endpoint) + self.assertValidEndpointListResponse(r, ref=self.endpoint) def test_create_endpoint(self): """POST /endpoints""" diff --git a/tests/test_v3_identity.py b/tests/test_v3_identity.py index 5236cddcfc..8805b6d848 100644 --- a/tests/test_v3_identity.py +++ b/tests/test_v3_identity.py @@ -49,12 +49,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # domain validation - def assertValidDomainListResponse(self, resp, ref): + def assertValidDomainListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'domains', self.assertValidDomain, - ref) + **kwargs) def assertValidDomainResponse(self, resp, ref): return self.assertValidResponse( @@ -70,12 +70,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # project validation - def assertValidProjectListResponse(self, resp, ref): + def assertValidProjectListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'projects', self.assertValidProject, - ref) + **kwargs) def assertValidProjectResponse(self, resp, ref): return self.assertValidResponse( @@ -92,12 +92,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # user validation - def assertValidUserListResponse(self, resp, ref): + def assertValidUserListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'users', self.assertValidUser, - ref) + **kwargs) def assertValidUserResponse(self, resp, ref): return self.assertValidResponse( @@ -117,12 +117,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # group validation - def assertValidGroupListResponse(self, resp, ref): + def assertValidGroupListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'groups', self.assertValidGroup, - ref) + **kwargs) def assertValidGroupResponse(self, resp, ref): return self.assertValidResponse( @@ -139,12 +139,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # credential validation - def assertValidCredentialListResponse(self, resp, ref): + def assertValidCredentialListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'credentials', self.assertValidCredential, - ref) + **kwargs) def assertValidCredentialResponse(self, resp, ref): return self.assertValidResponse( @@ -166,12 +166,12 @@ class IdentityTestCase(test_v3.RestfulTestCase): # role validation - def assertValidRoleListResponse(self, resp, ref): + def assertValidRoleListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'roles', self.assertValidRole, - ref) + **kwargs) def assertValidRoleResponse(self, resp, ref): return self.assertValidResponse( @@ -186,27 +186,6 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertEqual(ref['name'], entity['name']) return entity - # grant validation - - def assertValidGrantListResponse(self, resp, ref): - entities = resp.body - self.assertIsNotNone(entities) - self.assertTrue(len(entities)) - for i, entity in enumerate(entities): - self.assertValidEntity(entity) - self.assertValidGrant(entity, ref) - if ref and entity['id'] == ref['id'][0]: - self.assertValidEntity(entity, ref) - self.assertValidGrant(entity, ref) - - def assertValidGrant(self, entity, ref=None): - self.assertIsNotNone(entity.get('id')) - self.assertIsNotNone(entity.get('name')) - if ref: - self.assertEqual(ref['id'], entity['id']) - self.assertEqual(ref['name'], entity['name']) - return entity - # domain crud tests def test_create_domain(self): @@ -220,7 +199,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_domains(self): """GET /domains""" r = self.get('/domains') - self.assertValidDomainListResponse(r, self.domain) + self.assertValidDomainListResponse(r, ref=self.domain) def test_get_domain(self): """GET /domains/{domain_id}""" @@ -268,7 +247,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_projects(self): """GET /projects""" r = self.get('/projects') - self.assertValidProjectListResponse(r, self.project) + self.assertValidProjectListResponse(r, ref=self.project) def test_create_project(self): """POST /projects""" @@ -314,7 +293,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_users(self): """GET /users""" r = self.get('/users') - self.assertValidUserListResponse(r, self.user) + self.assertValidUserListResponse(r, ref=self.user) def test_get_user(self): """GET /users/{user_id}""" @@ -340,7 +319,9 @@ class IdentityTestCase(test_v3.RestfulTestCase): 'group_id': self.group_id, 'user_id': self.user_id}) r = self.get('/groups/%(group_id)s/users' % { 'group_id': self.group_id}) - self.assertValidUserListResponse(r, self.user) + self.assertValidUserListResponse(r, ref=self.user) + self.assertIn('/groups/%(group_id)s/users' % { + 'group_id': self.group_id}, r.body['links']['self']) def test_remove_user_from_group(self): """DELETE /groups/{group_id}/users/{user_id}""" @@ -376,7 +357,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_groups(self): """GET /groups""" r = self.get('/groups') - self.assertValidGroupListResponse(r, self.group) + self.assertValidGroupListResponse(r, ref=self.group) def test_get_group(self): """GET /groups/{group_id}""" @@ -403,7 +384,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_credentials(self): """GET /credentials""" r = self.get('/credentials') - self.assertValidCredentialListResponse(r, self.credential) + self.assertValidCredentialListResponse(r, ref=self.credential) def test_create_credential(self): """POST /credentials""" @@ -451,7 +432,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): def test_list_roles(self): """GET /roles""" r = self.get('/roles') - self.assertValidRoleListResponse(r, self.role) + self.assertValidRoleListResponse(r, ref=self.role) def test_get_role(self): """GET /roles/{role_id}""" @@ -473,82 +454,82 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.delete('/roles/%(role_id)s' % { 'role_id': self.role_id}) - def test_create_user_project_grant(self): - """PUT /projects/{project_id}/users/{user_id}/roles/{role_id}""" - self.put('/projects/%(project_id)s/users/%(user_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'user_id': self.user_id, - 'role_id': self.role_id}) - self.head('/projects/%(project_id)s/users/%(user_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'user_id': self.user_id, - 'role_id': self.role_id}) + def test_crud_user_project_role_grants(self): + collection_url = ( + '/projects/%(project_id)s/users/%(user_id)s/roles' % { + 'project_id': self.project_id, + 'user_id': self.user_id}) + member_url = '%(collection_url)s/%(role_id)s' % { + 'collection_url': collection_url, + 'role_id': self.role_id} - def test_create_group_project_grant(self): - """PUT /projects/{project_id}/groups/{group_id}/roles/{role_id}""" - self.put('/projects/%(project_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) - self.head('/projects/%(project_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=self.role) + self.assertIn(collection_url, r.body['links']['self']) - def test_create_group_domain_grant(self): - """PUT /domains/{domain_id}/groups/{group_id}/roles/{role_id}""" - self.put('/domains/%(domain_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'domain_id': self.domain_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) - self.head('/domains/%(domain_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'domain_id': self.domain_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) + self.delete(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, expected_length=0) + self.assertIn(collection_url, r.body['links']['self']) - def test_list_user_project_grants(self): - """GET /projects/{project_id}/users/{user_id}/roles""" - self.put('/projects/%(project_id)s/users/%(user_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'user_id': self.user_id, - 'role_id': self.role_id}) - r = self.get('/projects/%(project_id)s/users/%(user_id)s/roles' % { - 'project_id': self.project_id, - 'user_id': self.user_id}) - self.assertValidGrantListResponse(r, self.role) + def test_crud_user_domain_role_grants(self): + collection_url = ( + '/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': self.domain_id, + 'user_id': self.user_id}) + member_url = '%(collection_url)s/%(role_id)s' % { + 'collection_url': collection_url, + 'role_id': self.role_id} - def test_list_group_project_grants(self): - """GET /projects/{project_id}/groups/{group_id}/roles""" - self.put('/projects/%(project_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) - r = self.get('/projects/%(project_id)s/groups/%(group_id)s/roles' % { - 'project_id': self.project_id, - 'group_id': self.group_id}) - self.assertValidGrantListResponse(r, self.role) + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=self.role) + self.assertIn(collection_url, r.body['links']['self']) - def test_delete_group_project_grant(self): - """DELETE /projects/{project_id}/groups/{group_id}/roles/{role_id}""" - self.put('/projects/%(project_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) - self.delete('/projects/%(project_id)s/groups/%(group_id)s/roles/' - '%(role_id)s' % { - 'project_id': self.project_id, - 'group_id': self.group_id, - 'role_id': self.role_id}) - r = self.get('/projects/%(project_id)s/groups/%(group_id)s/roles' % { - 'project_id': self.project_id, - 'group_id': self.group_id}) - self.assertEquals(len(r.body), 0) + self.delete(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, expected_length=0) + self.assertIn(collection_url, r.body['links']['self']) + + def test_crud_group_project_role_grants(self): + collection_url = ( + '/projects/%(project_id)s/groups/%(group_id)s/roles' % { + 'project_id': self.project_id, + 'group_id': self.group_id}) + member_url = '%(collection_url)s/%(role_id)s' % { + 'collection_url': collection_url, + 'role_id': self.role_id} + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=self.role) + self.assertIn(collection_url, r.body['links']['self']) + + self.delete(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, expected_length=0) + self.assertIn(collection_url, r.body['links']['self']) + + def test_crud_group_domain_role_grants(self): + collection_url = ( + '/domains/%(domain_id)s/groups/%(group_id)s/roles' % { + 'domain_id': self.domain_id, + 'group_id': self.group_id}) + member_url = '%(collection_url)s/%(role_id)s' % { + 'collection_url': collection_url, + 'role_id': self.role_id} + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=self.role) + self.assertIn(collection_url, r.body['links']['self']) + + self.delete(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, expected_length=0) + self.assertIn(collection_url, r.body['links']['self']) diff --git a/tests/test_v3_policy.py b/tests/test_v3_policy.py index 5898aad3c0..811eb57789 100644 --- a/tests/test_v3_policy.py +++ b/tests/test_v3_policy.py @@ -17,12 +17,12 @@ class PolicyTestCase(test_v3.RestfulTestCase): # policy validation - def assertValidPolicyListResponse(self, resp, ref): + def assertValidPolicyListResponse(self, resp, **kwargs): return self.assertValidListResponse( resp, 'policies', self.assertValidPolicy, - ref) + **kwargs) def assertValidPolicyResponse(self, resp, ref): return self.assertValidResponse( @@ -52,7 +52,7 @@ class PolicyTestCase(test_v3.RestfulTestCase): def test_list_policies(self): """GET /policies""" r = self.get('/policies') - self.assertValidPolicyListResponse(r, self.policy) + self.assertValidPolicyListResponse(r, ref=self.policy) def test_get_policy(self): """GET /policies/{policy_id}"""