##// END OF EJS Templates
my-account: security change, added select filed with email from extra emails while editing user profile, now adding extra emails required type password. Task #5386
Bartłomiej Wołyńczyk -
r2592:0e0508de default
parent child Browse files
Show More
@@ -109,7 +109,7 b' class TestMyAccountEdit(TestController):'
109 109 # ('extern_name', {'extern_name': None}),
110 110 ('active', {'active': False}),
111 111 ('active', {'active': True}),
112 ('email', {'email': 'some@email.com'}),
112 ('email', {'email': u'some@email.com'}),
113 113 ])
114 114 def test_my_account_update(self, name, attrs, user_util):
115 115 usr = user_util.create_user(password='qweqwe')
@@ -120,13 +120,17 b' class TestMyAccountEdit(TestController):'
120 120
121 121 params.update({'password_confirmation': ''})
122 122 params.update({'new_password': ''})
123 params.update({'extern_type': 'rhodecode'})
124 params.update({'extern_name': 'rhodecode'})
123 params.update({'extern_type': u'rhodecode'})
124 params.update({'extern_name': u'rhodecode'})
125 125 params.update({'csrf_token': self.csrf_token})
126 126
127 127 params.update(attrs)
128 128 # my account page cannot set language param yet, only for admins
129 129 del params['language']
130 if name == 'email':
131 uem = user_util.create_additional_user_email(usr, attrs['email'])
132 email_before = User.get(user_id).email
133
130 134 response = self.app.post(route_path('my_account_update'), params)
131 135
132 136 assert_session_flash(
@@ -146,7 +150,7 b' class TestMyAccountEdit(TestController):'
146 150 params['language'] = updated_params['language']
147 151
148 152 if name == 'email':
149 params['emails'] = [attrs['email']]
153 params['emails'] = [attrs['email'], email_before]
150 154 if name == 'extern_type':
151 155 # cannot update this via form, expected value is original one
152 156 params['extern_type'] = "rhodecode"
@@ -162,10 +166,10 b' class TestMyAccountEdit(TestController):'
162 166
163 167 assert params == updated_params
164 168
165 def test_my_account_update_err_email_exists(self):
169 def test_my_account_update_err_email_not_exists_in_emails(self):
166 170 self.log_user()
167 171
168 new_email = 'test_regular@mail.com' # already existing email
172 new_email = 'test_regular@mail.com' # not in emails
169 173 params = {
170 174 'username': 'test_admin',
171 175 'new_password': 'test12',
@@ -179,7 +183,7 b' class TestMyAccountEdit(TestController):'
179 183 response = self.app.post(route_path('my_account_update'),
180 184 params=params)
181 185
182 response.mustcontain('This e-mail address is already taken')
186 response.mustcontain('"test_regular@mail.com" is not one of test_admin@mail.com')
183 187
184 188 def test_my_account_update_bad_email_address(self):
185 189 self.log_user('test_regular2', 'test12')
@@ -197,7 +201,4 b' class TestMyAccountEdit(TestController):'
197 201 response = self.app.post(route_path('my_account_update'),
198 202 params=params)
199 203
200 response.mustcontain('An email address must contain a single @')
201 msg = u'Username "%(username)s" already exists'
202 msg = h.html_escape(msg % {'username': 'test_admin'})
203 response.mustcontain(u"%s" % msg)
204 response.mustcontain('"newmail.pl" is not one of test_regular2@mail.com')
@@ -24,7 +24,7 b' from rhodecode.apps._base import ADMIN_P'
24 24 from rhodecode.model.db import User, UserEmailMap
25 25 from rhodecode.tests import (
26 26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
27 assert_session_flash)
27 assert_session_flash, TEST_USER_REGULAR_PASS)
28 28 from rhodecode.tests.fixture import Fixture
29 29
30 30 fixture = Fixture()
@@ -47,30 +47,14 b' class TestMyAccountEmails(TestController'
47 47 response = self.app.get(route_path('my_account_emails'))
48 48 response.mustcontain('No additional emails specified')
49 49
50 def test_my_account_my_emails_add_existing_email(self):
51 self.log_user()
52 response = self.app.get(route_path('my_account_emails'))
53 response.mustcontain('No additional emails specified')
54 response = self.app.post(route_path('my_account_emails_add'),
55 {'new_email': TEST_USER_REGULAR_EMAIL,
56 'csrf_token': self.csrf_token})
57 assert_session_flash(response, 'This e-mail address is already taken')
58
59 def test_my_account_my_emails_add_mising_email_in_form(self):
60 self.log_user()
61 response = self.app.get(route_path('my_account_emails'))
62 response.mustcontain('No additional emails specified')
63 response = self.app.post(route_path('my_account_emails_add'),
64 {'csrf_token': self.csrf_token})
65 assert_session_flash(response, 'Please enter an email address')
66
67 50 def test_my_account_my_emails_add_remove(self):
68 51 self.log_user()
69 52 response = self.app.get(route_path('my_account_emails'))
70 53 response.mustcontain('No additional emails specified')
71 54
72 55 response = self.app.post(route_path('my_account_emails_add'),
73 {'new_email': 'foo@barz.com',
56 {'email': 'foo@barz.com',
57 'current_password': TEST_USER_REGULAR_PASS,
74 58 'csrf_token': self.csrf_token})
75 59
76 60 response = self.app.get(route_path('my_account_emails'))
@@ -232,40 +232,59 b' class MyAccountView(BaseAppView, DataGri'
232 232
233 233 c.user_email_map = UserEmailMap.query()\
234 234 .filter(UserEmailMap.user == c.user).all()
235
236 schema = user_schema.AddEmailSchema().bind(
237 username=c.user.username, user_emails=c.user.emails)
238
239 form = forms.RcForm(schema,
240 action=h.route_path('my_account_emails_add'),
241 buttons=(forms.buttons.save, forms.buttons.reset))
242
243 c.form = form
235 244 return self._get_template_context(c)
236 245
237 246 @LoginRequired()
238 247 @NotAnonymous()
239 248 @CSRFRequired()
240 249 @view_config(
241 route_name='my_account_emails_add', request_method='POST')
250 route_name='my_account_emails_add', request_method='POST',
251 renderer='rhodecode:templates/admin/my_account/my_account.mako')
242 252 def my_account_emails_add(self):
243 253 _ = self.request.translate
244 254 c = self.load_default_context()
255 c.active = 'emails'
245 256
246 email = self.request.POST.get('new_email')
257 schema = user_schema.AddEmailSchema().bind(
258 username=c.user.username, user_emails=c.user.emails)
247 259
260 form = forms.RcForm(
261 schema, action=h.route_path('my_account_emails_add'),
262 buttons=(forms.buttons.save, forms.buttons.reset))
263
264 controls = self.request.POST.items()
248 265 try:
249 form = UserExtraEmailForm(self.request.translate)()
250 data = form.to_python({'email': email})
251 email = data['email']
252
253 UserModel().add_extra_email(c.user.user_id, email)
266 valid_data = form.validate(controls)
267 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
254 268 audit_logger.store_web(
255 269 'user.edit.email.add', action_data={
256 'data': {'email': email, 'user': 'self'}},
270 'data': {'email': valid_data['email'], 'user': 'self'}},
257 271 user=self._rhodecode_user,)
258
259 272 Session().commit()
260 h.flash(_("Added new email address `%s` for user account") % email,
261 category='success')
262 273 except formencode.Invalid as error:
263 274 h.flash(h.escape(error.error_dict['email']), category='error')
275 except forms.ValidationFailure as e:
276 c.user_email_map = UserEmailMap.query() \
277 .filter(UserEmailMap.user == c.user).all()
278 c.form = e
279 return self._get_template_context(c)
264 280 except Exception:
265 log.exception("Exception in my_account_emails")
266 h.flash(_('An error occurred during email saving'),
281 log.exception("Exception adding email")
282 h.flash(_('Error occurred during adding email'),
267 283 category='error')
268 return HTTPFound(h.route_path('my_account_emails'))
284 else:
285 h.flash(_("Successfully added email"), category='success')
286
287 raise HTTPFound(self.request.route_path('my_account_emails'))
269 288
270 289 @LoginRequired()
271 290 @NotAnonymous()
@@ -414,22 +433,23 b' class MyAccountView(BaseAppView, DataGri'
414 433 def my_account_edit(self):
415 434 c = self.load_default_context()
416 435 c.active = 'profile_edit'
417
418 c.perm_user = c.auth_user
419 436 c.extern_type = c.user.extern_type
420 437 c.extern_name = c.user.extern_name
421 438
422 defaults = c.user.get_dict()
439 schema = user_schema.UserProfileSchema().bind(
440 username=c.user.username, user_emails=c.user.emails)
441 appstruct = {
442 'username': c.user.username,
443 'email': c.user.email,
444 'firstname': c.user.firstname,
445 'lastname': c.user.lastname,
446 }
447 c.form = forms.RcForm(
448 schema, appstruct=appstruct,
449 action=h.route_path('my_account_update'),
450 buttons=(forms.buttons.save, forms.buttons.reset))
423 451
424 data = render('rhodecode:templates/admin/my_account/my_account.mako',
425 self._get_template_context(c), self.request)
426 html = formencode.htmlfill.render(
427 data,
428 defaults=defaults,
429 encoding="UTF-8",
430 force_defaults=False
431 )
432 return Response(html)
452 return self._get_template_context(c)
433 453
434 454 @LoginRequired()
435 455 @NotAnonymous()
@@ -442,55 +462,40 b' class MyAccountView(BaseAppView, DataGri'
442 462 _ = self.request.translate
443 463 c = self.load_default_context()
444 464 c.active = 'profile_edit'
445
446 465 c.perm_user = c.auth_user
447 466 c.extern_type = c.user.extern_type
448 467 c.extern_name = c.user.extern_name
449 468
450 _form = UserForm(self.request.translate, edit=True,
451 old_data={'user_id': self._rhodecode_user.user_id,
452 'email': self._rhodecode_user.email})()
453 form_result = {}
469 schema = user_schema.UserProfileSchema().bind(
470 username=c.user.username, user_emails=c.user.emails)
471 form = forms.RcForm(
472 schema, buttons=(forms.buttons.save, forms.buttons.reset))
473
474 controls = self.request.POST.items()
454 475 try:
455 post_data = dict(self.request.POST)
456 post_data['new_password'] = ''
457 post_data['password_confirmation'] = ''
458 form_result = _form.to_python(post_data)
459 # skip updating those attrs for my account
476 valid_data = form.validate(controls)
460 477 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
461 478 'new_password', 'password_confirmation']
462 # TODO: plugin should define if username can be updated
463 479 if c.extern_type != "rhodecode":
464 480 # forbid updating username for external accounts
465 481 skip_attrs.append('username')
466
482 old_email = c.user.email
467 483 UserModel().update_user(
468 484 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
469 **form_result)
470 h.flash(_('Your account was updated successfully'),
471 category='success')
485 **valid_data)
486 if old_email != valid_data['email']:
487 old = UserEmailMap.query() \
488 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
489 old.email = old_email
490 h.flash(_('Your account was updated successfully'), category='success')
472 491 Session().commit()
473
474 except formencode.Invalid as errors:
475 data = render(
476 'rhodecode:templates/admin/my_account/my_account.mako',
477 self._get_template_context(c), self.request)
478
479 html = formencode.htmlfill.render(
480 data,
481 defaults=errors.value,
482 errors=errors.error_dict or {},
483 prefix_error=False,
484 encoding="UTF-8",
485 force_defaults=False)
486 return Response(html)
487
492 except forms.ValidationFailure as e:
493 c.form = e
494 return self._get_template_context(c)
488 495 except Exception:
489 496 log.exception("Exception updating user")
490 h.flash(_('Error occurred during update of user %s')
491 % form_result.get('username'), category='error')
492 raise HTTPFound(h.route_path('my_account_profile'))
493
497 h.flash(_('Error occurred during update of user'),
498 category='error')
494 499 raise HTTPFound(h.route_path('my_account_profile'))
495 500
496 501 def _get_pull_requests_list(self, statuses):
@@ -22,10 +22,11 b' import re'
22 22 import colander
23 23
24 24 from rhodecode import forms
25 from rhodecode.model.db import User
25 from rhodecode.model.db import User, UserEmailMap
26 26 from rhodecode.model.validation_schema import types, validators
27 27 from rhodecode.translation import _
28 28 from rhodecode.lib.auth import check_password
29 from rhodecode.lib import helpers as h
29 30
30 31
31 32 @colander.deferred
@@ -40,6 +41,7 b' def deferred_user_password_validator(nod'
40 41 return _user_password_validator
41 42
42 43
44
43 45 class ChangePasswordSchema(colander.Schema):
44 46
45 47 current_password = colander.SchemaNode(
@@ -123,3 +125,64 b' class UserSchema(colander.Schema):'
123 125
124 126 appstruct = super(UserSchema, self).deserialize(cstruct)
125 127 return appstruct
128
129
130 @colander.deferred
131 def deferred_user_email_in_emails_validator(node, kw):
132 return colander.OneOf(kw.get('user_emails'))
133
134
135 @colander.deferred
136 def deferred_additional_email_validator(node, kw):
137 emails = kw.get('user_emails')
138
139 def name_validator(node, value):
140 if value in emails:
141 msg = _('This e-mail address is already taken')
142 raise colander.Invalid(node, msg)
143 user = User.get_by_email(value, case_insensitive=True)
144 if user:
145 msg = _(u'This e-mail address is already taken')
146 raise colander.Invalid(node, msg)
147 c = colander.Email()
148 return c(node, value)
149 return name_validator
150
151
152 @colander.deferred
153 def deferred_user_email_in_emails_widget(node, kw):
154 import deform.widget
155 emails = [(email, email) for email in kw.get('user_emails')]
156 return deform.widget.Select2Widget(values=emails)
157
158
159 class UserProfileSchema(colander.Schema):
160 username = colander.SchemaNode(
161 colander.String(),
162 validator=deferred_username_validator)
163
164 firstname = colander.SchemaNode(
165 colander.String(), missing='', title='First name')
166
167 lastname = colander.SchemaNode(
168 colander.String(), missing='', title='Last name')
169
170 email = colander.SchemaNode(
171 colander.String(), widget=deferred_user_email_in_emails_widget,
172 validator=deferred_user_email_in_emails_validator,
173 description=h.literal(
174 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
175 '/_admin/my_account/emails')),
176 )
177
178
179 class AddEmailSchema(colander.Schema):
180 current_password = colander.SchemaNode(
181 colander.String(),
182 missing=colander.required,
183 widget=forms.widget.PasswordWidget(redisplay=True),
184 validator=deferred_user_password_validator)
185
186 email = colander.SchemaNode(
187 colander.String(), title='New Email',
188 validator=deferred_additional_email_validator)
@@ -48,25 +48,7 b''
48 48 </div>
49 49
50 50 <div>
51 ${h.secure_form(h.route_path('my_account_emails_add'), request=request)}
52 <div class="form">
53 <!-- fields -->
54 <div class="fields">
55 <div class="field">
56 <div class="label">
57 <label for="new_email">${_('New email address')}:</label>
58 </div>
59 <div class="input">
60 ${h.text('new_email', class_='medium')}
61 </div>
62 </div>
63 <div class="buttons">
64 ${h.submit('save',_('Add'),class_="btn")}
65 ${h.reset('reset',_('Reset'),class_="btn")}
51 ${c.form.render() | n}
66 52 </div>
67 53 </div>
68 54 </div>
69 ${h.end_form()}
70 </div>
71 </div>
72 </div>
@@ -6,7 +6,6 b''
6 6 </div>
7 7
8 8 <div class="panel-body">
9 ${h.secure_form(h.route_path('my_account_update'), class_='form', request=request)}
10 9 <% readonly = None %>
11 10 <% disabled = "" %>
12 11
@@ -24,7 +23,7 b''
24 23 <label for="username">${_('Username')}:</label>
25 24 </div>
26 25 <div class="input">
27 ${h.text('username', class_='input-valuedisplay', readonly=readonly)}
26 ${c.user.username}
28 27 </div>
29 28 </div>
30 29
@@ -33,7 +32,7 b''
33 32 <label for="name">${_('First Name')}:</label>
34 33 </div>
35 34 <div class="input">
36 ${h.text('firstname', class_='input-valuedisplay', readonly=readonly)}
35 ${c.user.firstname}
37 36 </div>
38 37 </div>
39 38
@@ -42,7 +41,7 b''
42 41 <label for="lastname">${_('Last Name')}:</label>
43 42 </div>
44 43 <div class="input-valuedisplay">
45 ${h.text('lastname', class_='input-valuedisplay', readonly=readonly)}
44 ${c.user.lastname}
46 45 </div>
47 46 </div>
48 47 </div>
@@ -64,48 +63,7 b''
64 63 %endif
65 64 </div>
66 65 </div>
67 <div class="field">
68 <div class="label">
69 <label for="username">${_('Username')}:</label>
70 </div>
71 <div class="input">
72 ${h.text('username', class_='medium%s' % disabled, readonly=readonly)}
73 ${h.hidden('extern_name', c.extern_name)}
74 ${h.hidden('extern_type', c.extern_type)}
75 </div>
76 </div>
77 <div class="field">
78 <div class="label">
79 <label for="name">${_('First Name')}:</label>
80 </div>
81 <div class="input">
82 ${h.text('firstname', class_="medium")}
83 </div>
84 </div>
85
86 <div class="field">
87 <div class="label">
88 <label for="lastname">${_('Last Name')}:</label>
89 </div>
90 <div class="input">
91 ${h.text('lastname', class_="medium")}
92 </div>
93 </div>
94
95 <div class="field">
96 <div class="label">
97 <label for="email">${_('Email')}:</label>
98 </div>
99 <div class="input">
100 ## we should be able to edit email !
101 ${h.text('email', class_="medium")}
102 </div>
103 </div>
104
105 <div class="buttons">
106 ${h.submit('save', _('Save'), class_="btn")}
107 ${h.reset('reset', _('Reset'), class_="btn")}
108 </div>
66 ${c.form.render()| n}
109 67 </div>
110 68 </div>
111 69 % endif
@@ -30,7 +30,7 b' import shutil'
30 30 import configobj
31 31
32 32 from rhodecode.tests import *
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.user import UserModel
@@ -275,6 +275,13 b' class Fixture(object):'
275 275 UserModel().delete(userid)
276 276 Session().commit()
277 277
278 def create_additional_user_email(self, user, email):
279 uem = UserEmailMap()
280 uem.user = user
281 uem.email = email
282 Session().add(uem)
283 return uem
284
278 285 def destroy_users(self, userid_iter):
279 286 for user_id in userid_iter:
280 287 if User.get_by_username(user_id):
@@ -1178,6 +1178,10 b' class UserUtility(object):'
1178 1178 self.user_ids.append(user.user_id)
1179 1179 return user
1180 1180
1181 def create_additional_user_email(self, user, email):
1182 uem = self.fixture.create_additional_user_email(user=user, email=email)
1183 return uem
1184
1181 1185 def create_user_with_group(self):
1182 1186 user = self.create_user()
1183 1187 user_group = self.create_user_group(members=[user])
General Comments 0
You need to be logged in to leave comments. Login now