Allow users to change their passwords
Add change password panel on settings dashboard to enable users to change their passwords Change-Id: Ibfea2592e13aab3cc4892dce77ab62dcba65eacc Implements: blueprint change-user-passwords
This commit is contained in:
parent
ff573dae88
commit
2a97ce9602
@ -163,6 +163,7 @@ def keystoneclient(request, admin=False):
|
||||
endpoint=endpoint,
|
||||
original_ip=remote_addr,
|
||||
insecure=insecure,
|
||||
auth_url=endpoint,
|
||||
debug=settings.DEBUG)
|
||||
setattr(request, cache_attr, conn)
|
||||
return conn
|
||||
@ -314,6 +315,15 @@ def user_update_password(request, user, password, admin=True):
|
||||
return manager.update(user, password=password)
|
||||
|
||||
|
||||
def user_update_own_password(request, origpassword, password):
|
||||
client = keystoneclient(request, admin=False)
|
||||
if VERSIONS.active < 3:
|
||||
client.user_id = request.user.id
|
||||
return client.users.update_own_password(origpassword, password)
|
||||
else:
|
||||
return client.users.update(request.user.id, password=password)
|
||||
|
||||
|
||||
def user_update_tenant(request, user, project, admin=True):
|
||||
manager = keystoneclient(request, admin=admin).users
|
||||
if VERSIONS.active < 3:
|
||||
|
@ -23,7 +23,7 @@ import horizon
|
||||
class Settings(horizon.Dashboard):
|
||||
name = _("Settings")
|
||||
slug = "settings"
|
||||
panels = ('user',)
|
||||
panels = ('user', 'password', )
|
||||
default_panel = 'user'
|
||||
nav = False
|
||||
|
||||
|
68
openstack_dashboard/dashboards/settings/password/forms.py
Normal file
68
openstack_dashboard/dashboards/settings/password/forms.py
Normal file
@ -0,0 +1,68 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Centrin Data Systems Ltd.
|
||||
#
|
||||
# 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 _
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError
|
||||
from django.views.decorators.debug import sensitive_variables
|
||||
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon import exceptions
|
||||
from horizon.utils import validators
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
class PasswordForm(forms.SelfHandlingForm):
|
||||
current_password = forms.CharField(label=_("Current password"),
|
||||
widget=forms.PasswordInput(render_value=False))
|
||||
new_password = forms.RegexField(label=_("New password"),
|
||||
widget=forms.PasswordInput(render_value=False),
|
||||
regex=validators.password_validator(),
|
||||
error_messages={'invalid':
|
||||
validators.password_validator_msg()})
|
||||
confirm_password = forms.CharField(label=_("Confirm new password"),
|
||||
widget=forms.PasswordInput(render_value=False))
|
||||
|
||||
def clean(self):
|
||||
'''Check to make sure password fields match.'''
|
||||
data = super(forms.Form, self).clean()
|
||||
if 'new_password' in data:
|
||||
if data['new_password'] != data.get('confirm_password', None):
|
||||
raise ValidationError(_('Passwords do not match.'))
|
||||
return data
|
||||
|
||||
# We have to protect the entire "data" dict because it contains the
|
||||
# oldpassword and newpassword strings.
|
||||
@sensitive_variables('data')
|
||||
def handle(self, request, data):
|
||||
user_is_editable = api.keystone.keystone_can_edit_user()
|
||||
|
||||
if user_is_editable:
|
||||
try:
|
||||
passwd = api.keystone.user_update_own_password(request,
|
||||
data['current_password'],
|
||||
data['new_password'])
|
||||
messages.success(request, _('Password changed.'))
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to change password.'))
|
||||
return False
|
||||
else:
|
||||
messages.error(request, _('Changing password is not supported.'))
|
||||
return False
|
||||
|
||||
return True
|
29
openstack_dashboard/dashboards/settings/password/panel.py
Normal file
29
openstack_dashboard/dashboards/settings/password/panel.py
Normal file
@ -0,0 +1,29 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Centrin Data Systems Ltd.
|
||||
#
|
||||
# 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
|
||||
|
||||
from openstack_dashboard.dashboards.settings import dashboard
|
||||
|
||||
|
||||
class PasswordPanel(horizon.Panel):
|
||||
name = _("Change Password")
|
||||
slug = 'password'
|
||||
|
||||
|
||||
dashboard.Settings.register(PasswordPanel)
|
@ -0,0 +1,27 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}change_password_modal{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:settings:password:index' %}{% endblock %}
|
||||
|
||||
{% block modal_id %}change_password_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "From here you can change your password. We highly recommend you create a strong one. " %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<button type="submit" class="btn btn-primary">{% trans "Change" %}</button>
|
||||
{% if hide %}<a href="{% url 'horizon:settings:password:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Change Password") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "settings/password/_change.html" %}
|
||||
{% endblock %}
|
57
openstack_dashboard/dashboards/settings/password/tests.py
Normal file
57
openstack_dashboard/dashboards/settings/password/tests.py
Normal file
@ -0,0 +1,57 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Centrin Data Systems Ltd.
|
||||
#
|
||||
# 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 import http
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from mox import IsA
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:settings:password:index')
|
||||
|
||||
|
||||
class ChangePasswordTests(test.TestCase):
|
||||
|
||||
@test.create_stubs({api.keystone: ('user_update_own_password', )})
|
||||
def test_change_password(self):
|
||||
api.keystone.user_update_own_password(IsA(http.HttpRequest),
|
||||
'oldpwd',
|
||||
'normalpwd',).AndReturn(None)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'method': 'PasswordForm',
|
||||
'current_password': 'oldpwd',
|
||||
'new_password': 'normalpwd',
|
||||
'confirm_password': 'normalpwd'}
|
||||
|
||||
res = self.client.post(INDEX_URL, formData)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
|
||||
def test_change_validation_passwords_not_matching(self):
|
||||
|
||||
formData = {'method': 'PasswordForm',
|
||||
'current_password': 'currpasswd',
|
||||
'new_password': 'testpassword',
|
||||
'confirm_password': 'doesnotmatch'}
|
||||
|
||||
res = self.client.post(INDEX_URL, formData)
|
||||
|
||||
self.assertFormError(res, "form", None, ['Passwords do not match.'])
|
23
openstack_dashboard/dashboards/settings/password/urls.py
Normal file
23
openstack_dashboard/dashboards/settings/password/urls.py
Normal file
@ -0,0 +1,23 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Centrin Data Systems Ltd.
|
||||
#
|
||||
# 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.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import PasswordView
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', PasswordView.as_view(), name='index'))
|
26
openstack_dashboard/dashboards/settings/password/views.py
Normal file
26
openstack_dashboard/dashboards/settings/password/views.py
Normal file
@ -0,0 +1,26 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Centrin Data Systems Ltd.
|
||||
#
|
||||
# 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 horizon import forms
|
||||
|
||||
from .forms import PasswordForm
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
|
||||
class PasswordView(forms.ModalFormView):
|
||||
form_class = PasswordForm
|
||||
template_name = 'settings/password/change.html'
|
||||
success_url = reverse_lazy('logout')
|
Loading…
Reference in New Issue
Block a user