##// END OF EJS Templates
feat(forms): user profile form would detect nicely duplicates and show a form error.
super-admin -
r5353:7a7f1159 default
parent child Browse files
Show More
@@ -1,196 +1,202 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import re
19 import re
20 import colander
20 import colander
21
21
22 from rhodecode import forms
22 from rhodecode import forms
23 from rhodecode.model.db import User, UserEmailMap
23 from rhodecode.model.db import User, UserEmailMap
24 from rhodecode.model.validation_schema import types, validators
24 from rhodecode.model.validation_schema import types, validators
25 from rhodecode.translation import _
25 from rhodecode.translation import _
26 from rhodecode.lib.auth import check_password
26 from rhodecode.lib.auth import check_password
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28
28
29
29
30 @colander.deferred
30 @colander.deferred
31 def deferred_user_password_validator(node, kw):
31 def deferred_user_password_validator(node, kw):
32 username = kw.get('username')
32 username = kw.get('username')
33 user = User.get_by_username(username)
33 user = User.get_by_username(username)
34
34
35 def _user_password_validator(node, value):
35 def _user_password_validator(node, value):
36 if not check_password(value, user.password):
36 if not check_password(value, user.password):
37 msg = _('Password is incorrect')
37 msg = _('Password is incorrect')
38 raise colander.Invalid(node, msg)
38 raise colander.Invalid(node, msg)
39 return _user_password_validator
39 return _user_password_validator
40
40
41
41
42
42
43 class ChangePasswordSchema(colander.Schema):
43 class ChangePasswordSchema(colander.Schema):
44
44
45 current_password = colander.SchemaNode(
45 current_password = colander.SchemaNode(
46 colander.String(),
46 colander.String(),
47 missing=colander.required,
47 missing=colander.required,
48 widget=forms.widget.PasswordWidget(redisplay=True),
48 widget=forms.widget.PasswordWidget(redisplay=True),
49 validator=deferred_user_password_validator)
49 validator=deferred_user_password_validator)
50
50
51 new_password = colander.SchemaNode(
51 new_password = colander.SchemaNode(
52 colander.String(),
52 colander.String(),
53 missing=colander.required,
53 missing=colander.required,
54 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
54 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
55 validator=colander.Length(min=6))
55 validator=colander.Length(min=6))
56
56
57 def validator(self, form, values):
57 def validator(self, form, values):
58 if values['current_password'] == values['new_password']:
58 if values['current_password'] == values['new_password']:
59 exc = colander.Invalid(form)
59 exc = colander.Invalid(form)
60 exc['new_password'] = _('New password must be different '
60 exc['new_password'] = _('New password must be different '
61 'to old password')
61 'to old password')
62 raise exc
62 raise exc
63
63
64
64
65 @colander.deferred
65 @colander.deferred
66 def deferred_username_validator(node, kw):
66 def deferred_username_validator(node, kw):
67 old_username = kw.get('username')
67
68
68 def name_validator(node, value):
69 def name_validator(node, value):
69 msg = _(
70 msg = _(
70 'Username may only contain alphanumeric characters '
71 'Username may only contain alphanumeric characters '
71 'underscores, periods or dashes and must begin with '
72 'underscores, periods or dashes and must begin with '
72 'alphanumeric character or underscore')
73 'alphanumeric character or underscore')
73
74
74 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
75 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
75 raise colander.Invalid(node, msg)
76 raise colander.Invalid(node, msg)
76
77
78 if value != old_username:
79 existing_user = User.get_by_username(value, case_insensitive=True)
80 if existing_user:
81 raise colander.Invalid(node, 'Username is already taken')
82
77 return name_validator
83 return name_validator
78
84
79
85
80 @colander.deferred
86 @colander.deferred
81 def deferred_email_validator(node, kw):
87 def deferred_email_validator(node, kw):
82 # NOTE(marcink): we might provide uniqueness validation later here...
88 # NOTE(marcink): we might provide uniqueness validation later here...
83 return colander.Email()
89 return colander.Email()
84
90
85
91
86 class UserSchema(colander.Schema):
92 class UserSchema(colander.Schema):
87 username = colander.SchemaNode(
93 username = colander.SchemaNode(
88 colander.String(),
94 colander.String(),
89 validator=deferred_username_validator)
95 validator=deferred_username_validator)
90
96
91 email = colander.SchemaNode(
97 email = colander.SchemaNode(
92 colander.String(),
98 colander.String(),
93 validator=deferred_email_validator)
99 validator=deferred_email_validator)
94
100
95 password = colander.SchemaNode(
101 password = colander.SchemaNode(
96 colander.String(), missing='')
102 colander.String(), missing='')
97
103
98 first_name = colander.SchemaNode(
104 first_name = colander.SchemaNode(
99 colander.String(), missing='')
105 colander.String(), missing='')
100
106
101 last_name = colander.SchemaNode(
107 last_name = colander.SchemaNode(
102 colander.String(), missing='')
108 colander.String(), missing='')
103
109
104 description = colander.SchemaNode(
110 description = colander.SchemaNode(
105 colander.String(), missing='')
111 colander.String(), missing='')
106
112
107 active = colander.SchemaNode(
113 active = colander.SchemaNode(
108 types.StringBooleanType(),
114 types.StringBooleanType(),
109 missing=False)
115 missing=False)
110
116
111 admin = colander.SchemaNode(
117 admin = colander.SchemaNode(
112 types.StringBooleanType(),
118 types.StringBooleanType(),
113 missing=False)
119 missing=False)
114
120
115 extern_name = colander.SchemaNode(
121 extern_name = colander.SchemaNode(
116 colander.String(), missing='')
122 colander.String(), missing='')
117
123
118 extern_type = colander.SchemaNode(
124 extern_type = colander.SchemaNode(
119 colander.String(), missing='')
125 colander.String(), missing='')
120
126
121 def deserialize(self, cstruct):
127 def deserialize(self, cstruct):
122 """
128 """
123 Custom deserialize that allows to chain validation, and verify
129 Custom deserialize that allows to chain validation, and verify
124 permissions, and as last step uniqueness
130 permissions, and as last step uniqueness
125 """
131 """
126
132
127 appstruct = super().deserialize(cstruct)
133 appstruct = super().deserialize(cstruct)
128 return appstruct
134 return appstruct
129
135
130
136
131 @colander.deferred
137 @colander.deferred
132 def deferred_user_email_in_emails_validator(node, kw):
138 def deferred_user_email_in_emails_validator(node, kw):
133 return colander.OneOf(kw.get('user_emails'))
139 return colander.OneOf(kw.get('user_emails'))
134
140
135
141
136 @colander.deferred
142 @colander.deferred
137 def deferred_additional_email_validator(node, kw):
143 def deferred_additional_email_validator(node, kw):
138 emails = kw.get('user_emails')
144 emails = kw.get('user_emails')
139
145
140 def name_validator(node, value):
146 def name_validator(node, value):
141 if value in emails:
147 if value in emails:
142 msg = _('This e-mail address is already taken')
148 msg = _('This e-mail address is already taken')
143 raise colander.Invalid(node, msg)
149 raise colander.Invalid(node, msg)
144 user = User.get_by_email(value, case_insensitive=True)
150 user = User.get_by_email(value, case_insensitive=True)
145 if user:
151 if user:
146 msg = _('This e-mail address is already taken')
152 msg = _('This e-mail address is already taken')
147 raise colander.Invalid(node, msg)
153 raise colander.Invalid(node, msg)
148 c = colander.Email()
154 c = colander.Email()
149 return c(node, value)
155 return c(node, value)
150 return name_validator
156 return name_validator
151
157
152
158
153 @colander.deferred
159 @colander.deferred
154 def deferred_user_email_in_emails_widget(node, kw):
160 def deferred_user_email_in_emails_widget(node, kw):
155 import deform.widget
161 import deform.widget
156 emails = [(email, email) for email in kw.get('user_emails')]
162 emails = [(email, email) for email in kw.get('user_emails')]
157 return deform.widget.Select2Widget(values=emails)
163 return deform.widget.Select2Widget(values=emails)
158
164
159
165
160 class UserProfileSchema(colander.Schema):
166 class UserProfileSchema(colander.Schema):
161 username = colander.SchemaNode(
167 username = colander.SchemaNode(
162 colander.String(),
168 colander.String(),
163 validator=deferred_username_validator)
169 validator=deferred_username_validator)
164
170
165 firstname = colander.SchemaNode(
171 firstname = colander.SchemaNode(
166 colander.String(), missing='', title='First name')
172 colander.String(), missing='', title='First name')
167
173
168 lastname = colander.SchemaNode(
174 lastname = colander.SchemaNode(
169 colander.String(), missing='', title='Last name')
175 colander.String(), missing='', title='Last name')
170
176
171 description = colander.SchemaNode(
177 description = colander.SchemaNode(
172 colander.String(), missing='', title='Personal Description',
178 colander.String(), missing='', title='Personal Description',
173 widget=forms.widget.TextAreaWidget(),
179 widget=forms.widget.TextAreaWidget(),
174 validator=colander.Length(max=250)
180 validator=colander.Length(max=250)
175 )
181 )
176
182
177 email = colander.SchemaNode(
183 email = colander.SchemaNode(
178 colander.String(), widget=deferred_user_email_in_emails_widget,
184 colander.String(), widget=deferred_user_email_in_emails_widget,
179 validator=deferred_user_email_in_emails_validator,
185 validator=deferred_user_email_in_emails_validator,
180 description=h.literal(
186 description=h.literal(
181 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
187 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
182 '/_admin/my_account/emails')),
188 '/_admin/my_account/emails')),
183 )
189 )
184
190
185
191
186
192
187 class AddEmailSchema(colander.Schema):
193 class AddEmailSchema(colander.Schema):
188 current_password = colander.SchemaNode(
194 current_password = colander.SchemaNode(
189 colander.String(),
195 colander.String(),
190 missing=colander.required,
196 missing=colander.required,
191 widget=forms.widget.PasswordWidget(redisplay=True),
197 widget=forms.widget.PasswordWidget(redisplay=True),
192 validator=deferred_user_password_validator)
198 validator=deferred_user_password_validator)
193
199
194 email = colander.SchemaNode(
200 email = colander.SchemaNode(
195 colander.String(), title='New Email',
201 colander.String(), title='New Email',
196 validator=deferred_additional_email_validator)
202 validator=deferred_additional_email_validator)
General Comments 0
You need to be logged in to leave comments. Login now