Skip to content

Commit

Permalink
Enable/disable domains (bug 1100145)
Browse files Browse the repository at this point in the history
Disabling an individual domain denies auth to users and projects owned by
that domain, and revokes all associated tokens. Re-enabling the domain
does not re-enable tokens.

Change-Id: Ic64f59be4f39317f4c365bec185408e79d18c45f
  • Loading branch information
dolph authored and openstack-gerrit committed Jan 28, 2013
1 parent ac1ed36 commit 02da3af
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 29 deletions.
Expand Up @@ -28,6 +28,7 @@ def upgrade(migrate_engine):
meta,
sql.Column('id', sql.String(64), primary_key=True),
sql.Column('name', sql.String(64), unique=True, nullable=False),
sql.Column('enabled', sql.Boolean, nullable=False, default=True),
sql.Column('extra', sql.Text()))
domain_table.create(migrate_engine, checkfirst=True)

Expand Down
3 changes: 2 additions & 1 deletion keystone/identity/backends/sql.py
Expand Up @@ -72,9 +72,10 @@ class Credential(sql.ModelBase, sql.DictBase):

class Domain(sql.ModelBase, sql.DictBase):
__tablename__ = 'domain'
attributes = ['id', 'name']
attributes = ['id', 'name', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
enabled = sql.Column(sql.Boolean, default=True)
extra = sql.Column(sql.JsonBlob())


Expand Down
36 changes: 36 additions & 0 deletions keystone/identity/controllers.py
Expand Up @@ -409,6 +409,35 @@ def update_domain(self, context, domain_id, domain):
self._require_matching_id(domain_id, domain)

ref = self.identity_api.update_domain(context, domain_id, domain)

# disable owned users & projects when the API user specifically set
# enabled=False
# FIXME(dolph): need a driver call to directly revoke all tokens by
# project or domain, regardless of user
if not domain.get('enabled', True):
projects = [x for x in self.identity_api.list_projects(context)
if x.get('domain_id') == domain_id]
for user in self.identity_api.list_users(context):
# TODO(dolph): disable domain-scoped tokens
"""
self.token_api.revoke_tokens(
context,
user_id=user['id'],
domain_id=domain_id)
"""
# revoke all tokens for users owned by this domain
if user.get('domain_id') == domain_id:
self.token_api.revoke_tokens(
context,
user_id=user['id'])
else:
# only revoke tokens on projects owned by this domain
for project in projects:
self.token_api.revoke_tokens(
context,
user_id=user['id'],
tenant_id=project['id'])

return {'domain': ref}

@controller.protected
Expand Down Expand Up @@ -477,6 +506,13 @@ def update_user(self, context, user_id, user):
self._require_matching_id(user_id, user)

ref = self.identity_api.update_user(context, user_id, user)

if user.get('password') or not user.get('enabled', True):
# revoke all tokens owned by this user
self.token_api.revoke_tokens(
context,
user_id=user['id'])

return {'user': ref}

@controller.protected
Expand Down
34 changes: 29 additions & 5 deletions keystone/token/controllers.py
Expand Up @@ -83,13 +83,37 @@ def authenticate(self, context, auth=None):
LOG.warning(msg)
raise exception.Unauthorized(msg)

# If the tenant is disabled don't allow them to authenticate
if tenant_ref and not tenant_ref.get('enabled', True):
msg = 'Tenant is disabled: %s' % tenant_ref['id']
LOG.warning(msg)
raise exception.Unauthorized(msg)
# If the user's domain is disabled don't allow them to authenticate
# TODO(dolph): remove this check after default-domain migration
if user_ref.get('domain_id') is not None:
user_domain_ref = self.identity_api.get_domain(
context,
user_ref['domain_id'])
if user_domain_ref and not user_domain_ref.get('enabled', True):
msg = 'Domain is disabled: %s' % user_domain_ref['id']
LOG.warning(msg)
raise exception.Unauthorized(msg)

if tenant_ref:
# If the project is disabled don't allow them to authenticate
if not tenant_ref.get('enabled', True):
msg = 'Tenant is disabled: %s' % tenant_ref['id']
LOG.warning(msg)
raise exception.Unauthorized(msg)

# If the project's domain is disabled don't allow them to
# authenticate
# TODO(dolph): remove this check after default-domain migration
if tenant_ref.get('domain_id') is not None:
project_domain_ref = self.identity_api.get_domain(
context,
tenant_ref['domain_id'])
if (project_domain_ref and
not project_domain_ref.get('enabled', True)):
msg = 'Domain is disabled: %s' % project_domain_ref['id']
LOG.warning(msg)
raise exception.Unauthorized(msg)

catalog_ref = self.catalog_api.get_catalog(
context=context,
user_id=user_ref['id'],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sql_upgrade.py
Expand Up @@ -346,7 +346,7 @@ def test_upgrade_6_to_7(self):
self.assertTableColumns('credential', ['id', 'user_id', 'project_id',
'blob', 'type', 'extra'])
self.assertTableExists('domain')
self.assertTableColumns('domain', ['id', 'name', 'extra'])
self.assertTableColumns('domain', ['id', 'name', 'enabled', 'extra'])
self.assertTableExists('user_domain_metadata')
self.assertTableColumns('user_domain_metadata',
['user_id', 'domain_id', 'data'])
Expand Down
1 change: 1 addition & 0 deletions tests/test_v3.py
Expand Up @@ -26,6 +26,7 @@ def tearDown(self):
self.admin_server.kill()
self.public_server = None
self.admin_server = None
sql_util.teardown_test_database()

def new_ref(self):
"""Populates a ref with attributes common to all API entities."""
Expand Down
54 changes: 32 additions & 22 deletions tests/test_v3_identity.py
Expand Up @@ -12,34 +12,26 @@ def setUp(self):
self.domain_id = uuid.uuid4().hex
self.domain = self.new_domain_ref()
self.domain['id'] = self.domain_id
self.identity_api.create_domain(
self.domain_id,
self.domain.copy())
self.identity_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.identity_api.create_project(
self.project_id,
self.project.copy())
self.identity_api.create_project(self.project_id, self.project)

self.user_id = uuid.uuid4().hex
self.user = self.new_user_ref(
domain_id=self.domain_id,
project_id=self.project_id)
self.user['id'] = self.user_id
self.identity_api.create_user(
self.user_id,
self.user.copy())
self.identity_api.create_user(self.user_id, self.user)

self.group_id = uuid.uuid4().hex
self.group = self.new_group_ref(
domain_id=self.domain_id)
self.group['id'] = self.group_id
self.identity_api.create_group(
self.group_id,
self.group.copy())
self.identity_api.create_group(self.group_id, self.group)

self.credential_id = uuid.uuid4().hex
self.credential = self.new_credential_ref(
Expand All @@ -48,14 +40,12 @@ def setUp(self):
self.credential['id'] = self.credential_id
self.identity_api.create_credential(
self.credential_id,
self.credential.copy())
self.credential)

self.role_id = uuid.uuid4().hex
self.role = self.new_role_ref()
self.role['id'] = self.role_id
self.identity_api.create_role(
self.role_id,
self.role.copy())
self.identity_api.create_role(self.role_id, self.role)

# domain validation

Expand Down Expand Up @@ -202,7 +192,6 @@ def assertValidGrantListResponse(self, resp, ref):
entities = resp.body
self.assertIsNotNone(entities)
self.assertTrue(len(entities))
roles_ref_ids = []
for i, entity in enumerate(entities):
self.assertValidEntity(entity)
self.assertValidGrant(entity, ref)
Expand Down Expand Up @@ -248,6 +237,27 @@ def test_update_domain(self):
body={'domain': ref})
self.assertValidDomainResponse(r, ref)

def test_disable_domain(self):
"""PATCH /domains/{domain_id} (set enabled=False)"""
self.domain['enabled'] = False
r = self.patch('/domains/%(domain_id)s' % {
'domain_id': self.domain_id},
body={'domain': {'enabled': False}})
self.assertValidDomainResponse(r, self.domain)

# check that the project and user are still enabled
r = self.get('/projects/%(project_id)s' % {
'project_id': self.project_id})
self.assertValidProjectResponse(r, self.project)
self.assertTrue(r.body['project']['enabled'])

r = self.get('/users/%(user_id)s' % {
'user_id': self.user_id})
self.assertValidUserResponse(r, self.user)
self.assertTrue(r.body['user']['enabled'])

# TODO(dolph): assert that v2 & v3 auth return 401

def test_delete_domain(self):
"""DELETE /domains/{domain_id}"""
self.delete('/domains/%(domain_id)s' % {
Expand Down Expand Up @@ -314,14 +324,14 @@ def test_get_user(self):

def test_add_user_to_group(self):
"""PUT /groups/{group_id}/users/{user_id}"""
r = self.put('/groups/%(group_id)s/users/%(user_id)s' % {
self.put('/groups/%(group_id)s/users/%(user_id)s' % {
'group_id': self.group_id, 'user_id': self.user_id})

def test_check_user_in_group(self):
"""HEAD /groups/{group_id}/users/{user_id}"""
r = self.put('/groups/%(group_id)s/users/%(user_id)s' % {
self.put('/groups/%(group_id)s/users/%(user_id)s' % {
'group_id': self.group_id, 'user_id': self.user_id})
r = self.head('/groups/%(group_id)s/users/%(user_id)s' % {
self.head('/groups/%(group_id)s/users/%(user_id)s' % {
'group_id': self.group_id, 'user_id': self.user_id})

def test_list_users_in_group(self):
Expand All @@ -334,9 +344,9 @@ def test_list_users_in_group(self):

def test_remove_user_from_group(self):
"""DELETE /groups/{group_id}/users/{user_id}"""
r = self.put('/groups/%(group_id)s/users/%(user_id)s' % {
self.put('/groups/%(group_id)s/users/%(user_id)s' % {
'group_id': self.group_id, 'user_id': self.user_id})
r = self.delete('/groups/%(group_id)s/users/%(user_id)s' % {
self.delete('/groups/%(group_id)s/users/%(user_id)s' % {
'group_id': self.group_id, 'user_id': self.user_id})

def test_update_user(self):
Expand Down

0 comments on commit 02da3af

Please sign in to comment.