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:
Dolph Mathews 2013-02-19 10:39:22 -06:00 committed by Henry Nash
parent a066b69fbe
commit cd3f58a8d0
11 changed files with 134 additions and 50 deletions

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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,

View File

@ -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')

View File

@ -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.'
}
]

View File

@ -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"],

View File

@ -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)

View File

@ -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):

View File

@ -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()

View File

@ -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)"""