From e0062624e4f211718afb8171777b08c4cd34d24b Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 26 Jan 2021 11:03:32 +0700 Subject: [PATCH] feat: Require current password when change password --- flask_appbuilder/security/forms.py | 24 ++++++++++++++++++++++ flask_appbuilder/security/views.py | 21 +++++++++++++++---- flask_appbuilder/tests/test_mongoengine.py | 10 +++++++-- flask_appbuilder/tests/test_mvc.py | 19 ++++++++++++++++- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/flask_appbuilder/security/forms.py b/flask_appbuilder/security/forms.py index 765adfed1a..21468ffbc9 100644 --- a/flask_appbuilder/security/forms.py +++ b/flask_appbuilder/security/forms.py @@ -51,6 +51,30 @@ class ResetPasswordForm(DynamicForm): ) +class ResetMyPasswordForm(DynamicForm): + current_password = PasswordField( + lazy_gettext("Current Password"), + description=lazy_gettext("Your current password"), + validators=[DataRequired()], + widget=BS3PasswordFieldWidget(), + ) + password = PasswordField( + lazy_gettext("Password"), + description=lazy_gettext( + "Please use a good password policy," + " this application does not check this for you" + ), + validators=[DataRequired()], + widget=BS3PasswordFieldWidget(), + ) + conf_password = PasswordField( + lazy_gettext("Confirm Password"), + description=lazy_gettext("Please rewrite the password to confirm"), + validators=[EqualTo("password", message=lazy_gettext("Passwords must match"))], + widget=BS3PasswordFieldWidget(), + ) + + class RegisterUserDBForm(DynamicForm): username = StringField( lazy_gettext("User Name"), diff --git a/flask_appbuilder/security/views.py b/flask_appbuilder/security/views.py index 21eccb3bd4..f9948ff969 100644 --- a/flask_appbuilder/security/views.py +++ b/flask_appbuilder/security/views.py @@ -11,7 +11,13 @@ from wtforms.validators import EqualTo from .decorators import has_access -from .forms import LoginForm_db, LoginForm_oid, ResetPasswordForm, UserInfoEdit +from .forms import ( + LoginForm_db, + LoginForm_oid, + ResetMyPasswordForm, + ResetPasswordForm, + UserInfoEdit, +) from .._compat import as_unicode from ..actions import action from ..baseviews import BaseView @@ -69,12 +75,19 @@ class ResetMyPasswordView(SimpleFormView): """ route_base = "/resetmypassword" - form = ResetPasswordForm + route_form = f"{route_base}/form" + form = ResetMyPasswordForm form_title = lazy_gettext("Reset Password Form") redirect_url = "/" message = lazy_gettext("Password Changed") + invalid_current_password_message = lazy_gettext("Invalid current password") def form_post(self, form): + username = g.user.username + user = self.appbuilder.sm.auth_user_db(username, form.current_password.data) + if not user: + flash(as_unicode(self.invalid_current_password_message), "warning") + return redirect(self.route_form) self.appbuilder.sm.reset_password(g.user.id, form.password.data) flash(as_unicode(self.message), "info") @@ -682,7 +695,7 @@ def oauth_authorized(self, provider): log.debug("Authorized init") resp = self.appbuilder.sm.oauth_remotes[provider].authorize_access_token() if resp is None: - flash(u"You denied the request to sign in.", "warning") + flash("You denied the request to sign in.", "warning") return redirect(self.appbuilder.get_url_for_login) log.debug("OAUTH Authorized resp: {0}".format(resp)) # Retrieves specific user info from the provider @@ -703,7 +716,7 @@ def oauth_authorized(self, provider): allow = True break if not allow: - flash(u"You are not authorized.", "warning") + flash("You are not authorized.", "warning") return redirect(self.appbuilder.get_url_for_login) else: log.debug("No whitelist for OAuth provider") diff --git a/flask_appbuilder/tests/test_mongoengine.py b/flask_appbuilder/tests/test_mongoengine.py index 9a21ece569..8d58d7cd12 100644 --- a/flask_appbuilder/tests/test_mongoengine.py +++ b/flask_appbuilder/tests/test_mongoengine.py @@ -290,7 +290,11 @@ def test_sec_reset_password(self): self.assertIn("Reset Password Form", data) rv = client.post( "/resetmypassword/form", - data=dict(password="password", conf_password="password"), + data=dict( + current_password=DEFAULT_ADMIN_PASSWORD, + password="password", + conf_password="password", + ), follow_redirects=True, ) self.assertEqual(rv.status_code, 200) @@ -299,7 +303,9 @@ def test_sec_reset_password(self): rv = client.post( "/resetmypassword/form", data=dict( - password=DEFAULT_ADMIN_PASSWORD, conf_password=DEFAULT_ADMIN_PASSWORD + current_password="password", + password=DEFAULT_ADMIN_PASSWORD, + conf_password=DEFAULT_ADMIN_PASSWORD, ), follow_redirects=True, ) diff --git a/flask_appbuilder/tests/test_mvc.py b/flask_appbuilder/tests/test_mvc.py index 0d57e8fd56..2a368b6eff 100644 --- a/flask_appbuilder/tests/test_mvc.py +++ b/flask_appbuilder/tests/test_mvc.py @@ -663,7 +663,24 @@ def test_sec_reset_password(self): self.browser_login(client, USERNAME_ADMIN, "password") rv = client.post( "/resetmypassword/form", - data=dict(password=PASSWORD_ADMIN, conf_password=PASSWORD_ADMIN), + data=dict( + current_password="password", + password=PASSWORD_ADMIN, + conf_password=PASSWORD_ADMIN, + ), + follow_redirects=True, + ) + self.assertEqual(rv.status_code, 200) + + # Reset My Password wrong current pass + self.browser_login(client, USERNAME_ADMIN, "password") + rv = client.post( + "/resetmypassword/form", + data=dict( + current_password="1", + password=PASSWORD_ADMIN, + conf_password=PASSWORD_ADMIN, + ), follow_redirects=True, ) self.assertEqual(rv.status_code, 200)