Retrieve domain scoped token

This patch supports using domain scoped tokens against keystone v3.

Use Cases:

Cloud Admin - view and manage identity resources across domains
Domain Admin - view and manage identity resources in the domain logged in
User - view identity project in the domain logged in

Regression:

Supports keystone v2 through local_settings.py configuration
Supports keystone v3 with multidomain = False
Supports keystone v3 with mulitdomain = True

Relates to https://review.openstack.org/#/c/141153/

Background on how to test is here
https://wiki.openstack.org/wiki/Horizon/DomainWorkFlow

Co-Authored-By: Brad Pokorny <Brad_Pokorny@symantec.com>
Co-Authored-By: Brian Tully <brian.tully@hp.com>
Co-Authored-By: Michael Hagedorn <mike.hagedorn@hp.com>
Co-Authored-By: woomatt <matt.wood@hp.com>

Partially Implements: blueprint domain-scoped-tokens

Closes-Bug: #1413851
Change-Id: Iaa19bfef9b0c70304ff81d083c62b218b2d02479
This commit is contained in:
daniel-a-nguyen 2015-01-17 19:19:37 -08:00
parent a699e5a371
commit 2b846515f3
27 changed files with 826 additions and 251 deletions

View File

@ -40,6 +40,8 @@ from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
DEFAULT_ROLE = None
DEFAULT_DOMAIN = getattr(settings, 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'default')
# Set up our data structure for managing Identity API versions, and
@ -144,7 +146,17 @@ def keystoneclient(request, admin=False):
The client is cached so that subsequent API calls during the same
request/response cycle don't have to be re-authenticated.
"""
api_version = VERSIONS.get_active_version()
user = request.user
token_id = user.token.id
if is_multi_domain_enabled:
# Cloud Admin, Domain Admin or Mixed Domain Admin
if is_domain_admin(request):
domain_token = request.session.get('domain_token')
if domain_token:
token_id = getattr(domain_token, 'auth_token', None)
if admin:
if not policy.check((("identity", "admin_required"),), request):
raise exceptions.NotAuthorized
@ -154,8 +166,6 @@ def keystoneclient(request, admin=False):
'OPENSTACK_ENDPOINT_TYPE',
'internalURL')
api_version = VERSIONS.get_active_version()
# Take care of client connection caching/fetching a new client.
# Admin vs. non-admin clients are cached separately for token matching.
cache_attr = "_keystoneclient_admin" if admin \
@ -170,7 +180,7 @@ def keystoneclient(request, admin=False):
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
LOG.debug("Creating a new keystoneclient connection to %s." % endpoint)
remote_addr = request.environ.get('REMOTE_ADDR', '')
conn = api_version['client'].Client(token=user.token.id,
conn = api_version['client'].Client(token=token_id,
endpoint=endpoint,
original_ip=remote_addr,
insecure=insecure,
@ -183,7 +193,7 @@ def keystoneclient(request, admin=False):
def domain_create(request, name, description=None, enabled=None):
manager = keystoneclient(request, admin=True).domains
return manager.create(name,
return manager.create(name=name,
description=description,
enabled=enabled)
@ -203,10 +213,29 @@ def domain_list(request):
return manager.list()
def domain_lookup(request):
if policy.check((("identity", "identity:list_domains"),), request):
try:
domains = domain_list(request)
return dict((d.id, d.name) for d in domains)
except Exception:
LOG.warn("Pure project admin doesn't have a domain token")
return None
else:
domain = get_default_domain(request)
return {domain.id: domain.name}
def domain_update(request, domain_id, name=None, description=None,
enabled=None):
manager = keystoneclient(request, admin=True).domains
return manager.update(domain_id, name, description, enabled)
try:
response = manager.update(domain_id, name=name,
description=description, enabled=enabled)
except Exception as e:
LOG.exception("Unable to update Domain: %s" % domain_id)
raise e
return response
def tenant_create(request, name, description=None, enabled=None,
@ -223,28 +252,52 @@ def tenant_create(request, name, description=None, enabled=None,
raise exceptions.Conflict()
def get_default_domain(request):
def get_default_domain(request, get_name=True):
"""Gets the default domain object to use when creating Identity object.
Returns the domain context if is set, otherwise return the domain
of the logon user.
:param get_name: Whether to get the domain name from Keystone if the
context isn't set. Setting this to False prevents an unnecessary call
to Keystone if only the domain ID is needed.
"""
domain_id = request.session.get("domain_context", None)
domain_name = request.session.get("domain_context_name", None)
# if running in Keystone V3 or later
if VERSIONS.active >= 3 and not domain_id:
# if no domain context set, default to users' domain
if VERSIONS.active >= 3 and domain_id is None:
# if no domain context set, default to user's domain
domain_id = request.user.user_domain_id
try:
domain = domain_get(request, domain_id)
domain_name = domain.name
except Exception:
LOG.warning("Unable to retrieve Domain: %s" % domain_id)
domain_name = request.user.user_domain_name
if get_name:
try:
domain = domain_get(request, domain_id)
domain_name = domain.name
except Exception:
LOG.warning("Unable to retrieve Domain: %s" % domain_id)
domain = base.APIDictWrapper({"id": domain_id,
"name": domain_name})
return domain
def get_effective_domain_id(request):
"""Gets the id of the default domain to use when creating Identity objects.
If the requests default domain is the same as DEFAULT_DOMAIN, return None.
"""
domain_id = get_default_domain(request).get('id')
return None if domain_id == DEFAULT_DOMAIN else domain_id
def is_cloud_admin(request):
return policy.check((("identity", "cloud_admin"),), request)
def is_domain_admin(request):
# TODO(btully): check this to verify that domain id is in scope vs target
return policy.check(
(("identity", "admin_and_matching_domain_id"),), request)
# TODO(gabriel): Is there ever a valid case for admin to be false here?
# A quick search through the codebase reveals that it's always called with
# admin=true so I suspect we could eliminate it entirely as with the other
@ -280,15 +333,17 @@ def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
if paginate and len(tenants) > page_size:
tenants.pop(-1)
has_more_data = True
# V3 API
else:
domain_id = get_effective_domain_id(request)
kwargs = {
"domain": domain,
"domain": domain_id,
"user": user
}
if filters is not None:
kwargs.update(filters)
tenants = manager.list(**kwargs)
return (tenants, has_more_data)
return tenants, has_more_data
def tenant_update(request, project, name=None, description=None,
@ -514,23 +569,34 @@ def get_project_groups_roles(request, project):
groups_roles = collections.defaultdict(list)
project_role_assignments = role_assignments_list(request,
project=project)
for role_assignment in project_role_assignments:
if not hasattr(role_assignment, 'group'):
continue
group_id = role_assignment.group['id']
role_id = role_assignment.role['id']
groups_roles[group_id].append(role_id)
# filter by project_id
if ('project' in role_assignment.scope and
role_assignment.scope['project']['id'] == project):
groups_roles[group_id].append(role_id)
return groups_roles
def role_assignments_list(request, project=None, user=None, role=None,
group=None, domain=None, effective=False):
group=None, domain=None, effective=False,
include_subtree=True):
if VERSIONS.active < 3:
raise exceptions.NotAvailable
if include_subtree:
domain = None
manager = keystoneclient(request, admin=True).role_assignments
return manager.list(project=project, user=user, role=role, group=group,
domain=domain, effective=effective)
domain=domain, effective=effective,
include_subtree=include_subtree)
def role_create(request, name):
@ -570,13 +636,18 @@ def roles_for_user(request, user, project=None, domain=None):
def get_domain_users_roles(request, domain):
users_roles = collections.defaultdict(list)
domain_role_assignments = role_assignments_list(request,
domain=domain)
domain=domain,
include_subtree=False)
for role_assignment in domain_role_assignments:
if not hasattr(role_assignment, 'user'):
continue
user_id = role_assignment.user['id']
role_id = role_assignment.role['id']
users_roles[user_id].append(role_id)
# filter by domain_id
if ('domain' in role_assignment.scope and
role_assignment.scope['domain']['id'] == domain):
users_roles[user_id].append(role_id)
return users_roles
@ -609,7 +680,11 @@ def get_project_users_roles(request, project):
continue
user_id = role_assignment.user['id']
role_id = role_assignment.role['id']
users_roles[user_id].append(role_id)
# filter by project_id
if ('project' in role_assignment.scope and
role_assignment.scope['project']['id'] == project):
users_roles[user_id].append(role_id)
return users_roles
@ -755,6 +830,11 @@ def get_version():
return VERSIONS.active
def is_multi_domain_enabled():
return (VERSIONS.active >= 3 and
getattr(settings, 'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT', False))
def is_federation_management_enabled():
return getattr(settings, 'OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT', False)

View File

@ -21,6 +21,6 @@ class Admin(horizon.Dashboard):
name = _("Admin")
slug = "admin"
permissions = ('openstack.roles.admin',)
policy_rules = (("identity", "cloud_admin"),)
horizon.register(Admin)

View File

@ -28,3 +28,11 @@ class Domains(horizon.Panel):
@staticmethod
def can_register():
return keystone.VERSIONS.active >= 3
def can_access(self, context):
if keystone.VERSIONS.active < 3:
return super(Domains, self).can_access(context)
request = context['request']
domain_token = request.session.get('domain_token')
return super(Domains, self).can_access(context) and domain_token

View File

@ -39,9 +39,7 @@ class UpdateUsersLink(tables.LinkAction):
verbose_name = _("Manage Members")
url = "horizon:identity:domains:update"
classes = ("ajax-modal",)
policy_rules = (("identity", "identity:list_users"),
("identity", "identity:list_roles"),
("identity", "identity:list_role_assignments"))
policy_rules = (("identity", "identity:update_domain"),)
def get_link_url(self, domain):
step = 'update_user_members'
@ -56,6 +54,7 @@ class UpdateGroupsLink(tables.LinkAction):
url = "horizon:identity:domains:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("identity", "identity:update_domain"),)
def get_link_url(self, domain):
step = 'update_group_members'
@ -227,7 +226,7 @@ class SetDomainContext(tables.Action):
verbose_name = _("Set Domain Context")
url = constants.DOMAINS_INDEX_URL
preempt = True
policy_rules = (('identity', 'admin_required'),)
policy_rules = (('identity', 'identity:update_domain'),)
def allowed(self, request, datum):
multidomain_support = getattr(settings,
@ -262,7 +261,7 @@ class UnsetDomainContext(tables.Action):
url = constants.DOMAINS_INDEX_URL
preempt = True
requires_input = False
policy_rules = (('identity', 'admin_required'),)
policy_rules = (('identity', 'identity:update_domain'),)
def allowed(self, request, datum):
ctx = request.session.get("domain_context", None)

View File

@ -269,7 +269,8 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \
domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups)
@ -331,7 +332,8 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \
domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups)
@ -354,15 +356,19 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
# handle
api.keystone.domain_update(IsA(http.HttpRequest),
domain.id,
name=domain.name,
description=test_description,
domain_id=domain.id,
enabled=domain.enabled,
name=domain.name).AndReturn(None)
enabled=domain.enabled).AndReturn(None)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \
domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain.id).AndReturn(users)
# Give user 3 role 1
api.keystone.add_domain_user_role(IsA(http.HttpRequest),
domain=domain.id,
@ -468,7 +474,8 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \
domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups)
@ -492,10 +499,10 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
# handle
api.keystone.domain_update(IsA(http.HttpRequest),
domain.id,
name=domain.name,
description=test_description,
domain_id=domain.id,
enabled=domain.enabled,
name=domain.name) \
enabled=domain.enabled) \
.AndRaise(self.exceptions.keystone)
self.mox.ReplayAll()

View File

@ -37,13 +37,13 @@ class IndexView(tables.DataTableView):
def get_data(self):
domains = []
domain_context = self.request.session.get('domain_context', None)
domain_id = api.keystone.get_effective_domain_id(self.request)
if policy.check((("identity", "identity:list_domains"),),
self.request):
try:
if domain_context:
domain = api.keystone.domain_get(self.request,
domain_context)
if domain_id:
domain = api.keystone.domain_get(self.request, domain_id)
domains.append(domain)
else:
domains = api.keystone.domain_list(self.request)
@ -53,8 +53,7 @@ class IndexView(tables.DataTableView):
elif policy.check((("identity", "identity:get_domain"),),
self.request):
try:
domain = api.keystone.domain_get(self.request,
self.request.user.domain_id)
domain = api.keystone.domain_get(self.request, domain_id)
domains.append(domain)
except Exception:
exceptions.handle(self.request,

View File

@ -322,8 +322,16 @@ class UpdateDomain(workflows.Workflow, IdentityMixIn):
users_roles = api.keystone.get_domain_users_roles(request,
domain=domain_id)
users_to_modify = len(users_roles)
all_users = api.keystone.user_list(request,
domain=domain_id)
users_dict = {user.id: user.name for user in all_users}
for user_id in users_roles.keys():
# Don't remove roles if the user isn't in the domain
if user_id not in users_dict:
users_to_modify -= 1
continue
# Check if there have been any changes in the roles of
# Existing domain members.
current_role_ids = list(users_roles[user_id])
@ -484,7 +492,7 @@ class UpdateDomain(workflows.Workflow, IdentityMixIn):
try:
LOG.info('Updating domain with name "%s"' % data['name'])
api.keystone.domain_update(request,
domain_id=domain_id,
domain_id,
name=data['name'],
description=data['description'],
enabled=data['enabled'])

View File

@ -36,7 +36,7 @@ class CreateGroupForm(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Creating group with name "%s"' % data['name'])
domain_context = request.session.get('domain_context', None)
domain_context = api.keystone.get_effective_domain_id(request)
api.keystone.group_create(
request,
domain_id=domain_context,

View File

@ -27,3 +27,9 @@ class Groups(horizon.Panel):
@staticmethod
def can_register():
return keystone.VERSIONS.active >= 3
def can_access(self, context):
if keystone.is_multi_domain_enabled() \
and not keystone.is_domain_admin(context['request']):
return False
return super(Groups, self).can_access(context)

View File

@ -24,6 +24,7 @@ from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.dashboards.identity.groups import constants
from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
@ -46,7 +47,7 @@ class CreateGroupLink(tables.LinkAction):
return api.keystone.keystone_can_edit_group()
class EditGroupLink(tables.LinkAction):
class EditGroupLink(policy.PolicyTargetMixin, tables.LinkAction):
name = "edit"
verbose_name = _("Edit Group")
url = constants.GROUPS_UPDATE_URL
@ -58,7 +59,7 @@ class EditGroupLink(tables.LinkAction):
return api.keystone.keystone_can_edit_group()
class DeleteGroupsAction(tables.DeleteAction):
class DeleteGroupsAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(

View File

@ -65,11 +65,33 @@ class GroupsViewTests(test.BaseAdminViewTests):
self.assertContains(res, 'Edit')
self.assertContains(res, 'Delete Group')
@test.create_stubs({api.keystone: ('group_list',
'get_effective_domain_id')})
def test_index_with_domain(self):
domain = self.domains.get(id="1")
self.setSessionValues(domain_context=domain.id,
domain_context_name=domain.name)
self.test_index()
groups = self._get_groups(domain.id)
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain.id)
api.keystone.group_list(IsA(http.HttpRequest),
domain=domain.id).AndReturn(groups)
self.mox.ReplayAll()
res = self.client.get(GROUPS_INDEX_URL)
self.assertTemplateUsed(res, constants.GROUPS_INDEX_VIEW_TEMPLATE)
self.assertItemsEqual(res.context['table'].data, groups)
if domain.id:
for group in res.context['table'].data:
self.assertItemsEqual(group.domain_id, domain.id)
self.assertContains(res, 'Create Group')
self.assertContains(res, 'Edit')
self.assertContains(res, 'Delete Group')
@test.create_stubs({api.keystone: ('group_list',
'keystone_can_edit_group')})
@ -159,17 +181,27 @@ class GroupsViewTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, GROUPS_INDEX_URL)
@test.create_stubs({api.keystone: ('group_get',
@test.create_stubs({api.keystone: ('get_effective_domain_id',
'group_get',
'user_list',)})
def test_manage(self):
group = self.groups.get(id="1")
group_members = self.users.list()
domain_id = self._get_domain_id()
api.keystone.group_get(IsA(http.HttpRequest), group.id).\
AndReturn(group)
api.keystone.user_list(IgnoreArg(),
group=group.id).\
AndReturn(group_members)
if api.keystone.VERSIONS.active >= 3:
api.keystone.get_effective_domain_id(
IgnoreArg()).AndReturn(domain_id)
api.keystone.user_list(
IgnoreArg(), group=group.id, domain=domain_id).AndReturn(
group_members)
else:
api.keystone.user_list(
IgnoreArg(), group=group.id).AndReturn(group_members)
self.mox.ReplayAll()
res = self.client.get(GROUP_MANAGE_URL)
@ -177,15 +209,25 @@ class GroupsViewTests(test.BaseAdminViewTests):
self.assertTemplateUsed(res, constants.GROUPS_MANAGE_VIEW_TEMPLATE)
self.assertItemsEqual(res.context['table'].data, group_members)
@test.create_stubs({api.keystone: ('user_list',
@test.create_stubs({api.keystone: ('get_effective_domain_id',
'user_list',
'remove_group_user')})
def test_remove_user(self):
group = self.groups.get(id="1")
user = self.users.get(id="2")
domain_id = self._get_domain_id()
if api.keystone.VERSIONS.active >= 3:
api.keystone.get_effective_domain_id(
IgnoreArg()).AndReturn(domain_id)
api.keystone.user_list(
IgnoreArg(), group=group.id, domain=domain_id).AndReturn(
self.users.list())
else:
api.keystone.user_list(
IgnoreArg(), group=group.id).AndReturn(self.users.list())
api.keystone.user_list(IgnoreArg(),
group=group.id).\
AndReturn(self.users.list())
api.keystone.remove_group_user(IgnoreArg(),
group_id=group.id,
user_id=user.id)
@ -197,20 +239,24 @@ class GroupsViewTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, GROUP_MANAGE_URL)
self.assertMessageCount(success=1)
@test.create_stubs({api.keystone: ('group_get',
@test.create_stubs({api.keystone: ('get_effective_domain_id',
'group_get',
'user_list',
'add_group_user')})
def test_add_user(self):
group = self.groups.get(id="1")
user = self.users.get(id="2")
domain_id = group.domain_id
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain_id)
api.keystone.group_get(IsA(http.HttpRequest), group.id).\
AndReturn(group)
api.keystone.user_list(IgnoreArg(),
domain=group.domain_id).\
api.keystone.user_list(IgnoreArg(), domain=domain_id).\
AndReturn(self.users.list())
api.keystone.user_list(IgnoreArg(),
group=group.id).\
api.keystone.user_list(IgnoreArg(), domain=domain_id, group=group.id).\
AndReturn(self.users.list()[2:])
api.keystone.add_group_user(IgnoreArg(),

View File

@ -39,12 +39,13 @@ class IndexView(tables.DataTableView):
def get_data(self):
groups = []
domain_context = self.request.session.get('domain_context', None)
domain_id = api.keystone.get_effective_domain_id(self.request)
if policy.check((("identity", "identity:list_groups"),),
self.request):
try:
groups = api.keystone.group_list(self.request,
domain=domain_context)
domain=domain_id)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve group list.'))
@ -108,7 +109,9 @@ class GroupManageMixin(object):
@memoized.memoized_method
def _get_group_members(self):
group_id = self.kwargs['group_id']
return api.keystone.user_list(self.request, group=group_id)
domain_id = api.keystone.get_effective_domain_id(self.request)
return api.keystone.user_list(self.request, domain=domain_id,
group=group_id)
@memoized.memoized_method
def _get_group_non_members(self):

View File

@ -62,6 +62,14 @@ class UpdateMembersLink(tables.LinkAction):
param = urlencode({"step": step})
return "?".join([base_url, param])
def allowed(self, request, project):
if api.keystone.is_multi_domain_enabled():
# domain admin or cloud admin = True
# project admin or member = False
return api.keystone.is_domain_admin(request)
else:
return super(UpdateMembersLink, self).allowed(request, project)
class UpdateGroupsLink(tables.LinkAction):
name = "groups"
@ -72,7 +80,12 @@ class UpdateGroupsLink(tables.LinkAction):
policy_rules = (("identity", "identity:list_groups"),)
def allowed(self, request, project):
return api.keystone.VERSIONS.active >= 3
if api.keystone.is_multi_domain_enabled():
# domain admin or cloud admin = True
# project admin or member = False
return api.keystone.is_domain_admin(request)
else:
return super(UpdateGroupsLink, self).allowed(request, project)
def get_link_url(self, project):
step = 'update_group_members'
@ -101,19 +114,30 @@ class CreateProject(tables.LinkAction):
policy_rules = (('identity', 'identity:create_project'),)
def allowed(self, request, project):
return api.keystone.keystone_can_edit_project()
if api.keystone.is_multi_domain_enabled():
# domain admin or cloud admin = True
# project admin or member = False
return api.keystone.is_domain_admin(request)
else:
return api.keystone.keystone_can_edit_project()
class UpdateProject(tables.LinkAction):
class UpdateProject(policy.PolicyTargetMixin, tables.LinkAction):
name = "update"
verbose_name = _("Edit Project")
url = "horizon:identity:projects:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (('identity', 'identity:update_project'),)
policy_target_attrs = (("target.project.domain_id", "domain_id"),)
def allowed(self, request, project):
return api.keystone.keystone_can_edit_project()
if api.keystone.is_multi_domain_enabled():
# domain admin or cloud admin = True
# project admin or member = False
return api.keystone.is_domain_admin(request)
else:
return api.keystone.keystone_can_edit_project()
class ModifyQuotas(tables.LinkAction):
@ -124,6 +148,12 @@ class ModifyQuotas(tables.LinkAction):
icon = "pencil"
policy_rules = (('compute', "compute_extension:quotas:update"),)
def allowed(self, request, datum):
if api.keystone.VERSIONS.active < 3:
return True
else:
return api.keystone.is_cloud_admin(request)
def get_link_url(self, project):
step = 'update_quotas'
base_url = reverse(self.url, args=[project.id])
@ -131,7 +161,7 @@ class ModifyQuotas(tables.LinkAction):
return "?".join([base_url, param])
class DeleteTenantsAction(tables.DeleteAction):
class DeleteTenantsAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
@ -149,8 +179,12 @@ class DeleteTenantsAction(tables.DeleteAction):
)
policy_rules = (("identity", "identity:delete_project"),)
policy_target_attrs = ("target.project.domain_id", "domain_id"),
def allowed(self, request, project):
if api.keystone.is_multi_domain_enabled() \
and not api.keystone.is_domain_admin(request):
return False
return api.keystone.keystone_can_edit_project()
def delete(self, request, obj_id):
@ -238,6 +272,17 @@ class TenantsTable(tables.DataTable):
required=False),
update_action=UpdateCell)
if api.keystone.VERSIONS.active >= 3:
domain_name = tables.Column(
'domain_name', verbose_name=_('Domain Name'))
enabled = tables.Column('enabled', verbose_name=_('Enabled'),
status=True,
filters=(filters.yesno, filters.capfirst),
form_field=forms.BooleanField(
label=_('Enabled'),
required=False),
update_action=UpdateCell)
def get_project_detail_link(self, project):
# this method is an ugly monkey patch, needed because
# the column link method does not provide access to the request

View File

@ -51,31 +51,43 @@ PROJECT_DETAIL_URL = reverse('horizon:identity:projects:detail', args=[1])
class TenantsViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_list',)})
@test.create_stubs({api.keystone: ('tenant_list', 'domain_lookup')})
def test_index(self):
domain = self.domains.get(id="1")
api.keystone.tenant_list(IsA(http.HttpRequest),
domain=None,
paginate=True,
marker=None) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'identity/projects/index.html')
self.assertItemsEqual(res.context['table'].data, self.tenants.list())
@test.create_stubs({api.keystone: ('tenant_list', )})
@test.create_stubs({api.keystone: ('tenant_list',
'get_effective_domain_id',
'domain_lookup')})
def test_index_with_domain_context(self):
domain = self.domains.get(id="1")
self.setSessionValues(domain_context=domain.id,
domain_context_name=domain.name)
domain_tenants = [tenant for tenant in self.tenants.list()
if tenant.domain_id == domain.id]
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain.id)
api.keystone.tenant_list(IsA(http.HttpRequest),
domain=domain.id,
paginate=True,
marker=None) \
.AndReturn([domain_tenants, False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@ -86,14 +98,18 @@ class TenantsViewTests(test.BaseAdminViewTests):
class ProjectsViewNonAdminTests(test.TestCase):
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
@test.create_stubs({api.keystone: ('tenant_list',)})
@test.create_stubs({api.keystone: ('tenant_list',
'domain_lookup')})
def test_index(self):
domain = self.domains.get(id="1")
api.keystone.tenant_list(IsA(http.HttpRequest),
user=self.user.id,
paginate=True,
marker=None,
admin=False) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@ -393,6 +409,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
'role_list',
'group_list',
'get_default_domain',
'is_cloud_admin',
'get_default_role'),
quotas: ('get_default_quota_data',
'get_disabled_quotas')})
@ -407,8 +424,13 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain)
api.keystone.is_cloud_admin(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova)
@ -456,7 +478,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain)
.MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
@ -515,7 +537,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain)
.MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
@ -600,8 +622,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain)
api.keystone.get_default_domain(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
@ -772,7 +794,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data[GROUP_ROLE_PREFIX + "2"] = ['1', '2', '3']
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
.MultipleTimes().AndReturn(role_assignments)
# Give user 1 role 2
api.keystone.add_tenant_user_role(IsA(http.HttpRequest),
project=self.tenant.id,
@ -879,7 +901,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
.MultipleTimes().AndReturn(role_assignments)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
@ -890,10 +912,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user.id,
self.tenant.id).AndReturn(roles)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
self.mox.ReplayAll()
url = reverse('horizon:identity:projects:update',
@ -922,6 +940,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get',
'domain_get',
'get_effective_domain_id',
'tenant_update',
'get_default_role',
'roles_for_user',
@ -938,7 +957,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.cinder: ('tenant_quota_update',),
quotas: ('get_tenant_quota_data',
'get_disabled_quotas',
'tenant_quota_usages')})
'tenant_quota_usages',)})
def test_update_project_save(self, neutron=False):
keystone_api_version = api.keystone.VERSIONS.active
@ -979,11 +998,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {}
if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
else:
if keystone_api_version < 3:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(proj_users)
@ -993,10 +1008,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user.id,
self.tenant.id).AndReturn(roles)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
workflow_data[USER_ROLE_PREFIX + "1"] = ['3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['2'] # member role
# Group assignment form data
@ -1010,16 +1021,22 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota.metadata_items = 444
quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota)
# called once for tenant_update
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
# handle
api.keystone.tenant_update(IsA(http.HttpRequest),
project.id,
**updated_project) \
.AndReturn(project)
name=project._info["name"],
description=project._info['description'],
enabled=project.enabled,
domain=domain_id).AndReturn(project)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain_id).AndReturn(users)
self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data)
@ -1092,6 +1109,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get',
'domain_get',
'get_effective_domain_id',
'tenant_update',
'get_default_role',
'roles_for_user',
@ -1148,7 +1166,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
.MultipleTimes().AndReturn(role_assignments)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
@ -1164,10 +1182,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data.setdefault(USER_ROLE_PREFIX + role_ids[0], []) \
.append(user.id)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
role_ids = [role.id for role in roles]
for group in groups:
if role_ids:
@ -1181,17 +1195,21 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota.metadata_items = 444
quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota)
# handle
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
.AndReturn(quota_usages)
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest),
project.id,
**updated_project) \
name=project._info["name"],
domain=domain_id,
description=project._info['description'],
enabled=project.enabled) \
.AndRaise(self.exceptions.keystone)
self.mox.ReplayAll()
@ -1213,6 +1231,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get',
'domain_get',
'get_effective_domain_id',
'tenant_update',
'get_default_role',
'roles_for_user',
@ -1257,8 +1276,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.keystone.get_default_role(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
.AndReturn(users)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain_id).AndReturn(users)
api.keystone.role_list(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(roles)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \
@ -1266,24 +1287,16 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {}
if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(proj_users)
if keystone_api_version < 3:
api.keystone.user_list(
IsA(http.HttpRequest),
project=self.tenant.id).AndReturn(proj_users)
for user in proj_users:
api.keystone.roles_for_user(IsA(http.HttpRequest),
user.id,
self.tenant.id).AndReturn(roles)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
workflow_data[USER_ROLE_PREFIX + "1"] = ['1', '3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role
# Group role assignment data
@ -1297,16 +1310,21 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota[0].limit = 444
quota[1].limit = -1
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota)
# handle
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest),
project.id,
**updated_project) \
.AndReturn(project)
name=project._info["name"],
description=project._info['description'],
enabled=project.enabled,
domain=domain_id).AndReturn(project)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain_id).AndReturn(users)
self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data)
@ -1352,7 +1370,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
'add_group_role',
'group_list',
'role_list',
'role_assignments_list'),
'role_assignments_list',
'get_effective_domain_id'),
quotas: ('get_tenant_quota_data',
'get_disabled_quotas',
'tenant_quota_usages')})
@ -1384,8 +1403,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.keystone.get_default_role(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
.AndReturn(users)
api.keystone.role_list(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(roles)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \
@ -1393,24 +1414,16 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {}
if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(proj_users)
if keystone_api_version < 3:
api.keystone.user_list(
IsA(http.HttpRequest),
project=self.tenant.id).AndReturn(proj_users)
for user in proj_users:
api.keystone.roles_for_user(IsA(http.HttpRequest),
user.id,
self.tenant.id).AndReturn(roles)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
workflow_data[USER_ROLE_PREFIX + "1"] = ['1', '3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role
@ -1423,21 +1436,28 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota.metadata_items = 444
quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota)
# handle
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
.AndReturn(quota_usages)
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest),
project.id,
**updated_project) \
.AndReturn(project)
name=project._info["name"],
description=project._info['description'],
enabled=project.enabled,
domain=domain_id).AndReturn(project)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain_id).AndReturn(users)
self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data)
self.mox.ReplayAll()
# submit form data
@ -1592,7 +1612,8 @@ class DetailProjectViewTests(test.BaseAdminViewTests):
"The WITH_SELENIUM env variable is not set.")
class SeleniumTests(test.SeleniumAdminTestCase):
@test.create_stubs(
{api.keystone: ('tenant_list', 'tenant_get', 'tenant_update')})
{api.keystone: ('tenant_list', 'tenant_get', 'tenant_update',
'domain_lookup')})
def test_inline_editing_update(self):
# Tenant List
api.keystone.tenant_list(IgnoreArg(),
@ -1600,6 +1621,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
marker=None,
paginate=True) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
# Edit mod
api.keystone.tenant_get(IgnoreArg(),
u'1',
@ -1673,7 +1695,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
"'Changed test_tenant'")
@test.create_stubs(
{api.keystone: ('tenant_list', 'tenant_get')})
{api.keystone: ('tenant_list', 'tenant_get', 'domain_lookup')})
def test_inline_editing_cancel(self):
# Tenant List
api.keystone.tenant_list(IgnoreArg(),
@ -1681,6 +1703,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
marker=None,
paginate=True) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
# Edit mod
api.keystone.tenant_get(IgnoreArg(),
u'1',

View File

@ -77,10 +77,12 @@ class IndexView(tables.DataTableView):
tenants = []
marker = self.request.GET.get(
project_tables.TenantsTable._meta.pagination_param, None)
domain_context = self.request.session.get('domain_context', None)
self._more = False
if policy.check((("identity", "identity:list_projects"),),
self.request):
domain_context = api.keystone.get_effective_domain_id(self.request)
try:
tenants, self._more = api.keystone.tenant_list(
self.request,
@ -106,6 +108,11 @@ class IndexView(tables.DataTableView):
msg = \
_("Insufficient privilege level to view project information.")
messages.info(self.request, msg)
if api.keystone.VERSIONS.active >= 3:
domain_lookup = api.keystone.domain_lookup(self.request)
for t in tenants:
t.domain_name = domain_lookup.get(t.domain_id)
return tenants
@ -126,6 +133,11 @@ class CreateProjectView(workflows.WorkflowView):
workflow_class = project_workflows.CreateProject
def get_initial(self):
if (api.keystone.is_multi_domain_enabled() and
not api.keystone.is_cloud_admin(self.request)):
self.workflow_class = project_workflows.CreateProjectNoQuota
initial = super(CreateProjectView, self).get_initial()
# Set the domain of the project
@ -133,33 +145,36 @@ class CreateProjectView(workflows.WorkflowView):
initial["domain_id"] = domain.id
initial["domain_name"] = domain.name
# TODO(esp): fix this for Domain Admin or find a work around
# get initial quota defaults
try:
quota_defaults = quotas.get_default_quota_data(self.request)
if api.keystone.is_cloud_admin(self.request):
try:
if api.base.is_service_enabled(self.request, 'network') and \
api.neutron.is_quotas_extension_supported(
self.request):
# TODO(jpichon): There is no API to access the Neutron
# default quotas (LP#1204956). For now, use the values
# from the current project.
project_id = self.request.user.project_id
quota_defaults += api.neutron.tenant_quota_get(
self.request,
tenant_id=project_id)
quota_defaults = quotas.get_default_quota_data(self.request)
try:
if api.base.is_service_enabled(
self.request, 'network') and \
api.neutron.is_quotas_extension_supported(
self.request):
# TODO(jpichon): There is no API to access the Neutron
# default quotas (LP#1204956). For now, use the values
# from the current project.
project_id = self.request.user.project_id
quota_defaults += api.neutron.tenant_quota_get(
self.request,
tenant_id=project_id)
except Exception:
error_msg = _('Unable to retrieve default Neutron quota '
'values.')
self.add_error_to_step(error_msg, 'create_quotas')
for field in quotas.QUOTA_FIELDS:
initial[field] = quota_defaults.get(field).limit
except Exception:
error_msg = _('Unable to retrieve default Neutron quota '
'values.')
error_msg = _('Unable to retrieve default quota values.')
self.add_error_to_step(error_msg, 'create_quotas')
for field in quotas.QUOTA_FIELDS:
initial[field] = quota_defaults.get(field).limit
except Exception:
error_msg = _('Unable to retrieve default quota values.')
self.add_error_to_step(error_msg, 'create_quotas')
return initial
@ -167,6 +182,11 @@ class UpdateProjectView(workflows.WorkflowView):
workflow_class = project_workflows.UpdateProject
def get_initial(self):
if (api.keystone.is_multi_domain_enabled() and
not api.keystone.is_cloud_admin(self.request)):
self.workflow_class = project_workflows.UpdateProjectNoQuota
initial = super(UpdateProjectView, self).get_initial()
project_id = self.kwargs['tenant_id']
@ -182,23 +202,32 @@ class UpdateProjectView(workflows.WorkflowView):
# Retrieve the domain name where the project belong
if keystone.VERSIONS.active >= 3:
try:
domain = api.keystone.domain_get(self.request,
initial["domain_id"])
initial["domain_name"] = domain.name
if policy.check((("identity", "identity:get_domain"),),
self.request):
domain = api.keystone.domain_get(self.request,
initial["domain_id"])
initial["domain_name"] = domain.name
else:
domain = api.keystone.get_default_domain(self.request)
initial["domain_name"] = domain.name
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve project domain.'),
redirect=reverse(INDEX_URL))
# get initial project quota
quota_data = quotas.get_tenant_quota_data(self.request,
tenant_id=project_id)
if api.base.is_service_enabled(self.request, 'network') and \
api.neutron.is_quotas_extension_supported(self.request):
quota_data += api.neutron.tenant_quota_get(
self.request, tenant_id=project_id)
for field in quotas.QUOTA_FIELDS:
initial[field] = quota_data.get(field).limit
if keystone.is_cloud_admin(self.request):
quota_data = quotas.get_tenant_quota_data(self.request,
tenant_id=project_id)
if api.base.is_service_enabled(self.request, 'network') and \
api.neutron.is_quotas_extension_supported(
self.request):
quota_data += api.neutron.tenant_quota_get(
self.request, tenant_id=project_id)
for field in quotas.QUOTA_FIELDS:
initial[field] = quota_data.get(field).limit
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve project details.'),

View File

@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.conf import settings
from django.core.urlresolvers import reverse
@ -34,6 +35,9 @@ from openstack_dashboard.api import nova
from openstack_dashboard.usage import quotas
from openstack_dashboard.utils.identity import IdentityMixIn
LOG = logging.getLogger(__name__)
INDEX_URL = "horizon:identity:projects:index"
ADD_USER_URL = "horizon:identity:projects:create_user"
PROJECT_GROUP_ENABLED = keystone.VERSIONS.active >= 3
@ -111,6 +115,7 @@ class UpdateProjectQuotaAction(ProjectQuotaAction):
name = _("Quota")
slug = 'update_quotas'
help_text = _("Set maximum quotas for the project.")
permissions = ('openstack.roles.admin', 'openstack.services.compute')
class CreateProjectQuotaAction(ProjectQuotaAction):
@ -118,6 +123,7 @@ class CreateProjectQuotaAction(ProjectQuotaAction):
name = _("Quota")
slug = 'create_quotas'
help_text = _("Set maximum quotas for the project.")
permissions = ('openstack.roles.admin', 'openstack.services.compute')
class UpdateProjectQuota(workflows.Step):
@ -186,13 +192,14 @@ class UpdateProjectMembersAction(workflows.MembershipAction):
err_msg = _('Unable to retrieve user list. Please try again later.')
# Use the domain_id from the project
domain_id = self.initial.get("domain_id", None)
project_id = ''
if 'project_id' in self.initial:
project_id = self.initial['project_id']
# Get the default role
try:
default_role = api.keystone.get_default_role(self.request)
default_role = keystone.get_default_role(self.request)
# Default role is necessary to add members to a project
if default_role is None:
default = getattr(settings,
@ -528,12 +535,38 @@ class CreateProject(CommonQuotaWorkflow):
self._update_project_members(request, data, project_id)
if PROJECT_GROUP_ENABLED:
self._update_project_groups(request, data, project_id)
self._update_project_quota(request, data, project_id)
if keystone.is_cloud_admin(request):
self._update_project_quota(request, data, project_id)
return True
class CreateProjectNoQuota(CreateProject):
slug = "create_project"
name = _("Create Project")
finalize_button_name = _("Create Project")
success_message = _('Created new project "%s".')
failure_message = _('Unable to create project "%s".')
success_url = "horizon:identity:projects:index"
default_steps = (CreateProjectInfo, UpdateProjectMembers)
def __init__(self, request=None, context_seed=None, entry_point=None,
*args, **kwargs):
if PROJECT_GROUP_ENABLED:
self.default_steps = (CreateProjectInfo,
UpdateProjectMembers,
UpdateProjectGroups,)
super(CreateProject, self).__init__(request=request,
context_seed=context_seed,
entry_point=entry_point,
*args,
**kwargs)
class UpdateProjectInfoAction(CreateProjectInfoAction):
enabled = forms.BooleanField(required=False, label=_("Enabled"))
domain_name = forms.CharField(label=_("Domain Name"),
required=False,
widget=forms.HiddenInput())
def __init__(self, request, initial, *args, **kwargs):
super(UpdateProjectInfoAction, self).__init__(
@ -608,7 +641,8 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
return api.keystone.role_list(request)
def _update_project(self, request, data):
# update project info
"""Update project info"""
domain_id = api.keystone.get_effective_domain_id(self.request)
try:
project_id = data['project_id']
return api.keystone.tenant_update(
@ -616,12 +650,14 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
project_id,
name=data['name'],
description=data['description'],
enabled=data['enabled'])
enabled=data['enabled'],
domain=domain_id)
except exceptions.Conflict:
msg = _('Project name "%s" is already used.') % data['name']
self.failure_message = msg
return
except Exception:
except Exception as e:
LOG.debug('Project update failed: %s' % e)
exceptions.handle(request, ignore=True)
return
@ -699,7 +735,22 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
request, project=project_id)
users_to_modify = len(users_roles)
# TODO(bpokorny): The following lines are needed to make sure we
# only modify roles for users who are in the current domain.
# Otherwise, we'll end up removing roles for users who have roles
# on the project but aren't in the domain. For now, Horizon won't
# support managing roles across domains. The Keystone CLI
# supports it, so we may want to add that in the future.
all_users = api.keystone.user_list(request,
domain=data['domain_id'])
users_dict = {user.id: user.name for user in all_users}
for user_id in users_roles.keys():
# Don't remove roles if the user isn't in the domain
if user_id not in users_dict:
users_to_modify -= 1
continue
# Check if there have been any changes in the roles of
# Existing project members.
current_role_ids = list(users_roles[user_id])
@ -854,8 +905,32 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
if not ret:
return False
ret = self._update_project_quota(request, data, project_id)
if not ret:
return False
if api.keystone.is_cloud_admin(request):
ret = self._update_project_quota(request, data, project_id)
if not ret:
return False
return True
class UpdateProjectNoQuota(UpdateProject):
slug = "update_project"
name = _("Edit Project")
finalize_button_name = _("Save")
success_message = _('Modified project "%s".')
failure_message = _('Unable to modify project "%s".')
success_url = "horizon:identity:projects:index"
default_steps = (UpdateProjectInfo, UpdateProjectMembers)
def __init__(self, request=None, context_seed=None, entry_point=None,
*args, **kwargs):
if PROJECT_GROUP_ENABLED:
self.default_steps = (UpdateProjectInfo,
UpdateProjectMembers,
UpdateProjectGroups)
super(UpdateProject, self).__init__(request=request,
context_seed=context_seed,
entry_point=entry_point,
*args,
**kwargs)

View File

@ -24,6 +24,12 @@ class Roles(horizon.Panel):
slug = 'roles'
policy_rules = (("identity", "identity:list_roles"),)
def can_access(self, context):
if keystone.is_multi_domain_enabled() \
and not keystone.is_domain_admin(context['request']):
return False
return super(Roles, self).can_access(context)
@staticmethod
def can_register():
return keystone.VERSIONS.active >= 3

View File

@ -68,17 +68,26 @@ class BaseUserForm(forms.SelfHandlingForm):
# the user has access to.
user_id = kwargs['initial'].get('id', None)
domain_id = kwargs['initial'].get('domain_id', None)
projects, has_more = api.keystone.tenant_list(request,
domain=domain_id,
user=user_id)
for project in projects:
if project.enabled:
project_choices.append((project.id, project.name))
if not project_choices:
project_choices.insert(0, ('', _("No available projects")))
elif len(project_choices) > 1:
project_choices.insert(0, ('', _("Select a project")))
self.fields['project'].choices = project_choices
try:
if api.keystone.VERSIONS.active >= 3:
projects, has_more = api.keystone.tenant_list(
request, domain=domain_id)
else:
projects, has_more = api.keystone.tenant_list(
request, user=user_id)
for project in projects:
if project.enabled:
project_choices.append((project.id, project.name))
if not project_choices:
project_choices.insert(0, ('', _("No available projects")))
elif len(project_choices) > 1:
project_choices.insert(0, ('', _("Select a project")))
self.fields['project'].choices = project_choices
except Exception:
LOG.debug("User: %s has no projects" % user_id)
ADD_PROJECT_URL = "horizon:identity:projects:create"
@ -135,7 +144,7 @@ class CreateUserForm(PasswordMixin, BaseUserForm):
# password and confirm_password strings.
@sensitive_variables('data')
def handle(self, request, data):
domain = api.keystone.get_default_domain(self.request)
domain = api.keystone.get_default_domain(self.request, False)
try:
LOG.info('Creating user with name "%s"' % data['name'])
desc = data["description"]

View File

@ -20,9 +20,17 @@ from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.api import keystone
class Users(horizon.Panel):
name = _("Users")
slug = 'users'
policy_rules = (("identity", "identity:get_user"),
("identity", "identity:list_users"))
def can_access(self, context):
if keystone.is_multi_domain_enabled() \
and not keystone.is_domain_admin(context['request']):
return False
return super(Users, self).can_access(context)

View File

@ -50,7 +50,8 @@ class EditUserLink(policy.PolicyTargetMixin, tables.LinkAction):
icon = "pencil"
policy_rules = (("identity", "identity:update_user"),
("identity", "identity:list_projects"),)
policy_target_attrs = (("user_id", "id"),)
policy_target_attrs = (("user_id", "id"),
("target.user.domain_id", "domain_id"),)
def allowed(self, request, user):
return api.keystone.keystone_can_edit_user()
@ -103,7 +104,8 @@ class ToggleEnabled(policy.PolicyTargetMixin, tables.BatchAction):
)
classes = ("btn-toggle",)
policy_rules = (("identity", "identity:update_user"),)
policy_target_attrs = (("user_id", "id"),)
policy_target_attrs = (("user_id", "id"),
("target.user.domain_id", "domain_id"))
def allowed(self, request, user=None):
if not api.keystone.keystone_can_edit_user():
@ -137,7 +139,7 @@ class ToggleEnabled(policy.PolicyTargetMixin, tables.BatchAction):
self.current_past_action = ENABLE
class DeleteUsersAction(tables.DeleteAction):
class DeleteUsersAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
@ -256,6 +258,18 @@ class UsersTable(tables.DataTable):
defaultfilters.capfirst),
empty_value="False")
if api.keystone.VERSIONS.active >= 3:
domain_name = tables.Column(
'domain_name',
verbose_name=_('Domain Name'),
attrs={'data-type': 'uuid'})
enabled = tables.Column('enabled', verbose_name=_('Enabled'),
status=True,
status_choices=STATUS_CHOICES,
filters=(defaultfilters.yesno,
defaultfilters.capfirst),
empty_value="False")
class Meta(object):
name = "users"
verbose_name = _("Users")

View File

@ -53,13 +53,20 @@ class UsersViewTests(test.BaseAdminViewTests):
if user.domain_id == domain_id]
return users
@test.create_stubs({api.keystone: ('user_list',)})
@test.create_stubs({api.keystone: ('user_list',
'get_effective_domain_id',
'domain_lookup')})
def test_index(self):
domain = self._get_default_domain()
domain_id = domain.id
users = self._get_users(domain_id)
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain_id)
api.keystone.user_list(IgnoreArg(),
domain=domain_id).AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
res = self.client.get(USERS_INDEX_URL)
@ -91,11 +98,19 @@ class UsersViewTests(test.BaseAdminViewTests):
role = self.roles.first()
api.keystone.get_default_domain(IgnoreArg()) \
.MultipleTimes().AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(),
domain=domain_id,
user=None) \
.AndReturn([self.tenants.list(), False])
.AndReturn(domain)
api.keystone.get_default_domain(IgnoreArg(), False) \
.AndReturn(domain)
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain.id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=None).AndReturn(
[self.tenants.list(), False])
api.keystone.user_create(IgnoreArg(),
name=user.name,
description=user.description,
@ -146,11 +161,19 @@ class UsersViewTests(test.BaseAdminViewTests):
domain_id = domain.id
role = self.roles.first()
api.keystone.get_default_domain(IgnoreArg()) \
.MultipleTimes().AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(),
domain=domain_id,
user=None) \
.AndReturn([self.tenants.list(), False])
.AndReturn(domain)
api.keystone.get_default_domain(IgnoreArg(), False) \
.AndReturn(domain)
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain.id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=user.id).AndReturn(
[self.tenants.list(), False])
api.keystone.user_create(IgnoreArg(),
name=user.name,
description=user.description,
@ -192,8 +215,16 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.get_default_domain(IgnoreArg()) \
.MultipleTimes().AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(), domain=domain_id, user=None) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain_id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=None).AndReturn(
[self.tenants.list(), False])
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
@ -224,8 +255,16 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.get_default_domain(IgnoreArg()) \
.MultipleTimes().AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(), domain=domain_id, user=None) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain_id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=None).AndReturn(
[self.tenants.list(), False])
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
@ -259,8 +298,16 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.get_default_domain(IgnoreArg()) \
.MultipleTimes().AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(), domain=domain_id, user=None) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain_id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=None).AndReturn(
[self.tenants.list(), False])
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
@ -299,10 +346,16 @@ class UsersViewTests(test.BaseAdminViewTests):
admin=True).AndReturn(user)
api.keystone.domain_get(IsA(http.HttpRequest),
domain_id).AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(),
domain=domain_id,
user=user.id) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain.id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=user.id).AndReturn(
[self.tenants.list(), False])
api.keystone.user_update(IsA(http.HttpRequest),
user.id,
email=user.email,
@ -337,10 +390,16 @@ class UsersViewTests(test.BaseAdminViewTests):
admin=True).AndReturn(user)
api.keystone.domain_get(IsA(http.HttpRequest),
domain_id).AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(),
domain=domain_id,
user=user.id) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain_id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=user.id).AndReturn(
[self.tenants.list(), False])
api.keystone.user_update(IsA(http.HttpRequest),
user.id,
email=user.email,
@ -376,8 +435,15 @@ class UsersViewTests(test.BaseAdminViewTests):
admin=True).AndReturn(user)
api.keystone.domain_get(IsA(http.HttpRequest), domain_id) \
.AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(), domain=domain_id, user=user.id) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain_id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=user.id).AndReturn(
[self.tenants.list(), False])
api.keystone.keystone_can_edit_user().AndReturn(False)
api.keystone.keystone_can_edit_user().AndReturn(False)
@ -486,7 +552,9 @@ class UsersViewTests(test.BaseAdminViewTests):
res, "form", 'password',
['Password must be between 8 and 18 characters.'])
@test.create_stubs({api.keystone: ('user_update_enabled', 'user_list')})
@test.create_stubs({api.keystone: ('user_update_enabled',
'user_list',
'domain_lookup')})
def test_enable_user(self):
domain = self._get_default_domain()
domain_id = domain.id
@ -498,6 +566,8 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.user_update_enabled(IgnoreArg(),
user.id,
True).AndReturn(user)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
@ -506,7 +576,9 @@ class UsersViewTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, USERS_INDEX_URL)
@test.create_stubs({api.keystone: ('user_update_enabled', 'user_list')})
@test.create_stubs({api.keystone: ('user_update_enabled',
'user_list',
'domain_lookup')})
def test_disable_user(self):
domain = self._get_default_domain()
domain_id = domain.id
@ -520,6 +592,8 @@ class UsersViewTests(test.BaseAdminViewTests):
api.keystone.user_update_enabled(IgnoreArg(),
user.id,
False).AndReturn(user)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
@ -528,7 +602,9 @@ class UsersViewTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, USERS_INDEX_URL)
@test.create_stubs({api.keystone: ('user_update_enabled', 'user_list')})
@test.create_stubs({api.keystone: ('user_update_enabled',
'user_list',
'domain_lookup')})
def test_enable_disable_user_exception(self):
domain = self._get_default_domain()
domain_id = domain.id
@ -540,6 +616,8 @@ class UsersViewTests(test.BaseAdminViewTests):
.AndReturn(users)
api.keystone.user_update_enabled(IgnoreArg(), user.id, True) \
.AndRaise(self.exceptions.keystone)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
formData = {'action': 'users__toggle__%s' % user.id}
@ -547,7 +625,7 @@ class UsersViewTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, USERS_INDEX_URL)
@test.create_stubs({api.keystone: ('user_list',)})
@test.create_stubs({api.keystone: ('user_list', 'domain_lookup')})
def test_disabling_current_user(self):
domain = self._get_default_domain()
domain_id = domain.id
@ -555,6 +633,33 @@ class UsersViewTests(test.BaseAdminViewTests):
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
formData = {'action': 'users__toggle__%s' % self.request.user.id}
res = self.client.post(USERS_INDEX_URL, formData, follow=True)
self.assertEqual(list(res.context['messages'])[0].message,
u'You cannot disable the user you are currently '
u'logged in as.')
@test.create_stubs({api.keystone: ('user_list', 'domain_lookup')})
def test_disabling_current_user_domain_name(self):
domain = self._get_default_domain()
domains = self.domains.list()
domain_id = domain.id
users = self._get_users(domain_id)
domain_lookup = dict((d.id, d.name) for d in domains)
for u in users:
u.domain_name = domain_lookup.get(u.domain_id)
for i in range(0, 2):
api.keystone.domain_lookup(IgnoreArg()).AndReturn(domain_lookup)
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
.AndReturn(users)
self.mox.ReplayAll()
@ -565,7 +670,7 @@ class UsersViewTests(test.BaseAdminViewTests):
u'You cannot disable the user you are currently '
u'logged in as.')
@test.create_stubs({api.keystone: ('user_list',)})
@test.create_stubs({api.keystone: ('user_list', 'domain_lookup')})
def test_delete_user_with_improper_permissions(self):
domain = self._get_default_domain()
domain_id = domain.id
@ -573,6 +678,33 @@ class UsersViewTests(test.BaseAdminViewTests):
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll()
formData = {'action': 'users__delete__%s' % self.request.user.id}
res = self.client.post(USERS_INDEX_URL, formData, follow=True)
self.assertEqual(list(res.context['messages'])[0].message,
u'You are not allowed to delete user: %s'
% self.request.user.username)
@test.create_stubs({api.keystone: ('user_list', 'domain_lookup')})
def test_delete_user_with_improper_permissions_domain_name(self):
domain = self._get_default_domain()
domains = self.domains.list()
domain_id = domain.id
users = self._get_users(domain_id)
domain_lookup = dict((d.id, d.name) for d in domains)
for u in users:
u.domain_name = domain_lookup.get(u.domain_id)
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn(domain_lookup)
self.mox.ReplayAll()
@ -662,10 +794,16 @@ class UsersViewTests(test.BaseAdminViewTests):
admin=True).AndReturn(user)
api.keystone.domain_get(IsA(http.HttpRequest),
domain_id).AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(),
domain=domain_id,
user=user.id) \
.AndReturn([self.tenants.list(), False])
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=domain.id).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=user.id).AndReturn(
[self.tenants.list(), False])
api.keystone.user_update(IsA(http.HttpRequest),
user.id,
email=user.email,
@ -696,17 +834,27 @@ class SeleniumTests(test.SeleniumAdminTestCase):
'tenant_list',
'get_default_role',
'role_list',
'user_list')})
'user_list',
'domain_lookup')})
def test_modal_create_user_with_passwords_not_matching(self):
domain = self._get_default_domain()
api.keystone.get_default_domain(IgnoreArg()) \
.AndReturn(domain)
api.keystone.tenant_list(IgnoreArg(), domain=None, user=None) \
.AndReturn([self.tenants.list(), False])
.MultipleTimes().AndReturn(domain)
if api.keystone.VERSIONS.active >= 3:
api.keystone.tenant_list(
IgnoreArg(), domain=None).AndReturn(
[self.tenants.list(), False])
else:
api.keystone.tenant_list(
IgnoreArg(), user=None).AndReturn(
[self.tenants.list(), False])
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
api.keystone.user_list(IgnoreArg(), domain=None) \
.AndReturn(self.users.list())
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
api.keystone.get_default_role(IgnoreArg()) \
.AndReturn(self.roles.first())
self.mox.ReplayAll()
@ -726,6 +874,10 @@ class SeleniumTests(test.SeleniumAdminTestCase):
self.selenium.find_element_by_id("id_password").send_keys("test")
self.selenium.find_element_by_id("id_confirm_password").send_keys("te")
self.selenium.find_element_by_id("id_email").send_keys("a@b.com")
wait.until(lambda x: self.selenium.find_element_by_id(
"id_confirm_password_error"))
self.assertTrue(self._is_element_present("id_confirm_password_error"),
"Couldn't find password error element.")

View File

@ -50,9 +50,10 @@ class IndexView(tables.DataTableView):
def get_data(self):
users = []
domain_context = self.request.session.get('domain_context', None)
if policy.check((("identity", "identity:list_users"),),
self.request):
domain_context = api.keystone.get_effective_domain_id(self.request)
try:
users = api.keystone.user_list(self.request,
domain=domain_context)
@ -71,6 +72,11 @@ class IndexView(tables.DataTableView):
else:
msg = _("Insufficient privilege level to view user information.")
messages.info(self.request, msg)
if api.keystone.VERSIONS.active >= 3:
domain_lookup = api.keystone.domain_lookup(self.request)
for u in users:
u.domain_name = domain_lookup.get(u.domain_id)
return users
@ -108,12 +114,18 @@ class UpdateView(forms.ModalFormView):
user = self.get_object()
domain_id = getattr(user, "domain_id", None)
domain_name = ''
# Retrieve the domain name where the project belong
# Retrieve the domain name where the project belongs
if api.keystone.VERSIONS.active >= 3:
try:
domain = api.keystone.domain_get(self.request,
domain_id)
domain_name = domain.name
if policy.check((("identity", "identity:get_domain"),),
self.request):
domain = api.keystone.domain_get(self.request, domain_id)
domain_name = domain.name
else:
domain = api.keystone.get_default_domain(self.request)
domain_name = domain.get('name')
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve project domain.'))
@ -176,8 +188,14 @@ class DetailView(views.HorizonTemplateView):
domain_name = ''
if api.keystone.VERSIONS.active >= 3:
try:
domain = api.keystone.domain_get(self.request, domain_id)
domain_name = domain.name
if policy.check((("identity", "identity:get_domain"),),
self.request):
domain = api.keystone.domain_get(
self.request, domain_id)
domain_name = domain.name
else:
domain = api.keystone.get_default_domain(self.request)
domain_name = domain.get('name')
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve project domain.'))

View File

@ -21,4 +21,9 @@ class Project(horizon.Dashboard):
name = _("Project")
slug = "project"
def can_access(self, context):
request = context['request']
has_project = request.user.token.project.get('id') is not None
return super(Project, self).can_access(context) and has_project
horizon.register(Project)

View File

@ -66,7 +66,11 @@ WEBROOT = '/'
# Overrides the default domain used when running on single-domain model
# with Keystone V3. All entities will be created in the default domain.
#OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default'
# NOTE: This value must be the ID of the default domain, NOT the name.
# Also, you will most likely have a value in the keystone policy file like this
# "cloud_admin": "rule:admin_required and domain_id:<your domain id>"
# This value must match the domain id specified there.
#OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'default'
# Set this to True to enable panels that provide the ability for users to
# manage Identity Providers (IdPs) and establish a set of rules to map

View File

@ -40,7 +40,10 @@ class PolicyTargetMixin(object):
policy_target_attrs = (("project_id", "tenant_id"),
("user_id", "user_id"),
("domain_id", "domain_id"))
("domain_id", "domain_id"),
("target.project.domain_id", "domain_id"),
("target.user.domain_id", "domain_id"),
("target.group.domain_id", "domain_id"))
def get_policy_target(self, request, datum=None):
policy_target = {}

View File

@ -36,6 +36,11 @@ def get_user_home(user):
if dashboard is None:
dashboard = horizon.get_default_dashboard()
# Domain Admin, Project Admin will default to identity
if (user.token.project.get('id') is None or
(user.is_superuser and user.token.project.get('id'))):
dashboard = horizon.get_dashboard('identity')
return dashboard.get_absolute_url()

View File

@ -0,0 +1,22 @@
---
features:
- Added support for managing domains and projects when using Keystone v3.
Horizon now maintains a domain scoped token for users who have a role on a
domain, a project scoped token for users who have a role on a project, or
both a domain scoped token and project scoped token for users who have
roles on both.
- |
Domain management supports the following use cases:
* Cloud Admin - View and manage identity resources across domains
* Domain Admin - View and manage identity resources in the domain logged in
* User - View identity project in the domain logged in
other:
- |
Current limitations on managing identity resources with Keystone v3:
* Does not support role assignments across domains, such as giving a user
in domain1 access to domain2.
* Does not support project admins managing Keystone projects.
* Does not support hierarchical project management.