]> git.ipfire.org Git - pbs.git/commitdiff
Add password recovery feature
authorJonatan Schlag <jonatan.schlag@ipfire.org>
Wed, 1 Nov 2017 18:26:46 +0000 (19:26 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 1 Nov 2017 18:55:16 +0000 (18:55 +0000)
It is now possible to reset the password, we only need to implement the
mail feature. At the moment we cannot send a mail with the recovery code
to the user.

Fixes: #10095
Signed-off-by: Jonatan Schlag <jonatan.schlag@ipfire.org>
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/buildservice/users.py
src/templates/user-forgot-password.html
src/templates/user-requested-password-recovery.html [new file with mode: 0644]
src/templates/user-reset-password-fail.html [new file with mode: 0644]
src/templates/user-reset-password-success.html [new file with mode: 0644]
src/templates/user-reset-password.html [new file with mode: 0644]
src/web/__init__.py
src/web/auth.py

index 6a3f3f14316f21f9c0d884e78a0abe32a74931f4..cf6c610a66ec71d6420e82f53c327f55b8da15de 100644 (file)
@@ -214,7 +214,11 @@ dist_templates_DATA = \
        src/templates/user-profile.html \
        src/templates/user-profile-need-activation.html \
        src/templates/user-profile-passwd.html \
-       src/templates/user-profile-passwd-ok.html
+       src/templates/user-profile-passwd-ok.html \
+       src/templates/user-requested-password-recovery.html \
+       src/templates/user-reset-password.html \
+       src/templates//user-reset-password-success.html \
+       src/templates//user-reset-password-fail.html
 
 templatesdir = $(datadir)/templates
 
index 8007b849ad89ed04b954e0ebb5fc78e29f56815a..a886b824b5e7b1eca4a0197026c166ea75f473fd 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/python
 
+import datetime
 import email.utils
 import hashlib
 import logging
@@ -185,6 +186,10 @@ class Users(base.Object):
                        LEFT JOIN users_emails ON users.id = users_emails.user_id \
                        WHERE users_emails.email = %s", email)
 
+       def get_by_password_recovery_code(self, code):
+               return self._get_user("SELECT * FROM users \
+                       WHERE password_recovery_code = %s AND password_recovery_code_expires_at > NOW()", code)
+
        def find_maintainers(self, maintainers):
                email_addresses = []
 
@@ -297,6 +302,10 @@ class User(base.DataObject):
                """
                        Update the passphrase the users uses to log on.
                """
+               # We cannot set the password for ldap users
+               if self.ldap_dn:
+                       raise AttributeError("Cannot set passphrase for LDAP user")
+
                self.db.execute("UPDATE users SET passphrase = %s WHERE id = %s",
                        generate_password_hash(passphrase), self.id)
 
@@ -443,6 +452,32 @@ class User(base.DataObject):
 
        timezone = property(get_timezone, set_timezone)
 
+       def get_password_recovery_code(self):
+               return self.data.password_recovery_code
+
+       def set_password_recovery_code(self, code):
+               self._set_attribute("password_recovery_code", code)
+
+               self._set_attribute("password_recovery_code_expires_at",
+                       datetime.datetime.utcnow() + datetime.timedelta(days=1))
+
+       password_recovery_code = property(get_password_recovery_code, set_password_recovery_code)
+
+       def forgot_password(self):
+               log.debug("User %s reqested password recovery" % self.name)
+
+               # We cannot reset te password for ldap users
+               if self.ldap_dn:
+                       # Maybe we should send an email with an explanation
+                       return
+
+               # Add a recovery code to the database and a timestamp when this code expires
+               self.password_recovery_code = generate_random_string(64)
+
+               # XXX
+               # We should send an email with the activation code
+
+
        @property
        def activated(self):
                return self.data.activated
index 2896ea41b39a8ba13070468ca6795de55ca94653..3c2180400e0de72b4b2fced58138fa26926f4ade 100644 (file)
                <h1>{{ _("Forgot password") }}</h1>
        </div>
 
-       <!-- XXX --->
-       <div class="alert alert-warning">
-               {{ _("Work in progress!") }}
-       </div>
-
        <div class="row">
                <div class="span6">
                        <p>
@@ -29,7 +24,7 @@
                                {{ _("However, we allow to re-activate your account.") }}
                        </p>
                        <p>
-                               {{ _("You need to enter your username below.") }}
+                               {{ _("You need to enter your username or your email address below") }}
                                {{ _("After that, you will receive an email with intructions how to go on.") }}
                        </p>
                        <hr>
@@ -39,7 +34,7 @@
 
                                <fieldset>
                                        <div class="control-group">
-                                               <label class="control-label" for="name">{{ _("Your username") }}</label>
+                                               <label class="control-label" for="name">{{ _("Your username or email address") }}</label>
                                                <div class="controls">
                                                        <input type="text" class="input-xlarge" id="name" name="name" />
                                                </div>
diff --git a/src/templates/user-requested-password-recovery.html b/src/templates/user-requested-password-recovery.html
new file mode 100644 (file)
index 0000000..29eb95a
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base-form2.html" %}
+
+{% block title %}{{ _("Requested password recovery") }}{% end block %}
+
+{% block body %}
+       <div class="page-header">
+               <h1>{{ _("Password recovery requested") }}</h1>
+       </div>
+
+       <div class="row">
+               <div class="span6">
+                       <p>
+                               {{ _("An email with instructions how to recover your password was send to your primary email address.") }}
+                       </p>
+                       <hr>
+               </div>
+       </div>
+{% end %}
diff --git a/src/templates/user-reset-password-fail.html b/src/templates/user-reset-password-fail.html
new file mode 100644 (file)
index 0000000..1daaef3
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base-form2.html" %}
+
+{% block title %}{{ _("Password reset failed") }}{% end block %}
+
+{% block body %}
+       <div class="page-header">
+               <h1>{{ _("Password reset failed") }}</h1>
+       </div>
+
+       <div class="row">
+               <div class="span6">
+                       <p>
+                               {{ message }}
+                       </p>
+                       <hr>
+               </div>
+       </div>
+{% end %}
diff --git a/src/templates/user-reset-password-success.html b/src/templates/user-reset-password-success.html
new file mode 100644 (file)
index 0000000..f75b3a7
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base-form2.html" %}
+
+{% block title %}{{ _("Password reset succeeded") }}{% end block %}
+
+{% block body %}
+       <div class="page-header">
+               <h1>{{ _("Password reset succeeded") }}</h1>
+       </div>
+
+       <div class="row">
+               <div class="span6">
+                       <p>
+                               {{ _("Successfully reset your password") }}
+                       </p>
+                       <hr>
+               </div>
+       </div>
+{% end %}
diff --git a/src/templates/user-reset-password.html b/src/templates/user-reset-password.html
new file mode 100644 (file)
index 0000000..1fa07e4
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block title %}{{ _("Register a new account") }}{% end block %}
+
+{% block body %}
+       <div class="page-header">
+               <h2>
+                       {{ _("Reset password") }}
+               </h2>
+       </div>
+
+       <form class="form-horizontal" method="POST" action="">
+               {% raw xsrf_form_html() %}
+               <input type="hidden" name="code" value="{{ user.password_recovery_code }}">
+
+               <fieldset>
+                       <div class="control-group">
+                               <label class="control-label" for="password1">{{ _("Password") }}</label>
+                               <div class="controls">
+                                       <input type="password" class="input-xlarge" id="password1" name="password1">
+                               </div>
+                       </div>
+
+                       <div class="control-group">
+                               <label class="control-label" for="password2">{{ _("Confirm password") }}</label>
+                               <div class="controls">
+                                       <input type="password" class="input-xlarge" id="password2" name="password2">
+                               </div>
+                       </div>
+               </fieldset>
+
+               <div class="form-actions">
+                       <button type="submit" class="btn btn-primary">{{ _("Reset password") }}</button>
+               </div>
+       </form>
+{% end block %}
index 5be08d83bc05dae596b34055cde772915f9020c3..f44a1239c4f4a1cbf29a5887f89a37753650904e 100644 (file)
@@ -118,6 +118,7 @@ class Application(tornado.web.Application):
                        (r"/logout", auth.LogoutHandler),
                        (r"/register", auth.RegisterHandler),
                        (r"/password-recovery", auth.PasswordRecoveryHandler),
