Show More
@@ -0,0 +1,33 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2010-2016 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | """ | |
|
22 | Base module for form rendering / validation - currently just a wrapper for | |
|
23 | deform - later can be replaced with something custom. | |
|
24 | """ | |
|
25 | ||
|
26 | from rhodecode.translation import _ | |
|
27 | from deform import Button, Form, widget, ValidationFailure | |
|
28 | ||
|
29 | ||
|
30 | class buttons: | |
|
31 | save = Button(name='Save', type='submit') | |
|
32 | reset = Button(name=_('Reset'), type='reset') | |
|
33 | delete = Button(name=_('Delete'), type='submit') |
@@ -0,0 +1,61 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2016 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import colander | |
|
22 | ||
|
23 | from rhodecode import forms | |
|
24 | from rhodecode.model.db import User | |
|
25 | from rhodecode.translation import _ | |
|
26 | from rhodecode.lib.auth import check_password | |
|
27 | ||
|
28 | ||
|
29 | @colander.deferred | |
|
30 | def deferred_user_password_validator(node, kw): | |
|
31 | username = kw.get('username') | |
|
32 | user = User.get_by_username(username) | |
|
33 | ||
|
34 | def _user_password_validator(node, value): | |
|
35 | if not check_password(value, user.password): | |
|
36 | msg = _('Password is incorrect') | |
|
37 | raise colander.Invalid(node, msg) | |
|
38 | return _user_password_validator | |
|
39 | ||
|
40 | ||
|
41 | class ChangePasswordSchema(colander.Schema): | |
|
42 | ||
|
43 | current_password = colander.SchemaNode( | |
|
44 | colander.String(), | |
|
45 | missing=colander.required, | |
|
46 | widget=forms.widget.PasswordWidget(redisplay=True), | |
|
47 | validator=deferred_user_password_validator) | |
|
48 | ||
|
49 | new_password = colander.SchemaNode( | |
|
50 | colander.String(), | |
|
51 | missing=colander.required, | |
|
52 | widget=forms.widget.CheckedPasswordWidget(redisplay=True), | |
|
53 | validator=colander.Length(min=6)) | |
|
54 | ||
|
55 | ||
|
56 | def validator(self, form, values): | |
|
57 | if values['current_password'] == values['new_password']: | |
|
58 | exc = colander.Invalid(form) | |
|
59 | exc['new_password'] = _('New password must be different ' | |
|
60 | 'to old password') | |
|
61 | raise exc |
@@ -0,0 +1,10 b'' | |||
|
1 | <%def name="panel(title, class_='default')"> | |
|
2 | <div class="panel panel-${class_}"> | |
|
3 | <div class="panel-heading"> | |
|
4 | <h3 class="panel-title">${title}</h3> | |
|
5 | </div> | |
|
6 | <div class="panel-body"> | |
|
7 | ${caller.body()} | |
|
8 | </div> | |
|
9 | </div> | |
|
10 | </%def> |
@@ -0,0 +1,72 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2016-2016 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import colander | |
|
22 | import pytest | |
|
23 | ||
|
24 | from rhodecode.model import validation_schema | |
|
25 | from rhodecode.model.validation_schema.schemas import user_schema | |
|
26 | ||
|
27 | ||
|
28 | class TestChangePasswordSchema(object): | |
|
29 | original_password = 'm092d903fnio0m' | |
|
30 | ||
|
31 | def test_deserialize_bad_data(self, user_regular): | |
|
32 | schema = user_schema.ChangePasswordSchema().bind( | |
|
33 | username=user_regular.username) | |
|
34 | ||
|
35 | with pytest.raises(validation_schema.Invalid) as exc_info: | |
|
36 | schema.deserialize('err') | |
|
37 | err = exc_info.value.asdict() | |
|
38 | assert err[''] == '"err" is not a mapping type: ' \ | |
|
39 | 'Does not implement dict-like functionality.' | |
|
40 | ||
|
41 | def test_validate_valid_change_password_data(self, user_util): | |
|
42 | user = user_util.create_user(password=self.original_password) | |
|
43 | schema = user_schema.ChangePasswordSchema().bind( | |
|
44 | username=user.username) | |
|
45 | ||
|
46 | schema.deserialize({ | |
|
47 | 'current_password': self.original_password, | |
|
48 | 'new_password': '23jf04rm04imr' | |
|
49 | }) | |
|
50 | ||
|
51 | @pytest.mark.parametrize( | |
|
52 | 'current_password,new_password,key,message', [ | |
|
53 | ('', 'abcdef123', 'current_password', 'required'), | |
|
54 | ('wrong_pw', 'abcdef123', 'current_password', 'incorrect'), | |
|
55 | (original_password, original_password, 'new_password', 'different'), | |
|
56 | (original_password, '', 'new_password', 'Required'), | |
|
57 | (original_password, 'short', 'new_password', 'minimum'), | |
|
58 | ]) | |
|
59 | def test_validate_invalid_change_password_data(self, current_password, | |
|
60 | new_password, key, message, | |
|
61 | user_util): | |
|
62 | user = user_util.create_user(password=self.original_password) | |
|
63 | schema = user_schema.ChangePasswordSchema().bind( | |
|
64 | username=user.username) | |
|
65 | ||
|
66 | with pytest.raises(validation_schema.Invalid) as exc_info: | |
|
67 | schema.deserialize({ | |
|
68 | 'current_password': current_password, | |
|
69 | 'new_password': new_password | |
|
70 | }) | |
|
71 | err = exc_info.value.asdict() | |
|
72 | assert message.lower() in err[key].lower() |
@@ -531,9 +531,7 b' def make_map(config):' | |||
|
531 | 531 | action='my_account_update', conditions={'method': ['POST']}) |
|
532 | 532 | |
|
533 | 533 | m.connect('my_account_password', '/my_account/password', |
|
534 | action='my_account_password', conditions={'method': ['GET']}) | |
|
535 | m.connect('my_account_password', '/my_account/password', | |
|
536 | action='my_account_password_update', conditions={'method': ['POST']}) | |
|
534 | action='my_account_password', conditions={'method': ['GET', 'POST']}) | |
|
537 | 535 | |
|
538 | 536 | m.connect('my_account_repos', '/my_account/repos', |
|
539 | 537 | action='my_account_repos', conditions={'method': ['GET']}) |
@@ -32,6 +32,7 b' from pylons.controllers.util import redi' | |||
|
32 | 32 | from pylons.i18n.translation import _ |
|
33 | 33 | from sqlalchemy.orm import joinedload |
|
34 | 34 | |
|
35 | from rhodecode import forms | |
|
35 | 36 | from rhodecode.lib import helpers as h |
|
36 | 37 | from rhodecode.lib import auth |
|
37 | 38 | from rhodecode.lib.auth import ( |
@@ -39,10 +40,12 b' from rhodecode.lib.auth import (' | |||
|
39 | 40 | from rhodecode.lib.base import BaseController, render |
|
40 | 41 | from rhodecode.lib.utils2 import safe_int, md5 |
|
41 | 42 | from rhodecode.lib.ext_json import json |
|
43 | ||
|
44 | from rhodecode.model.validation_schema.schemas import user_schema | |
|
42 | 45 | from rhodecode.model.db import ( |
|
43 | 46 | Repository, PullRequest, PullRequestReviewers, UserEmailMap, User, |
|
44 | 47 | UserFollowing) |
|
45 |
from rhodecode.model.forms import UserForm |
|
|
48 | from rhodecode.model.forms import UserForm | |
|
46 | 49 | from rhodecode.model.scm import RepoList |
|
47 | 50 | from rhodecode.model.user import UserModel |
|
48 | 51 | from rhodecode.model.repo import RepoModel |
@@ -185,38 +188,44 b' class MyAccountController(BaseController' | |||
|
185 | 188 | force_defaults=False |
|
186 | 189 | ) |
|
187 | 190 | |
|
188 | @auth.CSRFRequired() | |
|
189 |
def my_account_password |
|
|
191 | @auth.CSRFRequired(except_methods=['GET']) | |
|
192 | def my_account_password(self): | |
|
190 | 193 | c.active = 'password' |
|
191 | 194 | self.__load_data() |
|
192 | _form = PasswordChangeForm(c.rhodecode_user.username)() | |
|
195 | ||
|
196 | schema = user_schema.ChangePasswordSchema().bind( | |
|
197 | username=c.rhodecode_user.username) | |
|
198 | ||
|
199 | form = forms.Form(schema, | |
|
200 | buttons=(forms.buttons.save, forms.buttons.reset)) | |
|
201 | ||
|
202 | if request.method == 'POST': | |
|
203 | controls = request.POST.items() | |
|
193 | 204 | try: |
|
194 | form_result = _form.to_python(request.POST) | |
|
195 |
UserModel().update_user(c.rhodecode_user.user_id, ** |
|
|
205 | valid_data = form.validate(controls) | |
|
206 | UserModel().update_user(c.rhodecode_user.user_id, **valid_data) | |
|
196 | 207 | instance = c.rhodecode_user.get_instance() |
|
197 | 208 | instance.update_userdata(force_password_change=False) |
|
198 | 209 | Session().commit() |
|
210 | except forms.ValidationFailure as e: | |
|
211 | request.session.flash( | |
|
212 | _('Error occurred during update of user password'), | |
|
213 | queue='error') | |
|
214 | form = e | |
|
215 | except Exception: | |
|
216 | log.exception("Exception updating password") | |
|
217 | request.session.flash( | |
|
218 | _('Error occurred during update of user password'), | |
|
219 | queue='error') | |
|
220 | else: | |
|
199 | 221 | session.setdefault('rhodecode_user', {}).update( |
|
200 | 222 | {'password': md5(instance.password)}) |
|
201 | 223 | session.save() |
|
202 | h.flash(_("Successfully updated password"), category='success') | |
|
203 | except formencode.Invalid as errors: | |
|
204 | return htmlfill.render( | |
|
205 | render('admin/my_account/my_account.html'), | |
|
206 | defaults=errors.value, | |
|
207 | errors=errors.error_dict or {}, | |
|
208 | prefix_error=False, | |
|
209 | encoding="UTF-8", | |
|
210 | force_defaults=False) | |
|
211 | except Exception: | |
|
212 | log.exception("Exception updating password") | |
|
213 | h.flash(_('Error occurred during update of user password'), | |
|
214 | category='error') | |
|
215 | return render('admin/my_account/my_account.html') | |
|
224 | request.session.flash( | |
|
225 | _("Successfully updated password"), queue='success') | |
|
226 | return redirect(url('my_account_password')) | |
|
216 | 227 | |
|
217 | def my_account_password(self): | |
|
218 | c.active = 'password' | |
|
219 | self.__load_data() | |
|
228 | c.form = form | |
|
220 | 229 | return render('admin/my_account/my_account.html') |
|
221 | 230 | |
|
222 | 231 | def my_account_repos(self): |
@@ -1116,9 +1116,11 b' class CSRFRequired(object):' | |||
|
1116 | 1116 | For use with the ``webhelpers.secure_form`` helper functions. |
|
1117 | 1117 | |
|
1118 | 1118 | """ |
|
1119 |
def __init__(self, token=csrf_token_key, header='X-CSRF-Token' |
|
|
1119 | def __init__(self, token=csrf_token_key, header='X-CSRF-Token', | |
|
1120 | except_methods=None): | |
|
1120 | 1121 | self.token = token |
|
1121 | 1122 | self.header = header |
|
1123 | self.except_methods = except_methods or [] | |
|
1122 | 1124 | |
|
1123 | 1125 | def __call__(self, func): |
|
1124 | 1126 | return get_cython_compat_decorator(self.__wrapper, func) |
@@ -1131,6 +1133,9 b' class CSRFRequired(object):' | |||
|
1131 | 1133 | return supplied_token and supplied_token == cur_token |
|
1132 | 1134 | |
|
1133 | 1135 | def __wrapper(self, func, *fargs, **fkwargs): |
|
1136 | if request.method in self.except_methods: | |
|
1137 | return func(*fargs, **fkwargs) | |
|
1138 | ||
|
1134 | 1139 | cur_token = get_csrf_token(save_if_missing=False) |
|
1135 | 1140 | if self.check_csrf(request, cur_token): |
|
1136 | 1141 | if request.POST.get(self.token): |
@@ -102,20 +102,6 b' def LoginForm():' | |||
|
102 | 102 | return _LoginForm |
|
103 | 103 | |
|
104 | 104 | |
|
105 | def PasswordChangeForm(username): | |
|
106 | class _PasswordChangeForm(formencode.Schema): | |
|
107 | allow_extra_fields = True | |
|
108 | filter_extra_fields = True | |
|
109 | ||
|
110 | current_password = v.ValidOldPassword(username)(not_empty=True) | |
|
111 | new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6)) | |
|
112 | new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6)) | |
|
113 | ||
|
114 | chained_validators = [v.ValidPasswordsMatch('new_password', | |
|
115 | 'new_password_confirmation')] | |
|
116 | return _PasswordChangeForm | |
|
117 | ||
|
118 | ||
|
119 | 105 | def UserForm(edit=False, available_languages=[], old_data={}): |
|
120 | 106 | class _UserForm(formencode.Schema): |
|
121 | 107 | allow_extra_fields = True |
@@ -147,7 +147,6 b' class UserModel(BaseModel):' | |||
|
147 | 147 | # cleanups, my_account password change form |
|
148 | 148 | kwargs.pop('current_password', None) |
|
149 | 149 | kwargs.pop('new_password', None) |
|
150 | kwargs.pop('new_password_confirmation', None) | |
|
151 | 150 | |
|
152 | 151 | # cleanups, user edit password change form |
|
153 | 152 | kwargs.pop('password_confirmation', None) |
@@ -13,7 +13,3 b' def ip_addr_validator(node, value):' | |||
|
13 | 13 | except ValueError: |
|
14 | 14 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
15 | 15 | raise colander.Invalid(node, msg) |
|
16 | ||
|
17 | ||
|
18 | ||
|
19 |
@@ -12,6 +12,7 b'' | |||
|
12 | 12 | |
|
13 | 13 | .control-label { |
|
14 | 14 | width: 200px; |
|
15 | padding: 10px; | |
|
15 | 16 | float: left; |
|
16 | 17 | } |
|
17 | 18 | .control-inputs { |
@@ -26,6 +27,13 b'' | |||
|
26 | 27 | |
|
27 | 28 | .form-group { |
|
28 | 29 | clear: left; |
|
30 | margin-bottom: 20px; | |
|
31 | ||
|
32 | &:after { /* clear fix */ | |
|
33 | content: " "; | |
|
34 | display: block; | |
|
35 | clear: left; | |
|
36 | } | |
|
29 | 37 | } |
|
30 | 38 | |
|
31 | 39 | .form-control { |
@@ -34,6 +42,11 b'' | |||
|
34 | 42 | |
|
35 | 43 | .error-block { |
|
36 | 44 | color: red; |
|
45 | margin: 0; | |
|
46 | } | |
|
47 | ||
|
48 | .help-block { | |
|
49 | margin: 0; | |
|
37 | 50 | } |
|
38 | 51 | |
|
39 | 52 | .deform-seq-container .control-inputs { |
@@ -62,7 +75,9 b'' | |||
|
62 | 75 | } |
|
63 | 76 | } |
|
64 | 77 | |
|
65 |
.form-control.select2-container { |
|
|
78 | .form-control.select2-container { | |
|
79 | height: 40px; | |
|
80 | } | |
|
66 | 81 | |
|
67 | 82 | .deform-two-field-sequence .deform-seq-container .deform-seq-item label { |
|
68 | 83 | display: none; |
@@ -74,7 +89,7 b'' | |||
|
74 | 89 | display: none; |
|
75 | 90 | } |
|
76 | 91 | .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group { |
|
77 |
|
|
|
92 | margin: 0; | |
|
78 | 93 | } |
|
79 | 94 | .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group { |
|
80 | 95 | width: 45%; padding: 0 2px; float: left; clear: none; |
@@ -1,42 +1,5 b'' | |||
|
1 | <div class="panel panel-default"> | |
|
2 | <div class="panel-heading"> | |
|
3 | <h3 class="panel-title">${_('Change Your Account Password')}</h3> | |
|
4 | </div> | |
|
5 | ${h.secure_form(url('my_account_password'), method='post')} | |
|
6 | <div class="panel-body"> | |
|
7 | <div class="fields"> | |
|
8 | <div class="field"> | |
|
9 | <div class="label"> | |
|
10 | <label for="current_password">${_('Current Password')}:</label> | |
|
11 | </div> | |
|
12 | <div class="input"> | |
|
13 | ${h.password('current_password',class_='medium',autocomplete="off")} | |
|
14 | </div> | |
|
15 | </div> | |
|
1 | <%namespace name="widgets" file="/widgets.html"/> | |
|
16 | 2 | |
|
17 | <div class="field"> | |
|
18 | <div class="label"> | |
|
19 | <label for="new_password">${_('New Password')}:</label> | |
|
20 | </div> | |
|
21 | <div class="input"> | |
|
22 | ${h.password('new_password',class_='medium', autocomplete="off")} | |
|
23 | </div> | |
|
24 | </div> | |
|
25 | ||
|
26 | <div class="field"> | |
|
27 | <div class="label"> | |
|
28 | <label for="password_confirmation">${_('Confirm New Password')}:</label> | |
|
29 | </div> | |
|
30 | <div class="input"> | |
|
31 | ${h.password('new_password_confirmation',class_='medium', autocomplete="off")} | |
|
32 | </div> | |
|
33 | </div> | |
|
34 | ||
|
35 | <div class="buttons"> | |
|
36 | ${h.submit('save',_('Save'),class_="btn")} | |
|
37 | ${h.reset('reset',_('Reset'),class_="btn")} | |
|
38 | </div> | |
|
39 | </div> | |
|
40 | </div> | |
|
41 | ${h.end_form()} | |
|
42 | </div> No newline at end of file | |
|
3 | <%widgets:panel title="${_('Change Your Account Password')}"> | |
|
4 | ${c.form.render() | n} | |
|
5 | </%widgets:panel> |
@@ -15,7 +15,6 b" if getattr(c, 'rhodecode_user', None) an" | |||
|
15 | 15 | |
|
16 | 16 | c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer') |
|
17 | 17 | %> |
|
18 | ||
|
19 | 18 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
20 | 19 | <head> |
|
21 | 20 | <title>${self.title()}</title> |
@@ -21,6 +21,7 b'' | |||
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.lib import helpers as h |
|
24 | from rhodecode.lib.auth import check_password | |
|
24 | 25 | from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys |
|
25 | 26 | from rhodecode.model.meta import Session |
|
26 | 27 | from rhodecode.tests import ( |
@@ -34,6 +35,7 b' fixture = Fixture()' | |||
|
34 | 35 | |
|
35 | 36 | class TestMyAccountController(TestController): |
|
36 | 37 | test_user_1 = 'testme' |
|
38 | test_user_1_password = '0jd83nHNS/d23n' | |
|
37 | 39 | destroy_users = set() |
|
38 | 40 | |
|
39 | 41 | @classmethod |
@@ -158,7 +160,8 b' class TestMyAccountController(TestContro' | |||
|
158 | 160 | ('email', {'email': 'some@email.com'}), |
|
159 | 161 | ]) |
|
160 | 162 | def test_my_account_update(self, name, attrs): |
|
161 |
usr = fixture.create_user(self.test_user_1, |
|
|
163 | usr = fixture.create_user(self.test_user_1, | |
|
164 | password=self.test_user_1_password, | |
|
162 | 165 | email='testme@rhodecode.org', |
|
163 | 166 | extern_type='rhodecode', |
|
164 | 167 | extern_name=self.test_user_1, |
@@ -167,7 +170,8 b' class TestMyAccountController(TestContro' | |||
|
167 | 170 | |
|
168 | 171 | params = usr.get_api_data() # current user data |
|
169 | 172 | user_id = usr.user_id |
|
170 | self.log_user(username=self.test_user_1, password='qweqwe') | |
|
173 | self.log_user( | |
|
174 | username=self.test_user_1, password=self.test_user_1_password) | |
|
171 | 175 | |
|
172 | 176 | params.update({'password_confirmation': ''}) |
|
173 | 177 | params.update({'new_password': ''}) |
@@ -321,8 +325,55 b' class TestMyAccountController(TestContro' | |||
|
321 | 325 | response = response.follow() |
|
322 | 326 | response.mustcontain(no=[api_key]) |
|
323 | 327 | |
|
324 | def test_password_is_updated_in_session_on_password_change( | |
|
325 | self, user_util): | |
|
328 | def test_valid_change_password(self, user_util): | |
|
329 | new_password = 'my_new_valid_password' | |
|
330 | user = user_util.create_user(password=self.test_user_1_password) | |
|
331 | session = self.log_user(user.username, self.test_user_1_password) | |
|
332 | form_data = [ | |
|
333 | ('current_password', self.test_user_1_password), | |
|
334 | ('__start__', 'new_password:mapping'), | |
|
335 | ('new_password', new_password), | |
|
336 | ('new_password-confirm', new_password), | |
|
337 | ('__end__', 'new_password:mapping'), | |
|
338 | ('csrf_token', self.csrf_token), | |
|
339 | ] | |
|
340 | response = self.app.post(url('my_account_password'), form_data).follow() | |
|
341 | assert 'Successfully updated password' in response | |
|
342 | ||
|
343 | # check_password depends on user being in session | |
|
344 | Session().add(user) | |
|
345 | try: | |
|
346 | assert check_password(new_password, user.password) | |
|
347 | finally: | |
|
348 | Session().expunge(user) | |
|
349 | ||
|
350 | @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [ | |
|
351 | ('', 'abcdef123', 'abcdef123'), | |
|
352 | ('wrong_pw', 'abcdef123', 'abcdef123'), | |
|
353 | (test_user_1_password, test_user_1_password, test_user_1_password), | |
|
354 | (test_user_1_password, '', ''), | |
|
355 | (test_user_1_password, 'abcdef123', ''), | |
|
356 | (test_user_1_password, '', 'abcdef123'), | |
|
357 | (test_user_1_password, 'not_the', 'same_pw'), | |
|
358 | (test_user_1_password, 'short', 'short'), | |
|
359 | ]) | |
|
360 | def test_invalid_change_password(self, current_pw, new_pw, confirm_pw, | |
|
361 | user_util): | |
|
362 | user = user_util.create_user(password=self.test_user_1_password) | |
|
363 | session = self.log_user(user.username, self.test_user_1_password) | |
|
364 | old_password_hash = session['password'] | |
|
365 | form_data = [ | |
|
366 | ('current_password', current_pw), | |
|
367 | ('__start__', 'new_password:mapping'), | |
|
368 | ('new_password', new_pw), | |
|
369 | ('new_password-confirm', confirm_pw), | |
|
370 | ('__end__', 'new_password:mapping'), | |
|
371 | ('csrf_token', self.csrf_token), | |
|
372 | ] | |
|
373 | response = self.app.post(url('my_account_password'), form_data) | |
|
374 | assert 'Error occurred' in response | |
|
375 | ||
|
376 | def test_password_is_updated_in_session_on_password_change(self, user_util): | |
|
326 | 377 | old_password = 'abcdef123' |
|
327 | 378 | new_password = 'abcdef124' |
|
328 | 379 | |
@@ -330,12 +381,14 b' class TestMyAccountController(TestContro' | |||
|
330 | 381 | session = self.log_user(user.username, old_password) |
|
331 | 382 | old_password_hash = session['password'] |
|
332 | 383 | |
|
333 |
form_data = |
|
|
334 |
'current_password' |
|
|
335 | 'new_password': new_password, | |
|
336 |
'new_password |
|
|
337 | 'csrf_token': self.csrf_token | |
|
338 | } | |
|
384 | form_data = [ | |
|
385 | ('current_password', old_password), | |
|
386 | ('__start__', 'new_password:mapping'), | |
|
387 | ('new_password', new_password), | |
|
388 | ('new_password-confirm', new_password), | |
|
389 | ('__end__', 'new_password:mapping'), | |
|
390 | ('csrf_token', self.csrf_token), | |
|
391 | ] | |
|
339 | 392 | self.app.post(url('my_account_password'), form_data) |
|
340 | 393 | |
|
341 | 394 | response = self.app.get(url('home')) |
General Comments 0
You need to be logged in to leave comments.
Login now