diff options
author | Ruan HE <ruan.he@orange.com> | 2016-06-09 08:12:34 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@172.30.200.206> | 2016-06-09 08:12:34 +0000 |
commit | 4bc079a2664f9a407e332291f34d174625a9d5ea (patch) | |
tree | 7481cd5d0a9b3ce37c44c797a1e0d39881221cbe /keystone-moon/keystone/tests/unit/test_v3.py | |
parent | 2f179c5790fbbf6144205d3c6e5089e6eb5f048a (diff) | |
parent | 2e7b4f2027a1147ca28301e4f88adf8274b39a1f (diff) |
Merge "Update Keystone core to Mitaka."
Diffstat (limited to 'keystone-moon/keystone/tests/unit/test_v3.py')
-rw-r--r-- | keystone-moon/keystone/tests/unit/test_v3.py | 681 |
1 files changed, 475 insertions, 206 deletions
diff --git a/keystone-moon/keystone/tests/unit/test_v3.py b/keystone-moon/keystone/tests/unit/test_v3.py index 32c5e295..216d8c79 100644 --- a/keystone-moon/keystone/tests/unit/test_v3.py +++ b/keystone-moon/keystone/tests/unit/test_v3.py @@ -12,20 +12,25 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime import uuid +import mock from oslo_config import cfg +import oslo_context.context from oslo_serialization import jsonutils from oslo_utils import timeutils +from six.moves import http_client from testtools import matchers +import webtest from keystone import auth from keystone.common import authorization from keystone.common import cache +from keystone.common.validation import validators from keystone import exception from keystone import middleware -from keystone.policy.backends import rules +from keystone.middleware import auth as middleware_auth +from keystone.tests.common import auth as common_auth from keystone.tests import unit from keystone.tests.unit import rest @@ -38,6 +43,7 @@ TIME_FORMAT = unit.TIME_FORMAT class AuthTestMixin(object): """To hold auth building helper functions.""" + def build_auth_scope(self, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, domain_id=None, domain_name=None, trust_id=None, @@ -116,7 +122,127 @@ class AuthTestMixin(object): class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, - AuthTestMixin): + common_auth.AuthTestMixin): + + def generate_token_schema(self, domain_scoped=False, project_scoped=False): + """Return a dictionary of token properties to validate against.""" + properties = { + 'audit_ids': { + 'type': 'array', + 'items': { + 'type': 'string', + }, + 'minItems': 1, + 'maxItems': 2, + }, + 'bind': { + 'type': 'object', + 'properties': { + 'kerberos': { + 'type': 'string', + }, + }, + 'required': ['kerberos'], + 'additionalProperties': False, + }, + 'expires_at': {'type': 'string'}, + 'issued_at': {'type': 'string'}, + 'methods': { + 'type': 'array', + 'items': { + 'type': 'string', + }, + }, + 'user': { + 'type': 'object', + 'required': ['id', 'name', 'domain'], + 'properties': { + 'id': {'type': 'string'}, + 'name': {'type': 'string'}, + 'domain': { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'required': ['id', 'name'], + 'additonalProperties': False, + } + }, + 'additionalProperties': False, + } + } + + if domain_scoped: + properties['catalog'] = {'type': 'array'} + properties['roles'] = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': {'type': 'string', }, + 'name': {'type': 'string', }, + }, + 'required': ['id', 'name', ], + 'additionalProperties': False, + }, + 'minItems': 1, + } + properties['domain'] = { + 'domain': { + 'type': 'object', + 'required': ['id', 'name'], + 'properties': { + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'additionalProperties': False + } + } + elif project_scoped: + properties['is_admin_project'] = {'type': 'boolean'} + properties['catalog'] = {'type': 'array'} + properties['roles'] = {'type': 'array'} + properties['project'] = { + 'type': ['object'], + 'required': ['id', 'name', 'domain'], + 'properties': { + 'id': {'type': 'string'}, + 'name': {'type': 'string'}, + 'domain': { + 'type': ['object'], + 'required': ['id', 'name'], + 'properties': { + 'id': {'type': 'string'}, + 'name': {'type': 'string'} + }, + 'additionalProperties': False + } + }, + 'additionalProperties': False + } + + schema = { + 'type': 'object', + 'properties': properties, + 'required': ['audit_ids', 'expires_at', 'issued_at', 'methods', + 'user'], + 'optional': ['bind'], + 'additionalProperties': False + } + + if domain_scoped: + schema['required'].extend(['domain', 'roles']) + schema['optional'].append('catalog') + elif project_scoped: + schema['required'].append('project') + schema['optional'].append('bind') + schema['optional'].append('catalog') + schema['optional'].append('OS-TRUST:trust') + schema['optional'].append('is_admin_project') + + return schema + def config_files(self): config_files = super(RestfulTestCase, self).config_files() config_files.append(unit.dirs.tests_conf('backend_sql.conf')) @@ -146,9 +272,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, pass def setUp(self, app_conf='keystone'): - """Setup for v3 Restful Test Cases. - - """ + """Setup for v3 Restful Test Cases.""" new_paste_file = self.generate_paste_config() self.addCleanup(self.remove_generated_paste_config) if new_paste_file: @@ -158,16 +282,9 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, self.empty_context = {'environment': {}} - # Initialize the policy engine and allow us to write to a temp - # file in each test to create the policies - rules.reset() - - # drop the policy rules - self.addCleanup(rules.reset) - def load_backends(self): # ensure the cache region instance is setup - cache.configure_cache_region(cache.REGION) + cache.configure_cache() super(RestfulTestCase, self).load_backends() @@ -183,53 +300,42 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, try: self.resource_api.get_domain(DEFAULT_DOMAIN_ID) except exception.DomainNotFound: - domain = {'description': (u'Owns users and tenants (i.e. ' - u'projects) available on Identity ' - u'API v2.'), - 'enabled': True, - 'id': DEFAULT_DOMAIN_ID, - 'name': u'Default'} + domain = unit.new_domain_ref( + description=(u'The default domain'), + id=DEFAULT_DOMAIN_ID, + name=u'Default') self.resource_api.create_domain(DEFAULT_DOMAIN_ID, domain) def load_sample_data(self): self._populate_default_domain() - self.domain_id = uuid.uuid4().hex - self.domain = self.new_domain_ref() - self.domain['id'] = self.domain_id + self.domain = unit.new_domain_ref() + self.domain_id = self.domain['id'] self.resource_api.create_domain(self.domain_id, self.domain) - self.project_id = uuid.uuid4().hex - self.project = self.new_project_ref( - domain_id=self.domain_id) - self.project['id'] = self.project_id - self.resource_api.create_project(self.project_id, self.project) + self.project = unit.new_project_ref(domain_id=self.domain_id) + self.project_id = self.project['id'] + self.project = self.resource_api.create_project(self.project_id, + self.project) - self.user = self.new_user_ref(domain_id=self.domain_id) - password = self.user['password'] - self.user = self.identity_api.create_user(self.user) - self.user['password'] = password + self.user = unit.create_user(self.identity_api, + domain_id=self.domain_id) self.user_id = self.user['id'] self.default_domain_project_id = uuid.uuid4().hex - self.default_domain_project = self.new_project_ref( + self.default_domain_project = unit.new_project_ref( domain_id=DEFAULT_DOMAIN_ID) self.default_domain_project['id'] = self.default_domain_project_id self.resource_api.create_project(self.default_domain_project_id, self.default_domain_project) - self.default_domain_user = self.new_user_ref( + self.default_domain_user = unit.create_user( + self.identity_api, domain_id=DEFAULT_DOMAIN_ID) - password = self.default_domain_user['password'] - self.default_domain_user = ( - self.identity_api.create_user(self.default_domain_user)) - self.default_domain_user['password'] = password self.default_domain_user_id = self.default_domain_user['id'] # create & grant policy.json's default role for admin_required - self.role_id = uuid.uuid4().hex - self.role = self.new_role_ref() - self.role['id'] = self.role_id - self.role['name'] = 'admin' + self.role = unit.new_role_ref(name='admin') + self.role_id = self.role['id'] self.role_api.create_role(self.role_id, self.role) self.assignment_api.add_role_to_user_and_project( self.user_id, self.project_id, self.role_id) @@ -240,81 +346,35 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, self.default_domain_user_id, self.project_id, self.role_id) - self.region_id = uuid.uuid4().hex - self.region = self.new_region_ref() - self.region['id'] = self.region_id - self.catalog_api.create_region( - self.region.copy()) - - self.service_id = uuid.uuid4().hex - self.service = self.new_service_ref() - self.service['id'] = self.service_id - self.catalog_api.create_service( - self.service_id, - self.service.copy()) - - self.endpoint_id = uuid.uuid4().hex - self.endpoint = self.new_endpoint_ref(service_id=self.service_id) - self.endpoint['id'] = self.endpoint_id - self.endpoint['region_id'] = self.region['id'] - self.catalog_api.create_endpoint( - self.endpoint_id, - self.endpoint.copy()) - # The server adds 'enabled' and defaults to True. - self.endpoint['enabled'] = True - - def new_ref(self): - """Populates a ref with attributes common to some API entities.""" - return unit.new_ref() - - def new_region_ref(self): - return unit.new_region_ref() - - def new_service_ref(self): - return unit.new_service_ref() - - def new_endpoint_ref(self, service_id, interface='public', **kwargs): - return unit.new_endpoint_ref( - service_id, interface=interface, default_region_id=self.region_id, - **kwargs) - - def new_domain_ref(self): - return unit.new_domain_ref() - - def new_project_ref(self, domain_id=None, parent_id=None, is_domain=False): - return unit.new_project_ref(domain_id=domain_id, parent_id=parent_id, - is_domain=is_domain) - - def new_user_ref(self, domain_id, project_id=None): - return unit.new_user_ref(domain_id, project_id=project_id) - - def new_group_ref(self, domain_id): - return unit.new_group_ref(domain_id) - - def new_credential_ref(self, user_id, project_id=None, cred_type=None): - return unit.new_credential_ref(user_id, project_id=project_id, - cred_type=cred_type) + # Create "req_admin" user for simulating a real user instead of the + # admin_token_auth middleware + self.user_reqadmin = unit.create_user(self.identity_api, + DEFAULT_DOMAIN_ID) + self.assignment_api.add_role_to_user_and_project( + self.user_reqadmin['id'], + self.default_domain_project_id, + self.role_id) - def new_role_ref(self): - return unit.new_role_ref() + self.region = unit.new_region_ref() + self.region_id = self.region['id'] + self.catalog_api.create_region(self.region) - def new_policy_ref(self): - return unit.new_policy_ref() + self.service = unit.new_service_ref() + self.service_id = self.service['id'] + self.catalog_api.create_service(self.service_id, self.service.copy()) - def new_trust_ref(self, trustor_user_id, trustee_user_id, project_id=None, - impersonation=None, expires=None, role_ids=None, - role_names=None, remaining_uses=None, - allow_redelegation=False): - return unit.new_trust_ref( - trustor_user_id, trustee_user_id, project_id=project_id, - impersonation=impersonation, expires=expires, role_ids=role_ids, - role_names=role_names, remaining_uses=remaining_uses, - allow_redelegation=allow_redelegation) + self.endpoint = unit.new_endpoint_ref(service_id=self.service_id, + interface='public', + region_id=self.region_id) + self.endpoint_id = self.endpoint['id'] + self.catalog_api.create_endpoint(self.endpoint_id, + self.endpoint.copy()) + # The server adds 'enabled' and defaults to True. + self.endpoint['enabled'] = True def create_new_default_project_for_user(self, user_id, domain_id, enable_project=True): - ref = self.new_project_ref(domain_id=domain_id) - ref['enabled'] = enable_project + ref = unit.new_project_ref(domain_id=domain_id, enabled=enable_project) r = self.post('/projects', body={'project': ref}) project = self.assertValidProjectResponse(r, ref) # set the user's preferred project @@ -326,6 +386,34 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, return project + def get_admin_token(self): + """Convenience method so that we can test authenticated requests.""" + r = self.admin_request( + method='POST', + path='/v3/auth/tokens', + body={ + 'auth': { + 'identity': { + 'methods': ['password'], + 'password': { + 'user': { + 'name': self.user_reqadmin['name'], + 'password': self.user_reqadmin['password'], + 'domain': { + 'id': self.user_reqadmin['domain_id'] + } + } + } + }, + 'scope': { + 'project': { + 'id': self.default_domain_project_id, + } + } + } + }) + return r.headers.get('X-Subject-Token') + def get_unscoped_token(self): """Convenience method so that we can test authenticated requests.""" r = self.admin_request( @@ -407,11 +495,10 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, def get_requested_token(self, auth): """Request the specific token we want.""" - - r = self.v3_authenticate_token(auth) + r = self.v3_create_token(auth) return r.headers.get('X-Subject-Token') - def v3_authenticate_token(self, auth, expected_status=201): + def v3_create_token(self, auth, expected_status=http_client.CREATED): return self.admin_request(method='POST', path='/v3/auth/tokens', body=auth, @@ -440,42 +527,31 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, return self.admin_request(path=path, token=token, **kwargs) - def get(self, path, **kwargs): - r = self.v3_request(method='GET', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 200) - return r + def get(self, path, expected_status=http_client.OK, **kwargs): + return self.v3_request(path, method='GET', + expected_status=expected_status, **kwargs) - def head(self, path, **kwargs): - r = self.v3_request(method='HEAD', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 204) - self.assertEqual('', r.body) + def head(self, path, expected_status=http_client.NO_CONTENT, **kwargs): + r = self.v3_request(path, method='HEAD', + expected_status=expected_status, **kwargs) + self.assertEqual(b'', r.body) return r - def post(self, path, **kwargs): - r = self.v3_request(method='POST', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 201) - return r + def post(self, path, expected_status=http_client.CREATED, **kwargs): + return self.v3_request(path, method='POST', + expected_status=expected_status, **kwargs) - def put(self, path, **kwargs): - r = self.v3_request(method='PUT', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 204) - return r + def put(self, path, expected_status=http_client.NO_CONTENT, **kwargs): + return self.v3_request(path, method='PUT', + expected_status=expected_status, **kwargs) - def patch(self, path, **kwargs): - r = self.v3_request(method='PATCH', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 200) - return r + def patch(self, path, expected_status=http_client.OK, **kwargs): + return self.v3_request(path, method='PATCH', + expected_status=expected_status, **kwargs) - def delete(self, path, **kwargs): - r = self.v3_request(method='DELETE', path=path, **kwargs) - if 'expected_status' not in kwargs: - self.assertResponseStatus(r, 204) - return r + def delete(self, path, expected_status=http_client.NO_CONTENT, **kwargs): + return self.v3_request(path, method='DELETE', + expected_status=expected_status, **kwargs) def assertValidErrorResponse(self, r): resp = r.result @@ -582,7 +658,6 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, except Exception: msg = '%s is not a valid ISO 8601 extended format date time.' % dt raise AssertionError(msg) - self.assertIsInstance(dt, datetime.datetime) def assertValidTokenResponse(self, r, user=None): self.assertTrue(r.headers.get('X-Subject-Token')) @@ -611,11 +686,10 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, 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) + validator_object = validators.SchemaValidator( + self.generate_token_schema() + ) + validator_object.validate(token) return token @@ -623,6 +697,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, require_catalog = kwargs.pop('require_catalog', True) endpoint_filter = kwargs.pop('endpoint_filter', False) ep_filter_assoc = kwargs.pop('ep_filter_assoc', 0) + is_admin_project = kwargs.pop('is_admin_project', False) token = self.assertValidTokenResponse(r, *args, **kwargs) if require_catalog: @@ -650,40 +725,66 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, self.assertIn('id', role) self.assertIn('name', role) + if is_admin_project: + # NOTE(samueldmq): We want to explicitly test for boolean + self.assertIs(True, token['is_admin_project']) + else: + self.assertNotIn('is_admin_project', token) + 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']) + project_scoped_token_schema = self.generate_token_schema( + project_scoped=True) + + if token.get('OS-TRUST:trust'): + trust_properties = { + 'OS-TRUST:trust': { + 'type': ['object'], + 'required': ['id', 'impersonation', 'trustor_user', + 'trustee_user'], + 'properties': { + 'id': {'type': 'string'}, + 'impersonation': {'type': 'boolean'}, + 'trustor_user': { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': {'type': 'string'} + }, + 'additionalProperties': False + }, + 'trustee_user': { + 'type': 'object', + 'required': ['id'], + 'properties': { + 'id': {'type': 'string'} + }, + 'additionalProperties': False + } + }, + 'additionalProperties': False + } + } + project_scoped_token_schema['properties'].update(trust_properties) + + validator_object = validators.SchemaValidator( + project_scoped_token_schema) + validator_object.validate(token) self.assertEqual(self.role_id, token['roles'][0]['id']) return token - def assertValidProjectTrustScopedTokenResponse(self, r, *args, **kwargs): - token = self.assertValidProjectScopedTokenResponse(r, *args, **kwargs) - - trust = token.get('OS-TRUST:trust') - self.assertIsNotNone(trust) - self.assertIsNotNone(trust.get('id')) - self.assertIsInstance(trust.get('impersonation'), bool) - self.assertIsNotNone(trust.get('trustor_user')) - self.assertIsNotNone(trust.get('trustee_user')) - self.assertIsNotNone(trust['trustor_user'].get('id')) - self.assertIsNotNone(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']) + validator_object = validators.SchemaValidator( + self.generate_token_schema(domain_scoped=True) + ) + validator_object.validate(token) return token @@ -876,7 +977,6 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, **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 @@ -888,6 +988,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, resp, 'users', self.assertValidUser, + keys_to_check=['name', 'enabled'], *args, **kwargs) @@ -896,6 +997,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, resp, 'user', self.assertValidUser, + keys_to_check=['name', 'enabled'], *args, **kwargs) @@ -920,6 +1022,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, resp, 'groups', self.assertValidGroup, + keys_to_check=['name', 'description', 'domain_id'], *args, **kwargs) @@ -928,6 +1031,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, resp, 'group', self.assertValidGroup, + keys_to_check=['name', 'description', 'domain_id'], *args, **kwargs) @@ -979,6 +1083,21 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, *args, **kwargs) + def assertRoleInListResponse(self, resp, ref, expected=1): + found_count = 0 + for entity in resp.result.get('roles'): + try: + self.assertValidRole(entity, ref=ref) + except Exception: + # It doesn't match, so let's go onto the next one + pass + else: + found_count += 1 + self.assertEqual(expected, found_count) + + def assertRoleNotInListResponse(self, resp, ref): + self.assertRoleInListResponse(resp, ref=ref, expected=0) + def assertValidRoleResponse(self, resp, *args, **kwargs): return self.assertValidResponse( resp, @@ -992,6 +1111,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, self.assertIsNotNone(entity.get('name')) if ref: self.assertEqual(ref['name'], entity['name']) + self.assertEqual(ref['domain_id'], entity['domain_id']) return entity # role assignment validation @@ -1161,6 +1281,27 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase, return entity + # Service providers (federation) + + def assertValidServiceProvider(self, entity, ref=None, *args, **kwargs): + + attributes = frozenset(['auth_url', 'id', 'enabled', 'description', + 'links', 'relay_state_prefix', 'sp_url']) + for attribute in attributes: + self.assertIsNotNone(entity.get(attribute)) + + def assertValidServiceProviderListResponse(self, resp, *args, **kwargs): + if kwargs.get('keys_to_check') is None: + kwargs['keys_to_check'] = ['auth_url', 'id', 'enabled', + 'description', 'relay_state_prefix', + 'sp_url'] + return self.assertValidListResponse( + resp, + 'service_providers', + self.assertValidServiceProvider, + *args, + **kwargs) + def build_external_auth_request(self, remote_user, remote_domain=None, auth_data=None, kerberos=False): @@ -1182,24 +1323,81 @@ class VersionTestCase(RestfulTestCase): pass +# NOTE(morganfainberg): To be removed when admin_token_auth is removed. This +# has been split out to allow testing admin_token auth without enabling it +# for other tests. +class AuthContextMiddlewareAdminTokenTestCase(RestfulTestCase): + EXTENSION_TO_ADD = 'admin_token_auth' + + def config_overrides(self): + super(AuthContextMiddlewareAdminTokenTestCase, self).config_overrides() + self.config_fixture.config( + admin_token='ADMIN') + + # NOTE(morganfainberg): This is knowingly copied from below for simplicity + # during the deprecation cycle. + def _middleware_request(self, token, extra_environ=None): + + def application(environ, start_response): + body = b'body' + headers = [('Content-Type', 'text/html; charset=utf8'), + ('Content-Length', str(len(body)))] + start_response('200 OK', headers) + return [body] + + app = webtest.TestApp(middleware.AuthContextMiddleware(application), + extra_environ=extra_environ) + resp = app.get('/', headers={middleware.AUTH_TOKEN_HEADER: token}) + self.assertEqual('body', resp.text) # just to make sure it worked + return resp.request + + def test_admin_auth_context(self): + # test to make sure AuthContextMiddleware does not attempt to build the + # auth context if the admin_token middleware indicates it's admin + # already. + token_id = uuid.uuid4().hex # token doesn't matter. + # the admin_token middleware sets is_admin in the context. + extra_environ = {middleware.CONTEXT_ENV: {'is_admin': True}} + req = self._middleware_request(token_id, extra_environ) + auth_context = req.environ.get(authorization.AUTH_CONTEXT_ENV) + self.assertDictEqual({}, auth_context) + + @mock.patch.object(middleware_auth.versionutils, + 'report_deprecated_feature') + def test_admin_token_auth_context_deprecated(self, mock_report_deprecated): + # For backwards compatibility AuthContextMiddleware will check that the + # admin token (as configured in the CONF file) is present and not + # attempt to build the auth context. This is deprecated. + req = self._middleware_request('ADMIN') + auth_context = req.environ.get(authorization.AUTH_CONTEXT_ENV) + self.assertDictEqual({}, auth_context) + self.assertEqual(1, mock_report_deprecated.call_count) + + # NOTE(gyee): test AuthContextMiddleware here instead of test_middleware.py # because we need the token class AuthContextMiddlewareTestCase(RestfulTestCase): - def _mock_request_object(self, token_id): - class fake_req(object): - headers = {middleware.AUTH_TOKEN_HEADER: token_id} - environ = {} + def _middleware_request(self, token, extra_environ=None): + + def application(environ, start_response): + body = b'body' + headers = [('Content-Type', 'text/html; charset=utf8'), + ('Content-Length', str(len(body)))] + start_response('200 OK', headers) + return [body] - return fake_req() + app = webtest.TestApp(middleware.AuthContextMiddleware(application), + extra_environ=extra_environ) + resp = app.get('/', headers={middleware.AUTH_TOKEN_HEADER: token}) + self.assertEqual(b'body', resp.body) # just to make sure it worked + return resp.request def test_auth_context_build_by_middleware(self): # test to make sure AuthContextMiddleware successful build the auth # context from the incoming auth token admin_token = self.get_scoped_token() - req = self._mock_request_object(admin_token) - application = None - middleware.AuthContextMiddleware(application).process_request(req) + req = self._middleware_request(admin_token) self.assertEqual( self.user['id'], req.environ.get(authorization.AUTH_CONTEXT_ENV)['user_id']) @@ -1208,28 +1406,16 @@ class AuthContextMiddlewareTestCase(RestfulTestCase): overridden_context = 'OVERRIDDEN_CONTEXT' # this token should not be used token = uuid.uuid4().hex - req = self._mock_request_object(token) - req.environ[authorization.AUTH_CONTEXT_ENV] = overridden_context - application = None - middleware.AuthContextMiddleware(application).process_request(req) + + extra_environ = {authorization.AUTH_CONTEXT_ENV: overridden_context} + req = self._middleware_request(token, extra_environ=extra_environ) # make sure overridden context take precedence self.assertEqual(overridden_context, req.environ.get(authorization.AUTH_CONTEXT_ENV)) - def test_admin_token_auth_context(self): - # test to make sure AuthContextMiddleware does not attempt to build - # auth context if the incoming auth token is the special admin token - req = self._mock_request_object(CONF.admin_token) - application = None - middleware.AuthContextMiddleware(application).process_request(req) - self.assertDictEqual(req.environ.get(authorization.AUTH_CONTEXT_ENV), - {}) - def test_unscoped_token_auth_context(self): unscoped_token = self.get_unscoped_token() - req = self._mock_request_object(unscoped_token) - application = None - middleware.AuthContextMiddleware(application).process_request(req) + req = self._middleware_request(unscoped_token) for key in ['project_id', 'domain_id', 'domain_name']: self.assertNotIn( key, @@ -1237,9 +1423,7 @@ class AuthContextMiddlewareTestCase(RestfulTestCase): def test_project_scoped_token_auth_context(self): project_scoped_token = self.get_scoped_token() - req = self._mock_request_object(project_scoped_token) - application = None - middleware.AuthContextMiddleware(application).process_request(req) + req = self._middleware_request(project_scoped_token) self.assertEqual( self.project['id'], req.environ.get(authorization.AUTH_CONTEXT_ENV)['project_id']) @@ -1251,9 +1435,7 @@ class AuthContextMiddlewareTestCase(RestfulTestCase): self.put(path=path) domain_scoped_token = self.get_domain_scoped_token() - req = self._mock_request_object(domain_scoped_token) - application = None - middleware.AuthContextMiddleware(application).process_request(req) + req = self._middleware_request(domain_scoped_token) self.assertEqual( self.domain['id'], req.environ.get(authorization.AUTH_CONTEXT_ENV)['domain_id']) @@ -1261,6 +1443,30 @@ class AuthContextMiddlewareTestCase(RestfulTestCase): self.domain['name'], req.environ.get(authorization.AUTH_CONTEXT_ENV)['domain_name']) + def test_oslo_context(self): + # After AuthContextMiddleware runs, an + # oslo_context.context.RequestContext was created so that its fields + # can be logged. This test validates that the RequestContext was + # created and the fields are set as expected. + + # Use a scoped token so more fields can be set. + token = self.get_scoped_token() + + # oslo_middleware RequestId middleware sets openstack.request_id. + request_id = uuid.uuid4().hex + environ = {'openstack.request_id': request_id} + self._middleware_request(token, extra_environ=environ) + + req_context = oslo_context.context.get_current() + self.assertEqual(request_id, req_context.request_id) + self.assertEqual(token, req_context.auth_token) + self.assertEqual(self.user['id'], req_context.user) + self.assertEqual(self.project['id'], req_context.tenant) + self.assertIsNone(req_context.domain) + self.assertEqual(self.user['domain_id'], req_context.user_domain) + self.assertEqual(self.project['domain_id'], req_context.project_domain) + self.assertFalse(req_context.is_admin) + class JsonHomeTestMixin(object): """JSON Home test @@ -1273,6 +1479,7 @@ class JsonHomeTestMixin(object): data must be in the response. """ + def test_get_json_home(self): resp = self.get('/', convert=False, headers={'Accept': 'application/json-home'}) @@ -1295,7 +1502,6 @@ class AssignmentTestMixin(object): Available filters are: domain_id, project_id, user_id, group_id, role_id and inherited_to_projects. """ - query_params = '?effective' if effective else '' for k, v in filters.items(): @@ -1320,7 +1526,6 @@ class AssignmentTestMixin(object): Provided attributes are expected to contain: domain_id or project_id, user_id or group_id, role_id and, optionally, inherited_to_projects. """ - if attribs.get('domain_id'): link = '/domains/' + attribs['domain_id'] else: @@ -1338,13 +1543,13 @@ class AssignmentTestMixin(object): return link - def build_role_assignment_entity(self, link=None, **attribs): + def build_role_assignment_entity( + self, link=None, prior_role_link=None, **attribs): """Build and return a role assignment entity with provided attributes. Provided attributes are expected to contain: domain_id or project_id, user_id or group_id, role_id and, optionally, inherited_to_projects. """ - entity = {'links': {'assignment': ( link or self.build_role_assignment_link(**attribs))}} @@ -1368,4 +1573,68 @@ class AssignmentTestMixin(object): if attribs.get('inherited_to_projects'): entity['scope']['OS-INHERIT:inherited_to'] = 'projects' + if prior_role_link: + entity['links']['prior_role'] = prior_role_link + + return entity + + def build_role_assignment_entity_include_names(self, + domain_ref=None, + role_ref=None, + group_ref=None, + user_ref=None, + project_ref=None, + inherited_assignment=None): + """Build and return a role assignment entity with provided attributes. + + The expected attributes are: domain_ref or project_ref, + user_ref or group_ref, role_ref and, optionally, inherited_to_projects. + """ + entity = {'links': {}} + attributes_for_links = {} + if project_ref: + dmn_name = self.resource_api.get_domain( + project_ref['domain_id'])['name'] + + entity['scope'] = {'project': { + 'id': project_ref['id'], + 'name': project_ref['name'], + 'domain': { + 'id': project_ref['domain_id'], + 'name': dmn_name}}} + attributes_for_links['project_id'] = project_ref['id'] + else: + entity['scope'] = {'domain': {'id': domain_ref['id'], + 'name': domain_ref['name']}} + attributes_for_links['domain_id'] = domain_ref['id'] + if user_ref: + dmn_name = self.resource_api.get_domain( + user_ref['domain_id'])['name'] + entity['user'] = {'id': user_ref['id'], + 'name': user_ref['name'], + 'domain': {'id': user_ref['domain_id'], + 'name': dmn_name}} + attributes_for_links['user_id'] = user_ref['id'] + else: + dmn_name = self.resource_api.get_domain( + group_ref['domain_id'])['name'] + entity['group'] = {'id': group_ref['id'], + 'name': group_ref['name'], + 'domain': { + 'id': group_ref['domain_id'], + 'name': dmn_name}} + attributes_for_links['group_id'] = group_ref['id'] + + if role_ref: + entity['role'] = {'id': role_ref['id'], + 'name': role_ref['name']} + attributes_for_links['role_id'] = role_ref['id'] + + if inherited_assignment: + entity['scope']['OS-INHERIT:inherited_to'] = 'projects' + attributes_for_links['inherited_to_projects'] = True + + entity['links']['assignment'] = self.build_role_assignment_link( + **attributes_for_links) + return entity |