diff --git a/horizon/base.py b/horizon/base.py
index ab696a683f..16b3877503 100644
--- a/horizon/base.py
+++ b/horizon/base.py
@@ -61,6 +61,8 @@ class NotRegistered(Exception):
class HorizonComponent(object):
+ policy_rules = None
+
def __init__(self):
super(HorizonComponent, self).__init__()
if not self.slug:
@@ -88,6 +90,29 @@ class HorizonComponent(object):
urlpatterns = patterns('')
return urlpatterns
+ def can_access(self, context):
+ """Checks to see that the user has role based access to this component.
+
+ This method should be overridden to return the result of
+ any policy checks required for the user to access this component
+ when more complex checks are required.
+ """
+ return self._can_access(context['request'])
+
+ def _can_access(self, request):
+ policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
+
+ # this check is an OR check rather than an AND check that is the
+ # default in the policy engine, so calling each rule individually
+ if policy_check and self.policy_rules:
+ for rule in self.policy_rules:
+ if policy_check((rule,), request):
+ return True
+ return False
+
+ # default to allowed
+ return True
+
class Registry(object):
def __init__(self):
@@ -543,6 +568,30 @@ class Dashboard(Registry, HorizonComponent):
del loaders.panel_template_dirs[key]
return success
+ def can_access(self, context):
+ """Checks for role based access for this dashboard.
+
+ Checks for access to any panels in the dashboard and of the the
+ dashboard itself.
+
+ This method should be overridden to return the result of
+ any policy checks required for the user to access this dashboard
+ when more complex checks are required.
+ """
+
+ # if the dashboard has policy rules, honor those above individual
+ # panels
+ if not self._can_access(context['request']):
+ return False
+
+ # check if access is allowed to a single panel,
+ # the default for each panel is True
+ for panel in self.get_panels():
+ if panel.can_access(context):
+ return True
+
+ return False
+
class Workflow(object):
def __init__(*args, **kwargs):
diff --git a/horizon/templates/horizon/_subnav_list.html b/horizon/templates/horizon/_subnav_list.html
index 273589ad33..955ec0d288 100644
--- a/horizon/templates/horizon/_subnav_list.html
+++ b/horizon/templates/horizon/_subnav_list.html
@@ -3,12 +3,16 @@
{% for heading, panels in components.iteritems %}
{% with panels|has_permissions_on_list:user as filtered_panels %}
{% if filtered_panels %}
- {% if heading %}
{{ heading }} {% endif %}
+ {% if accessible_panels %}
+ {% if heading %}{{ heading }} {% endif %}
+ {% endif %}
{% for panel in filtered_panels %}
-
- {{ panel.name }}
-
+ {% if panel in accessible_panels or current == panel.slug %}
+
+ {{ panel.name }}
+
+ {% endif %}
{% endfor %}
{% endif %}
diff --git a/horizon/templatetags/horizon.py b/horizon/templatetags/horizon.py
index d420a95177..0fc80cabb5 100644
--- a/horizon/templatetags/horizon.py
+++ b/horizon/templatetags/horizon.py
@@ -53,15 +53,19 @@ def horizon_nav(context):
for group in panel_groups.values():
allowed_panels = []
for panel in group:
- if callable(panel.nav) and panel.nav(context):
+ if callable(panel.nav) and panel.nav(context) and \
+ panel.can_access(context):
allowed_panels.append(panel)
- elif not callable(panel.nav) and panel.nav:
+ elif not callable(panel.nav) and panel.nav and \
+ panel.can_access(context):
allowed_panels.append(panel)
if allowed_panels:
non_empty_groups.append((group.name, allowed_panels))
- if callable(dash.nav) and dash.nav(context):
+ if callable(dash.nav) and dash.nav(context) and \
+ dash.can_access(context):
dashboards.append((dash, SortedDict(non_empty_groups)))
- elif not callable(dash.nav) and dash.nav:
+ elif not callable(dash.nav) and dash.nav and \
+ dash.can_access(context):
dashboards.append((dash, SortedDict(non_empty_groups)))
return {'components': dashboards,
'user': context['request'].user,
@@ -78,10 +82,11 @@ def horizon_main_nav(context):
current_dashboard = context['request'].horizon.get('dashboard', None)
dashboards = []
for dash in Horizon.get_dashboards():
- if callable(dash.nav) and dash.nav(context):
- dashboards.append(dash)
- elif dash.nav:
- dashboards.append(dash)
+ if dash.can_access(context['request']):
+ if callable(dash.nav) and dash.nav(context):
+ dashboards.append(dash)
+ elif dash.nav:
+ dashboards.append(dash)
return {'components': dashboards,
'user': context['request'].user,
'current': current_dashboard,
@@ -100,9 +105,11 @@ def horizon_dashboard_nav(context):
for group in panel_groups.values():
allowed_panels = []
for panel in group:
- if callable(panel.nav) and panel.nav(context):
+ if callable(panel.nav) and panel.nav(context) and \
+ panel.can_access(context):
allowed_panels.append(panel)
- elif not callable(panel.nav) and panel.nav:
+ elif not callable(panel.nav) and panel.nav and \
+ panel.can_access(context):
allowed_panels.append(panel)
if allowed_panels:
non_empty_groups.append((group.name, allowed_panels))
diff --git a/horizon/test/tests/base.py b/horizon/test/tests/base.py
index 40dd64ea37..7624d27232 100644
--- a/horizon/test/tests/base.py
+++ b/horizon/test/tests/base.py
@@ -53,6 +53,19 @@ class AdminPanel(horizon.Panel):
urls = 'horizon.test.test_dashboards.cats.kittens.urls'
+class RbacNoAccessPanel(horizon.Panel):
+ name = "RBAC Panel No"
+ slug = "rbac_panel_no"
+
+ def _can_access(self, request):
+ return False
+
+
+class RbacYesAccessPanel(horizon.Panel):
+ name = "RBAC Panel Yes"
+ slug = "rbac_panel_yes"
+
+
class BaseHorizonTests(test.TestCase):
def setUp(self):
@@ -439,3 +452,67 @@ class CustomPermissionsTests(BaseHorizonTests):
follow=False,
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 200)
+
+
+class RbacHorizonTests(test.TestCase):
+
+ def setUp(self):
+ super(RbacHorizonTests, self).setUp()
+ # Adjust our horizon config and register our custom dashboards/panels.
+ self.old_default_dash = settings.HORIZON_CONFIG['default_dashboard']
+ settings.HORIZON_CONFIG['default_dashboard'] = 'cats'
+ self.old_dashboards = settings.HORIZON_CONFIG['dashboards']
+ settings.HORIZON_CONFIG['dashboards'] = ('cats', 'dogs')
+ base.Horizon.register(Cats)
+ base.Horizon.register(Dogs)
+ Cats.register(RbacNoAccessPanel)
+ Cats.default_panel = 'rbac_panel_no'
+ Dogs.register(RbacYesAccessPanel)
+ Dogs.default_panel = 'rbac_panel_yes'
+ # Trigger discovery, registration, and URLconf generation if it
+ # hasn't happened yet.
+ base.Horizon._urls()
+ # Store our original dashboards
+ self._discovered_dashboards = base.Horizon._registry.keys()
+ # Gather up and store our original panels for each dashboard
+ self._discovered_panels = {}
+ for dash in self._discovered_dashboards:
+ panels = base.Horizon._registry[dash]._registry.keys()
+ self._discovered_panels[dash] = panels
+
+ def tearDown(self):
+ super(RbacHorizonTests, self).tearDown()
+ # Restore our settings
+ settings.HORIZON_CONFIG['default_dashboard'] = self.old_default_dash
+ settings.HORIZON_CONFIG['dashboards'] = self.old_dashboards
+ # Destroy our singleton and re-create it.
+ base.HorizonSite._instance = None
+ del base.Horizon
+ base.Horizon = base.HorizonSite()
+ # Reload the convenience references to Horizon stored in __init__
+ reload(import_module("horizon"))
+ # Re-register our original dashboards and panels.
+ # This is necessary because autodiscovery only works on the first
+ # import, and calling reload introduces innumerable additional
+ # problems. Manual re-registration is the only good way for testing.
+ self._discovered_dashboards.remove(Cats)
+ self._discovered_dashboards.remove(Dogs)
+ for dash in self._discovered_dashboards:
+ base.Horizon.register(dash)
+ for panel in self._discovered_panels[dash]:
+ dash.register(panel)
+
+ def test_rbac_panels(self):
+ context = {'request': None}
+ cats = horizon.get_dashboard("cats")
+ self.assertEqual(cats._registered_with, base.Horizon)
+ self.assertQuerysetEqual(cats.get_panels(),
+ [''])
+ self.assertFalse(cats.can_access(context))
+
+ dogs = horizon.get_dashboard("dogs")
+ self.assertEqual(dogs._registered_with, base.Horizon)
+ self.assertQuerysetEqual(dogs.get_panels(),
+ [''])
+
+ self.assertTrue(dogs.can_access(context))
diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py
index bea3a2c2cb..12890a8ec3 100644
--- a/openstack_dashboard/api/keystone.py
+++ b/openstack_dashboard/api/keystone.py
@@ -252,8 +252,9 @@ def tenant_delete(request, project):
return manager.delete(project)
-def tenant_list(request, paginate=False, marker=None, domain=None, user=None):
- manager = VERSIONS.get_project_manager(request, admin=True)
+def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
+ admin=True):
+ manager = VERSIONS.get_project_manager(request, admin=admin)
page_size = utils.get_page_size(request)
limit = None
diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py
index 5eb41ef7cf..25d198f956 100644
--- a/openstack_dashboard/dashboards/admin/dashboard.py
+++ b/openstack_dashboard/dashboards/admin/dashboard.py
@@ -25,16 +25,10 @@ class SystemPanels(horizon.PanelGroup):
'networks', 'routers', 'info')
-class IdentityPanels(horizon.PanelGroup):
- slug = "identity"
- name = _("Identity")
- panels = ('domains', 'projects', 'users', 'groups', 'roles')
-
-
class Admin(horizon.Dashboard):
name = _("Admin")
slug = "admin"
- panels = (SystemPanels, IdentityPanels)
+ panels = (SystemPanels,)
default_panel = 'overview'
permissions = ('openstack.roles.admin',)
diff --git a/openstack_dashboard/dashboards/admin/groups/constants.py b/openstack_dashboard/dashboards/admin/groups/constants.py
deleted file mode 100644
index d8a6350546..0000000000
--- a/openstack_dashboard/dashboards/admin/groups/constants.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-GROUPS_INDEX_URL = 'horizon:admin:groups:index'
-GROUPS_INDEX_VIEW_TEMPLATE = 'admin/groups/index.html'
-GROUPS_CREATE_URL = 'horizon:admin:groups:create'
-GROUPS_CREATE_VIEW_TEMPLATE = 'admin/groups/create.html'
-GROUPS_UPDATE_URL = 'horizon:admin:groups:update'
-GROUPS_UPDATE_VIEW_TEMPLATE = 'admin/groups/update.html'
-GROUPS_MANAGE_URL = 'horizon:admin:groups:manage_members'
-GROUPS_MANAGE_VIEW_TEMPLATE = 'admin/groups/manage.html'
-GROUPS_ADD_MEMBER_URL = 'horizon:admin:groups:add_members'
-GROUPS_ADD_MEMBER_VIEW_TEMPLATE = 'admin/groups/add_non_member.html'
-GROUPS_ADD_MEMBER_AJAX_VIEW_TEMPLATE = 'admin/groups/_add_non_member.html'
diff --git a/openstack_dashboard/dashboards/admin/domains/__init__.py b/openstack_dashboard/dashboards/identity/__init__.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/domains/__init__.py
rename to openstack_dashboard/dashboards/identity/__init__.py
diff --git a/openstack_dashboard/dashboards/identity/dashboard.py b/openstack_dashboard/dashboards/identity/dashboard.py
new file mode 100644
index 0000000000..6b02140451
--- /dev/null
+++ b/openstack_dashboard/dashboards/identity/dashboard.py
@@ -0,0 +1,28 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+
+class Identity(horizon.Dashboard):
+ name = _("Identity")
+ slug = "identity"
+ default_panel = 'projects'
+ panels = ('domains', 'projects', 'users', 'groups', 'roles',)
+
+
+horizon.register(Identity)
diff --git a/openstack_dashboard/dashboards/admin/groups/__init__.py b/openstack_dashboard/dashboards/identity/domains/__init__.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/groups/__init__.py
rename to openstack_dashboard/dashboards/identity/domains/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/domains/constants.py b/openstack_dashboard/dashboards/identity/domains/constants.py
similarity index 77%
rename from openstack_dashboard/dashboards/admin/domains/constants.py
rename to openstack_dashboard/dashboards/identity/domains/constants.py
index 783571b70b..32e02b2daf 100644
--- a/openstack_dashboard/dashboards/admin/domains/constants.py
+++ b/openstack_dashboard/dashboards/identity/domains/constants.py
@@ -15,8 +15,8 @@
DOMAIN_INFO_FIELDS = ("name",
"description",
"enabled")
-DOMAINS_INDEX_URL = 'horizon:admin:domains:index'
-DOMAINS_INDEX_VIEW_TEMPLATE = 'admin/domains/index.html'
-DOMAINS_CREATE_URL = 'horizon:admin:domains:create'
-DOMAINS_UPDATE_URL = 'horizon:admin:domains:update'
+DOMAINS_INDEX_URL = 'horizon:identity:domains:index'
+DOMAINS_INDEX_VIEW_TEMPLATE = 'identity/domains/index.html'
+DOMAINS_CREATE_URL = 'horizon:identity:domains:create'
+DOMAINS_UPDATE_URL = 'horizon:identity:domains:update'
DOMAIN_GROUP_MEMBER_SLUG = "update_group_members"
diff --git a/openstack_dashboard/dashboards/admin/domains/panel.py b/openstack_dashboard/dashboards/identity/domains/panel.py
similarity index 79%
rename from openstack_dashboard/dashboards/admin/domains/panel.py
rename to openstack_dashboard/dashboards/identity/domains/panel.py
index f13110c1e2..e5c1cb7b75 100644
--- a/openstack_dashboard/dashboards/admin/domains/panel.py
+++ b/openstack_dashboard/dashboards/identity/domains/panel.py
@@ -17,13 +17,15 @@ from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.api import keystone
-from openstack_dashboard.dashboards.admin import dashboard
+from openstack_dashboard.dashboards.identity import dashboard
class Domains(horizon.Panel):
name = _("Domains")
slug = 'domains'
+ policy_rules = (("identity", "identity:get_domain"),
+ ("identity", "identity:list_domains"))
if keystone.VERSIONS.active >= 3:
- dashboard.Admin.register(Domains)
+ dashboard.Identity.register(Domains)
diff --git a/openstack_dashboard/dashboards/admin/domains/tables.py b/openstack_dashboard/dashboards/identity/domains/tables.py
similarity index 98%
rename from openstack_dashboard/dashboards/admin/domains/tables.py
rename to openstack_dashboard/dashboards/identity/domains/tables.py
index 60ce476df1..5d31cab865 100644
--- a/openstack_dashboard/dashboards/admin/domains/tables.py
+++ b/openstack_dashboard/dashboards/identity/domains/tables.py
@@ -26,7 +26,7 @@ from horizon import tables
from openstack_dashboard import api
-from openstack_dashboard.dashboards.admin.domains import constants
+from openstack_dashboard.dashboards.identity.domains import constants
LOG = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ LOG = logging.getLogger(__name__)
class ViewGroupsLink(tables.LinkAction):
name = "groups"
verbose_name = _("Modify Groups")
- url = "horizon:admin:domains:update"
+ url = "horizon:identity:domains:update"
classes = ("ajax-modal",)
icon = "pencil"
diff --git a/openstack_dashboard/dashboards/admin/domains/templates/domains/index.html b/openstack_dashboard/dashboards/identity/domains/templates/domains/index.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/domains/templates/domains/index.html
rename to openstack_dashboard/dashboards/identity/domains/templates/domains/index.html
diff --git a/openstack_dashboard/dashboards/admin/domains/tests.py b/openstack_dashboard/dashboards/identity/domains/tests.py
similarity index 98%
rename from openstack_dashboard/dashboards/admin/domains/tests.py
rename to openstack_dashboard/dashboards/identity/domains/tests.py
index c995b7b82c..64a4bb1064 100644
--- a/openstack_dashboard/dashboards/admin/domains/tests.py
+++ b/openstack_dashboard/dashboards/identity/domains/tests.py
@@ -24,8 +24,8 @@ from horizon.workflows import views
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
-from openstack_dashboard.dashboards.admin.domains import constants
-from openstack_dashboard.dashboards.admin.domains import workflows
+from openstack_dashboard.dashboards.identity.domains import constants
+from openstack_dashboard.dashboards.identity.domains import workflows
DOMAINS_INDEX_URL = reverse(constants.DOMAINS_INDEX_URL)
@@ -134,7 +134,7 @@ class CreateDomainWorkflowTests(test.BaseAdminViewTests):
return domain_info
def test_add_domain_get(self):
- url = reverse('horizon:admin:domains:create')
+ url = reverse('horizon:identity:domains:create')
res = self.client.get(url)
self.assertTemplateUsed(res, views.WorkflowView.template_name)
diff --git a/openstack_dashboard/dashboards/admin/domains/urls.py b/openstack_dashboard/dashboards/identity/domains/urls.py
similarity index 93%
rename from openstack_dashboard/dashboards/admin/domains/urls.py
rename to openstack_dashboard/dashboards/identity/domains/urls.py
index 426dcf3246..3877856544 100644
--- a/openstack_dashboard/dashboards/admin/domains/urls.py
+++ b/openstack_dashboard/dashboards/identity/domains/urls.py
@@ -15,7 +15,7 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
-from openstack_dashboard.dashboards.admin.domains import views
+from openstack_dashboard.dashboards.identity.domains import views
urlpatterns = patterns('',
diff --git a/openstack_dashboard/dashboards/admin/domains/views.py b/openstack_dashboard/dashboards/identity/domains/views.py
similarity index 62%
rename from openstack_dashboard/dashboards/admin/domains/views.py
rename to openstack_dashboard/dashboards/identity/domains/views.py
index f0f1a24dd4..fca4359fb3 100644
--- a/openstack_dashboard/dashboards/admin/domains/views.py
+++ b/openstack_dashboard/dashboards/identity/domains/views.py
@@ -16,15 +16,17 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
+from horizon import messages
from horizon import tables
from horizon import workflows
from openstack_dashboard import api
+from openstack_dashboard import policy
-from openstack_dashboard.dashboards.admin.domains import constants
-from openstack_dashboard.dashboards.admin.domains \
+from openstack_dashboard.dashboards.identity.domains import constants
+from openstack_dashboard.dashboards.identity.domains \
import tables as project_tables
-from openstack_dashboard.dashboards.admin.domains \
+from openstack_dashboard.dashboards.identity.domains \
import workflows as project_workflows
@@ -35,16 +37,30 @@ class IndexView(tables.DataTableView):
def get_data(self):
domains = []
domain_context = self.request.session.get('domain_context', None)
- try:
- if domain_context:
+ if policy.check((("identity", "identity:list_domains"),),
+ self.request):
+ try:
+ if domain_context:
+ domain = api.keystone.domain_get(self.request,
+ domain_context)
+ domains.append(domain)
+ else:
+ domains = api.keystone.domain_list(self.request)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve domain list.'))
+ elif policy.check((("identity", "identity:get_domain"),),
+ self.request):
+ try:
domain = api.keystone.domain_get(self.request,
- domain_context)
+ self.request.user.domain_id)
domains.append(domain)
- else:
- domains = api.keystone.domain_list(self.request)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve domain list.'))
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve domain information.'))
+ else:
+ msg = _("Insufficient privilege level to view domain information.")
+ messages.info(self.request, msg)
return domains
diff --git a/openstack_dashboard/dashboards/admin/domains/workflows.py b/openstack_dashboard/dashboards/identity/domains/workflows.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/domains/workflows.py
rename to openstack_dashboard/dashboards/identity/domains/workflows.py
index 3d224ee2bb..4b8c21fda8 100644
--- a/openstack_dashboard/dashboards/admin/domains/workflows.py
+++ b/openstack_dashboard/dashboards/identity/domains/workflows.py
@@ -24,7 +24,7 @@ from horizon import workflows
from openstack_dashboard import api
-from openstack_dashboard.dashboards.admin.domains import constants
+from openstack_dashboard.dashboards.identity.domains import constants
LOG = logging.getLogger(__name__)
diff --git a/openstack_dashboard/dashboards/admin/projects/__init__.py b/openstack_dashboard/dashboards/identity/groups/__init__.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/projects/__init__.py
rename to openstack_dashboard/dashboards/identity/groups/__init__.py
diff --git a/openstack_dashboard/dashboards/identity/groups/constants.py b/openstack_dashboard/dashboards/identity/groups/constants.py
new file mode 100644
index 0000000000..0edcf38ae4
--- /dev/null
+++ b/openstack_dashboard/dashboards/identity/groups/constants.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+GROUPS_INDEX_URL = 'horizon:identity:groups:index'
+GROUPS_INDEX_VIEW_TEMPLATE = 'identity/groups/index.html'
+GROUPS_CREATE_URL = 'horizon:identity:groups:create'
+GROUPS_CREATE_VIEW_TEMPLATE = 'identity/groups/create.html'
+GROUPS_UPDATE_URL = 'horizon:identity:groups:update'
+GROUPS_UPDATE_VIEW_TEMPLATE = 'identity/groups/update.html'
+GROUPS_MANAGE_URL = 'horizon:identity:groups:manage_members'
+GROUPS_MANAGE_VIEW_TEMPLATE = 'identity/groups/manage.html'
+GROUPS_ADD_MEMBER_URL = 'horizon:identity:groups:add_members'
+GROUPS_ADD_MEMBER_VIEW_TEMPLATE = 'identity/groups/add_non_member.html'
+GROUPS_ADD_MEMBER_AJAX_VIEW_TEMPLATE = 'identity/groups/_add_non_member.html'
diff --git a/openstack_dashboard/dashboards/admin/groups/forms.py b/openstack_dashboard/dashboards/identity/groups/forms.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/groups/forms.py
rename to openstack_dashboard/dashboards/identity/groups/forms.py
diff --git a/openstack_dashboard/dashboards/admin/groups/panel.py b/openstack_dashboard/dashboards/identity/groups/panel.py
similarity index 84%
rename from openstack_dashboard/dashboards/admin/groups/panel.py
rename to openstack_dashboard/dashboards/identity/groups/panel.py
index 35d7a8ce2e..301ba124eb 100644
--- a/openstack_dashboard/dashboards/admin/groups/panel.py
+++ b/openstack_dashboard/dashboards/identity/groups/panel.py
@@ -17,13 +17,14 @@ from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.api import keystone
-from openstack_dashboard.dashboards.admin import dashboard
+from openstack_dashboard.dashboards.identity import dashboard
class Groups(horizon.Panel):
name = _("Groups")
slug = 'groups'
+ policy_rules = (("identity", "identity:list_groups"),)
if keystone.VERSIONS.active >= 3:
- dashboard.Admin.register(Groups)
+ dashboard.Identity.register(Groups)
diff --git a/openstack_dashboard/dashboards/admin/groups/tables.py b/openstack_dashboard/dashboards/identity/groups/tables.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/groups/tables.py
rename to openstack_dashboard/dashboards/identity/groups/tables.py
index 9d5644e028..782e2758bd 100644
--- a/openstack_dashboard/dashboards/admin/groups/tables.py
+++ b/openstack_dashboard/dashboards/identity/groups/tables.py
@@ -22,7 +22,7 @@ from horizon import tables
from openstack_dashboard import api
-from openstack_dashboard.dashboards.admin.groups import constants
+from openstack_dashboard.dashboards.identity.groups import constants
LOG = logging.getLogger(__name__)
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/_add_non_member.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/_add_non_member.html
similarity index 60%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/_add_non_member.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/_add_non_member.html
index c21b75ba4e..4e49ca0596 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/_add_non_member.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/_add_non_member.html
@@ -5,5 +5,5 @@
{% block modal-header %}{% trans "Add Group Assignment" %}{% endblock %}
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/_create.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/_create.html
similarity index 76%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/_create.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/_create.html
index d997ae0942..7f9138c2e6 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/_create.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/_create.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}create_group_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:groups:create' %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:groups:create' %}{% endblock %}
{% block modal-header %}{% trans "Create Group" %}{% endblock %}
@@ -21,5 +21,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/_update.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/_update.html
similarity index 74%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/_update.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/_update.html
index 9432442693..0a872f3a08 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/_update.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/_update.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}update_group_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:groups:update' group.id %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:groups:update' group.id %}{% endblock %}
{% block modal-header %}{% trans "Update Group" %}{% endblock %}
@@ -21,5 +21,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/add_non_member.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/add_non_member.html
similarity index 70%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/add_non_member.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/add_non_member.html
index fe380dbee8..0a9b69a86c 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/add_non_member.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/add_non_member.html
@@ -3,5 +3,5 @@
{% block title %}{% trans 'Add User to Group' %}{% endblock %}
{% block main %}
- {% include 'admin/groups/_add_non_member.html' %}
+ {% include 'identity/groups/_add_non_member.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/create.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/create.html
similarity index 84%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/create.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/create.html
index 126c8aa6c2..8810517c3d 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/create.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/create.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/groups/_create.html' %}
+ {% include 'identity/groups/_create.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/index.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/index.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/index.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/index.html
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/manage.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/manage.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/manage.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/manage.html
diff --git a/openstack_dashboard/dashboards/admin/groups/templates/groups/update.html b/openstack_dashboard/dashboards/identity/groups/templates/groups/update.html
similarity index 84%
rename from openstack_dashboard/dashboards/admin/groups/templates/groups/update.html
rename to openstack_dashboard/dashboards/identity/groups/templates/groups/update.html
index abb88a4345..7b2ec826c3 100644
--- a/openstack_dashboard/dashboards/admin/groups/templates/groups/update.html
+++ b/openstack_dashboard/dashboards/identity/groups/templates/groups/update.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/groups/_update.html' %}
+ {% include 'identity/groups/_update.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/groups/tests.py b/openstack_dashboard/dashboards/identity/groups/tests.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/groups/tests.py
rename to openstack_dashboard/dashboards/identity/groups/tests.py
index 5c8922f331..70f1718b09 100644
--- a/openstack_dashboard/dashboards/admin/groups/tests.py
+++ b/openstack_dashboard/dashboards/identity/groups/tests.py
@@ -21,7 +21,7 @@ from mox import IsA # noqa
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
-from openstack_dashboard.dashboards.admin.groups import constants
+from openstack_dashboard.dashboards.identity.groups import constants
GROUPS_INDEX_URL = reverse(constants.GROUPS_INDEX_URL)
diff --git a/openstack_dashboard/dashboards/admin/groups/urls.py b/openstack_dashboard/dashboards/identity/groups/urls.py
similarity index 94%
rename from openstack_dashboard/dashboards/admin/groups/urls.py
rename to openstack_dashboard/dashboards/identity/groups/urls.py
index 19548bab82..e461c28d60 100644
--- a/openstack_dashboard/dashboards/admin/groups/urls.py
+++ b/openstack_dashboard/dashboards/identity/groups/urls.py
@@ -15,7 +15,7 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
-from openstack_dashboard.dashboards.admin.groups import views
+from openstack_dashboard.dashboards.identity.groups import views
urlpatterns = patterns('',
diff --git a/openstack_dashboard/dashboards/admin/groups/views.py b/openstack_dashboard/dashboards/identity/groups/views.py
similarity index 85%
rename from openstack_dashboard/dashboards/admin/groups/views.py
rename to openstack_dashboard/dashboards/identity/groups/views.py
index b0a964a35e..74bf0336ae 100644
--- a/openstack_dashboard/dashboards/admin/groups/views.py
+++ b/openstack_dashboard/dashboards/identity/groups/views.py
@@ -18,15 +18,17 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
+from horizon import messages
from horizon import tables
from horizon.utils import memoized
from openstack_dashboard import api
+from openstack_dashboard import policy
-from openstack_dashboard.dashboards.admin.groups import constants
-from openstack_dashboard.dashboards.admin.groups \
+from openstack_dashboard.dashboards.identity.groups import constants
+from openstack_dashboard.dashboards.identity.groups \
import forms as project_forms
-from openstack_dashboard.dashboards.admin.groups \
+from openstack_dashboard.dashboards.identity.groups \
import tables as project_tables
@@ -37,12 +39,17 @@ class IndexView(tables.DataTableView):
def get_data(self):
groups = []
domain_context = self.request.session.get('domain_context', None)
- try:
- groups = api.keystone.group_list(self.request,
- domain=domain_context)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve group list.'))
+ if policy.check((("identity", "identity:list_groups"),),
+ self.request):
+ try:
+ groups = api.keystone.group_list(self.request,
+ domain=domain_context)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve group list.'))
+ else:
+ msg = _("Insufficient privilege level to view group information.")
+ messages.info(self.request, msg)
return groups
diff --git a/openstack_dashboard/dashboards/admin/roles/__init__.py b/openstack_dashboard/dashboards/identity/projects/__init__.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/roles/__init__.py
rename to openstack_dashboard/dashboards/identity/projects/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/projects/panel.py b/openstack_dashboard/dashboards/identity/projects/panel.py
similarity index 80%
rename from openstack_dashboard/dashboards/admin/projects/panel.py
rename to openstack_dashboard/dashboards/identity/projects/panel.py
index 5eb0adb8e9..595f3a2fb8 100644
--- a/openstack_dashboard/dashboards/admin/projects/panel.py
+++ b/openstack_dashboard/dashboards/identity/projects/panel.py
@@ -20,12 +20,14 @@ from django.utils.translation import ugettext_lazy as _
import horizon
-from openstack_dashboard.dashboards.admin import dashboard
+from openstack_dashboard.dashboards.identity import dashboard
class Tenants(horizon.Panel):
name = _("Projects")
slug = 'projects'
+ policy_rules = (("identity", "identity:list_projects"),
+ ("identity", "identity:list_user_projects"))
-dashboard.Admin.register(Tenants)
+dashboard.Identity.register(Tenants)
diff --git a/openstack_dashboard/dashboards/admin/projects/tables.py b/openstack_dashboard/dashboards/identity/projects/tables.py
similarity index 90%
rename from openstack_dashboard/dashboards/admin/projects/tables.py
rename to openstack_dashboard/dashboards/identity/projects/tables.py
index 39d3cd9945..60f351107c 100644
--- a/openstack_dashboard/dashboards/admin/projects/tables.py
+++ b/openstack_dashboard/dashboards/identity/projects/tables.py
@@ -21,13 +21,13 @@ from horizon import tables
from keystoneclient.exceptions import Conflict # noqa
from openstack_dashboard import api
-from openstack_dashboard.api import keystone
+from openstack_dashboard import policy
class ViewMembersLink(tables.LinkAction):
name = "users"
verbose_name = _("Modify Users")
- url = "horizon:admin:projects:update"
+ url = "horizon:identity:projects:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("identity", "identity:list_users"),
@@ -43,12 +43,13 @@ class ViewMembersLink(tables.LinkAction):
class ViewGroupsLink(tables.LinkAction):
name = "groups"
verbose_name = _("Modify Groups")
- url = "horizon:admin:projects:update"
+ url = "horizon:identity:projects:update"
classes = ("ajax-modal",)
icon = "pencil"
+ policy_rules = (("identity", "identity:list_groups"),)
def allowed(self, request, project):
- return keystone.VERSIONS.active >= 3
+ return api.keystone.VERSIONS.active >= 3
def get_link_url(self, project):
step = 'update_group_members'
@@ -60,15 +61,18 @@ class ViewGroupsLink(tables.LinkAction):
class UsageLink(tables.LinkAction):
name = "usage"
verbose_name = _("View Usage")
- url = "horizon:admin:projects:usage"
+ url = "horizon:identity:projects:usage"
icon = "stats"
policy_rules = (("compute", "compute_extension:simple_tenant_usage:show"),)
+ def allowed(self, request, project):
+ return request.user.is_superuser
+
class CreateProject(tables.LinkAction):
name = "create"
verbose_name = _("Create Project")
- url = "horizon:admin:projects:create"
+ url = "horizon:identity:projects:create"
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (('identity', 'identity:create_project'),)
@@ -80,7 +84,7 @@ class CreateProject(tables.LinkAction):
class UpdateProject(tables.LinkAction):
name = "update"
verbose_name = _("Edit Project")
- url = "horizon:admin:projects:update"
+ url = "horizon:identity:projects:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (('identity', 'identity:update_project'),)
@@ -92,7 +96,7 @@ class UpdateProject(tables.LinkAction):
class ModifyQuotas(tables.LinkAction):
name = "quotas"
verbose_name = _("Modify Quotas")
- url = "horizon:admin:projects:update"
+ url = "horizon:identity:projects:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (('compute', "compute_extension:quotas:update"),)
@@ -141,7 +145,9 @@ class UpdateRow(tables.Row):
class UpdateCell(tables.UpdateAction):
def allowed(self, request, project, cell):
- return api.keystone.keystone_can_edit_project()
+ return api.keystone.keystone_can_edit_project() and \
+ policy.check((("identity", "identity:update_project"),),
+ request)
def update_cell(self, request, datum, project_id,
cell_name, new_cell_value):
diff --git a/openstack_dashboard/dashboards/admin/projects/templates/projects/index.html b/openstack_dashboard/dashboards/identity/projects/templates/projects/index.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/projects/templates/projects/index.html
rename to openstack_dashboard/dashboards/identity/projects/templates/projects/index.html
diff --git a/openstack_dashboard/dashboards/admin/projects/templates/projects/usage.html b/openstack_dashboard/dashboards/identity/projects/templates/projects/usage.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/projects/templates/projects/usage.html
rename to openstack_dashboard/dashboards/identity/projects/templates/projects/usage.html
diff --git a/openstack_dashboard/dashboards/admin/projects/tests.py b/openstack_dashboard/dashboards/identity/projects/tests.py
similarity index 96%
rename from openstack_dashboard/dashboards/admin/projects/tests.py
rename to openstack_dashboard/dashboards/identity/projects/tests.py
index 69d18f86b1..006723b4ea 100644
--- a/openstack_dashboard/dashboards/admin/projects/tests.py
+++ b/openstack_dashboard/dashboards/identity/projects/tests.py
@@ -31,7 +31,7 @@ from horizon import exceptions
from horizon.workflows import views
from openstack_dashboard import api
-from openstack_dashboard.dashboards.admin.projects import workflows
+from openstack_dashboard.dashboards.identity.projects import workflows
from openstack_dashboard.test import helpers as test
from openstack_dashboard import usage
from openstack_dashboard.usage import quotas
@@ -44,22 +44,23 @@ if with_sel:
from socket import timeout as socket_timeout # noqa
-INDEX_URL = reverse('horizon:admin:projects:index')
+INDEX_URL = reverse('horizon:identity:projects:index')
USER_ROLE_PREFIX = workflows.PROJECT_GROUP_MEMBER_SLUG + "_role_"
GROUP_ROLE_PREFIX = workflows.PROJECT_USER_MEMBER_SLUG + "_role_"
-@test.create_stubs({api.keystone: ('tenant_list',)})
class TenantsViewTests(test.BaseAdminViewTests):
+ @test.create_stubs({api.keystone: ('tenant_list',)})
def test_index(self):
api.keystone.tenant_list(IsA(http.HttpRequest),
domain=None,
- paginate=True) \
+ paginate=True,
+ marker=None) \
.AndReturn([self.tenants.list(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
- self.assertTemplateUsed(res, 'admin/projects/index.html')
+ self.assertTemplateUsed(res, 'identity/projects/index.html')
self.assertItemsEqual(res.context['table'].data, self.tenants.list())
@test.create_stubs({api.keystone: ('tenant_list', )})
@@ -70,16 +71,34 @@ class TenantsViewTests(test.BaseAdminViewTests):
domain_tenants = [tenant for tenant in self.tenants.list()
if tenant.domain_id == domain.id]
api.keystone.tenant_list(IsA(http.HttpRequest),
- domain=domain.id) \
- .AndReturn(domain_tenants)
+ domain=domain.id,
+ paginate=True,
+ marker=None) \
+ .AndReturn([domain_tenants, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
- self.assertTemplateUsed(res, 'admin/projects/index.html')
+ self.assertTemplateUsed(res, 'identity/projects/index.html')
self.assertItemsEqual(res.context['table'].data, domain_tenants)
self.assertContains(res, "test_domain: ")
+class ProjectsViewNonAdminTests(test.TestCase):
+ @test.create_stubs({api.keystone: ('tenant_list',)})
+ def test_index(self):
+ api.keystone.tenant_list(IsA(http.HttpRequest),
+ user=self.user.id,
+ paginate=True,
+ marker=None,
+ admin=False) \
+ .AndReturn([self.tenants.list(), False])
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'identity/projects/index.html')
+ self.assertItemsEqual(res.context['table'].data, self.tenants.list())
+
+
class CreateProjectWorkflowTests(test.BaseAdminViewTests):
def _get_project_info(self, project):
domain = self._get_default_domain()
@@ -179,7 +198,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.get(url)
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@@ -240,7 +259,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
.AndReturn(self.roles.list())
self.mox.ReplayAll()
- res = self.client.get(reverse('horizon:admin:projects:create'))
+ res = self.client.get(reverse('horizon:identity:projects:create'))
self.assertTemplateUsed(res, views.WorkflowView.template_name)
if django.VERSION >= (1, 6):
@@ -343,7 +362,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data.update(self._get_workflow_data(project, quota))
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.post(url, workflow_data)
self.assertNoFormErrors(res)
@@ -406,7 +425,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.get(url)
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@@ -462,7 +481,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = self._get_workflow_data(project, quota)
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.post(url, workflow_data)
self.assertNoFormErrors(res)
@@ -546,7 +565,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data.update(self._get_workflow_data(project, quota))
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.post(url, workflow_data)
self.assertNoFormErrors(res)
@@ -631,7 +650,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data.update(self._get_workflow_data(project, quota))
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.post(url, workflow_data)
self.assertNoFormErrors(res)
@@ -681,7 +700,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
workflow_data = self._get_workflow_data(project, quota)
workflow_data["name"] = ""
- url = reverse('horizon:admin:projects:create')
+ url = reverse('horizon:identity:projects:create')
res = self.client.post(url, workflow_data)
self.assertContains(res, "field is required")
@@ -796,7 +815,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.get(url)
@@ -1028,7 +1047,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
"enabled": project.enabled}
workflow_data.update(project_data)
workflow_data.update(updated_quota)
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.post(url, workflow_data)
@@ -1064,7 +1083,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.get(url)
@@ -1180,7 +1199,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
"enabled": project.enabled}
workflow_data.update(project_data)
workflow_data.update(updated_quota)
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.post(url, workflow_data)
@@ -1354,7 +1373,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
"enabled": project.enabled}
workflow_data.update(project_data)
workflow_data.update(updated_quota)
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.post(url, workflow_data)
@@ -1488,7 +1507,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
"enabled": project.enabled}
workflow_data.update(project_data)
workflow_data.update(updated_quota)
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
res = self.client.post(url, workflow_data)
@@ -1520,7 +1539,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
.AndReturn(quota)
self.mox.ReplayAll()
- url = reverse('horizon:admin:projects:update',
+ url = reverse('horizon:identity:projects:update',
args=[self.tenant.id])
try:
@@ -1586,7 +1605,7 @@ class UsageViewTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
project_id = self.tenants.first().id
- csv_url = reverse('horizon:admin:projects:usage',
+ csv_url = reverse('horizon:identity:projects:usage',
args=[project_id]) + "?format=csv"
res = self.client.get(csv_url)
self.assertTemplateUsed(res, 'project/overview/usage.csv')
@@ -1639,7 +1658,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
# Check the presence of the important elements
td_element = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']")
cell_wrapper = td_element.find_element_by_class_name(
'table_cell_wrapper')
@@ -1656,7 +1675,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
wait.until(lambda x: self.selenium.find_element_by_name("name__1"))
# Changing project name in cell form
td_element = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']")
name_input = td_element.find_element_by_tag_name('input')
name_input.send_keys(keys.Keys.HOME)
@@ -1667,13 +1686,13 @@ class SeleniumTests(test.SeleniumAdminTestCase):
wait = self.ui.WebDriverWait(self.selenium, 10,
ignored_exceptions=[socket_timeout])
wait.until(lambda x: self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']"
"/div[@class='table_cell_wrapper']"
"/div[@class='table_cell_data_wrapper']"))
# Checking new project name after cell refresh
data_wrapper = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']"
"/div[@class='table_cell_wrapper']"
"/div[@class='table_cell_data_wrapper']")
@@ -1703,7 +1722,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
# Check the presence of the important elements
td_element = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']")
cell_wrapper = td_element.find_element_by_class_name(
'table_cell_wrapper')
@@ -1720,13 +1739,13 @@ class SeleniumTests(test.SeleniumAdminTestCase):
wait.until(lambda x: self.selenium.find_element_by_name("name__1"))
# Click on cancel button
td_element = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']")
td_element.find_element_by_class_name('inline-edit-cancel').click()
# Cancel is via javascript, so it should be immediate
# Checking that tenant name is not changed
data_wrapper = self.selenium.find_element_by_xpath(
- "//td[@data-update-url='/admin/projects/?action=cell_update"
+ "//td[@data-update-url='/identity/?action=cell_update"
"&table=tenants&cell_name=name&obj_id=1']"
"/div[@class='table_cell_wrapper']"
"/div[@class='table_cell_data_wrapper']")
@@ -1768,7 +1787,7 @@ class SeleniumTests(test.SeleniumAdminTestCase):
self.mox.ReplayAll()
self.selenium.get("%s%s" % (self.live_server_url,
- reverse('horizon:admin:projects:create')))
+ reverse('horizon:identity:projects:create')))
members = self.selenium.find_element_by_css_selector(member_css_class)
diff --git a/openstack_dashboard/dashboards/admin/projects/urls.py b/openstack_dashboard/dashboards/identity/projects/urls.py
similarity index 94%
rename from openstack_dashboard/dashboards/admin/projects/urls.py
rename to openstack_dashboard/dashboards/identity/projects/urls.py
index a0853d5ece..9b58cc0baa 100644
--- a/openstack_dashboard/dashboards/admin/projects/urls.py
+++ b/openstack_dashboard/dashboards/identity/projects/urls.py
@@ -19,7 +19,7 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
-from openstack_dashboard.dashboards.admin.projects import views
+from openstack_dashboard.dashboards.identity.projects import views
urlpatterns = patterns('',
diff --git a/openstack_dashboard/dashboards/admin/projects/views.py b/openstack_dashboard/dashboards/identity/projects/views.py
similarity index 80%
rename from openstack_dashboard/dashboards/admin/projects/views.py
rename to openstack_dashboard/dashboards/identity/projects/views.py
index 60f657f068..49fce4ecdb 100644
--- a/openstack_dashboard/dashboards/admin/projects/views.py
+++ b/openstack_dashboard/dashboards/identity/projects/views.py
@@ -20,18 +20,20 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
+from horizon import messages
from horizon import tables
from horizon.utils import memoized
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.api import keystone
+from openstack_dashboard import policy
from openstack_dashboard import usage
from openstack_dashboard.usage import quotas
-from openstack_dashboard.dashboards.admin.projects \
+from openstack_dashboard.dashboards.identity.projects \
import tables as project_tables
-from openstack_dashboard.dashboards.admin.projects \
+from openstack_dashboard.dashboards.identity.projects \
import workflows as project_workflows
from openstack_dashboard.dashboards.project.overview \
import views as project_views
@@ -42,7 +44,7 @@ PROJECT_INFO_FIELDS = ("domain_id",
"description",
"enabled")
-INDEX_URL = "horizon:admin:projects:index"
+INDEX_URL = "horizon:identity:projects:index"
class TenantContextMixin(object):
@@ -64,7 +66,7 @@ class TenantContextMixin(object):
class IndexView(tables.DataTableView):
table_class = project_tables.TenantsTable
- template_name = 'admin/projects/index.html'
+ template_name = 'identity/projects/index.html'
def has_more_data(self, table):
return self._more
@@ -74,23 +76,43 @@ class IndexView(tables.DataTableView):
marker = self.request.GET.get(
project_tables.TenantsTable._meta.pagination_param, None)
domain_context = self.request.session.get('domain_context', None)
- try:
- tenants, self._more = api.keystone.tenant_list(
- self.request,
- domain=domain_context,
- paginate=True,
- marker=marker)
- except Exception:
+ if policy.check((("identity", "identity:list_projects"),),
+ self.request):
+ try:
+ tenants, self._more = api.keystone.tenant_list(
+ self.request,
+ domain=domain_context,
+ paginate=True,
+ marker=marker)
+ except Exception:
+ self._more = False
+ exceptions.handle(self.request,
+ _("Unable to retrieve project list."))
+ elif policy.check((("identity", "identity:list_user_projects"),),
+ self.request):
+ try:
+ tenants, self._more = api.keystone.tenant_list(
+ self.request,
+ user=self.request.user.id,
+ paginate=True,
+ marker=marker,
+ admin=False)
+ except Exception:
+ self._more = False
+ exceptions.handle(self.request,
+ _("Unable to retrieve project information."))
+ else:
self._more = False
- exceptions.handle(self.request,
- _("Unable to retrieve project list."))
+ msg = \
+ _("Insufficient privilege level to view project information.")
+ messages.info(self.request, msg)
return tenants
class ProjectUsageView(usage.UsageView):
table_class = usage.ProjectUsageTable
usage_class = usage.ProjectUsage
- template_name = 'admin/projects/usage.html'
+ template_name = 'identity/projects/usage.html'
csv_response_class = project_views.ProjectUsageCsvRenderer
csv_template_name = 'project/overview/usage.csv'
diff --git a/openstack_dashboard/dashboards/admin/projects/workflows.py b/openstack_dashboard/dashboards/identity/projects/workflows.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/projects/workflows.py
rename to openstack_dashboard/dashboards/identity/projects/workflows.py
index a3eefef9ee..7d18db0720 100644
--- a/openstack_dashboard/dashboards/admin/projects/workflows.py
+++ b/openstack_dashboard/dashboards/identity/projects/workflows.py
@@ -33,8 +33,8 @@ from openstack_dashboard.api import keystone
from openstack_dashboard.api import nova
from openstack_dashboard.usage import quotas
-INDEX_URL = "horizon:admin:projects:index"
-ADD_USER_URL = "horizon:admin:projects:create_user"
+INDEX_URL = "horizon:identity:projects:index"
+ADD_USER_URL = "horizon:identity:projects:create_user"
PROJECT_GROUP_ENABLED = keystone.VERSIONS.active >= 3
PROJECT_USER_MEMBER_SLUG = "update_members"
PROJECT_GROUP_MEMBER_SLUG = "update_group_members"
@@ -340,7 +340,7 @@ class CreateProject(workflows.Workflow):
finalize_button_name = _("Create Project")
success_message = _('Created new project "%s".')
failure_message = _('Unable to create project "%s".')
- success_url = "horizon:admin:projects:index"
+ success_url = "horizon:identity:projects:index"
default_steps = (CreateProjectInfo,
UpdateProjectMembers,
UpdateProjectQuota)
@@ -493,7 +493,7 @@ class UpdateProject(workflows.Workflow):
finalize_button_name = _("Save")
success_message = _('Modified project "%s".')
failure_message = _('Unable to modify project "%s".')
- success_url = "horizon:admin:projects:index"
+ success_url = "horizon:identity:projects:index"
default_steps = (UpdateProjectInfo,
UpdateProjectMembers,
UpdateProjectQuota)
diff --git a/openstack_dashboard/dashboards/admin/users/__init__.py b/openstack_dashboard/dashboards/identity/roles/__init__.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/users/__init__.py
rename to openstack_dashboard/dashboards/identity/roles/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/roles/forms.py b/openstack_dashboard/dashboards/identity/roles/forms.py
similarity index 100%
rename from openstack_dashboard/dashboards/admin/roles/forms.py
rename to openstack_dashboard/dashboards/identity/roles/forms.py
diff --git a/openstack_dashboard/dashboards/admin/roles/panel.py b/openstack_dashboard/dashboards/identity/roles/panel.py
similarity index 84%
rename from openstack_dashboard/dashboards/admin/roles/panel.py
rename to openstack_dashboard/dashboards/identity/roles/panel.py
index 5377fdbdff..cc1a2a3e15 100644
--- a/openstack_dashboard/dashboards/admin/roles/panel.py
+++ b/openstack_dashboard/dashboards/identity/roles/panel.py
@@ -17,12 +17,14 @@ from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.api import keystone
-from openstack_dashboard.dashboards.admin import dashboard
+from openstack_dashboard.dashboards.identity import dashboard
class Roles(horizon.Panel):
name = _("Roles")
slug = 'roles'
+ policy_rules = (("identity", "identity:list_roles"),)
+
if keystone.VERSIONS.active >= 3:
- dashboard.Admin.register(Roles)
+ dashboard.Identity.register(Roles)
diff --git a/openstack_dashboard/dashboards/admin/roles/tables.py b/openstack_dashboard/dashboards/identity/roles/tables.py
similarity index 96%
rename from openstack_dashboard/dashboards/admin/roles/tables.py
rename to openstack_dashboard/dashboards/identity/roles/tables.py
index e09e8bad52..4296fbfaf6 100644
--- a/openstack_dashboard/dashboards/admin/roles/tables.py
+++ b/openstack_dashboard/dashboards/identity/roles/tables.py
@@ -22,7 +22,7 @@ from openstack_dashboard import api
class CreateRoleLink(tables.LinkAction):
name = "create"
verbose_name = _("Create Role")
- url = "horizon:admin:roles:create"
+ url = "horizon:identity:roles:create"
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("identity", "identity:create_role"),)
@@ -34,7 +34,7 @@ class CreateRoleLink(tables.LinkAction):
class EditRoleLink(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
- url = "horizon:admin:roles:update"
+ url = "horizon:identity:roles:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("identity", "identity:update_role"),)
diff --git a/openstack_dashboard/dashboards/admin/roles/templates/roles/_create.html b/openstack_dashboard/dashboards/identity/roles/templates/roles/_create.html
similarity index 75%
rename from openstack_dashboard/dashboards/admin/roles/templates/roles/_create.html
rename to openstack_dashboard/dashboards/identity/roles/templates/roles/_create.html
index aba973e897..58c82394ba 100644
--- a/openstack_dashboard/dashboards/admin/roles/templates/roles/_create.html
+++ b/openstack_dashboard/dashboards/identity/roles/templates/roles/_create.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}create_role_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:roles:create' %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:roles:create' %}{% endblock %}
{% block modal-header %}{% trans "Create Role" %}{% endblock %}
@@ -21,5 +21,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/roles/templates/roles/_update.html b/openstack_dashboard/dashboards/identity/roles/templates/roles/_update.html
similarity index 74%
rename from openstack_dashboard/dashboards/admin/roles/templates/roles/_update.html
rename to openstack_dashboard/dashboards/identity/roles/templates/roles/_update.html
index 7c1b183232..8dbb0bdad4 100644
--- a/openstack_dashboard/dashboards/admin/roles/templates/roles/_update.html
+++ b/openstack_dashboard/dashboards/identity/roles/templates/roles/_update.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}update_role_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:roles:update' role.id %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:roles:update' role.id %}{% endblock %}
{% block modal-header %}{% trans "Update Role" %}{% endblock %}
@@ -21,5 +21,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/roles/templates/roles/create.html b/openstack_dashboard/dashboards/identity/roles/templates/roles/create.html
similarity index 87%
rename from openstack_dashboard/dashboards/admin/roles/templates/roles/create.html
rename to openstack_dashboard/dashboards/identity/roles/templates/roles/create.html
index 42835afeac..9447ab2e0a 100644
--- a/openstack_dashboard/dashboards/admin/roles/templates/roles/create.html
+++ b/openstack_dashboard/dashboards/identity/roles/templates/roles/create.html
@@ -8,5 +8,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/roles/_create.html' %}
+ {% include 'identity/roles/_create.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/roles/templates/roles/index.html b/openstack_dashboard/dashboards/identity/roles/templates/roles/index.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/roles/templates/roles/index.html
rename to openstack_dashboard/dashboards/identity/roles/templates/roles/index.html
diff --git a/openstack_dashboard/dashboards/admin/roles/templates/roles/update.html b/openstack_dashboard/dashboards/identity/roles/templates/roles/update.html
similarity index 87%
rename from openstack_dashboard/dashboards/admin/roles/templates/roles/update.html
rename to openstack_dashboard/dashboards/identity/roles/templates/roles/update.html
index 233d54cd63..b64a3ec9d9 100644
--- a/openstack_dashboard/dashboards/admin/roles/templates/roles/update.html
+++ b/openstack_dashboard/dashboards/identity/roles/templates/roles/update.html
@@ -8,5 +8,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/roles/_update.html' %}
+ {% include 'identity/roles/_update.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/roles/tests.py b/openstack_dashboard/dashboards/identity/roles/tests.py
similarity index 92%
rename from openstack_dashboard/dashboards/admin/roles/tests.py
rename to openstack_dashboard/dashboards/identity/roles/tests.py
index 9d2d94ec3c..26dafed16d 100644
--- a/openstack_dashboard/dashboards/admin/roles/tests.py
+++ b/openstack_dashboard/dashboards/identity/roles/tests.py
@@ -22,9 +22,9 @@ from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
-ROLES_INDEX_URL = reverse('horizon:admin:roles:index')
-ROLES_CREATE_URL = reverse('horizon:admin:roles:create')
-ROLES_UPDATE_URL = reverse('horizon:admin:roles:update', args=[1])
+ROLES_INDEX_URL = reverse('horizon:identity:roles:index')
+ROLES_CREATE_URL = reverse('horizon:identity:roles:create')
+ROLES_UPDATE_URL = reverse('horizon:identity:roles:update', args=[1])
class RolesViewTests(test.BaseAdminViewTests):
@@ -39,7 +39,7 @@ class RolesViewTests(test.BaseAdminViewTests):
self.assertContains(res, 'Edit')
self.assertContains(res, 'Delete Role')
- self.assertTemplateUsed(res, 'admin/roles/index.html')
+ self.assertTemplateUsed(res, 'identity/roles/index.html')
self.assertItemsEqual(res.context['table'].data, self.roles.list())
@test.create_stubs({api.keystone: ('role_list',
@@ -56,7 +56,7 @@ class RolesViewTests(test.BaseAdminViewTests):
self.assertNotContains(res, 'Edit')
self.assertNotContains(res, 'Delete Role')
- self.assertTemplateUsed(res, 'admin/roles/index.html')
+ self.assertTemplateUsed(res, 'identity/roles/index.html')
self.assertItemsEqual(res.context['table'].data, self.roles.list())
@test.create_stubs({api.keystone: ('role_create', )})
diff --git a/openstack_dashboard/dashboards/admin/roles/urls.py b/openstack_dashboard/dashboards/identity/roles/urls.py
similarity index 86%
rename from openstack_dashboard/dashboards/admin/roles/urls.py
rename to openstack_dashboard/dashboards/identity/roles/urls.py
index 72f4b2cea8..0e5e0b6371 100644
--- a/openstack_dashboard/dashboards/admin/roles/urls.py
+++ b/openstack_dashboard/dashboards/identity/roles/urls.py
@@ -15,9 +15,9 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
-from openstack_dashboard.dashboards.admin.roles import views
+from openstack_dashboard.dashboards.identity.roles import views
-urlpatterns = patterns('openstack_dashboard.dashboards.admin.roles.views',
+urlpatterns = patterns('openstack_dashboard.dashboards.identity.roles.views',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P[^/]+)/update/$',
views.UpdateView.as_view(), name='update'),
diff --git a/openstack_dashboard/dashboards/admin/roles/views.py b/openstack_dashboard/dashboards/identity/roles/views.py
similarity index 66%
rename from openstack_dashboard/dashboards/admin/roles/views.py
rename to openstack_dashboard/dashboards/identity/roles/views.py
index a386727504..9f6e2d539a 100644
--- a/openstack_dashboard/dashboards/admin/roles/views.py
+++ b/openstack_dashboard/dashboards/identity/roles/views.py
@@ -18,42 +18,49 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
+from horizon import messages
from horizon import tables
from horizon.utils import memoized
from openstack_dashboard import api
+from openstack_dashboard import policy
-from openstack_dashboard.dashboards.admin.roles \
+from openstack_dashboard.dashboards.identity.roles \
import forms as project_forms
-from openstack_dashboard.dashboards.admin.roles \
+from openstack_dashboard.dashboards.identity.roles \
import tables as project_tables
class IndexView(tables.DataTableView):
table_class = project_tables.RolesTable
- template_name = 'admin/roles/index.html'
+ template_name = 'identity/roles/index.html'
def get_data(self):
roles = []
- try:
- roles = api.keystone.role_list(self.request)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve roles list.'))
+ if policy.check((("identity", "identity:list_roles"),),
+ self.request):
+ try:
+ roles = api.keystone.role_list(self.request)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve roles list.'))
+ else:
+ msg = _("Insufficient privilege level to view role information.")
+ messages.info(self.request, msg)
return roles
class UpdateView(forms.ModalFormView):
form_class = project_forms.UpdateRoleForm
- template_name = 'admin/roles/update.html'
- success_url = reverse_lazy('horizon:admin:roles:index')
+ template_name = 'identity/roles/update.html'
+ success_url = reverse_lazy('horizon:identity:roles:index')
@memoized.memoized_method
def get_object(self):
try:
return api.keystone.role_get(self.request, self.kwargs['role_id'])
except Exception:
- redirect = reverse("horizon:admin:roles:index")
+ redirect = reverse("horizon:identity:roles:index")
exceptions.handle(self.request,
_('Unable to update role.'),
redirect=redirect)
@@ -71,5 +78,5 @@ class UpdateView(forms.ModalFormView):
class CreateView(forms.ModalFormView):
form_class = project_forms.CreateRoleForm
- template_name = 'admin/roles/create.html'
- success_url = reverse_lazy('horizon:admin:roles:index')
+ template_name = 'identity/roles/create.html'
+ success_url = reverse_lazy('horizon:identity:roles:index')
diff --git a/openstack_dashboard/dashboards/identity/users/__init__.py b/openstack_dashboard/dashboards/identity/users/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openstack_dashboard/dashboards/admin/users/forms.py b/openstack_dashboard/dashboards/identity/users/forms.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/users/forms.py
rename to openstack_dashboard/dashboards/identity/users/forms.py
index 2debd121cf..b488df5fab 100644
--- a/openstack_dashboard/dashboards/admin/users/forms.py
+++ b/openstack_dashboard/dashboards/identity/users/forms.py
@@ -67,7 +67,7 @@ class BaseUserForm(forms.SelfHandlingForm):
return data
-ADD_PROJECT_URL = "horizon:admin:projects:create"
+ADD_PROJECT_URL = "horizon:identity:projects:create"
class CreateUserForm(BaseUserForm):
diff --git a/openstack_dashboard/dashboards/admin/users/panel.py b/openstack_dashboard/dashboards/identity/users/panel.py
similarity index 81%
rename from openstack_dashboard/dashboards/admin/users/panel.py
rename to openstack_dashboard/dashboards/identity/users/panel.py
index 431ba73a73..465994c573 100644
--- a/openstack_dashboard/dashboards/admin/users/panel.py
+++ b/openstack_dashboard/dashboards/identity/users/panel.py
@@ -20,12 +20,14 @@ from django.utils.translation import ugettext_lazy as _
import horizon
-from openstack_dashboard.dashboards.admin import dashboard
+from openstack_dashboard.dashboards.identity import dashboard
class Users(horizon.Panel):
name = _("Users")
slug = 'users'
+ policy_rules = (("identity", "identity:get_user"),
+ ("identity", "identity:list_users"))
-dashboard.Admin.register(Users)
+dashboard.Identity.register(Users)
diff --git a/openstack_dashboard/dashboards/admin/users/tables.py b/openstack_dashboard/dashboards/identity/users/tables.py
similarity index 98%
rename from openstack_dashboard/dashboards/admin/users/tables.py
rename to openstack_dashboard/dashboards/identity/users/tables.py
index 0ac4cbc918..5fc0ac5d10 100644
--- a/openstack_dashboard/dashboards/admin/users/tables.py
+++ b/openstack_dashboard/dashboards/identity/users/tables.py
@@ -26,7 +26,7 @@ DISABLE = 1
class CreateUserLink(tables.LinkAction):
name = "create"
verbose_name = _("Create User")
- url = "horizon:admin:users:create"
+ url = "horizon:identity:users:create"
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (('identity', 'identity:create_grant'),
@@ -41,7 +41,7 @@ class CreateUserLink(tables.LinkAction):
class EditUserLink(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
- url = "horizon:admin:users:update"
+ url = "horizon:identity:users:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("identity", "identity:update_user"),
diff --git a/openstack_dashboard/dashboards/admin/users/templates/users/_create.html b/openstack_dashboard/dashboards/identity/users/templates/users/_create.html
similarity index 80%
rename from openstack_dashboard/dashboards/admin/users/templates/users/_create.html
rename to openstack_dashboard/dashboards/identity/users/templates/users/_create.html
index 13488a8dc7..3ab96e9c07 100644
--- a/openstack_dashboard/dashboards/admin/users/templates/users/_create.html
+++ b/openstack_dashboard/dashboards/identity/users/templates/users/_create.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}create_user_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:users:create' %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:users:create' %}{% endblock %}
{% block modal-header %}{% trans "Create User" %}{% endblock %}
@@ -31,5 +31,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/users/templates/users/_update.html b/openstack_dashboard/dashboards/identity/users/templates/users/_update.html
similarity index 80%
rename from openstack_dashboard/dashboards/admin/users/templates/users/_update.html
rename to openstack_dashboard/dashboards/identity/users/templates/users/_update.html
index 652fd52435..736dcd71d7 100644
--- a/openstack_dashboard/dashboards/admin/users/templates/users/_update.html
+++ b/openstack_dashboard/dashboards/identity/users/templates/users/_update.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}update_user_form{% endblock %}
-{% block form_action %}{% url 'horizon:admin:users:update' user.id %}{% endblock %}
+{% block form_action %}{% url 'horizon:identity:users:update' user.id %}{% endblock %}
{% block modal-header %}{% trans "Update User" %}{% endblock %}
@@ -31,5 +31,5 @@
{% block modal-footer %}
- {% trans "Cancel" %}
+ {% trans "Cancel" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/users/templates/users/create.html b/openstack_dashboard/dashboards/identity/users/templates/users/create.html
similarity index 87%
rename from openstack_dashboard/dashboards/admin/users/templates/users/create.html
rename to openstack_dashboard/dashboards/identity/users/templates/users/create.html
index 6daa711704..5b1644a73b 100644
--- a/openstack_dashboard/dashboards/admin/users/templates/users/create.html
+++ b/openstack_dashboard/dashboards/identity/users/templates/users/create.html
@@ -8,5 +8,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/users/_create.html' %}
+ {% include 'identity/users/_create.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/users/templates/users/index.html b/openstack_dashboard/dashboards/identity/users/templates/users/index.html
similarity index 100%
rename from openstack_dashboard/dashboards/admin/users/templates/users/index.html
rename to openstack_dashboard/dashboards/identity/users/templates/users/index.html
diff --git a/openstack_dashboard/dashboards/admin/users/templates/users/update.html b/openstack_dashboard/dashboards/identity/users/templates/users/update.html
similarity index 87%
rename from openstack_dashboard/dashboards/admin/users/templates/users/update.html
rename to openstack_dashboard/dashboards/identity/users/templates/users/update.html
index e9bac52a23..e689fd3840 100644
--- a/openstack_dashboard/dashboards/admin/users/templates/users/update.html
+++ b/openstack_dashboard/dashboards/identity/users/templates/users/update.html
@@ -8,5 +8,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/users/_update.html' %}
+ {% include 'identity/users/_update.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/users/tests.py b/openstack_dashboard/dashboards/identity/users/tests.py
similarity index 99%
rename from openstack_dashboard/dashboards/admin/users/tests.py
rename to openstack_dashboard/dashboards/identity/users/tests.py
index 73ddd3a736..930d9c24af 100644
--- a/openstack_dashboard/dashboards/admin/users/tests.py
+++ b/openstack_dashboard/dashboards/identity/users/tests.py
@@ -28,9 +28,9 @@ from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
-USERS_INDEX_URL = reverse('horizon:admin:users:index')
-USER_CREATE_URL = reverse('horizon:admin:users:create')
-USER_UPDATE_URL = reverse('horizon:admin:users:update', args=[1])
+USERS_INDEX_URL = reverse('horizon:identity:users:index')
+USER_CREATE_URL = reverse('horizon:identity:users:create')
+USER_UPDATE_URL = reverse('horizon:identity:users:update', args=[1])
class UsersViewTests(test.BaseAdminViewTests):
@@ -59,7 +59,7 @@ class UsersViewTests(test.BaseAdminViewTests):
self.mox.ReplayAll()
res = self.client.get(USERS_INDEX_URL)
- self.assertTemplateUsed(res, 'admin/users/index.html')
+ self.assertTemplateUsed(res, 'identity/users/index.html')
self.assertItemsEqual(res.context['table'].data, users)
if domain_id:
diff --git a/openstack_dashboard/dashboards/admin/users/urls.py b/openstack_dashboard/dashboards/identity/users/urls.py
similarity index 88%
rename from openstack_dashboard/dashboards/admin/users/urls.py
rename to openstack_dashboard/dashboards/identity/users/urls.py
index a5ae47f99e..2143390ad2 100644
--- a/openstack_dashboard/dashboards/admin/users/urls.py
+++ b/openstack_dashboard/dashboards/identity/users/urls.py
@@ -19,9 +19,9 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
-from openstack_dashboard.dashboards.admin.users import views
+from openstack_dashboard.dashboards.identity.users import views
-urlpatterns = patterns('openstack_dashboard.dashboards.admin.users.views',
+urlpatterns = patterns('openstack_dashboard.dashboards.identity.users.views',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P[^/]+)/update/$',
views.UpdateView.as_view(), name='update'),
diff --git a/openstack_dashboard/dashboards/admin/users/views.py b/openstack_dashboard/dashboards/identity/users/views.py
similarity index 73%
rename from openstack_dashboard/dashboards/admin/users/views.py
rename to openstack_dashboard/dashboards/identity/users/views.py
index abba0786f8..7acb251034 100644
--- a/openstack_dashboard/dashboards/admin/users/views.py
+++ b/openstack_dashboard/dashboards/identity/users/views.py
@@ -26,37 +26,53 @@ from django.views.decorators.debug import sensitive_post_parameters # noqa
from horizon import exceptions
from horizon import forms
+from horizon import messages
from horizon import tables
from horizon.utils import memoized
from openstack_dashboard import api
+from openstack_dashboard import policy
-from openstack_dashboard.dashboards.admin.users \
+from openstack_dashboard.dashboards.identity.users \
import forms as project_forms
-from openstack_dashboard.dashboards.admin.users \
+from openstack_dashboard.dashboards.identity.users \
import tables as project_tables
class IndexView(tables.DataTableView):
table_class = project_tables.UsersTable
- template_name = 'admin/users/index.html'
+ template_name = 'identity/users/index.html'
def get_data(self):
users = []
domain_context = self.request.session.get('domain_context', None)
- try:
- users = api.keystone.user_list(self.request,
- domain=domain_context)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve user list.'))
+ if policy.check((("identity", "identity:list_users"),),
+ self.request):
+ try:
+ users = api.keystone.user_list(self.request,
+ domain=domain_context)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve user list.'))
+ elif policy.check((("identity", "identity:get_user"),),
+ self.request):
+ try:
+ user = api.keystone.user_get(self.request,
+ self.request.user.id)
+ users.append(user)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve user information.'))
+ else:
+ msg = _("Insufficient privilege level to view user information.")
+ messages.info(self.request, msg)
return users
class UpdateView(forms.ModalFormView):
form_class = project_forms.UpdateUserForm
- template_name = 'admin/users/update.html'
- success_url = reverse_lazy('horizon:admin:users:index')
+ template_name = 'identity/users/update.html'
+ success_url = reverse_lazy('horizon:identity:users:index')
@method_decorator(sensitive_post_parameters('password',
'confirm_password'))
@@ -69,7 +85,7 @@ class UpdateView(forms.ModalFormView):
return api.keystone.user_get(self.request, self.kwargs['user_id'],
admin=True)
except Exception:
- redirect = reverse("horizon:admin:users:index")
+ redirect = reverse("horizon:identity:users:index")
exceptions.handle(self.request,
_('Unable to update user.'),
redirect=redirect)
@@ -102,8 +118,8 @@ class UpdateView(forms.ModalFormView):
class CreateView(forms.ModalFormView):
form_class = project_forms.CreateUserForm
- template_name = 'admin/users/create.html'
- success_url = reverse_lazy('horizon:admin:users:index')
+ template_name = 'identity/users/create.html'
+ success_url = reverse_lazy('horizon:identity:users:index')
@method_decorator(sensitive_post_parameters('password',
'confirm_password'))
@@ -115,7 +131,7 @@ class CreateView(forms.ModalFormView):
try:
roles = api.keystone.role_list(self.request)
except Exception:
- redirect = reverse("horizon:admin:users:index")
+ redirect = reverse("horizon:identity:users:index")
exceptions.handle(self.request,
_("Unable to retrieve user roles."),
redirect=redirect)
diff --git a/openstack_dashboard/enabled/_25_identity.py b/openstack_dashboard/enabled/_25_identity.py
new file mode 100644
index 0000000000..c61a833ecb
--- /dev/null
+++ b/openstack_dashboard/enabled/_25_identity.py
@@ -0,0 +1,8 @@
+# The name of the dashboard to be added to HORIZON['dashboards']. Required.
+DASHBOARD = 'identity'
+# If set to True, this dashboard will be set as the default dashboard.
+DEFAULT = False
+# A dictionary of exception classes to be added to HORIZON['exceptions'].
+ADD_EXCEPTIONS = {}
+# A list of applications to be added to INSTALLED_APPS.
+ADD_INSTALLED_APPS = ['openstack_dashboard.dashboards.identity']
diff --git a/openstack_dashboard/policy.py b/openstack_dashboard/policy.py
index f51cde99fe..ae7b48600e 100644
--- a/openstack_dashboard/policy.py
+++ b/openstack_dashboard/policy.py
@@ -110,6 +110,9 @@ def check(actions, request, target={}):
# same for user_id
if target.get('user_id') is None:
target['user_id'] = user.id
+ # same for domain_id
+ if target.get('domain_id') is None:
+ target['domain_id'] = user.domain_id
credentials = _user_to_credentials(request, user)
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
index 6c9bcd9a54..ee6a8858c4 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
@@ -56,7 +56,7 @@ STATIC_URL = '/static/'
ROOT_URLCONF = 'openstack_dashboard.urls'
HORIZON_CONFIG = {
- 'dashboards': ('project', 'admin', 'settings', 'router',),
+ 'dashboards': ('project', 'admin', 'router',),
'default_dashboard': 'project',
'user_home': 'openstack_dashboard.views.get_user_home',
'ajax_queue_limit': 10,
diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py
index a827414dd6..bb33eb4d0e 100644
--- a/openstack_dashboard/test/settings.py
+++ b/openstack_dashboard/test/settings.py
@@ -45,6 +45,7 @@ INSTALLED_APPS = (
'openstack_dashboard',
'openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
+ 'openstack_dashboard.dashboards.identity',
'openstack_dashboard.dashboards.settings',
'openstack_dashboard.dashboards.router',
)
@@ -54,7 +55,7 @@ AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
SITE_BRANDING = 'OpenStack'
HORIZON_CONFIG = {
- 'dashboards': ('project', 'admin', 'settings', 'router',),
+ 'dashboards': ('project', 'admin', 'identity', 'settings', 'router',),
'default_dashboard': 'project',
"password_validator": {
"regex": '^.{8,18}$',