+                       (r"/password-reset", auth.PasswordResetHandler),
 
                        # User profiles
                        (r"/users", users.UsersHandler),
index 4538db54b6842897f30426d684af1178a988a129..811b3e99e4b86bf901a9813318f0962b89ab49fc 100644 (file)
@@ -143,10 +143,48 @@ class PasswordRecoveryHandler(base.BaseHandler):
        def post(self):
                username = self.get_argument("name", None)
 
-               if not username:
-                       return self.get()
+               with self.db.transaction():
+                       user = self.backend.users.get_by_email(username) \
+                               or self.backend.users.get_by_name(username)
+
+                       if user:
+                               user.forgot_password()
+
+               self.render("user-requested-password-recovery.html")
+
+
+class PasswordResetHandler(base.BaseHandler):
+       def get(self):
+               code = self.get_argument("code")
+
+               user = self.backend.users.get_by_password_recovery_code(code)
+               if not user:
+                       raise tornado.web.HTTPError(400)
+
+               self.render("user-reset-password.html", user=user)
+
+       def post(self):
+               _ = self.locale.translate
+
+               code = self.get_argument("code")
+               pass1 = self.get_argument("password1")
+               pass2 = self.get_argument("password2")
+
+               user = self.backend.users.get_by_password_recovery_code(code)
+               if not user:
+                       raise tornado.web.HTTPError(400)
+
+               if not pass1 == pass2:
+                       return self.render("user-reset-password-fail.html",
+                               message=_("Second password does not match"))
+
+               # XXX Check password strength
+
+               with self.db.transaction():
+                       user.passphrase = pass1
+                       user.password_recovery_code = None
 
-               # XXX TODO
+               self.render("user-reset-password-success.html")
 
 
 class LogoutHandler(base.BaseHandler):