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 | action='my_account_update', conditions={'method': ['POST']}) |
|
531 | action='my_account_update', conditions={'method': ['POST']}) | |
532 |
|
532 | |||
533 | m.connect('my_account_password', '/my_account/password', |
|
533 | m.connect('my_account_password', '/my_account/password', | |
534 | action='my_account_password', conditions={'method': ['GET']}) |
|
534 | action='my_account_password', conditions={'method': ['GET', 'POST']}) | |
535 | m.connect('my_account_password', '/my_account/password', |
|
|||
536 | action='my_account_password_update', conditions={'method': ['POST']}) |
|
|||
537 |
|
535 | |||
538 | m.connect('my_account_repos', '/my_account/repos', |
|
536 | m.connect('my_account_repos', '/my_account/repos', | |
539 | action='my_account_repos', conditions={'method': ['GET']}) |
|
537 | action='my_account_repos', conditions={'method': ['GET']}) |
@@ -32,6 +32,7 b' from pylons.controllers.util import redi' | |||||
32 | from pylons.i18n.translation import _ |
|
32 | from pylons.i18n.translation import _ | |
33 | from sqlalchemy.orm import joinedload |
|
33 | from sqlalchemy.orm import joinedload | |
34 |
|
34 | |||
|
35 | from rhodecode import forms | |||
35 | from rhodecode.lib import helpers as h |
|
36 | from rhodecode.lib import helpers as h | |
36 | from rhodecode.lib import auth |
|
37 | from rhodecode.lib import auth | |
37 | from rhodecode.lib.auth import ( |
|
38 | from rhodecode.lib.auth import ( | |
@@ -39,10 +40,12 b' from rhodecode.lib.auth import (' | |||||
39 | from rhodecode.lib.base import BaseController, render |
|
40 | from rhodecode.lib.base import BaseController, render | |
40 | from rhodecode.lib.utils2 import safe_int, md5 |
|
41 | from rhodecode.lib.utils2 import safe_int, md5 | |
41 | from rhodecode.lib.ext_json import json |
|
42 | from rhodecode.lib.ext_json import json | |
|
43 | ||||
|
44 | from rhodecode.model.validation_schema.schemas import user_schema | |||
42 | from rhodecode.model.db import ( |
|
45 | from rhodecode.model.db import ( | |
43 | Repository, PullRequest, PullRequestReviewers, UserEmailMap, User, |
|
46 | Repository, PullRequest, PullRequestReviewers, UserEmailMap, User, | |
44 | UserFollowing) |
|
47 | UserFollowing) | |
45 |
from rhodecode.model.forms import UserForm |
|
48 | from rhodecode.model.forms import UserForm | |
46 | from rhodecode.model.scm import RepoList |
|
49 | from rhodecode.model.scm import RepoList | |
47 | from rhodecode.model.user import UserModel |
|
50 | from rhodecode.model.user import UserModel | |
48 | from rhodecode.model.repo import RepoModel |
|
51 | from rhodecode.model.repo import RepoModel | |
@@ -185,38 +188,44 b' class MyAccountController(BaseController' | |||||
185 | force_defaults=False |
|
188 | force_defaults=False | |
186 | ) |
|
189 | ) | |
187 |
|
190 | |||
188 | @auth.CSRFRequired() |
|
191 | @auth.CSRFRequired(except_methods=['GET']) | |
189 | def my_account_password_update(self): |
|
|||
190 | c.active = 'password' |
|
|||
191 | self.__load_data() |
|
|||
192 | _form = PasswordChangeForm(c.rhodecode_user.username)() |
|
|||
193 | try: |
|
|||
194 | form_result = _form.to_python(request.POST) |
|
|||
195 | UserModel().update_user(c.rhodecode_user.user_id, **form_result) |
|
|||
196 | instance = c.rhodecode_user.get_instance() |
|
|||
197 | instance.update_userdata(force_password_change=False) |
|
|||
198 | Session().commit() |
|
|||
199 | session.setdefault('rhodecode_user', {}).update( |
|
|||
200 | {'password': md5(instance.password)}) |
|
|||
201 | 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') |
|
|||
216 |
|
||||
217 | def my_account_password(self): |
|
192 | def my_account_password(self): | |
218 | c.active = 'password' |
|
193 | c.active = 'password' | |
219 | self.__load_data() |
|
194 | self.__load_data() | |
|
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() | |||
|
204 | try: | |||
|
205 | valid_data = form.validate(controls) | |||
|
206 | UserModel().update_user(c.rhodecode_user.user_id, **valid_data) | |||
|
207 | instance = c.rhodecode_user.get_instance() | |||
|
208 | instance.update_userdata(force_password_change=False) | |||
|
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: | |||
|
221 | session.setdefault('rhodecode_user', {}).update( | |||
|
222 | {'password': md5(instance.password)}) | |||
|
223 | session.save() | |||
|
224 | request.session.flash( | |||
|
225 | _("Successfully updated password"), queue='success') | |||
|
226 | return redirect(url('my_account_password')) | |||
|
227 | ||||
|
228 | c.form = form | |||
220 | return render('admin/my_account/my_account.html') |
|
229 | return render('admin/my_account/my_account.html') | |
221 |
|
230 | |||
222 | def my_account_repos(self): |
|
231 | def my_account_repos(self): |
@@ -1116,9 +1116,11 b' class CSRFRequired(object):' | |||||
1116 | For use with the ``webhelpers.secure_form`` helper functions. |
|
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 | self.token = token |
|
1121 | self.token = token | |
1121 | self.header = header |
|
1122 | self.header = header | |
|
1123 | self.except_methods = except_methods or [] | |||
1122 |
|
1124 | |||
1123 | def __call__(self, func): |
|
1125 | def __call__(self, func): | |
1124 | return get_cython_compat_decorator(self.__wrapper, func) |
|
1126 | return get_cython_compat_decorator(self.__wrapper, func) | |
@@ -1131,6 +1133,9 b' class CSRFRequired(object):' | |||||
1131 | return supplied_token and supplied_token == cur_token |
|
1133 | return supplied_token and supplied_token == cur_token | |
1132 |
|
1134 | |||
1133 | def __wrapper(self, func, *fargs, **fkwargs): |
|
1135 | def __wrapper(self, func, *fargs, **fkwargs): | |
|
1136 | if request.method in self.except_methods: | |||
|
1137 | return func(*fargs, **fkwargs) | |||
|
1138 | ||||
1134 | cur_token = get_csrf_token(save_if_missing=False) |
|
1139 | cur_token = get_csrf_token(save_if_missing=False) | |
1135 | if self.check_csrf(request, cur_token): |
|
1140 | if self.check_csrf(request, cur_token): | |
1136 | if request.POST.get(self.token): |
|
1141 | if request.POST.get(self.token): |
@@ -102,20 +102,6 b' def LoginForm():' | |||||
102 | return _LoginForm |
|
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 | def UserForm(edit=False, available_languages=[], old_data={}): |
|
105 | def UserForm(edit=False, available_languages=[], old_data={}): | |
120 | class _UserForm(formencode.Schema): |
|
106 | class _UserForm(formencode.Schema): | |
121 | allow_extra_fields = True |
|
107 | allow_extra_fields = True |
@@ -147,7 +147,6 b' class UserModel(BaseModel):' | |||||
147 | # cleanups, my_account password change form |
|
147 | # cleanups, my_account password change form | |
148 | kwargs.pop('current_password', None) |
|
148 | kwargs.pop('current_password', None) | |
149 | kwargs.pop('new_password', None) |
|
149 | kwargs.pop('new_password', None) | |
150 | kwargs.pop('new_password_confirmation', None) |
|
|||
151 |
|
150 | |||
152 | # cleanups, user edit password change form |
|
151 | # cleanups, user edit password change form | |
153 | kwargs.pop('password_confirmation', None) |
|
152 | kwargs.pop('password_confirmation', None) |
@@ -13,7 +13,3 b' def ip_addr_validator(node, value):' | |||||
13 | except ValueError: |
|
13 | except ValueError: | |
14 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
14 | msg = _(u'Please enter a valid IPv4 or IpV6 address') | |
15 | raise colander.Invalid(node, msg) |
|
15 | raise colander.Invalid(node, msg) | |
16 |
|
||||
17 |
|
||||
18 |
|
||||
19 |
|
@@ -12,6 +12,7 b'' | |||||
12 |
|
12 | |||
13 | .control-label { |
|
13 | .control-label { | |
14 | width: 200px; |
|
14 | width: 200px; | |
|
15 | padding: 10px; | |||
15 | float: left; |
|
16 | float: left; | |
16 | } |
|
17 | } | |
17 | .control-inputs { |
|
18 | .control-inputs { | |
@@ -26,6 +27,13 b'' | |||||
26 |
|
27 | |||
27 | .form-group { |
|
28 | .form-group { | |
28 | clear: left; |
|
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 | .form-control { |
|
39 | .form-control { | |
@@ -34,6 +42,11 b'' | |||||
34 |
|
42 | |||
35 | .error-block { |
|
43 | .error-block { | |
36 | color: red; |
|
44 | color: red; | |
|
45 | margin: 0; | |||
|
46 | } | |||
|
47 | ||||
|
48 | .help-block { | |||
|
49 | margin: 0; | |||
37 | } |
|
50 | } | |
38 |
|
51 | |||
39 | .deform-seq-container .control-inputs { |
|
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 | .deform-two-field-sequence .deform-seq-container .deform-seq-item label { |
|
82 | .deform-two-field-sequence .deform-seq-container .deform-seq-item label { | |
68 | display: none; |
|
83 | display: none; | |
@@ -74,7 +89,7 b'' | |||||
74 | display: none; |
|
89 | display: none; | |
75 | } |
|
90 | } | |
76 | .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group { |
|
91 | .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group { | |
77 |
|
|
92 | margin: 0; | |
78 | } |
|
93 | } | |
79 | .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group { |
|
94 | .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group { | |
80 | width: 45%; padding: 0 2px; float: left; clear: none; |
|
95 | width: 45%; padding: 0 2px; float: left; clear: none; |
@@ -1,42 +1,5 b'' | |||||
1 | <div class="panel panel-default"> |
|
1 | <%namespace name="widgets" file="/widgets.html"/> | |
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> |
|
|||
16 |
|
2 | |||
17 | <div class="field"> |
|
3 | <%widgets:panel title="${_('Change Your Account Password')}"> | |
18 | <div class="label"> |
|
4 | ${c.form.render() | n} | |
19 | <label for="new_password">${_('New Password')}:</label> |
|
5 | </%widgets:panel> | |
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 |
|
@@ -15,7 +15,6 b" if getattr(c, 'rhodecode_user', None) an" | |||||
15 |
|
15 | |||
16 | c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer') |
|
16 | c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer') | |
17 | %> |
|
17 | %> | |
18 |
|
||||
19 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
18 | <html xmlns="http://www.w3.org/1999/xhtml"> | |
20 | <head> |
|
19 | <head> | |
21 | <title>${self.title()}</title> |
|
20 | <title>${self.title()}</title> |
@@ -21,6 +21,7 b'' | |||||
21 | import pytest |
|
21 | import pytest | |
22 |
|
22 | |||
23 | from rhodecode.lib import helpers as h |
|
23 | from rhodecode.lib import helpers as h | |
|
24 | from rhodecode.lib.auth import check_password | |||
24 | from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys |
|
25 | from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys | |
25 | from rhodecode.model.meta import Session |
|
26 | from rhodecode.model.meta import Session | |
26 | from rhodecode.tests import ( |
|
27 | from rhodecode.tests import ( | |
@@ -34,6 +35,7 b' fixture = Fixture()' | |||||
34 |
|
35 | |||
35 | class TestMyAccountController(TestController): |
|
36 | class TestMyAccountController(TestController): | |
36 | test_user_1 = 'testme' |
|
37 | test_user_1 = 'testme' | |
|
38 | test_user_1_password = '0jd83nHNS/d23n' | |||
37 | destroy_users = set() |
|
39 | destroy_users = set() | |
38 |
|
40 | |||
39 | @classmethod |
|
41 | @classmethod | |
@@ -158,7 +160,8 b' class TestMyAccountController(TestContro' | |||||
158 | ('email', {'email': 'some@email.com'}), |
|
160 | ('email', {'email': 'some@email.com'}), | |
159 | ]) |
|
161 | ]) | |
160 | def test_my_account_update(self, name, attrs): |
|
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 | email='testme@rhodecode.org', |
|
165 | email='testme@rhodecode.org', | |
163 | extern_type='rhodecode', |
|
166 | extern_type='rhodecode', | |
164 | extern_name=self.test_user_1, |
|
167 | extern_name=self.test_user_1, | |
@@ -167,7 +170,8 b' class TestMyAccountController(TestContro' | |||||
167 |
|
170 | |||
168 | params = usr.get_api_data() # current user data |
|
171 | params = usr.get_api_data() # current user data | |
169 | user_id = usr.user_id |
|
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 | params.update({'password_confirmation': ''}) |
|
176 | params.update({'password_confirmation': ''}) | |
173 | params.update({'new_password': ''}) |
|
177 | params.update({'new_password': ''}) | |
@@ -321,8 +325,55 b' class TestMyAccountController(TestContro' | |||||
321 | response = response.follow() |
|
325 | response = response.follow() | |
322 | response.mustcontain(no=[api_key]) |
|
326 | response.mustcontain(no=[api_key]) | |
323 |
|
327 | |||
324 | def test_password_is_updated_in_session_on_password_change( |
|
328 | def test_valid_change_password(self, user_util): | |
325 | 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 | old_password = 'abcdef123' |
|
377 | old_password = 'abcdef123' | |
327 | new_password = 'abcdef124' |
|
378 | new_password = 'abcdef124' | |
328 |
|
379 | |||
@@ -330,12 +381,14 b' class TestMyAccountController(TestContro' | |||||
330 | session = self.log_user(user.username, old_password) |
|
381 | session = self.log_user(user.username, old_password) | |
331 | old_password_hash = session['password'] |
|
382 | old_password_hash = session['password'] | |
332 |
|
383 | |||
333 |
form_data = |
|
384 | form_data = [ | |
334 |
'current_password' |
|
385 | ('current_password', old_password), | |
335 | 'new_password': new_password, |
|
386 | ('__start__', 'new_password:mapping'), | |
336 |
'new_password |
|
387 | ('new_password', new_password), | |
337 | 'csrf_token': self.csrf_token |
|
388 | ('new_password-confirm', new_password), | |
338 | } |
|
389 | ('__end__', 'new_password:mapping'), | |
|
390 | ('csrf_token', self.csrf_token), | |||
|
391 | ] | |||
339 | self.app.post(url('my_account_password'), form_data) |
|
392 | self.app.post(url('my_account_password'), form_data) | |
340 |
|
393 | |||
341 | response = self.app.get(url('home')) |
|
394 | response = self.app.get(url('home')) |
General Comments 0
You need to be logged in to leave comments.
Login now