Validate domains unconditionally (bug 1130236)
Ensure that we validate the domain status of user/project for a user authenticating via the v2 API. This patch builds on the initial functional change done by Dolph, and fixes up the tests that broke sure to domain being required in any tests that setup data directly in the backends. Fixes Bug #1130236 Change-Id: I66dfd453fb95fa4fa3fde713b663386a2c2ecdf8
This commit is contained in:
parent
a066b69fbe
commit
cd3f58a8d0
|
@ -91,6 +91,9 @@ class UserAuthInfo(object):
|
||||||
else:
|
else:
|
||||||
user_ref = self.identity_api.get_user(
|
user_ref = self.identity_api.get_user(
|
||||||
context=self.context, user_id=user_id)
|
context=self.context, user_id=user_id)
|
||||||
|
domain_ref = self.identity_api.get_domain(
|
||||||
|
context=self.context, domain_id=user_ref['domain_id'])
|
||||||
|
self._assert_domain_is_enabled(domain_ref)
|
||||||
except exception.UserNotFound as e:
|
except exception.UserNotFound as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
raise exception.Unauthorized(e)
|
raise exception.Unauthorized(e)
|
||||||
|
|
|
@ -20,7 +20,10 @@ from keystone import exception
|
||||||
class DictKvs(dict):
|
class DictKvs(dict):
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
try:
|
try:
|
||||||
return self[key]
|
if isinstance(self[key], dict):
|
||||||
|
return self[key].copy()
|
||||||
|
else:
|
||||||
|
return self[key][:]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
|
|
|
@ -242,25 +242,43 @@ class TestCase(NoModule, unittest.TestCase):
|
||||||
# TODO(termie): doing something from json, probably based on Django's
|
# TODO(termie): doing something from json, probably based on Django's
|
||||||
# loaddata will be much preferred.
|
# loaddata will be much preferred.
|
||||||
if hasattr(self, 'identity_api'):
|
if hasattr(self, 'identity_api'):
|
||||||
|
for domain in fixtures.DOMAINS:
|
||||||
|
try:
|
||||||
|
rv = self.identity_api.create_domain(domain['id'], domain)
|
||||||
|
except (exception.Conflict, exception.NotImplemented):
|
||||||
|
pass
|
||||||
|
setattr(self, 'domain_%s' % domain['id'], domain)
|
||||||
|
|
||||||
for tenant in fixtures.TENANTS:
|
for tenant in fixtures.TENANTS:
|
||||||
rv = self.identity_api.create_project(tenant['id'], tenant)
|
try:
|
||||||
|
rv = self.identity_api.create_project(tenant['id'], tenant)
|
||||||
|
except exception.Conflict:
|
||||||
|
rv = self.identity_api.get_project(tenant['id'])
|
||||||
|
pass
|
||||||
setattr(self, 'tenant_%s' % tenant['id'], rv)
|
setattr(self, 'tenant_%s' % tenant['id'], rv)
|
||||||
|
|
||||||
for role in fixtures.ROLES:
|
for role in fixtures.ROLES:
|
||||||
try:
|
try:
|
||||||
rv = self.identity_api.create_role(role['id'], role)
|
rv = self.identity_api.create_role(role['id'], role)
|
||||||
except exception.Conflict:
|
except exception.Conflict:
|
||||||
|
rv = self.identity_api.get_role(role['id'])
|
||||||
pass
|
pass
|
||||||
setattr(self, 'role_%s' % role['id'], rv)
|
setattr(self, 'role_%s' % role['id'], rv)
|
||||||
|
|
||||||
for user in fixtures.USERS:
|
for user in fixtures.USERS:
|
||||||
user_copy = user.copy()
|
user_copy = user.copy()
|
||||||
tenants = user_copy.pop('tenants')
|
tenants = user_copy.pop('tenants')
|
||||||
rv = self.identity_api.create_user(user['id'],
|
try:
|
||||||
user_copy.copy())
|
rv = self.identity_api.create_user(user['id'],
|
||||||
|
user_copy.copy())
|
||||||
|
except exception.Conflict:
|
||||||
|
pass
|
||||||
for tenant_id in tenants:
|
for tenant_id in tenants:
|
||||||
self.identity_api.add_user_to_project(tenant_id,
|
try:
|
||||||
user['id'])
|
self.identity_api.add_user_to_project(tenant_id,
|
||||||
|
user['id'])
|
||||||
|
except exception.Conflict:
|
||||||
|
pass
|
||||||
setattr(self, 'user_%s' % user['id'], user_copy)
|
setattr(self, 'user_%s' % user['id'], user_copy)
|
||||||
|
|
||||||
for metadata in fixtures.METADATA:
|
for metadata in fixtures.METADATA:
|
||||||
|
|
|
@ -79,6 +79,7 @@ class Auth(controller.V2Controller):
|
||||||
context, auth)
|
context, auth)
|
||||||
|
|
||||||
user_ref, tenant_ref, metadata_ref, expiry = auth_info
|
user_ref, tenant_ref, metadata_ref, expiry = auth_info
|
||||||
|
core.validate_auth_info(self, context, user_ref, tenant_ref)
|
||||||
trust_id = metadata_ref.get('trust_id')
|
trust_id = metadata_ref.get('trust_id')
|
||||||
user_ref = self._filter_domain_id(user_ref)
|
user_ref = self._filter_domain_id(user_ref)
|
||||||
if tenant_ref:
|
if tenant_ref:
|
||||||
|
@ -88,9 +89,6 @@ class Auth(controller.V2Controller):
|
||||||
metadata_ref,
|
metadata_ref,
|
||||||
expiry)
|
expiry)
|
||||||
|
|
||||||
# FIXME(dolph): domains will not be validated, as we just removed them
|
|
||||||
core.validate_auth_info(self, context, user_ref, tenant_ref)
|
|
||||||
|
|
||||||
if tenant_ref:
|
if tenant_ref:
|
||||||
catalog_ref = self.catalog_api.get_catalog(
|
catalog_ref = self.catalog_api.get_catalog(
|
||||||
context=context,
|
context=context,
|
||||||
|
|
|
@ -79,15 +79,13 @@ def validate_auth_info(self, context, user_ref, tenant_ref):
|
||||||
raise exception.Unauthorized(msg)
|
raise exception.Unauthorized(msg)
|
||||||
|
|
||||||
# If the user's domain is disabled don't allow them to authenticate
|
# If the user's domain is disabled don't allow them to authenticate
|
||||||
# TODO(dolph): remove this check after default-domain migration
|
user_domain_ref = self.identity_api.get_domain(
|
||||||
if user_ref.get('domain_id') is not None:
|
context,
|
||||||
user_domain_ref = self.identity_api.get_domain(
|
user_ref['domain_id'])
|
||||||
context,
|
if user_domain_ref and not user_domain_ref.get('enabled', True):
|
||||||
user_ref['domain_id'])
|
msg = 'Domain is disabled: %s' % user_domain_ref['id']
|
||||||
if user_domain_ref and not user_domain_ref.get('enabled', True):
|
LOG.warning(msg)
|
||||||
msg = 'Domain is disabled: %s' % user_domain_ref['id']
|
raise exception.Unauthorized(msg)
|
||||||
LOG.warning(msg)
|
|
||||||
raise exception.Unauthorized(msg)
|
|
||||||
|
|
||||||
if tenant_ref:
|
if tenant_ref:
|
||||||
# If the project is disabled don't allow them to authenticate
|
# If the project is disabled don't allow them to authenticate
|
||||||
|
@ -97,16 +95,14 @@ def validate_auth_info(self, context, user_ref, tenant_ref):
|
||||||
raise exception.Unauthorized(msg)
|
raise exception.Unauthorized(msg)
|
||||||
|
|
||||||
# If the project's domain is disabled don't allow them to authenticate
|
# If the project's domain is disabled don't allow them to authenticate
|
||||||
# TODO(dolph): remove this check after default-domain migration
|
project_domain_ref = self.identity_api.get_domain(
|
||||||
if tenant_ref.get('domain_id') is not None:
|
context,
|
||||||
project_domain_ref = self.identity_api.get_domain(
|
tenant_ref['domain_id'])
|
||||||
context,
|
if (project_domain_ref and
|
||||||
tenant_ref['domain_id'])
|
not project_domain_ref.get('enabled', True)):
|
||||||
if (project_domain_ref and
|
msg = 'Domain is disabled: %s' % project_domain_ref['id']
|
||||||
not project_domain_ref.get('enabled', True)):
|
LOG.warning(msg)
|
||||||
msg = 'Domain is disabled: %s' % project_domain_ref['id']
|
raise exception.Unauthorized(msg)
|
||||||
LOG.warning(msg)
|
|
||||||
raise exception.Unauthorized(msg)
|
|
||||||
|
|
||||||
|
|
||||||
@dependency.provider('token_api')
|
@dependency.provider('token_api')
|
||||||
|
|
|
@ -118,3 +118,13 @@ ROLES = [
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
DOMAINS = [
|
||||||
|
{
|
||||||
|
'id': DEFAULT_DOMAIN_ID,
|
||||||
|
'name': 'Default',
|
||||||
|
'enabled': True,
|
||||||
|
'description': 'Owns users and tenants (i.e. projects) available '
|
||||||
|
'on Identity API v2.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
@ -612,9 +612,6 @@ class AuthWithTrust(AuthTest):
|
||||||
return auth_response
|
return auth_response
|
||||||
|
|
||||||
def fetch_v3_token_from_trust(self):
|
def fetch_v3_token_from_trust(self):
|
||||||
self.identity_api.create_domain("default",
|
|
||||||
{"name": "default",
|
|
||||||
"id": "default"})
|
|
||||||
v3_password_data = {
|
v3_password_data = {
|
||||||
'identity': {
|
'identity': {
|
||||||
"methods": ["password"],
|
"methods": ["password"],
|
||||||
|
|
|
@ -23,7 +23,6 @@ from keystone.catalog import core
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
from keystone.openstack.common import timeutils
|
from keystone.openstack.common import timeutils
|
||||||
from keystone import config
|
|
||||||
from keystone import test
|
from keystone import test
|
||||||
|
|
||||||
|
|
||||||
|
@ -1582,10 +1581,11 @@ class IdentityTests(object):
|
||||||
self.identity_api.create_domain(domain1['id'], domain1)
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
self.identity_api.create_domain(domain2['id'], domain2)
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
domains = self.identity_api.list_domains()
|
domains = self.identity_api.list_domains()
|
||||||
self.assertEquals(len(domains), 2)
|
self.assertEquals(len(domains), 3)
|
||||||
domain_ids = []
|
domain_ids = []
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
domain_ids.append(domain.get('id'))
|
domain_ids.append(domain.get('id'))
|
||||||
|
self.assertIn(DEFAULT_DOMAIN_ID, domain_ids)
|
||||||
self.assertIn(domain1['id'], domain_ids)
|
self.assertIn(domain1['id'], domain_ids)
|
||||||
self.assertIn(domain2['id'], domain_ids)
|
self.assertIn(domain2['id'], domain_ids)
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ from keystone import exception
|
||||||
from keystone import test
|
from keystone import test
|
||||||
from keystone import token
|
from keystone import token
|
||||||
|
|
||||||
|
import default_fixtures
|
||||||
|
|
||||||
|
|
||||||
ROOTDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ROOTDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
SSLDIR = "%s/tests/ssl/" % ROOTDIR
|
SSLDIR = "%s/tests/ssl/" % ROOTDIR
|
||||||
CONF = test.CONF
|
CONF = test.CONF
|
||||||
|
@ -46,6 +49,7 @@ class CertSetupTestCase(test.TestCase):
|
||||||
CONF.signing.keyfile = os.path.join(KEYDIR, "signing_key.pem")
|
CONF.signing.keyfile = os.path.join(KEYDIR, "signing_key.pem")
|
||||||
|
|
||||||
self.load_backends()
|
self.load_backends()
|
||||||
|
self.load_fixtures(default_fixtures)
|
||||||
self.controller = token.controllers.Auth()
|
self.controller = token.controllers.Auth()
|
||||||
|
|
||||||
def test_can_handle_missing_certs(self):
|
def test_can_handle_missing_certs(self):
|
||||||
|
|
|
@ -340,7 +340,7 @@ class TestTokenAPIs(test_v3.RestfulTestCase):
|
||||||
self.assertIn('signed', r.body)
|
self.assertIn('signed', r.body)
|
||||||
|
|
||||||
|
|
||||||
class ATestTokenRevoking(test_v3.RestfulTestCase):
|
class TestTokenRevoking(test_v3.RestfulTestCase):
|
||||||
"""Test token revoking for relevant v3 identity apis"""
|
"""Test token revoking for relevant v3 identity apis"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -360,7 +360,7 @@ class ATestTokenRevoking(test_v3.RestfulTestCase):
|
||||||
- User3 is a member of group2
|
- User3 is a member of group2
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(ATestTokenRevoking, self).setUp()
|
super(TestTokenRevoking, self).setUp()
|
||||||
|
|
||||||
# Start by creating a couple of domains and projects
|
# Start by creating a couple of domains and projects
|
||||||
self.domainA = self.new_domain_ref()
|
self.domainA = self.new_domain_ref()
|
||||||
|
|
|
@ -79,26 +79,81 @@ class IdentityTestCase(test_v3.RestfulTestCase):
|
||||||
|
|
||||||
def test_disable_domain(self):
|
def test_disable_domain(self):
|
||||||
"""PATCH /domains/{domain_id} (set enabled=False)"""
|
"""PATCH /domains/{domain_id} (set enabled=False)"""
|
||||||
self.domain['enabled'] = False
|
# Create a 2nd set of entities in a 2nd domain
|
||||||
|
self.domain2 = self.new_domain_ref()
|
||||||
|
self.identity_api.create_domain(self.domain2['id'], self.domain2)
|
||||||
|
|
||||||
|
self.project2 = self.new_project_ref(
|
||||||
|
domain_id=self.domain2['id'])
|
||||||
|
self.identity_api.create_project(self.project2['id'], self.project2)
|
||||||
|
|
||||||
|
self.user2 = self.new_user_ref(
|
||||||
|
domain_id=self.domain2['id'],
|
||||||
|
project_id=self.project2['id'])
|
||||||
|
self.identity_api.create_user(self.user2['id'], self.user2)
|
||||||
|
|
||||||
|
self.identity_api.add_user_to_project(self.project2['id'],
|
||||||
|
self.user2['id'])
|
||||||
|
|
||||||
|
# First check a user in that domain can authenticate, via
|
||||||
|
# Both v2 and v3
|
||||||
|
body = {
|
||||||
|
'auth': {
|
||||||
|
'passwordCredentials': {
|
||||||
|
'userId': self.user2['id'],
|
||||||
|
'password': self.user2['password']
|
||||||
|
},
|
||||||
|
'tenantId': self.project2['id']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = self.admin_request(path='/v2.0/tokens',
|
||||||
|
method='POST',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
auth_data = self.build_authentication_request(
|
||||||
|
user_id=self.user2['id'],
|
||||||
|
password=self.user2['password'],
|
||||||
|
project_id=self.project2['id'])
|
||||||
|
resp = self.post('/auth/tokens', body=auth_data)
|
||||||
|
|
||||||
|
# Now disable the domain
|
||||||
|
self.domain2['enabled'] = False
|
||||||
r = self.patch('/domains/%(domain_id)s' % {
|
r = self.patch('/domains/%(domain_id)s' % {
|
||||||
'domain_id': self.domain_id},
|
'domain_id': self.domain2['id']},
|
||||||
body={'domain': {'enabled': False}})
|
body={'domain': {'enabled': False}})
|
||||||
self.assertValidDomainResponse(r, self.domain)
|
self.assertValidDomainResponse(r, self.domain2)
|
||||||
|
|
||||||
# check that the project and user are still enabled
|
# Make sure the user can no longer authenticate, via
|
||||||
# FIXME(gyee): are these tests still valid since user should not
|
# either API
|
||||||
# be able to authenticate into a disabled domain
|
body = {
|
||||||
#r = self.get('/projects/%(project_id)s' % {
|
'auth': {
|
||||||
# 'project_id': self.project_id})
|
'passwordCredentials': {
|
||||||
#self.assertValidProjectResponse(r, self.project)
|
'userId': self.user2['id'],
|
||||||
#self.assertTrue(r.body['project']['enabled'])
|
'password': self.user2['password']
|
||||||
|
},
|
||||||
|
'tenantId': self.project2['id']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = self.admin_request(path='/v2.0/tokens',
|
||||||
|
method='POST',
|
||||||
|
body=body,
|
||||||
|
expected_status=401)
|
||||||
|
|
||||||
#r = self.get('/users/%(user_id)s' % {
|
# Try looking up in v3 by name and id
|
||||||
# 'user_id': self.user['id']})
|
auth_data = self.build_authentication_request(
|
||||||
#self.assertValidUserResponse(r, self.user)
|
user_id=self.user2['id'],
|
||||||
#self.assertTrue(r.body['user']['enabled'])
|
password=self.user2['password'],
|
||||||
|
project_id=self.project2['id'])
|
||||||
|
resp = self.post('/auth/tokens', body=auth_data,
|
||||||
|
expected_status=401)
|
||||||
|
|
||||||
# TODO(dolph): assert that v2 & v3 auth return 401
|
auth_data = self.build_authentication_request(
|
||||||
|
username=self.user2['name'],
|
||||||
|
user_domain_id=self.domain2['id'],
|
||||||
|
password=self.user2['password'],
|
||||||
|
project_id=self.project2['id'])
|
||||||
|
resp = self.post('/auth/tokens', body=auth_data,
|
||||||
|
expected_status=401)
|
||||||
|
|
||||||
def test_delete_enabled_domain_fails(self):
|
def test_delete_enabled_domain_fails(self):
|
||||||
"""DELETE /domains/{domain_id}...(when domain enabled)"""
|
"""DELETE /domains/{domain_id}...(when domain enabled)"""
|
||||||
|
|
Loading…
Reference in New Issue