BryanDavis has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/328622 )

Change subject: Allow changing LDAP password
......................................................................

Allow changing LDAP password

Bug: T153935
Change-Id: I04b1bf0b5cf7122e23364505c39689e9a691181e
---
M striker/labsauth/models.py
M striker/profile/forms.py
M striker/profile/urls.py
M striker/profile/views.py
M striker/templates/profile/settings/accounts/ldap.html
M striker/templates/profile/settings/base.html
A striker/templates/profile/settings/change-password.html
7 files changed, 140 insertions(+), 4 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/labs/striker 
refs/changes/22/328622/1

diff --git a/striker/labsauth/models.py b/striker/labsauth/models.py
index 88c8d8e..771e6a8 100644
--- a/striker/labsauth/models.py
+++ b/striker/labsauth/models.py
@@ -30,6 +30,7 @@
 from django.utils.translation import ugettext_lazy as _
 
 from ldapdb.models import fields as ldap_fields
+import ldap
 import ldapdb.models
 import mwoauth
 
@@ -109,12 +110,28 @@
         verbose_name_plural = _('users')
 
     def set_password(self, raw_password):
-        """Can't change password."""
-        return
+        # Set password by directly manipulating the associated LDAP record.
+        # The ldap-auth backend we use does not support password
+        # checks/changes.
+        ldapuser = self.ldapuser
+        ldapuser.password = raw_password
+        ldapuser.save()
 
     def check_password(self, raw_password):
-        """Can't be used directly for authn."""
-        return False
+        """Return a boolean of whether the raw_password was correct."""
+        # Validate the current password by doing an authbind as the user.
+        # The ldap-auth backend we use does not support password
+        # checks/changes.
+        try:
+            con = ldap.initialize(settings.AUTH_LDAP_SERVER_URI)
+            if settings.AUTH_LDAP_START_TLS:
+                con.start_tls_s()
+            con.simple_bind_s(self.ldap_dn, raw_password)
+        except ldap.INVALID_CREDENTIALS:
+            return False
+        else:
+            con.unbind()
+            return True
 
     def set_unusable_password(self):
         return
diff --git a/striker/profile/forms.py b/striker/profile/forms.py
index bfae5b4..3087835 100644
--- a/striker/profile/forms.py
+++ b/striker/profile/forms.py
@@ -19,7 +19,10 @@
 # along with Striker.  If not, see <http://www.gnu.org/licenses/>.
 
 from django import forms
+from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
+
+from parsley.decorators import parsleyfy
 
 from striker.profile import utils
 
@@ -78,3 +81,57 @@
                 _('Invalid public key.'), code='key_invalid')
         self.key = key
         return pub_key
+
+
+@parsleyfy
+class PasswordChangeForm(forms.Form):
+    class Meta:
+        parsley_extras = {
+            'confirm': {
+                'equalto': 'passwd',
+                'error-message': _('Passwords do not match.'),
+            }
+        }
+
+    old_password = forms.CharField(
+        label=_('Old password'),
+        widget=forms.PasswordInput(
+            attrs={
+                'autofocus': 'autofocus',
+            }
+        )
+    )
+    passwd = forms.CharField(
+        label=_('New password'),
+        min_length=10,
+        widget=forms.PasswordInput
+    )
+    confirm = forms.CharField(
+        label=_('Confirm new password'),
+        widget=forms.PasswordInput
+    )
+
+    def __init__(self, user=None, *args, **kwargs):
+        self.user = user
+        super(PasswordChangeForm, self).__init__(*args, **kwargs)
+
+    def clean_old_password(self):
+        old_password = self.cleaned_data.get('old_password')
+        if not self.user.check_password(old_password):
+            raise forms.ValidationError(
+                _('Your old password was entered incorrectly. '
+                  'Please enter it again.'),
+                code='password_incorrect')
+        return old_password
+
+    def clean_confirm(self):
+        """Validate that both password entries match."""
+        passwd = self.cleaned_data.get('passwd')
+        confirm = self.cleaned_data.get('confirm')
+        if passwd != confirm:
+            raise forms.ValidationError(_('Passwords do not match.'))
+        return confirm
+
+    def save(self):
+        self.user.set_password(self.cleaned_data['passwd'])
+        return self.user
diff --git a/striker/profile/urls.py b/striker/profile/urls.py
index e6a4b5a..8d21080 100644
--- a/striker/profile/urls.py
+++ b/striker/profile/urls.py
@@ -55,4 +55,9 @@
         'striker.profile.views.ssh_key_add',
         name='ssh_key_add'
     ),
+    urls.url(
+        r'^settings/change_password$',
+        'striker.profile.views.change_password',
+        name='change_password'
+    ),
 ]
