##// END OF EJS Templates
my-account: switched my-password view to pyramid.
marcink -
r1537:9ff058ee default
parent child Browse files
Show More
@@ -0,0 +1,137 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 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 pytest
22 import mock
23
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import check_password
27 from rhodecode.model.meta import Session
28 from rhodecode.model.user import UserModel
29 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests.fixture import Fixture, TestController, error_function
31
32 fixture = Fixture()
33
34
35 def route_path(name, **kwargs):
36 return {
37 'home': '/',
38 'my_account_password':
39 ADMIN_PREFIX + '/my_account/password',
40 }[name].format(**kwargs)
41
42
43 test_user_1 = 'testme'
44 test_user_1_password = '0jd83nHNS/d23n'
45
46
47 class TestMyAccountPassword(TestController):
48 def test_valid_change_password(self, user_util):
49 new_password = 'my_new_valid_password'
50 user = user_util.create_user(password=test_user_1_password)
51 self.log_user(user.username, test_user_1_password)
52
53 form_data = [
54 ('current_password', test_user_1_password),
55 ('__start__', 'new_password:mapping'),
56 ('new_password', new_password),
57 ('new_password-confirm', new_password),
58 ('__end__', 'new_password:mapping'),
59 ('csrf_token', self.csrf_token),
60 ]
61 response = self.app.post(route_path('my_account_password'), form_data).follow()
62 assert 'Successfully updated password' in response
63
64 # check_password depends on user being in session
65 Session().add(user)
66 try:
67 assert check_password(new_password, user.password)
68 finally:
69 Session().expunge(user)
70
71 @pytest.mark.parametrize('current_pw, new_pw, confirm_pw', [
72 ('', 'abcdef123', 'abcdef123'),
73 ('wrong_pw', 'abcdef123', 'abcdef123'),
74 (test_user_1_password, test_user_1_password, test_user_1_password),
75 (test_user_1_password, '', ''),
76 (test_user_1_password, 'abcdef123', ''),
77 (test_user_1_password, '', 'abcdef123'),
78 (test_user_1_password, 'not_the', 'same_pw'),
79 (test_user_1_password, 'short', 'short'),
80 ])
81 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
82 user_util):
83 user = user_util.create_user(password=test_user_1_password)
84 self.log_user(user.username, test_user_1_password)
85
86 form_data = [
87 ('current_password', current_pw),
88 ('__start__', 'new_password:mapping'),
89 ('new_password', new_pw),
90 ('new_password-confirm', confirm_pw),
91 ('__end__', 'new_password:mapping'),
92 ('csrf_token', self.csrf_token),
93 ]
94 response = self.app.post(route_path('my_account_password'), form_data)
95
96 assert_response = response.assert_response()
97 assert assert_response.get_elements('.error-block')
98
99 @mock.patch.object(UserModel, 'update_user', error_function)
100 def test_invalid_change_password_exception(self, user_util):
101 user = user_util.create_user(password=test_user_1_password)
102 self.log_user(user.username, test_user_1_password)
103
104 form_data = [
105 ('current_password', test_user_1_password),
106 ('__start__', 'new_password:mapping'),
107 ('new_password', '123456'),
108 ('new_password-confirm', '123456'),
109 ('__end__', 'new_password:mapping'),
110 ('csrf_token', self.csrf_token),
111 ]
112 response = self.app.post(route_path('my_account_password'), form_data)
113 assert_session_flash(
114 response, 'Error occurred during update of user password')
115
116 def test_password_is_updated_in_session_on_password_change(self, user_util):
117 old_password = 'abcdef123'
118 new_password = 'abcdef124'
119
120 user = user_util.create_user(password=old_password)
121 session = self.log_user(user.username, old_password)
122 old_password_hash = session['password']
123
124 form_data = [
125 ('current_password', old_password),
126 ('__start__', 'new_password:mapping'),
127 ('new_password', new_password),
128 ('new_password-confirm', new_password),
129 ('__end__', 'new_password:mapping'),
130 ('csrf_token', self.csrf_token),
131 ]
132 self.app.post(route_path('my_account_password'), form_data)
133
134 response = self.app.get(route_path('home'))
135 new_password_hash = response.session['rhodecode_user']['password']
136
137 assert old_password_hash != new_password_hash No newline at end of file
@@ -20,6 +20,7 b''
20
20
21 import logging
21 import logging
22 from pylons import tmpl_context as c
22 from pylons import tmpl_context as c
23 from pyramid.httpexceptions import HTTPFound
23
24
24 from rhodecode.lib.utils2 import StrictAttributeDict
25 from rhodecode.lib.utils2 import StrictAttributeDict
25
26
@@ -41,6 +42,7 b' class BaseAppView(object):'
41 self.context = context
42 self.context = context
42 self.session = request.session
43 self.session = request.session
43 self._rhodecode_user = request.user # auth user
44 self._rhodecode_user = request.user # auth user
45 self._rhodecode_db_user = self._rhodecode_user.get_instance()
44
46
45 def _get_local_tmpl_context(self):
47 def _get_local_tmpl_context(self):
46 c = TemplateArgs()
48 c = TemplateArgs()
@@ -23,6 +23,15 b' from rhodecode.apps._base import ADMIN_P'
23
23
24
24
25 def includeme(config):
25 def includeme(config):
26
27 config.add_route(
28 name='my_account_password',
29 pattern=ADMIN_PREFIX + '/my_account/password')
30
31 config.add_route(
32 name='my_account_password_update',
33 pattern=ADMIN_PREFIX + '/my_account/password')
34
26 config.add_route(
35 config.add_route(
27 name='my_account_auth_tokens',
36 name='my_account_auth_tokens',
28 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
37 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
@@ -24,11 +24,14 b' from pyramid.httpexceptions import HTTPF'
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode import forms
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils2 import safe_int, md5
30 from rhodecode.model.auth_token import AuthTokenModel
31 from rhodecode.model.auth_token import AuthTokenModel
31 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.user import UserModel
34 from rhodecode.model.validation_schema.schemas import user_schema
32
35
33 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
34
37
@@ -42,7 +45,6 b' class MyAccountView(BaseAppView):'
42
45
43 def load_default_context(self):
46 def load_default_context(self):
44 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
45
46 c.user = c.auth_user.get_instance()
48 c.user = c.auth_user.get_instance()
47 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
48 self._register_global_c(c)
50 self._register_global_c(c)
@@ -51,6 +53,69 b' class MyAccountView(BaseAppView):'
51 @LoginRequired()
53 @LoginRequired()
52 @NotAnonymous()
54 @NotAnonymous()
53 @view_config(
55 @view_config(
56 route_name='my_account_password', request_method='GET',
57 renderer='rhodecode:templates/admin/my_account/my_account.mako')
58 def my_account_password(self):
59 c = self.load_default_context()
60 c.active = 'password'
61 c.extern_type = c.user.extern_type
62
63 schema = user_schema.ChangePasswordSchema().bind(
64 username=c.user.username)
65
66 form = forms.Form(
67 schema, buttons=(forms.buttons.save, forms.buttons.reset))
68
69 c.form = form
70 return self._get_template_context(c)
71
72 @LoginRequired()
73 @NotAnonymous()
74 @CSRFRequired()
75 @view_config(
76 route_name='my_account_password', request_method='POST',
77 renderer='rhodecode:templates/admin/my_account/my_account.mako')
78 def my_account_password_update(self):
79 _ = self.request.translate
80 c = self.load_default_context()
81 c.active = 'password'
82 c.extern_type = c.user.extern_type
83
84 schema = user_schema.ChangePasswordSchema().bind(
85 username=c.user.username)
86
87 form = forms.Form(
88 schema, buttons=(forms.buttons.save, forms.buttons.reset))
89
90 if c.extern_type != 'rhodecode':
91 raise HTTPFound(self.request.route_path('my_account_password'))
92
93 controls = self.request.POST.items()
94 try:
95 valid_data = form.validate(controls)
96 UserModel().update_user(c.user.user_id, **valid_data)
97 c.user.update_userdata(force_password_change=False)
98 Session().commit()
99 except forms.ValidationFailure as e:
100 c.form = e
101 return self._get_template_context(c)
102
103 except Exception:
104 log.exception("Exception updating password")
105 h.flash(_('Error occurred during update of user password'),
106 category='error')
107 else:
108 instance = c.auth_user.get_instance()
109 self.session.setdefault('rhodecode_user', {}).update(
110 {'password': md5(instance.password)})
111 self.session.save()
112 h.flash(_("Successfully updated password"), category='success')
113
114 raise HTTPFound(self.request.route_path('my_account_password'))
115
116 @LoginRequired()
117 @NotAnonymous()
118 @view_config(
54 route_name='my_account_auth_tokens', request_method='GET',
119 route_name='my_account_auth_tokens', request_method='GET',
55 renderer='rhodecode:templates/admin/my_account/my_account.mako')
120 renderer='rhodecode:templates/admin/my_account/my_account.mako')
56 def my_account_auth_tokens(self):
121 def my_account_auth_tokens(self):
@@ -512,8 +512,10 b' def make_map(config):'
512 m.connect('my_account', '/my_account',
512 m.connect('my_account', '/my_account',
513 action='my_account_update', conditions={'method': ['POST']})
513 action='my_account_update', conditions={'method': ['POST']})
514
514
515 # NOTE(marcink): this needs to be kept for password force flag to be
516 # handler, remove after migration to pyramid
515 m.connect('my_account_password', '/my_account/password',
517 m.connect('my_account_password', '/my_account/password',
516 action='my_account_password', conditions={'method': ['GET', 'POST']})
518 action='my_account_password', conditions={'method': ['GET']})
517
519
518 m.connect('my_account_repos', '/my_account/repos',
520 m.connect('my_account_repos', '/my_account/repos',
519 action='my_account_repos', conditions={'method': ['GET']})
521 action='my_account_repos', conditions={'method': ['GET']})
@@ -34,19 +34,17 b' from pylons.controllers.util import redi'
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36
36
37 from rhodecode import forms
38 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
39 from rhodecode.lib import auth
38 from rhodecode.lib import auth
40 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
41 LoginRequired, NotAnonymous, AuthUser)
40 LoginRequired, NotAnonymous, AuthUser)
42 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
43 from rhodecode.lib.utils2 import safe_int, str2bool
45 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.ext_json import json
46 from rhodecode.lib.channelstream import channelstream_request, \
45 from rhodecode.lib.channelstream import channelstream_request, \
47 ChannelstreamException
46 ChannelstreamException
48
47
49 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.db import (
48 from rhodecode.model.db import (
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
49 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 from rhodecode.model.forms import UserForm
50 from rhodecode.model.forms import UserForm
@@ -194,47 +192,6 b' class MyAccountController(BaseController'
194 force_defaults=False
192 force_defaults=False
195 )
193 )
196
194
197 @auth.CSRFRequired(except_methods=['GET'])
198 def my_account_password(self):
199 c.active = 'password'
200 self.__load_data()
201 c.extern_type = c.user.extern_type
202
203 schema = user_schema.ChangePasswordSchema().bind(
204 username=c.rhodecode_user.username)
205
206 form = forms.Form(schema,
207 buttons=(forms.buttons.save, forms.buttons.reset))
208
209 if request.method == 'POST' and c.extern_type == 'rhodecode':
210 controls = request.POST.items()
211 try:
212 valid_data = form.validate(controls)
213 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
214 instance = c.rhodecode_user.get_instance()
215 instance.update_userdata(force_password_change=False)
216 Session().commit()
217 except forms.ValidationFailure as e:
218 request.session.flash(
219 _('Error occurred during update of user password'),
220 queue='error')
221 form = e
222 except Exception:
223 log.exception("Exception updating password")
224 request.session.flash(
225 _('Error occurred during update of user password'),
226 queue='error')
227 else:
228 session.setdefault('rhodecode_user', {}).update(
229 {'password': md5(instance.password)})
230 session.save()
231 request.session.flash(
232 _("Successfully updated password"), queue='success')
233 return redirect(url('my_account_password'))
234
235 c.form = form
236 return render('admin/my_account/my_account.mako')
237
238 def my_account_repos(self):
195 def my_account_repos(self):
239 c.active = 'repos'
196 c.active = 'repos'
240 self.__load_data()
197 self.__load_data()
@@ -27,7 +27,7 b''
27 <div class="sidebar">
27 <div class="sidebar">
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 ## TODO: Find a better integration of oauth views into navigation.
32 ## TODO: Find a better integration of oauth views into navigation.
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
@@ -21,9 +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
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
26 from rhodecode.model.meta import Session
27 from rhodecode.tests import (
25 from rhodecode.tests import (
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
26 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
29 assert_session_flash)
27 assert_session_flash)
@@ -253,74 +251,3 b' class TestMyAccountController(TestContro'
253 edit=False, old_data={})._messages['username_exists']
251 edit=False, old_data={})._messages['username_exists']
254 msg = h.html_escape(msg % {'username': 'test_admin'})
252 msg = h.html_escape(msg % {'username': 'test_admin'})
255 response.mustcontain(u"%s" % msg)
253 response.mustcontain(u"%s" % msg)
256
257 def test_valid_change_password(self, user_util):
258 new_password = 'my_new_valid_password'
259 user = user_util.create_user(password=self.test_user_1_password)
260 session = self.log_user(user.username, self.test_user_1_password)
261 form_data = [
262 ('current_password', self.test_user_1_password),
263 ('__start__', 'new_password:mapping'),
264 ('new_password', new_password),
265 ('new_password-confirm', new_password),
266 ('__end__', 'new_password:mapping'),
267 ('csrf_token', self.csrf_token),
268 ]
269 response = self.app.post(url('my_account_password'), form_data).follow()
270 assert 'Successfully updated password' in response
271
272 # check_password depends on user being in session
273 Session().add(user)
274 try:
275 assert check_password(new_password, user.password)
276 finally:
277 Session().expunge(user)
278
279 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
280 ('', 'abcdef123', 'abcdef123'),
281 ('wrong_pw', 'abcdef123', 'abcdef123'),
282 (test_user_1_password, test_user_1_password, test_user_1_password),
283 (test_user_1_password, '', ''),
284 (test_user_1_password, 'abcdef123', ''),
285 (test_user_1_password, '', 'abcdef123'),
286 (test_user_1_password, 'not_the', 'same_pw'),
287 (test_user_1_password, 'short', 'short'),
288 ])
289 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
290 user_util):
291 user = user_util.create_user(password=self.test_user_1_password)
292 session = self.log_user(user.username, self.test_user_1_password)
293 old_password_hash = session['password']
294 form_data = [
295 ('current_password', current_pw),
296 ('__start__', 'new_password:mapping'),
297 ('new_password', new_pw),
298 ('new_password-confirm', confirm_pw),
299 ('__end__', 'new_password:mapping'),
300 ('csrf_token', self.csrf_token),
301 ]
302 response = self.app.post(url('my_account_password'), form_data)
303 assert 'Error occurred' in response
304
305 def test_password_is_updated_in_session_on_password_change(self, user_util):
306 old_password = 'abcdef123'
307 new_password = 'abcdef124'
308
309 user = user_util.create_user(password=old_password)
310 session = self.log_user(user.username, old_password)
311 old_password_hash = session['password']
312
313 form_data = [
314 ('current_password', old_password),
315 ('__start__', 'new_password:mapping'),
316 ('new_password', new_password),
317 ('new_password-confirm', new_password),
318 ('__end__', 'new_password:mapping'),
319 ('csrf_token', self.csrf_token),
320 ]
321 self.app.post(url('my_account_password'), form_data)
322
323 response = self.app.get(url('home'))
324 new_password_hash = response.session['rhodecode_user']['password']
325
326 assert old_password_hash != new_password_hash
General Comments 0
You need to be logged in to leave comments. Login now