Repository: getredash/redash

Issue:
Password reset form for disabled users 
When a disabled user asks to reset password, we should send them an email saying their account is disabled (instead of sending them a password reset link).


Discussion:
Instead of sending them an email, I'd recommend popping them an error message right there: "Your account is disabled, please contact your administrator (maybe email here)".
I thought of that, but it's a bad idea, as it leaks information about users: anyone can enter any email address to check if it's disabled.

Resolving patch:
diff --git a/redash/authentication/account.py b/redash/authentication/account.py
--- a/redash/authentication/account.py
+++ b/redash/authentication/account.py
@@ -71,3 +71,11 @@ def send_password_reset_email(user):
 
     send_mail.delay([user.email], subject, html_content, text_content)
     return reset_link
+
+  
+def send_user_disabled_email(user):
+    html_content = render_template('emails/reset_disabled.html', user=user)
+    text_content = render_template('emails/reset_disabled.txt', user=user)
+    subject = u"Your Redash account is disabled"
+
+    send_mail.delay([user.email], subject, html_content, text_content)
diff --git a/redash/handlers/authentication.py b/redash/handlers/authentication.py
--- a/redash/handlers/authentication.py
+++ b/redash/handlers/authentication.py
@@ -7,6 +7,7 @@
 from redash.authentication import current_org, get_login_url, get_next_path
 from redash.authentication.account import (BadSignature, SignatureExpired,
                                            send_password_reset_email,
+                                           send_user_disabled_email,
                                            send_verify_email,
                                            validate_token)
 from redash.handlers import routes
@@ -118,7 +119,10 @@ def forgot_password(org_slug=None):
         try:
             org = current_org._get_current_object()
             user = models.User.get_by_email_and_org(email, org)
-            send_password_reset_email(user)
+            if user.is_disabled:
+                send_user_disabled_email(user)
+            else:
+                send_password_reset_email(user)
         except NoResultFound:
             logging.error("No user found for forgot password: %s", email)
 


Test patch:
diff --git a/tests/test_authentication.py b/tests/test_authentication.py
--- a/tests/test_authentication.py
+++ b/tests/test_authentication.py
@@ -319,3 +319,26 @@ def test_remote_login_custom_header(self):
         })
 
         self.assert_correct_user_attributes(self.get_test_user())
+
+
+class TestUserForgotPassword(BaseTestCase):
+    def test_user_should_receive_password_reset_link(self):
+        user = self.factory.create_user()
+
+        with patch('redash.handlers.authentication.send_password_reset_email') as send_password_reset_email_mock:
+            response = self.post_request('/forgot', org=user.org, data={'email': user.email})
+            self.assertEqual(response.status_code, 200)
+            send_password_reset_email_mock.assert_called_with(user)
+
+    def test_disabled_user_should_not_receive_password_reset_link(self):
+        user = self.factory.create_user()
+        user.disable()
+        self.db.session.add(user)
+        self.db.session.commit()
+
+        with patch('redash.handlers.authentication.send_password_reset_email') as send_password_reset_email_mock,\
+                patch('redash.handlers.authentication.send_user_disabled_email') as send_user_disabled_email_mock:
+            response = self.post_request('/forgot', org=user.org, data={'email': user.email})
+            self.assertEqual(response.status_code, 200)
+            send_password_reset_email_mock.assert_not_called()
+            send_user_disabled_email_mock.assert_called_with(user)