diff --git a/striker/profile/views.py b/striker/profile/views.py
index 2f5c00e..9f84b04 100644
--- a/striker/profile/views.py
+++ b/striker/profile/views.py
@@ -26,6 +26,7 @@
 from django.contrib.auth.decorators import login_required
 from django.core import urlresolvers
 from django.db.utils import DatabaseError
+from django.views.decorators.debug import sensitive_post_parameters
 from django.utils.translation import ugettext_lazy as _
 
 from striker import decorators
@@ -124,3 +125,25 @@
         else:
             messages.error(req, _('Invalid public key.'))
     return shortcuts.redirect(urlresolvers.reverse('profile:ssh_keys'))
+
+
+@sensitive_post_parameters()
+@login_required
+def change_password(req):
+    if req.method == 'POST':
+        form = forms.PasswordChangeForm(data=req.POST, user=req.user)
+        if form.is_valid():
+            form.save()
+            messages.info(req, _('Password changed'))
+            # We do not need to mess with update_session_auth_hash because
+            # LDAP passwords are detached from the normal Django session
+            # management methods.
+            return shortcuts.redirect(
+                urlresolvers.reverse('profile:change_password'))
+    else:
+        form = forms.PasswordChangeForm(user=req.user)
+    ctx = {
+        'change_password_form': form,
+    }
+    return shortcuts.render(
+        req, 'profile/settings/change-password.html', ctx)
diff --git a/striker/templates/profile/settings/accounts/ldap.html 
b/striker/templates/profile/settings/accounts/ldap.html
index 60c0032..a51373f 100644
--- a/striker/templates/profile/settings/accounts/ldap.html
+++ b/striker/templates/profile/settings/accounts/ldap.html
@@ -12,4 +12,7 @@
       <dt>{% trans "Email" %}</dt><dd>{{ user.ldapemail }}</dd>
     </dl>
   </div>
+  <div class="panel-footer">
+    <a class="btn btn-default" href="{% url 'profile:change_password' %}">{% 
trans "Change password" %}</a>
+  </div>
 </div>
diff --git a/striker/templates/profile/settings/base.html 
b/striker/templates/profile/settings/base.html
index d84b878..750869c 100644
--- a/striker/templates/profile/settings/base.html
+++ b/striker/templates/profile/settings/base.html
@@ -6,6 +6,7 @@
 {% block pre_content %}
 {% url 'profile:accounts' as settings_accounts %}
 {% url 'profile:ssh_keys' as settings_ssh_keys %}
+{% url 'profile:change_password' as settings_change_password %}
 {{ block.super }}
 <div class="container-fluid">
   <div class="row">
@@ -17,6 +18,7 @@
         <div class="list-group" role="navigation">
             <a class="list-group-item {% if request.path == settings_accounts 
%}active{% endif %}" href="{{ settings_accounts }}">{% bootstrap_icon "user" %} 
{% trans "Linked accounts" %}</a>
             <a class="list-group-item {% if request.path == settings_ssh_keys 
%}active{% endif %}" href="{{ settings_ssh_keys }}">{% fa_icon "key" %} {% 
trans "SSH keys" %}</a>
+            <a class="list-group-item {% if request.path == 
settings_change_password %}active{% endif %}" href="{{ settings_change_password 
}}">{% bootstrap_icon "lock" %} {% trans "Change password" %}</a>
         </div>
       </div>
     </div>
diff --git a/striker/templates/profile/settings/change-password.html 
b/striker/templates/profile/settings/change-password.html
new file mode 100644
index 0000000..a59c121
--- /dev/null
+++ b/striker/templates/profile/settings/change-password.html
@@ -0,0 +1,29 @@
+{% extends "profile/settings/base.html" %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load staticfiles %}
+
+{% block title %}{% trans "Change password" %}{% endblock %}
+{% block content %}
+<div class="panel panel-default">
+  <div class="panel-heading">
+    <h3 class="panel-title">{% trans "Change LDAP password" %}</h3>
+  </div>
+  <div class="panel-body">
+    <form method="post" action="{% url 'profile:change_password' %}" 
class="form parsley">
+      {% csrf_token %}
+      {% bootstrap_form change_password_form %}
+      {% buttons %}
+      <button class="btn btn-primary" type="submit">{% trans "Update password" 
%}</button>
+      {% endbuttons %}
+    </form>
+  </div>
+</div>
+{% endblock %}
+
+{% block js %}
+{{ block.super }}
+<script lang="javascript" src="{% static 'js/parsley.min.js' %}"></script>
+<script lang="javascript" src="{% static 'js/parsley-bootstrap.js' 
%}"></script>
+{% endblock %}
+{# vim:sw=2:ts=2:sts=2:et: #}

-- 
To view, visit https://gerrit.wikimedia.org/r/328622
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I04b1bf0b5cf7122e23364505c39689e9a691181e
Gerrit-PatchSet: 1
Gerrit-Project: labs/striker
Gerrit-Branch: master
Gerrit-Owner: BryanDavis <bda...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to