Merge "Retrieve domain scoped token"

This commit is contained in:
Jenkins 2016-03-11 22:20:08 +00:00 committed by Gerrit Code Review
commit 711f88dd47
27 changed files with 826 additions and 251 deletions

View File

@ -40,6 +40,8 @@ from openstack_dashboard import policy
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DEFAULT_ROLE = None DEFAULT_ROLE = None
DEFAULT_DOMAIN = getattr(settings, 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'default')
# Set up our data structure for managing Identity API versions, and # 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 The client is cached so that subsequent API calls during the same
request/response cycle don't have to be re-authenticated. request/response cycle don't have to be re-authenticated.
""" """
api_version = VERSIONS.get_active_version()
user = request.user 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 admin:
if not policy.check((("identity", "admin_required"),), request): if not policy.check((("identity", "admin_required"),), request):
raise exceptions.NotAuthorized raise exceptions.NotAuthorized
@ -154,8 +166,6 @@ def keystoneclient(request, admin=False):
'OPENSTACK_ENDPOINT_TYPE', 'OPENSTACK_ENDPOINT_TYPE',
'internalURL') 'internalURL')
api_version = VERSIONS.get_active_version()
# Take care of client connection caching/fetching a new client. # Take care of client connection caching/fetching a new client.
# Admin vs. non-admin clients are cached separately for token matching. # Admin vs. non-admin clients are cached separately for token matching.
cache_attr = "_keystoneclient_admin" if admin \ cache_attr = "_keystoneclient_admin" if admin \
@ -170,7 +180,7 @@ def keystoneclient(request, admin=False):
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None) cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
LOG.debug("Creating a new keystoneclient connection to %s." % endpoint) LOG.debug("Creating a new keystoneclient connection to %s." % endpoint)
remote_addr = request.environ.get('REMOTE_ADDR', '') 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, endpoint=endpoint,
original_ip=remote_addr, original_ip=remote_addr,
insecure=insecure, insecure=insecure,
@ -183,7 +193,7 @@ def keystoneclient(request, admin=False):
def domain_create(request, name, description=None, enabled=None): def domain_create(request, name, description=None, enabled=None):
manager = keystoneclient(request, admin=True).domains manager = keystoneclient(request, admin=True).domains
return manager.create(name, return manager.create(name=name,
description=description, description=description,
enabled=enabled) enabled=enabled)
@ -203,10 +213,29 @@ def domain_list(request):
return manager.list() 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, def domain_update(request, domain_id, name=None, description=None,
enabled=None): enabled=None):
manager = keystoneclient(request, admin=True).domains 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, 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() 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. """Gets the default domain object to use when creating Identity object.
Returns the domain context if is set, otherwise return the domain Returns the domain context if is set, otherwise return the domain
of the logon user. 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_id = request.session.get("domain_context", None)
domain_name = request.session.get("domain_context_name", None) domain_name = request.session.get("domain_context_name", None)
# if running in Keystone V3 or later # if running in Keystone V3 or later
if VERSIONS.active >= 3 and not domain_id: if VERSIONS.active >= 3 and domain_id is None:
# if no domain context set, default to users' domain # if no domain context set, default to user's domain
domain_id = request.user.user_domain_id domain_id = request.user.user_domain_id
try: domain_name = request.user.user_domain_name
domain = domain_get(request, domain_id) if get_name:
domain_name = domain.name try:
except Exception: domain = domain_get(request, domain_id)
LOG.warning("Unable to retrieve Domain: %s" % domain_id) domain_name = domain.name
except Exception:
LOG.warning("Unable to retrieve Domain: %s" % domain_id)
domain = base.APIDictWrapper({"id": domain_id, domain = base.APIDictWrapper({"id": domain_id,
"name": domain_name}) "name": domain_name})
return domain 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? # 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 # 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 # 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: if paginate and len(tenants) > page_size:
tenants.pop(-1) tenants.pop(-1)
has_more_data = True has_more_data = True
# V3 API
else: else:
domain_id = get_effective_domain_id(request)
kwargs = { kwargs = {
"domain": domain, "domain": domain_id,
"user": user "user": user
} }
if filters is not None: if filters is not None:
kwargs.update(filters) kwargs.update(filters)
tenants = manager.list(**kwargs) tenants = manager.list(**kwargs)
return (tenants, has_more_data) return tenants, has_more_data
def tenant_update(request, project, name=None, description=None, 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) groups_roles = collections.defaultdict(list)
project_role_assignments = role_assignments_list(request, project_role_assignments = role_assignments_list(request,
project=project) project=project)
for role_assignment in project_role_assignments: for role_assignment in project_role_assignments:
if not hasattr(role_assignment, 'group'): if not hasattr(role_assignment, 'group'):
continue continue
group_id = role_assignment.group['id'] group_id = role_assignment.group['id']
role_id = role_assignment.role['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 return groups_roles
def role_assignments_list(request, project=None, user=None, role=None, 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: if VERSIONS.active < 3:
raise exceptions.NotAvailable raise exceptions.NotAvailable
if include_subtree:
domain = None
manager = keystoneclient(request, admin=True).role_assignments manager = keystoneclient(request, admin=True).role_assignments
return manager.list(project=project, user=user, role=role, group=group, 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): 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): def get_domain_users_roles(request, domain):
users_roles = collections.defaultdict(list) users_roles = collections.defaultdict(list)
domain_role_assignments = role_assignments_list(request, domain_role_assignments = role_assignments_list(request,
domain=domain) domain=domain,
include_subtree=False)
for role_assignment in domain_role_assignments: for role_assignment in domain_role_assignments:
if not hasattr(role_assignment, 'user'): if not hasattr(role_assignment, 'user'):
continue continue
user_id = role_assignment.user['id'] user_id = role_assignment.user['id']
role_id = role_assignment.role['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 return users_roles
@ -609,7 +680,11 @@ def get_project_users_roles(request, project):
continue continue
user_id = role_assignment.user['id'] user_id = role_assignment.user['id']
role_id = role_assignment.role['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 return users_roles
@ -759,6 +834,11 @@ def get_version():
return VERSIONS.active 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(): def is_federation_management_enabled():
return getattr(settings, 'OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT', False) return getattr(settings, 'OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT', False)

View File

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

View File

@ -28,3 +28,11 @@ class Domains(horizon.Panel):
@staticmethod @staticmethod
def can_register(): def can_register():
return keystone.VERSIONS.active >= 3 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") verbose_name = _("Manage Members")
url = "horizon:identity:domains:update" url = "horizon:identity:domains:update"
classes = ("ajax-modal",) classes = ("ajax-modal",)
policy_rules = (("identity", "identity:list_users"), policy_rules = (("identity", "identity:update_domain"),)
("identity", "identity:list_roles"),
("identity", "identity:list_role_assignments"))
def get_link_url(self, domain): def get_link_url(self, domain):
step = 'update_user_members' step = 'update_user_members'
@ -56,6 +54,7 @@ class UpdateGroupsLink(tables.LinkAction):
url = "horizon:identity:domains:update" url = "horizon:identity:domains:update"
classes = ("ajax-modal",) classes = ("ajax-modal",)
icon = "pencil" icon = "pencil"
policy_rules = (("identity", "identity:update_domain"),)
def get_link_url(self, domain): def get_link_url(self, domain):
step = 'update_group_members' step = 'update_group_members'
@ -227,7 +226,7 @@ class SetDomainContext(tables.Action):
verbose_name = _("Set Domain Context") verbose_name = _("Set Domain Context")
url = constants.DOMAINS_INDEX_URL url = constants.DOMAINS_INDEX_URL
preempt = True preempt = True
policy_rules = (('identity', 'admin_required'),) policy_rules = (('identity', 'identity:update_domain'),)
def allowed(self, request, datum): def allowed(self, request, datum):
multidomain_support = getattr(settings, multidomain_support = getattr(settings,
@ -262,7 +261,7 @@ class UnsetDomainContext(tables.Action):
url = constants.DOMAINS_INDEX_URL url = constants.DOMAINS_INDEX_URL
preempt = True preempt = True
requires_input = False requires_input = False
policy_rules = (('identity', 'admin_required'),) policy_rules = (('identity', 'identity:update_domain'),)
def allowed(self, request, datum): def allowed(self, request, datum):
ctx = request.session.get("domain_context", None) 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) \ api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users) .AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \ domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments) .AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \ api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups) .AndReturn(groups)
@ -331,7 +332,8 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \ api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users) .AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \ domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments) .AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \ api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups) .AndReturn(groups)
@ -354,15 +356,19 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
# handle # handle
api.keystone.domain_update(IsA(http.HttpRequest), api.keystone.domain_update(IsA(http.HttpRequest),
domain.id,
name=domain.name,
description=test_description, description=test_description,
domain_id=domain.id, enabled=domain.enabled).AndReturn(None)
enabled=domain.enabled,
name=domain.name).AndReturn(None)
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \ domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments) .AndReturn(role_assignments)
api.keystone.user_list(IsA(http.HttpRequest),
domain=domain.id).AndReturn(users)
# Give user 3 role 1 # Give user 3 role 1
api.keystone.add_domain_user_role(IsA(http.HttpRequest), api.keystone.add_domain_user_role(IsA(http.HttpRequest),
domain=domain.id, domain=domain.id,
@ -468,7 +474,8 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \ api.keystone.user_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(users) .AndReturn(users)
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
domain=domain.id) \ domain=domain.id,
include_subtree=False) \
.AndReturn(role_assignments) .AndReturn(role_assignments)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \ api.keystone.group_list(IsA(http.HttpRequest), domain=domain.id) \
.AndReturn(groups) .AndReturn(groups)
@ -492,10 +499,10 @@ class UpdateDomainWorkflowTests(test.BaseAdminViewTests):
# handle # handle
api.keystone.domain_update(IsA(http.HttpRequest), api.keystone.domain_update(IsA(http.HttpRequest),
domain.id,
name=domain.name,
description=test_description, description=test_description,
domain_id=domain.id, enabled=domain.enabled) \
enabled=domain.enabled,
name=domain.name) \
.AndRaise(self.exceptions.keystone) .AndRaise(self.exceptions.keystone)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

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

View File

@ -322,8 +322,16 @@ class UpdateDomain(workflows.Workflow, IdentityMixIn):
users_roles = api.keystone.get_domain_users_roles(request, users_roles = api.keystone.get_domain_users_roles(request,
domain=domain_id) domain=domain_id)
users_to_modify = len(users_roles) 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(): 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 # Check if there have been any changes in the roles of
# Existing domain members. # Existing domain members.
current_role_ids = list(users_roles[user_id]) current_role_ids = list(users_roles[user_id])
@ -484,7 +492,7 @@ class UpdateDomain(workflows.Workflow, IdentityMixIn):
try: try:
LOG.info('Updating domain with name "%s"' % data['name']) LOG.info('Updating domain with name "%s"' % data['name'])
api.keystone.domain_update(request, api.keystone.domain_update(request,
domain_id=domain_id, domain_id,
name=data['name'], name=data['name'],
description=data['description'], description=data['description'],
enabled=data['enabled']) enabled=data['enabled'])

View File

@ -36,7 +36,7 @@ class CreateGroupForm(forms.SelfHandlingForm):
def handle(self, request, data): def handle(self, request, data):
try: try:
LOG.info('Creating group with name "%s"' % data['name']) 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( api.keystone.group_create(
request, request,
domain_id=domain_context, domain_id=domain_context,

View File

@ -27,3 +27,9 @@ class Groups(horizon.Panel):
@staticmethod @staticmethod
def can_register(): def can_register():
return keystone.VERSIONS.active >= 3 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 import api
from openstack_dashboard.dashboards.identity.groups import constants from openstack_dashboard.dashboards.identity.groups import constants
from openstack_dashboard import policy
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -46,7 +47,7 @@ class CreateGroupLink(tables.LinkAction):
return api.keystone.keystone_can_edit_group() return api.keystone.keystone_can_edit_group()
class EditGroupLink(tables.LinkAction): class EditGroupLink(policy.PolicyTargetMixin, tables.LinkAction):
name = "edit" name = "edit"
verbose_name = _("Edit Group") verbose_name = _("Edit Group")
url = constants.GROUPS_UPDATE_URL url = constants.GROUPS_UPDATE_URL
@ -58,7 +59,7 @@ class EditGroupLink(tables.LinkAction):
return api.keystone.keystone_can_edit_group() return api.keystone.keystone_can_edit_group()
class DeleteGroupsAction(tables.DeleteAction): class DeleteGroupsAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod @staticmethod
def action_present(count): def action_present(count):
return ungettext_lazy( return ungettext_lazy(

View File

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

View File

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

View File

@ -62,6 +62,14 @@ class UpdateMembersLink(tables.LinkAction):
param = urlencode({"step": step}) param = urlencode({"step": step})
return "?".join([base_url, param]) 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): class UpdateGroupsLink(tables.LinkAction):
name = "groups" name = "groups"
@ -72,7 +80,12 @@ class UpdateGroupsLink(tables.LinkAction):
policy_rules = (("identity", "identity:list_groups"),) policy_rules = (("identity", "identity:list_groups"),)
def allowed(self, request, project): 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): def get_link_url(self, project):
step = 'update_group_members' step = 'update_group_members'
@ -101,19 +114,30 @@ class CreateProject(tables.LinkAction):
policy_rules = (('identity', 'identity:create_project'),) policy_rules = (('identity', 'identity:create_project'),)
def allowed(self, request, 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" name = "update"
verbose_name = _("Edit Project") verbose_name = _("Edit Project")
url = "horizon:identity:projects:update" url = "horizon:identity:projects:update"
classes = ("ajax-modal",) classes = ("ajax-modal",)
icon = "pencil" icon = "pencil"
policy_rules = (('identity', 'identity:update_project'),) policy_rules = (('identity', 'identity:update_project'),)
policy_target_attrs = (("target.project.domain_id", "domain_id"),)
def allowed(self, request, 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 ModifyQuotas(tables.LinkAction): class ModifyQuotas(tables.LinkAction):
@ -124,6 +148,12 @@ class ModifyQuotas(tables.LinkAction):
icon = "pencil" icon = "pencil"
policy_rules = (('compute', "compute_extension:quotas:update"),) 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): def get_link_url(self, project):
step = 'update_quotas' step = 'update_quotas'
base_url = reverse(self.url, args=[project.id]) base_url = reverse(self.url, args=[project.id])
@ -131,7 +161,7 @@ class ModifyQuotas(tables.LinkAction):
return "?".join([base_url, param]) return "?".join([base_url, param])
class DeleteTenantsAction(tables.DeleteAction): class DeleteTenantsAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod @staticmethod
def action_present(count): def action_present(count):
return ungettext_lazy( return ungettext_lazy(
@ -149,8 +179,12 @@ class DeleteTenantsAction(tables.DeleteAction):
) )
policy_rules = (("identity", "identity:delete_project"),) policy_rules = (("identity", "identity:delete_project"),)
policy_target_attrs = ("target.project.domain_id", "domain_id"),
def allowed(self, request, project): 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() return api.keystone.keystone_can_edit_project()
def delete(self, request, obj_id): def delete(self, request, obj_id):
@ -238,6 +272,17 @@ class TenantsTable(tables.DataTable):
required=False), required=False),
update_action=UpdateCell) 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): def get_project_detail_link(self, project):
# this method is an ugly monkey patch, needed because # this method is an ugly monkey patch, needed because
# the column link method does not provide access to the request # 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): class TenantsViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_list',)}) @test.create_stubs({api.keystone: ('tenant_list', 'domain_lookup')})
def test_index(self): def test_index(self):
domain = self.domains.get(id="1")
api.keystone.tenant_list(IsA(http.HttpRequest), api.keystone.tenant_list(IsA(http.HttpRequest),
domain=None, domain=None,
paginate=True, paginate=True,
marker=None) \ marker=None) \
.AndReturn([self.tenants.list(), False]) .AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(INDEX_URL) res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'identity/projects/index.html') self.assertTemplateUsed(res, 'identity/projects/index.html')
self.assertItemsEqual(res.context['table'].data, self.tenants.list()) 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): def test_index_with_domain_context(self):
domain = self.domains.get(id="1") domain = self.domains.get(id="1")
self.setSessionValues(domain_context=domain.id, self.setSessionValues(domain_context=domain.id,
domain_context_name=domain.name) domain_context_name=domain.name)
domain_tenants = [tenant for tenant in self.tenants.list() domain_tenants = [tenant for tenant in self.tenants.list()
if tenant.domain_id == domain.id] if tenant.domain_id == domain.id]
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain.id)
api.keystone.tenant_list(IsA(http.HttpRequest), api.keystone.tenant_list(IsA(http.HttpRequest),
domain=domain.id, domain=domain.id,
paginate=True, paginate=True,
marker=None) \ marker=None) \
.AndReturn([domain_tenants, False]) .AndReturn([domain_tenants, False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(INDEX_URL) res = self.client.get(INDEX_URL)
@ -86,14 +98,18 @@ class TenantsViewTests(test.BaseAdminViewTests):
class ProjectsViewNonAdminTests(test.TestCase): class ProjectsViewNonAdminTests(test.TestCase):
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check) @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): def test_index(self):
domain = self.domains.get(id="1")
api.keystone.tenant_list(IsA(http.HttpRequest), api.keystone.tenant_list(IsA(http.HttpRequest),
user=self.user.id, user=self.user.id,
paginate=True, paginate=True,
marker=None, marker=None,
admin=False) \ admin=False) \
.AndReturn([self.tenants.list(), False]) .AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(INDEX_URL) res = self.client.get(INDEX_URL)
@ -393,6 +409,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
'role_list', 'role_list',
'group_list', 'group_list',
'get_default_domain', 'get_default_domain',
'is_cloud_admin',
'get_default_role'), 'get_default_role'),
quotas: ('get_default_quota_data', quotas: ('get_default_quota_data',
'get_disabled_quotas')}) 'get_disabled_quotas')})
@ -407,8 +424,13 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init # init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \ api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain) .AndReturn(default_domain)
api.keystone.is_cloud_admin(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first()) .AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)) \ quotas.get_default_quota_data(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova) .AndRaise(self.exceptions.nova)
@ -456,7 +478,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init # init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \ api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain) .MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first()) .AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota) quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
@ -515,7 +537,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init # init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \ api.keystone.get_default_domain(IsA(http.HttpRequest)) \
.AndReturn(default_domain) .MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first()) .AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota) quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
@ -600,8 +622,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list() roles = self.roles.list()
# init # init
api.keystone.get_default_domain(IsA(http.HttpRequest)) \ api.keystone.get_default_domain(
.AndReturn(default_domain) IsA(http.HttpRequest)).MultipleTimes().AndReturn(default_domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first()) .AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota) 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'] workflow_data[GROUP_ROLE_PREFIX + "2"] = ['1', '2', '3']
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
.AndReturn(role_assignments) .MultipleTimes().AndReturn(role_assignments)
# Give user 1 role 2 # Give user 1 role 2
api.keystone.add_tenant_user_role(IsA(http.HttpRequest), api.keystone.add_tenant_user_role(IsA(http.HttpRequest),
project=self.tenant.id, project=self.tenant.id,
@ -879,7 +901,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
if keystone_api_version >= 3: if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
.AndReturn(role_assignments) .MultipleTimes().AndReturn(role_assignments)
else: else:
api.keystone.user_list(IsA(http.HttpRequest), api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
@ -890,10 +912,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user.id, user.id,
self.tenant.id).AndReturn(roles) self.tenant.id).AndReturn(roles)
api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(role_assignments)
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('horizon:identity:projects:update', url = reverse('horizon:identity:projects:update',
@ -922,6 +940,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get', @test.create_stubs({api.keystone: ('tenant_get',
'domain_get', 'domain_get',
'get_effective_domain_id',
'tenant_update', 'tenant_update',
'get_default_role', 'get_default_role',
'roles_for_user', 'roles_for_user',
@ -938,7 +957,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.cinder: ('tenant_quota_update',), api.cinder: ('tenant_quota_update',),
quotas: ('get_tenant_quota_data', quotas: ('get_tenant_quota_data',
'get_disabled_quotas', 'get_disabled_quotas',
'tenant_quota_usages')}) 'tenant_quota_usages',)})
def test_update_project_save(self, neutron=False): def test_update_project_save(self, neutron=False):
keystone_api_version = api.keystone.VERSIONS.active keystone_api_version = api.keystone.VERSIONS.active
@ -979,11 +998,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {} workflow_data = {}
if keystone_api_version >= 3: 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), api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
.AndReturn(proj_users) .AndReturn(proj_users)
@ -993,10 +1008,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user.id, user.id,
self.tenant.id).AndReturn(roles) 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 + "1"] = ['3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['2'] # member role workflow_data[USER_ROLE_PREFIX + "2"] = ['2'] # member role
# Group assignment form data # Group assignment form data
@ -1010,16 +1021,22 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota.metadata_items = 444 quota.metadata_items = 444
quota.volumes = 444 quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota) 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 # handle
api.keystone.tenant_update(IsA(http.HttpRequest), api.keystone.tenant_update(IsA(http.HttpRequest),
project.id, project.id,
**updated_project) \ name=project._info["name"],
.AndReturn(project) 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, self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data) proj_users, roles, workflow_data)
@ -1092,6 +1109,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get', @test.create_stubs({api.keystone: ('tenant_get',
'domain_get', 'domain_get',
'get_effective_domain_id',
'tenant_update', 'tenant_update',
'get_default_role', 'get_default_role',
'roles_for_user', 'roles_for_user',
@ -1148,7 +1166,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
if keystone_api_version >= 3: if keystone_api_version >= 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.role_assignments_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
.AndReturn(role_assignments) .MultipleTimes().AndReturn(role_assignments)
else: else:
api.keystone.user_list(IsA(http.HttpRequest), api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \ project=self.tenant.id) \
@ -1164,10 +1182,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data.setdefault(USER_ROLE_PREFIX + role_ids[0], []) \ workflow_data.setdefault(USER_ROLE_PREFIX + role_ids[0], []) \
.append(user.id) .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] role_ids = [role.id for role in roles]
for group in groups: for group in groups:
if role_ids: if role_ids:
@ -1181,17 +1195,21 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota.metadata_items = 444 quota.metadata_items = 444
quota.volumes = 444 quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota) updated_quota = self._get_quota_info(quota)
# handle # handle
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \ quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
.AndReturn(quota_usages) .AndReturn(quota_usages)
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest), api.keystone.tenant_update(IsA(http.HttpRequest),
project.id, project.id,
**updated_project) \ name=project._info["name"],
domain=domain_id,
description=project._info['description'],
enabled=project.enabled) \
.AndRaise(self.exceptions.keystone) .AndRaise(self.exceptions.keystone)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1213,6 +1231,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get', @test.create_stubs({api.keystone: ('tenant_get',
'domain_get', 'domain_get',
'get_effective_domain_id',
'tenant_update', 'tenant_update',
'get_default_role', 'get_default_role',
'roles_for_user', 'roles_for_user',
@ -1257,8 +1276,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.keystone.get_default_role(IsA(http.HttpRequest)) \ api.keystone.get_default_role(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(default_role) .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)) \ api.keystone.role_list(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(roles) .MultipleTimes().AndReturn(roles)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \ api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \
@ -1266,24 +1287,16 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {} workflow_data = {}
if keystone_api_version >= 3: if keystone_api_version < 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.user_list(
project=self.tenant.id) \ IsA(http.HttpRequest),
.AndReturn(role_assignments) project=self.tenant.id).AndReturn(proj_users)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(proj_users)
for user in proj_users: for user in proj_users:
api.keystone.roles_for_user(IsA(http.HttpRequest), api.keystone.roles_for_user(IsA(http.HttpRequest),
user.id, user.id,
self.tenant.id).AndReturn(roles) 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 + "1"] = ['1', '3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role
# Group role assignment data # Group role assignment data
@ -1297,16 +1310,21 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
quota[0].limit = 444 quota[0].limit = 444
quota[1].limit = -1 quota[1].limit = -1
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota) updated_quota = self._get_quota_info(quota)
# handle # handle
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest), api.keystone.tenant_update(IsA(http.HttpRequest),
project.id, project.id,
**updated_project) \ name=project._info["name"],
.AndReturn(project) 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, self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data) proj_users, roles, workflow_data)
@ -1352,7 +1370,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
'add_group_role', 'add_group_role',
'group_list', 'group_list',
'role_list', 'role_list',
'role_assignments_list'), 'role_assignments_list',
'get_effective_domain_id'),
quotas: ('get_tenant_quota_data', quotas: ('get_tenant_quota_data',
'get_disabled_quotas', 'get_disabled_quotas',
'tenant_quota_usages')}) 'tenant_quota_usages')})
@ -1384,8 +1403,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.keystone.get_default_role(IsA(http.HttpRequest)) \ api.keystone.get_default_role(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(default_role) .MultipleTimes().AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \ api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
.AndReturn(users) .AndReturn(users)
api.keystone.role_list(IsA(http.HttpRequest)) \ api.keystone.role_list(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(roles) .MultipleTimes().AndReturn(roles)
api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \ api.keystone.group_list(IsA(http.HttpRequest), domain=domain_id) \
@ -1393,24 +1414,16 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = {} workflow_data = {}
if keystone_api_version >= 3: if keystone_api_version < 3:
api.keystone.role_assignments_list(IsA(http.HttpRequest), api.keystone.user_list(
project=self.tenant.id) \ IsA(http.HttpRequest),
.AndReturn(role_assignments) project=self.tenant.id).AndReturn(proj_users)
else:
api.keystone.user_list(IsA(http.HttpRequest),
project=self.tenant.id) \
.AndReturn(proj_users)
for user in proj_users: for user in proj_users:
api.keystone.roles_for_user(IsA(http.HttpRequest), api.keystone.roles_for_user(IsA(http.HttpRequest),
user.id, user.id,
self.tenant.id).AndReturn(roles) 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 + "1"] = ['1', '3'] # admin role
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member 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.metadata_items = 444
quota.volumes = 444 quota.volumes = 444
updated_project = {"name": project._info["name"],
"description": project._info["description"],
"enabled": project.enabled}
updated_quota = self._get_quota_info(quota) updated_quota = self._get_quota_info(quota)
# handle # handle
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \ quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
.AndReturn(quota_usages) .AndReturn(quota_usages)
api.keystone.get_effective_domain_id(
IsA(http.HttpRequest)).MultipleTimes().AndReturn(domain_id)
api.keystone.tenant_update(IsA(http.HttpRequest), api.keystone.tenant_update(IsA(http.HttpRequest),
project.id, project.id,
**updated_project) \ name=project._info["name"],
.AndReturn(project) 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, self._check_role_list(keystone_api_version, role_assignments, groups,
proj_users, roles, workflow_data) proj_users, roles, workflow_data)
self.mox.ReplayAll() self.mox.ReplayAll()
# submit form data # submit form data
@ -1592,7 +1612,8 @@ class DetailProjectViewTests(test.BaseAdminViewTests):
"The WITH_SELENIUM env variable is not set.") "The WITH_SELENIUM env variable is not set.")
class SeleniumTests(test.SeleniumAdminTestCase): class SeleniumTests(test.SeleniumAdminTestCase):
@test.create_stubs( @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): def test_inline_editing_update(self):
# Tenant List # Tenant List
api.keystone.tenant_list(IgnoreArg(), api.keystone.tenant_list(IgnoreArg(),
@ -1600,6 +1621,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
marker=None, marker=None,
paginate=True) \ paginate=True) \
.AndReturn([self.tenants.list(), False]) .AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
# Edit mod # Edit mod
api.keystone.tenant_get(IgnoreArg(), api.keystone.tenant_get(IgnoreArg(),
u'1', u'1',
@ -1673,7 +1695,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
"'Changed test_tenant'") "'Changed test_tenant'")
@test.create_stubs( @test.create_stubs(
{api.keystone: ('tenant_list', 'tenant_get')}) {api.keystone: ('tenant_list', 'tenant_get', 'domain_lookup')})
def test_inline_editing_cancel(self): def test_inline_editing_cancel(self):
# Tenant List # Tenant List
api.keystone.tenant_list(IgnoreArg(), api.keystone.tenant_list(IgnoreArg(),
@ -1681,6 +1703,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
marker=None, marker=None,
paginate=True) \ paginate=True) \
.AndReturn([self.tenants.list(), False]) .AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({None: None})
# Edit mod # Edit mod
api.keystone.tenant_get(IgnoreArg(), api.keystone.tenant_get(IgnoreArg(),
u'1', u'1',

View File

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

View File

@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse 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.usage import quotas
from openstack_dashboard.utils.identity import IdentityMixIn from openstack_dashboard.utils.identity import IdentityMixIn
LOG = logging.getLogger(__name__)
INDEX_URL = "horizon:identity:projects:index" INDEX_URL = "horizon:identity:projects:index"
ADD_USER_URL = "horizon:identity:projects:create_user" ADD_USER_URL = "horizon:identity:projects:create_user"
PROJECT_GROUP_ENABLED = keystone.VERSIONS.active >= 3 PROJECT_GROUP_ENABLED = keystone.VERSIONS.active >= 3
@ -111,6 +115,7 @@ class UpdateProjectQuotaAction(ProjectQuotaAction):
name = _("Quota") name = _("Quota")
slug = 'update_quotas' slug = 'update_quotas'
help_text = _("Set maximum quotas for the project.") help_text = _("Set maximum quotas for the project.")
permissions = ('openstack.roles.admin', 'openstack.services.compute')
class CreateProjectQuotaAction(ProjectQuotaAction): class CreateProjectQuotaAction(ProjectQuotaAction):
@ -118,6 +123,7 @@ class CreateProjectQuotaAction(ProjectQuotaAction):
name = _("Quota") name = _("Quota")
slug = 'create_quotas' slug = 'create_quotas'
help_text = _("Set maximum quotas for the project.") help_text = _("Set maximum quotas for the project.")
permissions = ('openstack.roles.admin', 'openstack.services.compute')
class UpdateProjectQuota(workflows.Step): class UpdateProjectQuota(workflows.Step):
@ -186,13 +192,14 @@ class UpdateProjectMembersAction(workflows.MembershipAction):
err_msg = _('Unable to retrieve user list. Please try again later.') err_msg = _('Unable to retrieve user list. Please try again later.')
# Use the domain_id from the project # Use the domain_id from the project
domain_id = self.initial.get("domain_id", None) domain_id = self.initial.get("domain_id", None)
project_id = '' project_id = ''
if 'project_id' in self.initial: if 'project_id' in self.initial:
project_id = self.initial['project_id'] project_id = self.initial['project_id']
# Get the default role # Get the default role
try: 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 # Default role is necessary to add members to a project
if default_role is None: if default_role is None:
default = getattr(settings, default = getattr(settings,
@ -528,12 +535,38 @@ class CreateProject(CommonQuotaWorkflow):
self._update_project_members(request, data, project_id) self._update_project_members(request, data, project_id)
if PROJECT_GROUP_ENABLED: if PROJECT_GROUP_ENABLED:
self._update_project_groups(request, data, project_id) 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 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): class UpdateProjectInfoAction(CreateProjectInfoAction):
enabled = forms.BooleanField(required=False, label=_("Enabled")) 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): def __init__(self, request, initial, *args, **kwargs):
super(UpdateProjectInfoAction, self).__init__( super(UpdateProjectInfoAction, self).__init__(
@ -608,7 +641,8 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
return api.keystone.role_list(request) return api.keystone.role_list(request)
def _update_project(self, request, data): def _update_project(self, request, data):
# update project info """Update project info"""
domain_id = api.keystone.get_effective_domain_id(self.request)
try: try:
project_id = data['project_id'] project_id = data['project_id']
return api.keystone.tenant_update( return api.keystone.tenant_update(
@ -616,12 +650,14 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
project_id, project_id,
name=data['name'], name=data['name'],
description=data['description'], description=data['description'],
enabled=data['enabled']) enabled=data['enabled'],
domain=domain_id)
except exceptions.Conflict: except exceptions.Conflict:
msg = _('Project name "%s" is already used.') % data['name'] msg = _('Project name "%s" is already used.') % data['name']
self.failure_message = msg self.failure_message = msg
return return
except Exception: except Exception as e:
LOG.debug('Project update failed: %s' % e)
exceptions.handle(request, ignore=True) exceptions.handle(request, ignore=True)
return return
@ -699,7 +735,22 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
request, project=project_id) request, project=project_id)
users_to_modify = len(users_roles) 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(): 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 # Check if there have been any changes in the roles of
# Existing project members. # Existing project members.
current_role_ids = list(users_roles[user_id]) current_role_ids = list(users_roles[user_id])
@ -854,8 +905,32 @@ class UpdateProject(CommonQuotaWorkflow, IdentityMixIn):
if not ret: if not ret:
return False return False
ret = self._update_project_quota(request, data, project_id) if api.keystone.is_cloud_admin(request):
if not ret: ret = self._update_project_quota(request, data, project_id)
return False if not ret:
return False
return True 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' slug = 'roles'
policy_rules = (("identity", "identity:list_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 @staticmethod
def can_register(): def can_register():
return keystone.VERSIONS.active >= 3 return keystone.VERSIONS.active >= 3

View File

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

View File

@ -20,9 +20,17 @@ from django.utils.translation import ugettext_lazy as _
import horizon import horizon
from openstack_dashboard.api import keystone
class Users(horizon.Panel): class Users(horizon.Panel):
name = _("Users") name = _("Users")
slug = 'users' slug = 'users'
policy_rules = (("identity", "identity:get_user"), policy_rules = (("identity", "identity:get_user"),
("identity", "identity:list_users")) ("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" icon = "pencil"
policy_rules = (("identity", "identity:update_user"), policy_rules = (("identity", "identity:update_user"),
("identity", "identity:list_projects"),) ("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): def allowed(self, request, user):
return api.keystone.keystone_can_edit_user() return api.keystone.keystone_can_edit_user()
@ -103,7 +104,8 @@ class ToggleEnabled(policy.PolicyTargetMixin, tables.BatchAction):
) )
classes = ("btn-toggle",) classes = ("btn-toggle",)
policy_rules = (("identity", "identity:update_user"),) 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): def allowed(self, request, user=None):
if not api.keystone.keystone_can_edit_user(): if not api.keystone.keystone_can_edit_user():
@ -137,7 +139,7 @@ class ToggleEnabled(policy.PolicyTargetMixin, tables.BatchAction):
self.current_past_action = ENABLE self.current_past_action = ENABLE
class DeleteUsersAction(tables.DeleteAction): class DeleteUsersAction(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod @staticmethod
def action_present(count): def action_present(count):
return ungettext_lazy( return ungettext_lazy(
@ -256,6 +258,18 @@ class UsersTable(tables.DataTable):
defaultfilters.capfirst), defaultfilters.capfirst),
empty_value="False") 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): class Meta(object):
name = "users" name = "users"
verbose_name = _("Users") verbose_name = _("Users")

View File

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

View File

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

View File

@ -21,4 +21,9 @@ class Project(horizon.Dashboard):
name = _("Project") name = _("Project")
slug = "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) horizon.register(Project)

View File

@ -66,7 +66,11 @@ WEBROOT = '/'
# Overrides the default domain used when running on single-domain model # Overrides the default domain used when running on single-domain model
# with Keystone V3. All entities will be created in the default domain. # 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 # 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 # 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"), policy_target_attrs = (("project_id", "tenant_id"),
("user_id", "user_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): def get_policy_target(self, request, datum=None):
policy_target = {} policy_target = {}

View File

@ -36,6 +36,11 @@ def get_user_home(user):
if dashboard is None: if dashboard is None:
dashboard = horizon.get_default_dashboard() 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() 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.