From d4d81459a702503f59ad833302680da68c14c6aa Mon Sep 17 00:00:00 2001 From: Tres Henry Date: Thu, 8 Mar 2012 15:41:09 -0800 Subject: [PATCH] User crud no longer available in syspanel when Keystone is using something other than the native auth backend. Fixes bug: 948310 Change-Id: Ifebf0f5a60228c84d06f11e60f752f9ff474c929 --- horizon/api/keystone.py | 14 ++++++ .../dashboards/syspanel/services/tables.py | 14 ++++-- horizon/dashboards/syspanel/users/forms.py | 20 +++++++- horizon/dashboards/syspanel/users/tables.py | 11 +++-- horizon/dashboards/syspanel/users/tests.py | 48 +++++++++++++++++++ horizon/tests/testsettings.py | 5 ++ .../local/local_settings.py.example | 11 +++++ 7 files changed, 114 insertions(+), 9 deletions(-) diff --git a/horizon/api/keystone.py b/horizon/api/keystone.py index bde2d656bbc..d749a6796dd 100644 --- a/horizon/api/keystone.py +++ b/horizon/api/keystone.py @@ -268,3 +268,17 @@ def create_ec2_credentials(request, user_id, tenant_id): def get_user_ec2_credentials(request, user_id, access_token): return keystoneclient(request).ec2.get(user_id, access_token) + + +def keystone_can_edit_user(): + if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"): + return settings.OPENSTACK_KEYSTONE_BACKEND['can_edit_user'] + else: + return False + + +def keystone_backend_name(): + if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"): + return settings.OPENSTACK_KEYSTONE_BACKEND['name'] + else: + return 'unknown' diff --git a/horizon/dashboards/syspanel/services/tables.py b/horizon/dashboards/syspanel/services/tables.py index 4b6ba2ca599..4dce54d9b04 100644 --- a/horizon/dashboards/syspanel/services/tables.py +++ b/horizon/dashboards/syspanel/services/tables.py @@ -1,12 +1,10 @@ import logging -from django import shortcuts from django import template -from django.contrib import messages from django.utils.translation import ugettext_lazy as _ -from horizon import api from horizon import tables +from horizon import api LOG = logging.getLogger(__name__) @@ -36,9 +34,17 @@ def get_enabled(service, reverse=False): return options[0] if not service.disabled else options[1] +def get_service_name(service): + if(service.type == "identity"): + return _("%s (%s backend)") % (service.type, + api.keystone_backend_name()) + else: + return service.type + + class ServicesTable(tables.DataTable): id = tables.Column('id', verbose_name=_('Id'), hidden=True) - service = tables.Column('type', verbose_name=_('Service')) + service = tables.Column(get_service_name, verbose_name=_('Service')) host = tables.Column('host', verbose_name=_('Host')) enabled = tables.Column(get_enabled, verbose_name=_('Enabled'), diff --git a/horizon/dashboards/syspanel/users/forms.py b/horizon/dashboards/syspanel/users/forms.py index 78068283b81..76762e4ca81 100644 --- a/horizon/dashboards/syspanel/users/forms.py +++ b/horizon/dashboards/syspanel/users/forms.py @@ -23,6 +23,7 @@ from django import shortcuts from django.contrib import messages from django.utils.translation import ugettext as _ +from django.forms import ValidationError from horizon import api from horizon import exceptions @@ -46,13 +47,25 @@ def __init__(self, request, *args, **kwargs): def _instantiate(cls, request, *args, **kwargs): return cls(request, *args, **kwargs) + def clean(self): + '''Check to make sure password fields match.''' + super(forms.Form, self).clean() + if 'password' in self.cleaned_data and \ + 'confirm_password' in self.cleaned_data: + if self.cleaned_data['password'] != \ + self.cleaned_data['confirm_password']: + raise ValidationError(_('Passwords do not match.')) + return self.cleaned_data + class CreateUserForm(BaseUserForm): name = forms.CharField(label=_("Name")) email = forms.EmailField(label=_("Email")) password = forms.CharField(label=_("Password"), - widget=forms.PasswordInput(render_value=False), - required=False) + widget=forms.PasswordInput(render_value=False)) + confirm_password = forms.CharField( + label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False)) tenant_id = forms.ChoiceField(label=_("Primary Project")) def handle(self, request, data): @@ -90,6 +103,9 @@ class UpdateUserForm(BaseUserForm): password = forms.CharField(label=_("Password"), widget=forms.PasswordInput(render_value=False), required=False) + confirm_password = forms.CharField( + label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False)) tenant_id = forms.ChoiceField(label=_("Primary Project")) def handle(self, request, data): diff --git a/horizon/dashboards/syspanel/users/tables.py b/horizon/dashboards/syspanel/users/tables.py index 246e21b37bb..66ffc7a425e 100644 --- a/horizon/dashboards/syspanel/users/tables.py +++ b/horizon/dashboards/syspanel/users/tables.py @@ -131,6 +131,11 @@ class UsersTable(tables.DataTable): class Meta: name = "users" verbose_name = _("Users") - row_actions = (EditUserLink, EnableUsersAction, DisableUsersAction, - DeleteUsersAction) - table_actions = (UserFilterAction, CreateUserLink, DeleteUsersAction) + if api.keystone_can_edit_user(): + row_actions = (EditUserLink, EnableUsersAction, DisableUsersAction, + DeleteUsersAction) + table_actions = (UserFilterAction, CreateUserLink, + DeleteUsersAction) + else: + row_actions = (EnableUsersAction, DisableUsersAction) + table_actions = (UserFilterAction,) diff --git a/horizon/dashboards/syspanel/users/tests.py b/horizon/dashboards/syspanel/users/tests.py index 3004592e5ac..133020e7102 100644 --- a/horizon/dashboards/syspanel/users/tests.py +++ b/horizon/dashboards/syspanel/users/tests.py @@ -27,6 +27,7 @@ USERS_INDEX_URL = reverse('horizon:syspanel:users:index') +USER_CREATE_URL = reverse('horizon:syspanel:users:create') class UsersViewTests(test.BaseAdminViewTests): @@ -39,6 +40,53 @@ def test_index(self): self.assertTemplateUsed(res, 'syspanel/users/index.html') self.assertItemsEqual(res.context['table'].data, self.users.list()) + def test_create_user(self): + user = self.users.get(id="1") + role = self.roles.first() + self.mox.StubOutWithMock(api, 'user_create') + self.mox.StubOutWithMock(api, 'tenant_list') + self.mox.StubOutWithMock(api.keystone, 'get_default_role') + self.mox.StubOutWithMock(api, 'add_tenant_user_role') + api.tenant_list(IgnoreArg(), admin=True).AndReturn(self.tenants.list()) + api.user_create(IgnoreArg(), + user.name, + user.email, + user.password, + self.tenant.id, + True).AndReturn(user) + api.keystone.get_default_role(IgnoreArg()).AndReturn(role) + api.add_tenant_user_role(IgnoreArg(), self.tenant.id, user.id, role.id) + self.mox.ReplayAll() + + formData = {'method': 'CreateUserForm', + 'name': user.name, + 'email': user.email, + 'password': user.password, + 'tenant_id': self.tenant.id, + 'confirm_password': user.password} + res = self.client.post(USER_CREATE_URL, formData) + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + def test_create_user_password_mismatch(self): + user = self.users.get(id="1") + self.mox.StubOutWithMock(api, 'tenant_list') + api.tenant_list(IgnoreArg(), admin=True).AndReturn(self.tenants.list()) + self.mox.ReplayAll() + + formData = {'method': 'CreateUserForm', + 'name': user.name, + 'email': user.email, + 'password': user.password, + 'tenant_id': self.tenant.id, + 'confirm_password': "doesntmatch"} + + res = self.client.post(USER_CREATE_URL, formData) + self.assertFormError(res, + "form", + None, + ['Passwords do not match.']) + def test_enable_user(self): user = self.users.get(id="2") self.mox.StubOutWithMock(api.keystone, 'user_update_enabled') diff --git a/horizon/tests/testsettings.py b/horizon/tests/testsettings.py index 01761209d26..eb3776d7d02 100644 --- a/horizon/tests/testsettings.py +++ b/horizon/tests/testsettings.py @@ -102,6 +102,11 @@ OPENSTACK_KEYSTONE_ADMIN_URL = "http://%s:35357/v2.0" % OPENSTACK_ADDRESS OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member" +OPENSTACK_KEYSTONE_BACKEND = { + 'name': 'native', + 'can_edit_user': True +} + # Silence logging output during tests. LOGGING = { 'version': 1, diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 105a8abfcde..4871e20b08c 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -39,6 +39,17 @@ OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % OPENSTACK_HOST OPENSTACK_KEYSTONE_ADMIN_URL = "http://%s:35357/v2.0" % OPENSTACK_HOST OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member" +# The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the +# capabilities of the auth backend for Keystone. +# If Keystone has been configured to use LDAP as the auth backend then set +# can_edit_user to False and name to 'ldap'. +# +# TODO(tres): Remove these once Keystone has an API to identify auth backend. +OPENSTACK_KEYSTONE_BACKEND = { + 'name': 'native', + 'can_edit_user': True +} + # The number of Swift containers and objects to display on a single page before # providing a paging element (a "more" link) to paginate results. API_RESULT_LIMIT = 1000