##// END OF EJS Templates
users: add edition of description in admin view for users
marcink -
r4022:8d4c4139 default
parent child Browse files
Show More
@@ -1,760 +1,761 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import string
23 import string
24
24
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 from rhodecode.lib.channelstream import (
38 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
39 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 IntegrityError, joinedload,
44 IntegrityError, joinedload,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 PullRequest, UserBookmark, RepoGroup)
46 PullRequest, UserBookmark, RepoGroup)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.scm import RepoList
49 from rhodecode.model.scm import RepoList
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.validation_schema.schemas import user_schema
53 from rhodecode.model.validation_schema.schemas import user_schema
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class MyAccountView(BaseAppView, DataGridAppView):
58 class MyAccountView(BaseAppView, DataGridAppView):
59 ALLOW_SCOPED_TOKENS = False
59 ALLOW_SCOPED_TOKENS = False
60 """
60 """
61 This view has alternative version inside EE, if modified please take a look
61 This view has alternative version inside EE, if modified please take a look
62 in there as well.
62 in there as well.
63 """
63 """
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.user = c.auth_user.get_instance()
67 c.user = c.auth_user.get_instance()
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69
69
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='my_account_profile', request_method='GET',
75 route_name='my_account_profile', request_method='GET',
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 def my_account_profile(self):
77 def my_account_profile(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.active = 'profile'
79 c.active = 'profile'
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @NotAnonymous()
83 @NotAnonymous()
84 @view_config(
84 @view_config(
85 route_name='my_account_password', request_method='GET',
85 route_name='my_account_password', request_method='GET',
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 def my_account_password(self):
87 def my_account_password(self):
88 c = self.load_default_context()
88 c = self.load_default_context()
89 c.active = 'password'
89 c.active = 'password'
90 c.extern_type = c.user.extern_type
90 c.extern_type = c.user.extern_type
91
91
92 schema = user_schema.ChangePasswordSchema().bind(
92 schema = user_schema.ChangePasswordSchema().bind(
93 username=c.user.username)
93 username=c.user.username)
94
94
95 form = forms.Form(
95 form = forms.Form(
96 schema,
96 schema,
97 action=h.route_path('my_account_password_update'),
97 action=h.route_path('my_account_password_update'),
98 buttons=(forms.buttons.save, forms.buttons.reset))
98 buttons=(forms.buttons.save, forms.buttons.reset))
99
99
100 c.form = form
100 c.form = form
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 @LoginRequired()
103 @LoginRequired()
104 @NotAnonymous()
104 @NotAnonymous()
105 @CSRFRequired()
105 @CSRFRequired()
106 @view_config(
106 @view_config(
107 route_name='my_account_password_update', request_method='POST',
107 route_name='my_account_password_update', request_method='POST',
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 def my_account_password_update(self):
109 def my_account_password_update(self):
110 _ = self.request.translate
110 _ = self.request.translate
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'password'
112 c.active = 'password'
113 c.extern_type = c.user.extern_type
113 c.extern_type = c.user.extern_type
114
114
115 schema = user_schema.ChangePasswordSchema().bind(
115 schema = user_schema.ChangePasswordSchema().bind(
116 username=c.user.username)
116 username=c.user.username)
117
117
118 form = forms.Form(
118 form = forms.Form(
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120
120
121 if c.extern_type != 'rhodecode':
121 if c.extern_type != 'rhodecode':
122 raise HTTPFound(self.request.route_path('my_account_password'))
122 raise HTTPFound(self.request.route_path('my_account_password'))
123
123
124 controls = self.request.POST.items()
124 controls = self.request.POST.items()
125 try:
125 try:
126 valid_data = form.validate(controls)
126 valid_data = form.validate(controls)
127 UserModel().update_user(c.user.user_id, **valid_data)
127 UserModel().update_user(c.user.user_id, **valid_data)
128 c.user.update_userdata(force_password_change=False)
128 c.user.update_userdata(force_password_change=False)
129 Session().commit()
129 Session().commit()
130 except forms.ValidationFailure as e:
130 except forms.ValidationFailure as e:
131 c.form = e
131 c.form = e
132 return self._get_template_context(c)
132 return self._get_template_context(c)
133
133
134 except Exception:
134 except Exception:
135 log.exception("Exception updating password")
135 log.exception("Exception updating password")
136 h.flash(_('Error occurred during update of user password'),
136 h.flash(_('Error occurred during update of user password'),
137 category='error')
137 category='error')
138 else:
138 else:
139 instance = c.auth_user.get_instance()
139 instance = c.auth_user.get_instance()
140 self.session.setdefault('rhodecode_user', {}).update(
140 self.session.setdefault('rhodecode_user', {}).update(
141 {'password': md5(instance.password)})
141 {'password': md5(instance.password)})
142 self.session.save()
142 self.session.save()
143 h.flash(_("Successfully updated password"), category='success')
143 h.flash(_("Successfully updated password"), category='success')
144
144
145 raise HTTPFound(self.request.route_path('my_account_password'))
145 raise HTTPFound(self.request.route_path('my_account_password'))
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @NotAnonymous()
148 @NotAnonymous()
149 @view_config(
149 @view_config(
150 route_name='my_account_auth_tokens', request_method='GET',
150 route_name='my_account_auth_tokens', request_method='GET',
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 def my_account_auth_tokens(self):
152 def my_account_auth_tokens(self):
153 _ = self.request.translate
153 _ = self.request.translate
154
154
155 c = self.load_default_context()
155 c = self.load_default_context()
156 c.active = 'auth_tokens'
156 c.active = 'auth_tokens'
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 c.role_values = [
158 c.role_values = [
159 (x, AuthTokenModel.cls._get_role_name(x))
159 (x, AuthTokenModel.cls._get_role_name(x))
160 for x in AuthTokenModel.cls.ROLES]
160 for x in AuthTokenModel.cls.ROLES]
161 c.role_options = [(c.role_values, _("Role"))]
161 c.role_options = [(c.role_values, _("Role"))]
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 c.user.user_id, show_expired=True)
163 c.user.user_id, show_expired=True)
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 return self._get_template_context(c)
165 return self._get_template_context(c)
166
166
167 def maybe_attach_token_scope(self, token):
167 def maybe_attach_token_scope(self, token):
168 # implemented in EE edition
168 # implemented in EE edition
169 pass
169 pass
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @NotAnonymous()
172 @NotAnonymous()
173 @CSRFRequired()
173 @CSRFRequired()
174 @view_config(
174 @view_config(
175 route_name='my_account_auth_tokens_add', request_method='POST',)
175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 def my_account_auth_tokens_add(self):
176 def my_account_auth_tokens_add(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179
179
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 description = self.request.POST.get('description')
181 description = self.request.POST.get('description')
182 role = self.request.POST.get('role')
182 role = self.request.POST.get('role')
183
183
184 token = UserModel().add_auth_token(
184 token = UserModel().add_auth_token(
185 user=c.user.user_id,
185 user=c.user.user_id,
186 lifetime_minutes=lifetime, role=role, description=description,
186 lifetime_minutes=lifetime, role=role, description=description,
187 scope_callback=self.maybe_attach_token_scope)
187 scope_callback=self.maybe_attach_token_scope)
188 token_data = token.get_api_data()
188 token_data = token.get_api_data()
189
189
190 audit_logger.store_web(
190 audit_logger.store_web(
191 'user.edit.token.add', action_data={
191 'user.edit.token.add', action_data={
192 'data': {'token': token_data, 'user': 'self'}},
192 'data': {'token': token_data, 'user': 'self'}},
193 user=self._rhodecode_user, )
193 user=self._rhodecode_user, )
194 Session().commit()
194 Session().commit()
195
195
196 h.flash(_("Auth token successfully created"), category='success')
196 h.flash(_("Auth token successfully created"), category='success')
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @NotAnonymous()
200 @NotAnonymous()
201 @CSRFRequired()
201 @CSRFRequired()
202 @view_config(
202 @view_config(
203 route_name='my_account_auth_tokens_delete', request_method='POST')
203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 def my_account_auth_tokens_delete(self):
204 def my_account_auth_tokens_delete(self):
205 _ = self.request.translate
205 _ = self.request.translate
206 c = self.load_default_context()
206 c = self.load_default_context()
207
207
208 del_auth_token = self.request.POST.get('del_auth_token')
208 del_auth_token = self.request.POST.get('del_auth_token')
209
209
210 if del_auth_token:
210 if del_auth_token:
211 token = UserApiKeys.get_or_404(del_auth_token)
211 token = UserApiKeys.get_or_404(del_auth_token)
212 token_data = token.get_api_data()
212 token_data = token.get_api_data()
213
213
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.edit.token.delete', action_data={
216 'user.edit.token.delete', action_data={
217 'data': {'token': token_data, 'user': 'self'}},
217 'data': {'token': token_data, 'user': 'self'}},
218 user=self._rhodecode_user,)
218 user=self._rhodecode_user,)
219 Session().commit()
219 Session().commit()
220 h.flash(_("Auth token successfully deleted"), category='success')
220 h.flash(_("Auth token successfully deleted"), category='success')
221
221
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223
223
224 @LoginRequired()
224 @LoginRequired()
225 @NotAnonymous()
225 @NotAnonymous()
226 @view_config(
226 @view_config(
227 route_name='my_account_emails', request_method='GET',
227 route_name='my_account_emails', request_method='GET',
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 def my_account_emails(self):
229 def my_account_emails(self):
230 _ = self.request.translate
230 _ = self.request.translate
231
231
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'emails'
233 c.active = 'emails'
234
234
235 c.user_email_map = UserEmailMap.query()\
235 c.user_email_map = UserEmailMap.query()\
236 .filter(UserEmailMap.user == c.user).all()
236 .filter(UserEmailMap.user == c.user).all()
237
237
238 schema = user_schema.AddEmailSchema().bind(
238 schema = user_schema.AddEmailSchema().bind(
239 username=c.user.username, user_emails=c.user.emails)
239 username=c.user.username, user_emails=c.user.emails)
240
240
241 form = forms.RcForm(schema,
241 form = forms.RcForm(schema,
242 action=h.route_path('my_account_emails_add'),
242 action=h.route_path('my_account_emails_add'),
243 buttons=(forms.buttons.save, forms.buttons.reset))
243 buttons=(forms.buttons.save, forms.buttons.reset))
244
244
245 c.form = form
245 c.form = form
246 return self._get_template_context(c)
246 return self._get_template_context(c)
247
247
248 @LoginRequired()
248 @LoginRequired()
249 @NotAnonymous()
249 @NotAnonymous()
250 @CSRFRequired()
250 @CSRFRequired()
251 @view_config(
251 @view_config(
252 route_name='my_account_emails_add', request_method='POST',
252 route_name='my_account_emails_add', request_method='POST',
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 def my_account_emails_add(self):
254 def my_account_emails_add(self):
255 _ = self.request.translate
255 _ = self.request.translate
256 c = self.load_default_context()
256 c = self.load_default_context()
257 c.active = 'emails'
257 c.active = 'emails'
258
258
259 schema = user_schema.AddEmailSchema().bind(
259 schema = user_schema.AddEmailSchema().bind(
260 username=c.user.username, user_emails=c.user.emails)
260 username=c.user.username, user_emails=c.user.emails)
261
261
262 form = forms.RcForm(
262 form = forms.RcForm(
263 schema, action=h.route_path('my_account_emails_add'),
263 schema, action=h.route_path('my_account_emails_add'),
264 buttons=(forms.buttons.save, forms.buttons.reset))
264 buttons=(forms.buttons.save, forms.buttons.reset))
265
265
266 controls = self.request.POST.items()
266 controls = self.request.POST.items()
267 try:
267 try:
268 valid_data = form.validate(controls)
268 valid_data = form.validate(controls)
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 audit_logger.store_web(
270 audit_logger.store_web(
271 'user.edit.email.add', action_data={
271 'user.edit.email.add', action_data={
272 'data': {'email': valid_data['email'], 'user': 'self'}},
272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 user=self._rhodecode_user,)
273 user=self._rhodecode_user,)
274 Session().commit()
274 Session().commit()
275 except formencode.Invalid as error:
275 except formencode.Invalid as error:
276 h.flash(h.escape(error.error_dict['email']), category='error')
276 h.flash(h.escape(error.error_dict['email']), category='error')
277 except forms.ValidationFailure as e:
277 except forms.ValidationFailure as e:
278 c.user_email_map = UserEmailMap.query() \
278 c.user_email_map = UserEmailMap.query() \
279 .filter(UserEmailMap.user == c.user).all()
279 .filter(UserEmailMap.user == c.user).all()
280 c.form = e
280 c.form = e
281 return self._get_template_context(c)
281 return self._get_template_context(c)
282 except Exception:
282 except Exception:
283 log.exception("Exception adding email")
283 log.exception("Exception adding email")
284 h.flash(_('Error occurred during adding email'),
284 h.flash(_('Error occurred during adding email'),
285 category='error')
285 category='error')
286 else:
286 else:
287 h.flash(_("Successfully added email"), category='success')
287 h.flash(_("Successfully added email"), category='success')
288
288
289 raise HTTPFound(self.request.route_path('my_account_emails'))
289 raise HTTPFound(self.request.route_path('my_account_emails'))
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @NotAnonymous()
292 @NotAnonymous()
293 @CSRFRequired()
293 @CSRFRequired()
294 @view_config(
294 @view_config(
295 route_name='my_account_emails_delete', request_method='POST')
295 route_name='my_account_emails_delete', request_method='POST')
296 def my_account_emails_delete(self):
296 def my_account_emails_delete(self):
297 _ = self.request.translate
297 _ = self.request.translate
298 c = self.load_default_context()
298 c = self.load_default_context()
299
299
300 del_email_id = self.request.POST.get('del_email_id')
300 del_email_id = self.request.POST.get('del_email_id')
301 if del_email_id:
301 if del_email_id:
302 email = UserEmailMap.get_or_404(del_email_id).email
302 email = UserEmailMap.get_or_404(del_email_id).email
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 audit_logger.store_web(
304 audit_logger.store_web(
305 'user.edit.email.delete', action_data={
305 'user.edit.email.delete', action_data={
306 'data': {'email': email, 'user': 'self'}},
306 'data': {'email': email, 'user': 'self'}},
307 user=self._rhodecode_user,)
307 user=self._rhodecode_user,)
308 Session().commit()
308 Session().commit()
309 h.flash(_("Email successfully deleted"),
309 h.flash(_("Email successfully deleted"),
310 category='success')
310 category='success')
311 return HTTPFound(h.route_path('my_account_emails'))
311 return HTTPFound(h.route_path('my_account_emails'))
312
312
313 @LoginRequired()
313 @LoginRequired()
314 @NotAnonymous()
314 @NotAnonymous()
315 @CSRFRequired()
315 @CSRFRequired()
316 @view_config(
316 @view_config(
317 route_name='my_account_notifications_test_channelstream',
317 route_name='my_account_notifications_test_channelstream',
318 request_method='POST', renderer='json_ext')
318 request_method='POST', renderer='json_ext')
319 def my_account_notifications_test_channelstream(self):
319 def my_account_notifications_test_channelstream(self):
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 self._rhodecode_user.username, datetime.datetime.now())
321 self._rhodecode_user.username, datetime.datetime.now())
322 payload = {
322 payload = {
323 # 'channel': 'broadcast',
323 # 'channel': 'broadcast',
324 'type': 'message',
324 'type': 'message',
325 'timestamp': datetime.datetime.utcnow(),
325 'timestamp': datetime.datetime.utcnow(),
326 'user': 'system',
326 'user': 'system',
327 'pm_users': [self._rhodecode_user.username],
327 'pm_users': [self._rhodecode_user.username],
328 'message': {
328 'message': {
329 'message': message,
329 'message': message,
330 'level': 'info',
330 'level': 'info',
331 'topic': '/notifications'
331 'topic': '/notifications'
332 }
332 }
333 }
333 }
334
334
335 registry = self.request.registry
335 registry = self.request.registry
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338
338
339 try:
339 try:
340 channelstream_request(channelstream_config, [payload], '/message')
340 channelstream_request(channelstream_config, [payload], '/message')
341 except ChannelstreamException as e:
341 except ChannelstreamException as e:
342 log.exception('Failed to send channelstream data')
342 log.exception('Failed to send channelstream data')
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 return {"response": 'Channelstream data sent. '
344 return {"response": 'Channelstream data sent. '
345 'You should see a new live message now.'}
345 'You should see a new live message now.'}
346
346
347 def _load_my_repos_data(self, watched=False):
347 def _load_my_repos_data(self, watched=False):
348 if watched:
348 if watched:
349 admin = False
349 admin = False
350 follows_repos = Session().query(UserFollowing)\
350 follows_repos = Session().query(UserFollowing)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 .options(joinedload(UserFollowing.follows_repository))\
352 .options(joinedload(UserFollowing.follows_repository))\
353 .all()
353 .all()
354 repo_list = [x.follows_repository for x in follows_repos]
354 repo_list = [x.follows_repository for x in follows_repos]
355 else:
355 else:
356 admin = True
356 admin = True
357 repo_list = Repository.get_all_repos(
357 repo_list = Repository.get_all_repos(
358 user_id=self._rhodecode_user.user_id)
358 user_id=self._rhodecode_user.user_id)
359 repo_list = RepoList(repo_list, perm_set=[
359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'])
360 'repository.read', 'repository.write', 'repository.admin'])
361
361
362 repos_data = RepoModel().get_repos_as_dict(
362 repos_data = RepoModel().get_repos_as_dict(
363 repo_list=repo_list, admin=admin, short_name=False)
363 repo_list=repo_list, admin=admin, short_name=False)
364 # json used to render the grid
364 # json used to render the grid
365 return json.dumps(repos_data)
365 return json.dumps(repos_data)
366
366
367 @LoginRequired()
367 @LoginRequired()
368 @NotAnonymous()
368 @NotAnonymous()
369 @view_config(
369 @view_config(
370 route_name='my_account_repos', request_method='GET',
370 route_name='my_account_repos', request_method='GET',
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 def my_account_repos(self):
372 def my_account_repos(self):
373 c = self.load_default_context()
373 c = self.load_default_context()
374 c.active = 'repos'
374 c.active = 'repos'
375
375
376 # json used to render the grid
376 # json used to render the grid
377 c.data = self._load_my_repos_data()
377 c.data = self._load_my_repos_data()
378 return self._get_template_context(c)
378 return self._get_template_context(c)
379
379
380 @LoginRequired()
380 @LoginRequired()
381 @NotAnonymous()
381 @NotAnonymous()
382 @view_config(
382 @view_config(
383 route_name='my_account_watched', request_method='GET',
383 route_name='my_account_watched', request_method='GET',
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 def my_account_watched(self):
385 def my_account_watched(self):
386 c = self.load_default_context()
386 c = self.load_default_context()
387 c.active = 'watched'
387 c.active = 'watched'
388
388
389 # json used to render the grid
389 # json used to render the grid
390 c.data = self._load_my_repos_data(watched=True)
390 c.data = self._load_my_repos_data(watched=True)
391 return self._get_template_context(c)
391 return self._get_template_context(c)
392
392
393 @LoginRequired()
393 @LoginRequired()
394 @NotAnonymous()
394 @NotAnonymous()
395 @view_config(
395 @view_config(
396 route_name='my_account_bookmarks', request_method='GET',
396 route_name='my_account_bookmarks', request_method='GET',
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 def my_account_bookmarks(self):
398 def my_account_bookmarks(self):
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.active = 'bookmarks'
400 c.active = 'bookmarks'
401 return self._get_template_context(c)
401 return self._get_template_context(c)
402
402
403 def _process_bookmark_entry(self, entry, user_id):
403 def _process_bookmark_entry(self, entry, user_id):
404 position = safe_int(entry.get('position'))
404 position = safe_int(entry.get('position'))
405 cur_position = safe_int(entry.get('cur_position'))
405 cur_position = safe_int(entry.get('cur_position'))
406 if position is None or cur_position is None:
406 if position is None or cur_position is None:
407 return
407 return
408
408
409 # check if this is an existing entry
409 # check if this is an existing entry
410 is_new = False
410 is_new = False
411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
412
412
413 if db_entry and str2bool(entry.get('remove')):
413 if db_entry and str2bool(entry.get('remove')):
414 log.debug('Marked bookmark %s for deletion', db_entry)
414 log.debug('Marked bookmark %s for deletion', db_entry)
415 Session().delete(db_entry)
415 Session().delete(db_entry)
416 return
416 return
417
417
418 if not db_entry:
418 if not db_entry:
419 # new
419 # new
420 db_entry = UserBookmark()
420 db_entry = UserBookmark()
421 is_new = True
421 is_new = True
422
422
423 should_save = False
423 should_save = False
424 default_redirect_url = ''
424 default_redirect_url = ''
425
425
426 # save repo
426 # save repo
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
428 repo = Repository.get(entry['bookmark_repo'])
428 repo = Repository.get(entry['bookmark_repo'])
429 perm_check = HasRepoPermissionAny(
429 perm_check = HasRepoPermissionAny(
430 'repository.read', 'repository.write', 'repository.admin')
430 'repository.read', 'repository.write', 'repository.admin')
431 if repo and perm_check(repo_name=repo.repo_name):
431 if repo and perm_check(repo_name=repo.repo_name):
432 db_entry.repository = repo
432 db_entry.repository = repo
433 should_save = True
433 should_save = True
434 default_redirect_url = '${repo_url}'
434 default_redirect_url = '${repo_url}'
435 # save repo group
435 # save repo group
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
438 perm_check = HasRepoGroupPermissionAny(
438 perm_check = HasRepoGroupPermissionAny(
439 'group.read', 'group.write', 'group.admin')
439 'group.read', 'group.write', 'group.admin')
440
440
441 if repo_group and perm_check(group_name=repo_group.group_name):
441 if repo_group and perm_check(group_name=repo_group.group_name):
442 db_entry.repository_group = repo_group
442 db_entry.repository_group = repo_group
443 should_save = True
443 should_save = True
444 default_redirect_url = '${repo_group_url}'
444 default_redirect_url = '${repo_group_url}'
445 # save generic info
445 # save generic info
446 elif entry.get('title') and entry.get('redirect_url'):
446 elif entry.get('title') and entry.get('redirect_url'):
447 should_save = True
447 should_save = True
448
448
449 if should_save:
449 if should_save:
450 # mark user and position
450 # mark user and position
451 db_entry.user_id = user_id
451 db_entry.user_id = user_id
452 db_entry.position = position
452 db_entry.position = position
453 db_entry.title = entry.get('title')
453 db_entry.title = entry.get('title')
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
456
456
457 Session().add(db_entry)
457 Session().add(db_entry)
458
458
459 @LoginRequired()
459 @LoginRequired()
460 @NotAnonymous()
460 @NotAnonymous()
461 @CSRFRequired()
461 @CSRFRequired()
462 @view_config(
462 @view_config(
463 route_name='my_account_bookmarks_update', request_method='POST')
463 route_name='my_account_bookmarks_update', request_method='POST')
464 def my_account_bookmarks_update(self):
464 def my_account_bookmarks_update(self):
465 _ = self.request.translate
465 _ = self.request.translate
466 c = self.load_default_context()
466 c = self.load_default_context()
467 c.active = 'bookmarks'
467 c.active = 'bookmarks'
468
468
469 controls = peppercorn.parse(self.request.POST.items())
469 controls = peppercorn.parse(self.request.POST.items())
470 user_id = c.user.user_id
470 user_id = c.user.user_id
471
471
472 # validate positions
472 # validate positions
473 positions = {}
473 positions = {}
474 for entry in controls.get('bookmarks', []):
474 for entry in controls.get('bookmarks', []):
475 position = safe_int(entry['position'])
475 position = safe_int(entry['position'])
476 if position is None:
476 if position is None:
477 continue
477 continue
478
478
479 if position in positions:
479 if position in positions:
480 h.flash(_("Position {} is defined twice. "
480 h.flash(_("Position {} is defined twice. "
481 "Please correct this error.").format(position), category='error')
481 "Please correct this error.").format(position), category='error')
482 return HTTPFound(h.route_path('my_account_bookmarks'))
482 return HTTPFound(h.route_path('my_account_bookmarks'))
483
483
484 entry['position'] = position
484 entry['position'] = position
485 entry['cur_position'] = safe_int(entry.get('cur_position'))
485 entry['cur_position'] = safe_int(entry.get('cur_position'))
486 positions[position] = entry
486 positions[position] = entry
487
487
488 try:
488 try:
489 for entry in positions.values():
489 for entry in positions.values():
490 self._process_bookmark_entry(entry, user_id)
490 self._process_bookmark_entry(entry, user_id)
491
491
492 Session().commit()
492 Session().commit()
493 h.flash(_("Update Bookmarks"), category='success')
493 h.flash(_("Update Bookmarks"), category='success')
494 except IntegrityError:
494 except IntegrityError:
495 h.flash(_("Failed to update bookmarks. "
495 h.flash(_("Failed to update bookmarks. "
496 "Make sure an unique position is used."), category='error')
496 "Make sure an unique position is used."), category='error')
497
497
498 return HTTPFound(h.route_path('my_account_bookmarks'))
498 return HTTPFound(h.route_path('my_account_bookmarks'))
499
499
500 @LoginRequired()
500 @LoginRequired()
501 @NotAnonymous()
501 @NotAnonymous()
502 @view_config(
502 @view_config(
503 route_name='my_account_goto_bookmark', request_method='GET',
503 route_name='my_account_goto_bookmark', request_method='GET',
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
505 def my_account_goto_bookmark(self):
505 def my_account_goto_bookmark(self):
506
506
507 bookmark_id = self.request.matchdict['bookmark_id']
507 bookmark_id = self.request.matchdict['bookmark_id']
508 user_bookmark = UserBookmark().query()\
508 user_bookmark = UserBookmark().query()\
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
510 .filter(UserBookmark.position == bookmark_id).scalar()
510 .filter(UserBookmark.position == bookmark_id).scalar()
511
511
512 redirect_url = h.route_path('my_account_bookmarks')
512 redirect_url = h.route_path('my_account_bookmarks')
513 if not user_bookmark:
513 if not user_bookmark:
514 raise HTTPFound(redirect_url)
514 raise HTTPFound(redirect_url)
515
515
516 # repository set
516 # repository set
517 if user_bookmark.repository:
517 if user_bookmark.repository:
518 repo_name = user_bookmark.repository.repo_name
518 repo_name = user_bookmark.repository.repo_name
519 base_redirect_url = h.route_path(
519 base_redirect_url = h.route_path(
520 'repo_summary', repo_name=repo_name)
520 'repo_summary', repo_name=repo_name)
521 if user_bookmark.redirect_url and \
521 if user_bookmark.redirect_url and \
522 '${repo_url}' in user_bookmark.redirect_url:
522 '${repo_url}' in user_bookmark.redirect_url:
523 redirect_url = string.Template(user_bookmark.redirect_url)\
523 redirect_url = string.Template(user_bookmark.redirect_url)\
524 .safe_substitute({'repo_url': base_redirect_url})
524 .safe_substitute({'repo_url': base_redirect_url})
525 else:
525 else:
526 redirect_url = base_redirect_url
526 redirect_url = base_redirect_url
527 # repository group set
527 # repository group set
528 elif user_bookmark.repository_group:
528 elif user_bookmark.repository_group:
529 repo_group_name = user_bookmark.repository_group.group_name
529 repo_group_name = user_bookmark.repository_group.group_name
530 base_redirect_url = h.route_path(
530 base_redirect_url = h.route_path(
531 'repo_group_home', repo_group_name=repo_group_name)
531 'repo_group_home', repo_group_name=repo_group_name)
532 if user_bookmark.redirect_url and \
532 if user_bookmark.redirect_url and \
533 '${repo_group_url}' in user_bookmark.redirect_url:
533 '${repo_group_url}' in user_bookmark.redirect_url:
534 redirect_url = string.Template(user_bookmark.redirect_url)\
534 redirect_url = string.Template(user_bookmark.redirect_url)\
535 .safe_substitute({'repo_group_url': base_redirect_url})
535 .safe_substitute({'repo_group_url': base_redirect_url})
536 else:
536 else:
537 redirect_url = base_redirect_url
537 redirect_url = base_redirect_url
538 # custom URL set
538 # custom URL set
539 elif user_bookmark.redirect_url:
539 elif user_bookmark.redirect_url:
540 server_url = h.route_url('home').rstrip('/')
540 server_url = h.route_url('home').rstrip('/')
541 redirect_url = string.Template(user_bookmark.redirect_url) \
541 redirect_url = string.Template(user_bookmark.redirect_url) \
542 .safe_substitute({'server_url': server_url})
542 .safe_substitute({'server_url': server_url})
543
543
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
545 raise HTTPFound(redirect_url)
545 raise HTTPFound(redirect_url)
546
546
547 @LoginRequired()
547 @LoginRequired()
548 @NotAnonymous()
548 @NotAnonymous()
549 @view_config(
549 @view_config(
550 route_name='my_account_perms', request_method='GET',
550 route_name='my_account_perms', request_method='GET',
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
552 def my_account_perms(self):
552 def my_account_perms(self):
553 c = self.load_default_context()
553 c = self.load_default_context()
554 c.active = 'perms'
554 c.active = 'perms'
555
555
556 c.perm_user = c.auth_user
556 c.perm_user = c.auth_user
557 return self._get_template_context(c)
557 return self._get_template_context(c)
558
558
559 @LoginRequired()
559 @LoginRequired()
560 @NotAnonymous()
560 @NotAnonymous()
561 @view_config(
561 @view_config(
562 route_name='my_account_notifications', request_method='GET',
562 route_name='my_account_notifications', request_method='GET',
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
564 def my_notifications(self):
564 def my_notifications(self):
565 c = self.load_default_context()
565 c = self.load_default_context()
566 c.active = 'notifications'
566 c.active = 'notifications'
567
567
568 return self._get_template_context(c)
568 return self._get_template_context(c)
569
569
570 @LoginRequired()
570 @LoginRequired()
571 @NotAnonymous()
571 @NotAnonymous()
572 @CSRFRequired()
572 @CSRFRequired()
573 @view_config(
573 @view_config(
574 route_name='my_account_notifications_toggle_visibility',
574 route_name='my_account_notifications_toggle_visibility',
575 request_method='POST', renderer='json_ext')
575 request_method='POST', renderer='json_ext')
576 def my_notifications_toggle_visibility(self):
576 def my_notifications_toggle_visibility(self):
577 user = self._rhodecode_db_user
577 user = self._rhodecode_db_user
578 new_status = not user.user_data.get('notification_status', True)
578 new_status = not user.user_data.get('notification_status', True)
579 user.update_userdata(notification_status=new_status)
579 user.update_userdata(notification_status=new_status)
580 Session().commit()
580 Session().commit()
581 return user.user_data['notification_status']
581 return user.user_data['notification_status']
582
582
583 @LoginRequired()
583 @LoginRequired()
584 @NotAnonymous()
584 @NotAnonymous()
585 @view_config(
585 @view_config(
586 route_name='my_account_edit',
586 route_name='my_account_edit',
587 request_method='GET',
587 request_method='GET',
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
589 def my_account_edit(self):
589 def my_account_edit(self):
590 c = self.load_default_context()
590 c = self.load_default_context()
591 c.active = 'profile_edit'
591 c.active = 'profile_edit'
592 c.extern_type = c.user.extern_type
592 c.extern_type = c.user.extern_type
593 c.extern_name = c.user.extern_name
593 c.extern_name = c.user.extern_name
594
594
595 schema = user_schema.UserProfileSchema().bind(
595 schema = user_schema.UserProfileSchema().bind(
596 username=c.user.username, user_emails=c.user.emails)
596 username=c.user.username, user_emails=c.user.emails)
597 appstruct = {
597 appstruct = {
598 'username': c.user.username,
598 'username': c.user.username,
599 'email': c.user.email,
599 'email': c.user.email,
600 'firstname': c.user.firstname,
600 'firstname': c.user.firstname,
601 'lastname': c.user.lastname,
601 'lastname': c.user.lastname,
602 'description': c.user.description,
602 }
603 }
603 c.form = forms.RcForm(
604 c.form = forms.RcForm(
604 schema, appstruct=appstruct,
605 schema, appstruct=appstruct,
605 action=h.route_path('my_account_update'),
606 action=h.route_path('my_account_update'),
606 buttons=(forms.buttons.save, forms.buttons.reset))
607 buttons=(forms.buttons.save, forms.buttons.reset))
607
608
608 return self._get_template_context(c)
609 return self._get_template_context(c)
609
610
610 @LoginRequired()
611 @LoginRequired()
611 @NotAnonymous()
612 @NotAnonymous()
612 @CSRFRequired()
613 @CSRFRequired()
613 @view_config(
614 @view_config(
614 route_name='my_account_update',
615 route_name='my_account_update',
615 request_method='POST',
616 request_method='POST',
616 renderer='rhodecode:templates/admin/my_account/my_account.mako')
617 renderer='rhodecode:templates/admin/my_account/my_account.mako')
617 def my_account_update(self):
618 def my_account_update(self):
618 _ = self.request.translate
619 _ = self.request.translate
619 c = self.load_default_context()
620 c = self.load_default_context()
620 c.active = 'profile_edit'
621 c.active = 'profile_edit'
621 c.perm_user = c.auth_user
622 c.perm_user = c.auth_user
622 c.extern_type = c.user.extern_type
623 c.extern_type = c.user.extern_type
623 c.extern_name = c.user.extern_name
624 c.extern_name = c.user.extern_name
624
625
625 schema = user_schema.UserProfileSchema().bind(
626 schema = user_schema.UserProfileSchema().bind(
626 username=c.user.username, user_emails=c.user.emails)
627 username=c.user.username, user_emails=c.user.emails)
627 form = forms.RcForm(
628 form = forms.RcForm(
628 schema, buttons=(forms.buttons.save, forms.buttons.reset))
629 schema, buttons=(forms.buttons.save, forms.buttons.reset))
629
630
630 controls = self.request.POST.items()
631 controls = self.request.POST.items()
631 try:
632 try:
632 valid_data = form.validate(controls)
633 valid_data = form.validate(controls)
633 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
634 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
634 'new_password', 'password_confirmation']
635 'new_password', 'password_confirmation']
635 if c.extern_type != "rhodecode":
636 if c.extern_type != "rhodecode":
636 # forbid updating username for external accounts
637 # forbid updating username for external accounts
637 skip_attrs.append('username')
638 skip_attrs.append('username')
638 old_email = c.user.email
639 old_email = c.user.email
639 UserModel().update_user(
640 UserModel().update_user(
640 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
641 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
641 **valid_data)
642 **valid_data)
642 if old_email != valid_data['email']:
643 if old_email != valid_data['email']:
643 old = UserEmailMap.query() \
644 old = UserEmailMap.query() \
644 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
645 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
645 old.email = old_email
646 old.email = old_email
646 h.flash(_('Your account was updated successfully'), category='success')
647 h.flash(_('Your account was updated successfully'), category='success')
647 Session().commit()
648 Session().commit()
648 except forms.ValidationFailure as e:
649 except forms.ValidationFailure as e:
649 c.form = e
650 c.form = e
650 return self._get_template_context(c)
651 return self._get_template_context(c)
651 except Exception:
652 except Exception:
652 log.exception("Exception updating user")
653 log.exception("Exception updating user")
653 h.flash(_('Error occurred during update of user'),
654 h.flash(_('Error occurred during update of user'),
654 category='error')
655 category='error')
655 raise HTTPFound(h.route_path('my_account_profile'))
656 raise HTTPFound(h.route_path('my_account_profile'))
656
657
657 def _get_pull_requests_list(self, statuses):
658 def _get_pull_requests_list(self, statuses):
658 draw, start, limit = self._extract_chunk(self.request)
659 draw, start, limit = self._extract_chunk(self.request)
659 search_q, order_by, order_dir = self._extract_ordering(self.request)
660 search_q, order_by, order_dir = self._extract_ordering(self.request)
660 _render = self.request.get_partial_renderer(
661 _render = self.request.get_partial_renderer(
661 'rhodecode:templates/data_table/_dt_elements.mako')
662 'rhodecode:templates/data_table/_dt_elements.mako')
662
663
663 pull_requests = PullRequestModel().get_im_participating_in(
664 pull_requests = PullRequestModel().get_im_participating_in(
664 user_id=self._rhodecode_user.user_id,
665 user_id=self._rhodecode_user.user_id,
665 statuses=statuses,
666 statuses=statuses,
666 offset=start, length=limit, order_by=order_by,
667 offset=start, length=limit, order_by=order_by,
667 order_dir=order_dir)
668 order_dir=order_dir)
668
669
669 pull_requests_total_count = PullRequestModel().count_im_participating_in(
670 pull_requests_total_count = PullRequestModel().count_im_participating_in(
670 user_id=self._rhodecode_user.user_id, statuses=statuses)
671 user_id=self._rhodecode_user.user_id, statuses=statuses)
671
672
672 data = []
673 data = []
673 comments_model = CommentsModel()
674 comments_model = CommentsModel()
674 for pr in pull_requests:
675 for pr in pull_requests:
675 repo_id = pr.target_repo_id
676 repo_id = pr.target_repo_id
676 comments = comments_model.get_all_comments(
677 comments = comments_model.get_all_comments(
677 repo_id, pull_request=pr)
678 repo_id, pull_request=pr)
678 owned = pr.user_id == self._rhodecode_user.user_id
679 owned = pr.user_id == self._rhodecode_user.user_id
679
680
680 data.append({
681 data.append({
681 'target_repo': _render('pullrequest_target_repo',
682 'target_repo': _render('pullrequest_target_repo',
682 pr.target_repo.repo_name),
683 pr.target_repo.repo_name),
683 'name': _render('pullrequest_name',
684 'name': _render('pullrequest_name',
684 pr.pull_request_id, pr.target_repo.repo_name,
685 pr.pull_request_id, pr.target_repo.repo_name,
685 short=True),
686 short=True),
686 'name_raw': pr.pull_request_id,
687 'name_raw': pr.pull_request_id,
687 'status': _render('pullrequest_status',
688 'status': _render('pullrequest_status',
688 pr.calculated_review_status()),
689 pr.calculated_review_status()),
689 'title': _render('pullrequest_title', pr.title, pr.description),
690 'title': _render('pullrequest_title', pr.title, pr.description),
690 'description': h.escape(pr.description),
691 'description': h.escape(pr.description),
691 'updated_on': _render('pullrequest_updated_on',
692 'updated_on': _render('pullrequest_updated_on',
692 h.datetime_to_time(pr.updated_on)),
693 h.datetime_to_time(pr.updated_on)),
693 'updated_on_raw': h.datetime_to_time(pr.updated_on),
694 'updated_on_raw': h.datetime_to_time(pr.updated_on),
694 'created_on': _render('pullrequest_updated_on',
695 'created_on': _render('pullrequest_updated_on',
695 h.datetime_to_time(pr.created_on)),
696 h.datetime_to_time(pr.created_on)),
696 'created_on_raw': h.datetime_to_time(pr.created_on),
697 'created_on_raw': h.datetime_to_time(pr.created_on),
697 'state': pr.pull_request_state,
698 'state': pr.pull_request_state,
698 'author': _render('pullrequest_author',
699 'author': _render('pullrequest_author',
699 pr.author.full_contact, ),
700 pr.author.full_contact, ),
700 'author_raw': pr.author.full_name,
701 'author_raw': pr.author.full_name,
701 'comments': _render('pullrequest_comments', len(comments)),
702 'comments': _render('pullrequest_comments', len(comments)),
702 'comments_raw': len(comments),
703 'comments_raw': len(comments),
703 'closed': pr.is_closed(),
704 'closed': pr.is_closed(),
704 'owned': owned
705 'owned': owned
705 })
706 })
706
707
707 # json used to render the grid
708 # json used to render the grid
708 data = ({
709 data = ({
709 'draw': draw,
710 'draw': draw,
710 'data': data,
711 'data': data,
711 'recordsTotal': pull_requests_total_count,
712 'recordsTotal': pull_requests_total_count,
712 'recordsFiltered': pull_requests_total_count,
713 'recordsFiltered': pull_requests_total_count,
713 })
714 })
714 return data
715 return data
715
716
716 @LoginRequired()
717 @LoginRequired()
717 @NotAnonymous()
718 @NotAnonymous()
718 @view_config(
719 @view_config(
719 route_name='my_account_pullrequests',
720 route_name='my_account_pullrequests',
720 request_method='GET',
721 request_method='GET',
721 renderer='rhodecode:templates/admin/my_account/my_account.mako')
722 renderer='rhodecode:templates/admin/my_account/my_account.mako')
722 def my_account_pullrequests(self):
723 def my_account_pullrequests(self):
723 c = self.load_default_context()
724 c = self.load_default_context()
724 c.active = 'pullrequests'
725 c.active = 'pullrequests'
725 req_get = self.request.GET
726 req_get = self.request.GET
726
727
727 c.closed = str2bool(req_get.get('pr_show_closed'))
728 c.closed = str2bool(req_get.get('pr_show_closed'))
728
729
729 return self._get_template_context(c)
730 return self._get_template_context(c)
730
731
731 @LoginRequired()
732 @LoginRequired()
732 @NotAnonymous()
733 @NotAnonymous()
733 @view_config(
734 @view_config(
734 route_name='my_account_pullrequests_data',
735 route_name='my_account_pullrequests_data',
735 request_method='GET', renderer='json_ext')
736 request_method='GET', renderer='json_ext')
736 def my_account_pullrequests_data(self):
737 def my_account_pullrequests_data(self):
737 self.load_default_context()
738 self.load_default_context()
738 req_get = self.request.GET
739 req_get = self.request.GET
739 closed = str2bool(req_get.get('closed'))
740 closed = str2bool(req_get.get('closed'))
740
741
741 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
742 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
742 if closed:
743 if closed:
743 statuses += [PullRequest.STATUS_CLOSED]
744 statuses += [PullRequest.STATUS_CLOSED]
744
745
745 data = self._get_pull_requests_list(statuses=statuses)
746 data = self._get_pull_requests_list(statuses=statuses)
746 return data
747 return data
747
748
748 @LoginRequired()
749 @LoginRequired()
749 @NotAnonymous()
750 @NotAnonymous()
750 @view_config(
751 @view_config(
751 route_name='my_account_user_group_membership',
752 route_name='my_account_user_group_membership',
752 request_method='GET',
753 request_method='GET',
753 renderer='rhodecode:templates/admin/my_account/my_account.mako')
754 renderer='rhodecode:templates/admin/my_account/my_account.mako')
754 def my_account_user_group_membership(self):
755 def my_account_user_group_membership(self):
755 c = self.load_default_context()
756 c = self.load_default_context()
756 c.active = 'user_group_membership'
757 c.active = 'user_group_membership'
757 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
758 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
758 for group in self._rhodecode_db_user.group_member]
759 for group in self._rhodecode_db_user.group_member]
759 c.user_groups = json.dumps(groups)
760 c.user_groups = json.dumps(groups)
760 return self._get_template_context(c)
761 return self._get_template_context(c)
@@ -1,629 +1,630 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 kw['request'] = get_current_request()
69 kw['request'] = get_current_request()
70 return self.load(template_name)(**kw)
70 return self.load(template_name)(**kw)
71
71
72
72
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 deform.Form.set_default_renderer(form_renderer)
74 deform.Form.set_default_renderer(form_renderer)
75
75
76
76
77 def LoginForm(localizer):
77 def LoginForm(localizer):
78 _ = localizer
78 _ = localizer
79
79
80 class _LoginForm(formencode.Schema):
80 class _LoginForm(formencode.Schema):
81 allow_extra_fields = True
81 allow_extra_fields = True
82 filter_extra_fields = True
82 filter_extra_fields = True
83 username = v.UnicodeString(
83 username = v.UnicodeString(
84 strip=True,
84 strip=True,
85 min=1,
85 min=1,
86 not_empty=True,
86 not_empty=True,
87 messages={
87 messages={
88 'empty': _(u'Please enter a login'),
88 'empty': _(u'Please enter a login'),
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 }
90 }
91 )
91 )
92
92
93 password = v.UnicodeString(
93 password = v.UnicodeString(
94 strip=False,
94 strip=False,
95 min=3,
95 min=3,
96 max=72,
96 max=72,
97 not_empty=True,
97 not_empty=True,
98 messages={
98 messages={
99 'empty': _(u'Please enter a password'),
99 'empty': _(u'Please enter a password'),
100 'tooShort': _(u'Enter %(min)i characters or more')}
100 'tooShort': _(u'Enter %(min)i characters or more')}
101 )
101 )
102
102
103 remember = v.StringBoolean(if_missing=False)
103 remember = v.StringBoolean(if_missing=False)
104
104
105 chained_validators = [v.ValidAuth(localizer)]
105 chained_validators = [v.ValidAuth(localizer)]
106 return _LoginForm
106 return _LoginForm
107
107
108
108
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 old_data = old_data or {}
110 old_data = old_data or {}
111 available_languages = available_languages or []
111 available_languages = available_languages or []
112 _ = localizer
112 _ = localizer
113
113
114 class _UserForm(formencode.Schema):
114 class _UserForm(formencode.Schema):
115 allow_extra_fields = True
115 allow_extra_fields = True
116 filter_extra_fields = True
116 filter_extra_fields = True
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 v.ValidUsername(localizer, edit, old_data))
118 v.ValidUsername(localizer, edit, old_data))
119 if edit:
119 if edit:
120 new_password = All(
120 new_password = All(
121 v.ValidPassword(localizer),
121 v.ValidPassword(localizer),
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 )
123 )
124 password_confirmation = All(
124 password_confirmation = All(
125 v.ValidPassword(localizer),
125 v.ValidPassword(localizer),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 )
127 )
128 admin = v.StringBoolean(if_missing=False)
128 admin = v.StringBoolean(if_missing=False)
129 else:
129 else:
130 password = All(
130 password = All(
131 v.ValidPassword(localizer),
131 v.ValidPassword(localizer),
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 )
133 )
134 password_confirmation = All(
134 password_confirmation = All(
135 v.ValidPassword(localizer),
135 v.ValidPassword(localizer),
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 )
137 )
138
138
139 password_change = v.StringBoolean(if_missing=False)
139 password_change = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
141
141
142 active = v.StringBoolean(if_missing=False)
142 active = v.StringBoolean(if_missing=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False)
146 extern_name = v.UnicodeString(strip=True)
147 extern_name = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
148 extern_type = v.UnicodeString(strip=True)
148 language = v.OneOf(available_languages, hideList=False,
149 language = v.OneOf(available_languages, hideList=False,
149 testValueList=True, if_missing=None)
150 testValueList=True, if_missing=None)
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 return _UserForm
152 return _UserForm
152
153
153
154
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 old_data = old_data or {}
156 old_data = old_data or {}
156 _ = localizer
157 _ = localizer
157
158
158 class _UserGroupForm(formencode.Schema):
159 class _UserGroupForm(formencode.Schema):
159 allow_extra_fields = True
160 allow_extra_fields = True
160 filter_extra_fields = True
161 filter_extra_fields = True
161
162
162 users_group_name = All(
163 users_group_name = All(
163 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.ValidUserGroup(localizer, edit, old_data)
165 v.ValidUserGroup(localizer, edit, old_data)
165 )
166 )
166 user_group_description = v.UnicodeString(strip=True, min=1,
167 user_group_description = v.UnicodeString(strip=True, min=1,
167 not_empty=False)
168 not_empty=False)
168
169
169 users_group_active = v.StringBoolean(if_missing=False)
170 users_group_active = v.StringBoolean(if_missing=False)
170
171
171 if edit:
172 if edit:
172 # this is user group owner
173 # this is user group owner
173 user = All(
174 user = All(
174 v.UnicodeString(not_empty=True),
175 v.UnicodeString(not_empty=True),
175 v.ValidRepoUser(localizer, allow_disabled))
176 v.ValidRepoUser(localizer, allow_disabled))
176 return _UserGroupForm
177 return _UserGroupForm
177
178
178
179
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 can_create_in_root=False, allow_disabled=False):
181 can_create_in_root=False, allow_disabled=False):
181 _ = localizer
182 _ = localizer
182 old_data = old_data or {}
183 old_data = old_data or {}
183 available_groups = available_groups or []
184 available_groups = available_groups or []
184
185
185 class _RepoGroupForm(formencode.Schema):
186 class _RepoGroupForm(formencode.Schema):
186 allow_extra_fields = True
187 allow_extra_fields = True
187 filter_extra_fields = False
188 filter_extra_fields = False
188
189
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 v.SlugifyName(localizer),)
191 v.SlugifyName(localizer),)
191 group_description = v.UnicodeString(strip=True, min=1,
192 group_description = v.UnicodeString(strip=True, min=1,
192 not_empty=False)
193 not_empty=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
194 group_copy_permissions = v.StringBoolean(if_missing=False)
194
195
195 group_parent_id = v.OneOf(available_groups, hideList=False,
196 group_parent_id = v.OneOf(available_groups, hideList=False,
196 testValueList=True, not_empty=True)
197 testValueList=True, not_empty=True)
197 enable_locking = v.StringBoolean(if_missing=False)
198 enable_locking = v.StringBoolean(if_missing=False)
198 chained_validators = [
199 chained_validators = [
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200
201
201 if edit:
202 if edit:
202 # this is repo group owner
203 # this is repo group owner
203 user = All(
204 user = All(
204 v.UnicodeString(not_empty=True),
205 v.UnicodeString(not_empty=True),
205 v.ValidRepoUser(localizer, allow_disabled))
206 v.ValidRepoUser(localizer, allow_disabled))
206 return _RepoGroupForm
207 return _RepoGroupForm
207
208
208
209
209 def RegisterForm(localizer, edit=False, old_data=None):
210 def RegisterForm(localizer, edit=False, old_data=None):
210 _ = localizer
211 _ = localizer
211 old_data = old_data or {}
212 old_data = old_data or {}
212
213
213 class _RegisterForm(formencode.Schema):
214 class _RegisterForm(formencode.Schema):
214 allow_extra_fields = True
215 allow_extra_fields = True
215 filter_extra_fields = True
216 filter_extra_fields = True
216 username = All(
217 username = All(
217 v.ValidUsername(localizer, edit, old_data),
218 v.ValidUsername(localizer, edit, old_data),
218 v.UnicodeString(strip=True, min=1, not_empty=True)
219 v.UnicodeString(strip=True, min=1, not_empty=True)
219 )
220 )
220 password = All(
221 password = All(
221 v.ValidPassword(localizer),
222 v.ValidPassword(localizer),
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 )
224 )
224 password_confirmation = All(
225 password_confirmation = All(
225 v.ValidPassword(localizer),
226 v.ValidPassword(localizer),
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 )
228 )
228 active = v.StringBoolean(if_missing=False)
229 active = v.StringBoolean(if_missing=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
232 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
232
233
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 return _RegisterForm
235 return _RegisterForm
235
236
236
237
237 def PasswordResetForm(localizer):
238 def PasswordResetForm(localizer):
238 _ = localizer
239 _ = localizer
239
240
240 class _PasswordResetForm(formencode.Schema):
241 class _PasswordResetForm(formencode.Schema):
241 allow_extra_fields = True
242 allow_extra_fields = True
242 filter_extra_fields = True
243 filter_extra_fields = True
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 return _PasswordResetForm
245 return _PasswordResetForm
245
246
246
247
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
248 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
248 _ = localizer
249 _ = localizer
249 old_data = old_data or {}
250 old_data = old_data or {}
250 repo_groups = repo_groups or []
251 repo_groups = repo_groups or []
251 supported_backends = BACKENDS.keys()
252 supported_backends = BACKENDS.keys()
252
253
253 class _RepoForm(formencode.Schema):
254 class _RepoForm(formencode.Schema):
254 allow_extra_fields = True
255 allow_extra_fields = True
255 filter_extra_fields = False
256 filter_extra_fields = False
256 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
257 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
257 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
258 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
258 repo_group = All(v.CanWriteGroup(localizer, old_data),
259 repo_group = All(v.CanWriteGroup(localizer, old_data),
259 v.OneOf(repo_groups, hideList=True))
260 v.OneOf(repo_groups, hideList=True))
260 repo_type = v.OneOf(supported_backends, required=False,
261 repo_type = v.OneOf(supported_backends, required=False,
261 if_missing=old_data.get('repo_type'))
262 if_missing=old_data.get('repo_type'))
262 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
263 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
263 repo_private = v.StringBoolean(if_missing=False)
264 repo_private = v.StringBoolean(if_missing=False)
264 repo_copy_permissions = v.StringBoolean(if_missing=False)
265 repo_copy_permissions = v.StringBoolean(if_missing=False)
265 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
266 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
266
267
267 repo_enable_statistics = v.StringBoolean(if_missing=False)
268 repo_enable_statistics = v.StringBoolean(if_missing=False)
268 repo_enable_downloads = v.StringBoolean(if_missing=False)
269 repo_enable_downloads = v.StringBoolean(if_missing=False)
269 repo_enable_locking = v.StringBoolean(if_missing=False)
270 repo_enable_locking = v.StringBoolean(if_missing=False)
270
271
271 if edit:
272 if edit:
272 # this is repo owner
273 # this is repo owner
273 user = All(
274 user = All(
274 v.UnicodeString(not_empty=True),
275 v.UnicodeString(not_empty=True),
275 v.ValidRepoUser(localizer, allow_disabled))
276 v.ValidRepoUser(localizer, allow_disabled))
276 clone_uri_change = v.UnicodeString(
277 clone_uri_change = v.UnicodeString(
277 not_empty=False, if_missing=v.Missing)
278 not_empty=False, if_missing=v.Missing)
278
279
279 chained_validators = [v.ValidCloneUri(localizer),
280 chained_validators = [v.ValidCloneUri(localizer),
280 v.ValidRepoName(localizer, edit, old_data)]
281 v.ValidRepoName(localizer, edit, old_data)]
281 return _RepoForm
282 return _RepoForm
282
283
283
284
284 def RepoPermsForm(localizer):
285 def RepoPermsForm(localizer):
285 _ = localizer
286 _ = localizer
286
287
287 class _RepoPermsForm(formencode.Schema):
288 class _RepoPermsForm(formencode.Schema):
288 allow_extra_fields = True
289 allow_extra_fields = True
289 filter_extra_fields = False
290 filter_extra_fields = False
290 chained_validators = [v.ValidPerms(localizer, type_='repo')]
291 chained_validators = [v.ValidPerms(localizer, type_='repo')]
291 return _RepoPermsForm
292 return _RepoPermsForm
292
293
293
294
294 def RepoGroupPermsForm(localizer, valid_recursive_choices):
295 def RepoGroupPermsForm(localizer, valid_recursive_choices):
295 _ = localizer
296 _ = localizer
296
297
297 class _RepoGroupPermsForm(formencode.Schema):
298 class _RepoGroupPermsForm(formencode.Schema):
298 allow_extra_fields = True
299 allow_extra_fields = True
299 filter_extra_fields = False
300 filter_extra_fields = False
300 recursive = v.OneOf(valid_recursive_choices)
301 recursive = v.OneOf(valid_recursive_choices)
301 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
302 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
302 return _RepoGroupPermsForm
303 return _RepoGroupPermsForm
303
304
304
305
305 def UserGroupPermsForm(localizer):
306 def UserGroupPermsForm(localizer):
306 _ = localizer
307 _ = localizer
307
308
308 class _UserPermsForm(formencode.Schema):
309 class _UserPermsForm(formencode.Schema):
309 allow_extra_fields = True
310 allow_extra_fields = True
310 filter_extra_fields = False
311 filter_extra_fields = False
311 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
312 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
312 return _UserPermsForm
313 return _UserPermsForm
313
314
314
315
315 def RepoFieldForm(localizer):
316 def RepoFieldForm(localizer):
316 _ = localizer
317 _ = localizer
317
318
318 class _RepoFieldForm(formencode.Schema):
319 class _RepoFieldForm(formencode.Schema):
319 filter_extra_fields = True
320 filter_extra_fields = True
320 allow_extra_fields = True
321 allow_extra_fields = True
321
322
322 new_field_key = All(v.FieldKey(localizer),
323 new_field_key = All(v.FieldKey(localizer),
323 v.UnicodeString(strip=True, min=3, not_empty=True))
324 v.UnicodeString(strip=True, min=3, not_empty=True))
324 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
325 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
325 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
326 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
326 if_missing='str')
327 if_missing='str')
327 new_field_label = v.UnicodeString(not_empty=False)
328 new_field_label = v.UnicodeString(not_empty=False)
328 new_field_desc = v.UnicodeString(not_empty=False)
329 new_field_desc = v.UnicodeString(not_empty=False)
329 return _RepoFieldForm
330 return _RepoFieldForm
330
331
331
332
332 def RepoForkForm(localizer, edit=False, old_data=None,
333 def RepoForkForm(localizer, edit=False, old_data=None,
333 supported_backends=BACKENDS.keys(), repo_groups=None):
334 supported_backends=BACKENDS.keys(), repo_groups=None):
334 _ = localizer
335 _ = localizer
335 old_data = old_data or {}
336 old_data = old_data or {}
336 repo_groups = repo_groups or []
337 repo_groups = repo_groups or []
337
338
338 class _RepoForkForm(formencode.Schema):
339 class _RepoForkForm(formencode.Schema):
339 allow_extra_fields = True
340 allow_extra_fields = True
340 filter_extra_fields = False
341 filter_extra_fields = False
341 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
342 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
342 v.SlugifyName(localizer))
343 v.SlugifyName(localizer))
343 repo_group = All(v.CanWriteGroup(localizer, ),
344 repo_group = All(v.CanWriteGroup(localizer, ),
344 v.OneOf(repo_groups, hideList=True))
345 v.OneOf(repo_groups, hideList=True))
345 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
346 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
346 description = v.UnicodeString(strip=True, min=1, not_empty=True)
347 description = v.UnicodeString(strip=True, min=1, not_empty=True)
347 private = v.StringBoolean(if_missing=False)
348 private = v.StringBoolean(if_missing=False)
348 copy_permissions = v.StringBoolean(if_missing=False)
349 copy_permissions = v.StringBoolean(if_missing=False)
349 fork_parent_id = v.UnicodeString()
350 fork_parent_id = v.UnicodeString()
350 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
351 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
351 return _RepoForkForm
352 return _RepoForkForm
352
353
353
354
354 def ApplicationSettingsForm(localizer):
355 def ApplicationSettingsForm(localizer):
355 _ = localizer
356 _ = localizer
356
357
357 class _ApplicationSettingsForm(formencode.Schema):
358 class _ApplicationSettingsForm(formencode.Schema):
358 allow_extra_fields = True
359 allow_extra_fields = True
359 filter_extra_fields = False
360 filter_extra_fields = False
360 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
361 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
361 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
362 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
362 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
363 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
363 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
364 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
364 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
365 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
365 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
366 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
366 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
367 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
367 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
368 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
368 return _ApplicationSettingsForm
369 return _ApplicationSettingsForm
369
370
370
371
371 def ApplicationVisualisationForm(localizer):
372 def ApplicationVisualisationForm(localizer):
372 from rhodecode.model.db import Repository
373 from rhodecode.model.db import Repository
373 _ = localizer
374 _ = localizer
374
375
375 class _ApplicationVisualisationForm(formencode.Schema):
376 class _ApplicationVisualisationForm(formencode.Schema):
376 allow_extra_fields = True
377 allow_extra_fields = True
377 filter_extra_fields = False
378 filter_extra_fields = False
378 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
379 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
379 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
380 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
380 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
381 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
381
382
382 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
383 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
383 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
384 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
384 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
385 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
385 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
386 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
386 rhodecode_show_version = v.StringBoolean(if_missing=False)
387 rhodecode_show_version = v.StringBoolean(if_missing=False)
387 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
388 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
388 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
389 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
389 rhodecode_gravatar_url = v.UnicodeString(min=3)
390 rhodecode_gravatar_url = v.UnicodeString(min=3)
390 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
391 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
391 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
392 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
392 rhodecode_support_url = v.UnicodeString()
393 rhodecode_support_url = v.UnicodeString()
393 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
394 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
394 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
395 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
395 return _ApplicationVisualisationForm
396 return _ApplicationVisualisationForm
396
397
397
398
398 class _BaseVcsSettingsForm(formencode.Schema):
399 class _BaseVcsSettingsForm(formencode.Schema):
399
400
400 allow_extra_fields = True
401 allow_extra_fields = True
401 filter_extra_fields = False
402 filter_extra_fields = False
402 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
403 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
403 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
404 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
404 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
405 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
405
406
406 # PR/Code-review
407 # PR/Code-review
407 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
408 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
408 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
409 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
409
410
410 # hg
411 # hg
411 extensions_largefiles = v.StringBoolean(if_missing=False)
412 extensions_largefiles = v.StringBoolean(if_missing=False)
412 extensions_evolve = v.StringBoolean(if_missing=False)
413 extensions_evolve = v.StringBoolean(if_missing=False)
413 phases_publish = v.StringBoolean(if_missing=False)
414 phases_publish = v.StringBoolean(if_missing=False)
414
415
415 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
416 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
416 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
417 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
417
418
418 # git
419 # git
419 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
420 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
420 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
421 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
421 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
422 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
422
423
423 # svn
424 # svn
424 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
425 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
425 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
426 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
426
427
427 # cache
428 # cache
428 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
429 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
429
430
430
431
431 def ApplicationUiSettingsForm(localizer):
432 def ApplicationUiSettingsForm(localizer):
432 _ = localizer
433 _ = localizer
433
434
434 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
435 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
435 web_push_ssl = v.StringBoolean(if_missing=False)
436 web_push_ssl = v.StringBoolean(if_missing=False)
436 paths_root_path = All(
437 paths_root_path = All(
437 v.ValidPath(localizer),
438 v.ValidPath(localizer),
438 v.UnicodeString(strip=True, min=1, not_empty=True)
439 v.UnicodeString(strip=True, min=1, not_empty=True)
439 )
440 )
440 largefiles_usercache = All(
441 largefiles_usercache = All(
441 v.ValidPath(localizer),
442 v.ValidPath(localizer),
442 v.UnicodeString(strip=True, min=2, not_empty=True))
443 v.UnicodeString(strip=True, min=2, not_empty=True))
443 vcs_git_lfs_store_location = All(
444 vcs_git_lfs_store_location = All(
444 v.ValidPath(localizer),
445 v.ValidPath(localizer),
445 v.UnicodeString(strip=True, min=2, not_empty=True))
446 v.UnicodeString(strip=True, min=2, not_empty=True))
446 extensions_hgsubversion = v.StringBoolean(if_missing=False)
447 extensions_hgsubversion = v.StringBoolean(if_missing=False)
447 extensions_hggit = v.StringBoolean(if_missing=False)
448 extensions_hggit = v.StringBoolean(if_missing=False)
448 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
449 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
449 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
450 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
450 return _ApplicationUiSettingsForm
451 return _ApplicationUiSettingsForm
451
452
452
453
453 def RepoVcsSettingsForm(localizer, repo_name):
454 def RepoVcsSettingsForm(localizer, repo_name):
454 _ = localizer
455 _ = localizer
455
456
456 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
457 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
457 inherit_global_settings = v.StringBoolean(if_missing=False)
458 inherit_global_settings = v.StringBoolean(if_missing=False)
458 new_svn_branch = v.ValidSvnPattern(localizer,
459 new_svn_branch = v.ValidSvnPattern(localizer,
459 section='vcs_svn_branch', repo_name=repo_name)
460 section='vcs_svn_branch', repo_name=repo_name)
460 new_svn_tag = v.ValidSvnPattern(localizer,
461 new_svn_tag = v.ValidSvnPattern(localizer,
461 section='vcs_svn_tag', repo_name=repo_name)
462 section='vcs_svn_tag', repo_name=repo_name)
462 return _RepoVcsSettingsForm
463 return _RepoVcsSettingsForm
463
464
464
465
465 def LabsSettingsForm(localizer):
466 def LabsSettingsForm(localizer):
466 _ = localizer
467 _ = localizer
467
468
468 class _LabSettingsForm(formencode.Schema):
469 class _LabSettingsForm(formencode.Schema):
469 allow_extra_fields = True
470 allow_extra_fields = True
470 filter_extra_fields = False
471 filter_extra_fields = False
471 return _LabSettingsForm
472 return _LabSettingsForm
472
473
473
474
474 def ApplicationPermissionsForm(
475 def ApplicationPermissionsForm(
475 localizer, register_choices, password_reset_choices,
476 localizer, register_choices, password_reset_choices,
476 extern_activate_choices):
477 extern_activate_choices):
477 _ = localizer
478 _ = localizer
478
479
479 class _DefaultPermissionsForm(formencode.Schema):
480 class _DefaultPermissionsForm(formencode.Schema):
480 allow_extra_fields = True
481 allow_extra_fields = True
481 filter_extra_fields = True
482 filter_extra_fields = True
482
483
483 anonymous = v.StringBoolean(if_missing=False)
484 anonymous = v.StringBoolean(if_missing=False)
484 default_register = v.OneOf(register_choices)
485 default_register = v.OneOf(register_choices)
485 default_register_message = v.UnicodeString()
486 default_register_message = v.UnicodeString()
486 default_password_reset = v.OneOf(password_reset_choices)
487 default_password_reset = v.OneOf(password_reset_choices)
487 default_extern_activate = v.OneOf(extern_activate_choices)
488 default_extern_activate = v.OneOf(extern_activate_choices)
488 return _DefaultPermissionsForm
489 return _DefaultPermissionsForm
489
490
490
491
491 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
492 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
492 user_group_perms_choices):
493 user_group_perms_choices):
493 _ = localizer
494 _ = localizer
494
495
495 class _ObjectPermissionsForm(formencode.Schema):
496 class _ObjectPermissionsForm(formencode.Schema):
496 allow_extra_fields = True
497 allow_extra_fields = True
497 filter_extra_fields = True
498 filter_extra_fields = True
498 overwrite_default_repo = v.StringBoolean(if_missing=False)
499 overwrite_default_repo = v.StringBoolean(if_missing=False)
499 overwrite_default_group = v.StringBoolean(if_missing=False)
500 overwrite_default_group = v.StringBoolean(if_missing=False)
500 overwrite_default_user_group = v.StringBoolean(if_missing=False)
501 overwrite_default_user_group = v.StringBoolean(if_missing=False)
501
502
502 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
504 default_group_perm = v.OneOf(group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
505 default_user_group_perm = v.OneOf(user_group_perms_choices)
505
506
506 return _ObjectPermissionsForm
507 return _ObjectPermissionsForm
507
508
508
509
509 def BranchPermissionsForm(localizer, branch_perms_choices):
510 def BranchPermissionsForm(localizer, branch_perms_choices):
510 _ = localizer
511 _ = localizer
511
512
512 class _BranchPermissionsForm(formencode.Schema):
513 class _BranchPermissionsForm(formencode.Schema):
513 allow_extra_fields = True
514 allow_extra_fields = True
514 filter_extra_fields = True
515 filter_extra_fields = True
515 overwrite_default_branch = v.StringBoolean(if_missing=False)
516 overwrite_default_branch = v.StringBoolean(if_missing=False)
516 default_branch_perm = v.OneOf(branch_perms_choices)
517 default_branch_perm = v.OneOf(branch_perms_choices)
517
518
518 return _BranchPermissionsForm
519 return _BranchPermissionsForm
519
520
520
521
521 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
522 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
522 repo_group_create_choices, user_group_create_choices,
523 repo_group_create_choices, user_group_create_choices,
523 fork_choices, inherit_default_permissions_choices):
524 fork_choices, inherit_default_permissions_choices):
524 _ = localizer
525 _ = localizer
525
526
526 class _DefaultPermissionsForm(formencode.Schema):
527 class _DefaultPermissionsForm(formencode.Schema):
527 allow_extra_fields = True
528 allow_extra_fields = True
528 filter_extra_fields = True
529 filter_extra_fields = True
529
530
530 anonymous = v.StringBoolean(if_missing=False)
531 anonymous = v.StringBoolean(if_missing=False)
531
532
532 default_repo_create = v.OneOf(create_choices)
533 default_repo_create = v.OneOf(create_choices)
533 default_repo_create_on_write = v.OneOf(create_on_write_choices)
534 default_repo_create_on_write = v.OneOf(create_on_write_choices)
534 default_user_group_create = v.OneOf(user_group_create_choices)
535 default_user_group_create = v.OneOf(user_group_create_choices)
535 default_repo_group_create = v.OneOf(repo_group_create_choices)
536 default_repo_group_create = v.OneOf(repo_group_create_choices)
536 default_fork_create = v.OneOf(fork_choices)
537 default_fork_create = v.OneOf(fork_choices)
537 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
538 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
538 return _DefaultPermissionsForm
539 return _DefaultPermissionsForm
539
540
540
541
541 def UserIndividualPermissionsForm(localizer):
542 def UserIndividualPermissionsForm(localizer):
542 _ = localizer
543 _ = localizer
543
544
544 class _DefaultPermissionsForm(formencode.Schema):
545 class _DefaultPermissionsForm(formencode.Schema):
545 allow_extra_fields = True
546 allow_extra_fields = True
546 filter_extra_fields = True
547 filter_extra_fields = True
547
548
548 inherit_default_permissions = v.StringBoolean(if_missing=False)
549 inherit_default_permissions = v.StringBoolean(if_missing=False)
549 return _DefaultPermissionsForm
550 return _DefaultPermissionsForm
550
551
551
552
552 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
553 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
553 _ = localizer
554 _ = localizer
554 old_data = old_data or {}
555 old_data = old_data or {}
555
556
556 class _DefaultsForm(formencode.Schema):
557 class _DefaultsForm(formencode.Schema):
557 allow_extra_fields = True
558 allow_extra_fields = True
558 filter_extra_fields = True
559 filter_extra_fields = True
559 default_repo_type = v.OneOf(supported_backends)
560 default_repo_type = v.OneOf(supported_backends)
560 default_repo_private = v.StringBoolean(if_missing=False)
561 default_repo_private = v.StringBoolean(if_missing=False)
561 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
562 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
562 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
563 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
563 default_repo_enable_locking = v.StringBoolean(if_missing=False)
564 default_repo_enable_locking = v.StringBoolean(if_missing=False)
564 return _DefaultsForm
565 return _DefaultsForm
565
566
566
567
567 def AuthSettingsForm(localizer):
568 def AuthSettingsForm(localizer):
568 _ = localizer
569 _ = localizer
569
570
570 class _AuthSettingsForm(formencode.Schema):
571 class _AuthSettingsForm(formencode.Schema):
571 allow_extra_fields = True
572 allow_extra_fields = True
572 filter_extra_fields = True
573 filter_extra_fields = True
573 auth_plugins = All(v.ValidAuthPlugins(localizer),
574 auth_plugins = All(v.ValidAuthPlugins(localizer),
574 v.UniqueListFromString(localizer)(not_empty=True))
575 v.UniqueListFromString(localizer)(not_empty=True))
575 return _AuthSettingsForm
576 return _AuthSettingsForm
576
577
577
578
578 def UserExtraEmailForm(localizer):
579 def UserExtraEmailForm(localizer):
579 _ = localizer
580 _ = localizer
580
581
581 class _UserExtraEmailForm(formencode.Schema):
582 class _UserExtraEmailForm(formencode.Schema):
582 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
583 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
583 return _UserExtraEmailForm
584 return _UserExtraEmailForm
584
585
585
586
586 def UserExtraIpForm(localizer):
587 def UserExtraIpForm(localizer):
587 _ = localizer
588 _ = localizer
588
589
589 class _UserExtraIpForm(formencode.Schema):
590 class _UserExtraIpForm(formencode.Schema):
590 ip = v.ValidIp(localizer)(not_empty=True)
591 ip = v.ValidIp(localizer)(not_empty=True)
591 return _UserExtraIpForm
592 return _UserExtraIpForm
592
593
593
594
594 def PullRequestForm(localizer, repo_id):
595 def PullRequestForm(localizer, repo_id):
595 _ = localizer
596 _ = localizer
596
597
597 class ReviewerForm(formencode.Schema):
598 class ReviewerForm(formencode.Schema):
598 user_id = v.Int(not_empty=True)
599 user_id = v.Int(not_empty=True)
599 reasons = All()
600 reasons = All()
600 rules = All(v.UniqueList(localizer, convert=int)())
601 rules = All(v.UniqueList(localizer, convert=int)())
601 mandatory = v.StringBoolean()
602 mandatory = v.StringBoolean()
602
603
603 class _PullRequestForm(formencode.Schema):
604 class _PullRequestForm(formencode.Schema):
604 allow_extra_fields = True
605 allow_extra_fields = True
605 filter_extra_fields = True
606 filter_extra_fields = True
606
607
607 common_ancestor = v.UnicodeString(strip=True, required=True)
608 common_ancestor = v.UnicodeString(strip=True, required=True)
608 source_repo = v.UnicodeString(strip=True, required=True)
609 source_repo = v.UnicodeString(strip=True, required=True)
609 source_ref = v.UnicodeString(strip=True, required=True)
610 source_ref = v.UnicodeString(strip=True, required=True)
610 target_repo = v.UnicodeString(strip=True, required=True)
611 target_repo = v.UnicodeString(strip=True, required=True)
611 target_ref = v.UnicodeString(strip=True, required=True)
612 target_ref = v.UnicodeString(strip=True, required=True)
612 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
613 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
613 v.UniqueList(localizer)(not_empty=True))
614 v.UniqueList(localizer)(not_empty=True))
614 review_members = formencode.ForEach(ReviewerForm())
615 review_members = formencode.ForEach(ReviewerForm())
615 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
616 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
616 pullrequest_desc = v.UnicodeString(strip=True, required=False)
617 pullrequest_desc = v.UnicodeString(strip=True, required=False)
617 description_renderer = v.UnicodeString(strip=True, required=False)
618 description_renderer = v.UnicodeString(strip=True, required=False)
618
619
619 return _PullRequestForm
620 return _PullRequestForm
620
621
621
622
622 def IssueTrackerPatternsForm(localizer):
623 def IssueTrackerPatternsForm(localizer):
623 _ = localizer
624 _ = localizer
624
625
625 class _IssueTrackerPatternsForm(formencode.Schema):
626 class _IssueTrackerPatternsForm(formencode.Schema):
626 allow_extra_fields = True
627 allow_extra_fields = True
627 filter_extra_fields = False
628 filter_extra_fields = False
628 chained_validators = [v.ValidPattern(localizer)]
629 chained_validators = [v.ValidPattern(localizer)]
629 return _IssueTrackerPatternsForm
630 return _IssueTrackerPatternsForm
@@ -1,1000 +1,1003 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
41 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'description': user.description,
182 'active': user.active,
183 'active': user.active,
183 'admin': user.admin,
184 'admin': user.admin,
184 'extern_name': user.extern_name,
185 'extern_name': user.extern_name,
185 'extern_type': user.extern_type,
186 'extern_type': user.extern_type,
186 'language': user.user_data.get('language')
187 'language': user.user_data.get('language')
187 }
188 }
188
189
189 # in case there's new_password, that comes from form, use it to
190 # in case there's new_password, that comes from form, use it to
190 # store password
191 # store password
191 if kwargs.get('new_password'):
192 if kwargs.get('new_password'):
192 kwargs['password'] = kwargs['new_password']
193 kwargs['password'] = kwargs['new_password']
193
194
194 # cleanups, my_account password change form
195 # cleanups, my_account password change form
195 kwargs.pop('current_password', None)
196 kwargs.pop('current_password', None)
196 kwargs.pop('new_password', None)
197 kwargs.pop('new_password', None)
197
198
198 # cleanups, user edit password change form
199 # cleanups, user edit password change form
199 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_change', None)
201 kwargs.pop('password_change', None)
201
202
202 # create repo group on user creation
203 # create repo group on user creation
203 kwargs.pop('create_repo_group', None)
204 kwargs.pop('create_repo_group', None)
204
205
205 # legacy forms send name, which is the firstname
206 # legacy forms send name, which is the firstname
206 firstname = kwargs.pop('name', None)
207 firstname = kwargs.pop('name', None)
207 if firstname:
208 if firstname:
208 kwargs['firstname'] = firstname
209 kwargs['firstname'] = firstname
209
210
210 for k, v in kwargs.items():
211 for k, v in kwargs.items():
211 # skip if we don't want to update this
212 # skip if we don't want to update this
212 if skip_attrs and k in skip_attrs:
213 if skip_attrs and k in skip_attrs:
213 continue
214 continue
214
215
215 user_attrs[k] = v
216 user_attrs[k] = v
216
217
217 try:
218 try:
218 return self.create_or_update(**user_attrs)
219 return self.create_or_update(**user_attrs)
219 except Exception:
220 except Exception:
220 log.error(traceback.format_exc())
221 log.error(traceback.format_exc())
221 raise
222 raise
222
223
223 def create_or_update(
224 def create_or_update(
224 self, username, password, email, firstname='', lastname='',
225 self, username, password, email, firstname='', lastname='',
225 active=True, admin=False, extern_type=None, extern_name=None,
226 active=True, admin=False, extern_type=None, extern_name=None,
226 cur_user=None, plugin=None, force_password_change=False,
227 cur_user=None, plugin=None, force_password_change=False,
227 allow_to_create_user=True, create_repo_group=None,
228 allow_to_create_user=True, create_repo_group=None,
228 updating_user_id=None, language=None, strict_creation_check=True):
229 updating_user_id=None, language=None, description=None,
230 strict_creation_check=True):
229 """
231 """
230 Creates a new instance if not found, or updates current one
232 Creates a new instance if not found, or updates current one
231
233
232 :param username:
234 :param username:
233 :param password:
235 :param password:
234 :param email:
236 :param email:
235 :param firstname:
237 :param firstname:
236 :param lastname:
238 :param lastname:
237 :param active:
239 :param active:
238 :param admin:
240 :param admin:
239 :param extern_type:
241 :param extern_type:
240 :param extern_name:
242 :param extern_name:
241 :param cur_user:
243 :param cur_user:
242 :param plugin: optional plugin this method was called from
244 :param plugin: optional plugin this method was called from
243 :param force_password_change: toggles new or existing user flag
245 :param force_password_change: toggles new or existing user flag
244 for password change
246 for password change
245 :param allow_to_create_user: Defines if the method can actually create
247 :param allow_to_create_user: Defines if the method can actually create
246 new users
248 new users
247 :param create_repo_group: Defines if the method should also
249 :param create_repo_group: Defines if the method should also
248 create an repo group with user name, and owner
250 create an repo group with user name, and owner
249 :param updating_user_id: if we set it up this is the user we want to
251 :param updating_user_id: if we set it up this is the user we want to
250 update this allows to editing username.
252 update this allows to editing username.
251 :param language: language of user from interface.
253 :param language: language of user from interface.
252
254
253 :returns: new User object with injected `is_new_user` attribute.
255 :returns: new User object with injected `is_new_user` attribute.
254 """
256 """
255
257
256 if not cur_user:
258 if not cur_user:
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
259 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258
260
259 from rhodecode.lib.auth import (
261 from rhodecode.lib.auth import (
260 get_crypt_password, check_password, generate_auth_token)
262 get_crypt_password, check_password, generate_auth_token)
261 from rhodecode.lib.hooks_base import (
263 from rhodecode.lib.hooks_base import (
262 log_create_user, check_allowed_create_user)
264 log_create_user, check_allowed_create_user)
263
265
264 def _password_change(new_user, password):
266 def _password_change(new_user, password):
265 old_password = new_user.password or ''
267 old_password = new_user.password or ''
266 # empty password
268 # empty password
267 if not old_password:
269 if not old_password:
268 return False
270 return False
269
271
270 # password check is only needed for RhodeCode internal auth calls
272 # password check is only needed for RhodeCode internal auth calls
271 # in case it's a plugin we don't care
273 # in case it's a plugin we don't care
272 if not plugin:
274 if not plugin:
273
275
274 # first check if we gave crypted password back, and if it
276 # first check if we gave crypted password back, and if it
275 # matches it's not password change
277 # matches it's not password change
276 if new_user.password == password:
278 if new_user.password == password:
277 return False
279 return False
278
280
279 password_match = check_password(password, old_password)
281 password_match = check_password(password, old_password)
280 if not password_match:
282 if not password_match:
281 return True
283 return True
282
284
283 return False
285 return False
284
286
285 # read settings on default personal repo group creation
287 # read settings on default personal repo group creation
286 if create_repo_group is None:
288 if create_repo_group is None:
287 default_create_repo_group = RepoGroupModel()\
289 default_create_repo_group = RepoGroupModel()\
288 .get_default_create_personal_repo_group()
290 .get_default_create_personal_repo_group()
289 create_repo_group = default_create_repo_group
291 create_repo_group = default_create_repo_group
290
292
291 user_data = {
293 user_data = {
292 'username': username,
294 'username': username,
293 'password': password,
295 'password': password,
294 'email': email,
296 'email': email,
295 'firstname': firstname,
297 'firstname': firstname,
296 'lastname': lastname,
298 'lastname': lastname,
297 'active': active,
299 'active': active,
298 'admin': admin
300 'admin': admin
299 }
301 }
300
302
301 if updating_user_id:
303 if updating_user_id:
302 log.debug('Checking for existing account in RhodeCode '
304 log.debug('Checking for existing account in RhodeCode '
303 'database with user_id `%s` ', updating_user_id)
305 'database with user_id `%s` ', updating_user_id)
304 user = User.get(updating_user_id)
306 user = User.get(updating_user_id)
305 else:
307 else:
306 log.debug('Checking for existing account in RhodeCode '
308 log.debug('Checking for existing account in RhodeCode '
307 'database with username `%s` ', username)
309 'database with username `%s` ', username)
308 user = User.get_by_username(username, case_insensitive=True)
310 user = User.get_by_username(username, case_insensitive=True)
309
311
310 if user is None:
312 if user is None:
311 # we check internal flag if this method is actually allowed to
313 # we check internal flag if this method is actually allowed to
312 # create new user
314 # create new user
313 if not allow_to_create_user:
315 if not allow_to_create_user:
314 msg = ('Method wants to create new user, but it is not '
316 msg = ('Method wants to create new user, but it is not '
315 'allowed to do so')
317 'allowed to do so')
316 log.warning(msg)
318 log.warning(msg)
317 raise NotAllowedToCreateUserError(msg)
319 raise NotAllowedToCreateUserError(msg)
318
320
319 log.debug('Creating new user %s', username)
321 log.debug('Creating new user %s', username)
320
322
321 # only if we create user that is active
323 # only if we create user that is active
322 new_active_user = active
324 new_active_user = active
323 if new_active_user and strict_creation_check:
325 if new_active_user and strict_creation_check:
324 # raises UserCreationError if it's not allowed for any reason to
326 # raises UserCreationError if it's not allowed for any reason to
325 # create new active user, this also executes pre-create hooks
327 # create new active user, this also executes pre-create hooks
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
328 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 events.trigger(events.UserPreCreate(user_data))
329 events.trigger(events.UserPreCreate(user_data))
328 new_user = User()
330 new_user = User()
329 edit = False
331 edit = False
330 else:
332 else:
331 log.debug('updating user `%s`', username)
333 log.debug('updating user `%s`', username)
332 events.trigger(events.UserPreUpdate(user, user_data))
334 events.trigger(events.UserPreUpdate(user, user_data))
333 new_user = user
335 new_user = user
334 edit = True
336 edit = True
335
337
336 # we're not allowed to edit default user
338 # we're not allowed to edit default user
337 if user.username == User.DEFAULT_USER:
339 if user.username == User.DEFAULT_USER:
338 raise DefaultUserException(
340 raise DefaultUserException(
339 "You can't edit this user (`%(username)s`) since it's "
341 "You can't edit this user (`%(username)s`) since it's "
340 "crucial for entire application"
342 "crucial for entire application"
341 % {'username': user.username})
343 % {'username': user.username})
342
344
343 # inject special attribute that will tell us if User is new or old
345 # inject special attribute that will tell us if User is new or old
344 new_user.is_new_user = not edit
346 new_user.is_new_user = not edit
345 # for users that didn's specify auth type, we use RhodeCode built in
347 # for users that didn's specify auth type, we use RhodeCode built in
346 from rhodecode.authentication.plugins import auth_rhodecode
348 from rhodecode.authentication.plugins import auth_rhodecode
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
349 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
350 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
349
351
350 try:
352 try:
351 new_user.username = username
353 new_user.username = username
352 new_user.admin = admin
354 new_user.admin = admin
353 new_user.email = email
355 new_user.email = email
354 new_user.active = active
356 new_user.active = active
355 new_user.extern_name = safe_unicode(extern_name)
357 new_user.extern_name = safe_unicode(extern_name)
356 new_user.extern_type = safe_unicode(extern_type)
358 new_user.extern_type = safe_unicode(extern_type)
357 new_user.name = firstname
359 new_user.name = firstname
358 new_user.lastname = lastname
360 new_user.lastname = lastname
361 new_user.description = description
359
362
360 # set password only if creating an user or password is changed
363 # set password only if creating an user or password is changed
361 if not edit or _password_change(new_user, password):
364 if not edit or _password_change(new_user, password):
362 reason = 'new password' if edit else 'new user'
365 reason = 'new password' if edit else 'new user'
363 log.debug('Updating password reason=>%s', reason)
366 log.debug('Updating password reason=>%s', reason)
364 new_user.password = get_crypt_password(password) if password else None
367 new_user.password = get_crypt_password(password) if password else None
365
368
366 if force_password_change:
369 if force_password_change:
367 new_user.update_userdata(force_password_change=True)
370 new_user.update_userdata(force_password_change=True)
368 if language:
371 if language:
369 new_user.update_userdata(language=language)
372 new_user.update_userdata(language=language)
370 new_user.update_userdata(notification_status=True)
373 new_user.update_userdata(notification_status=True)
371
374
372 self.sa.add(new_user)
375 self.sa.add(new_user)
373
376
374 if not edit and create_repo_group:
377 if not edit and create_repo_group:
375 RepoGroupModel().create_personal_repo_group(
378 RepoGroupModel().create_personal_repo_group(
376 new_user, commit_early=False)
379 new_user, commit_early=False)
377
380
378 if not edit:
381 if not edit:
379 # add the RSS token
382 # add the RSS token
380 self.add_auth_token(
383 self.add_auth_token(
381 user=username, lifetime_minutes=-1,
384 user=username, lifetime_minutes=-1,
382 role=self.auth_token_role.ROLE_FEED,
385 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
386 description=u'Generated feed token')
384
387
385 kwargs = new_user.get_dict()
388 kwargs = new_user.get_dict()
386 # backward compat, require api_keys present
389 # backward compat, require api_keys present
387 kwargs['api_keys'] = kwargs['auth_tokens']
390 kwargs['api_keys'] = kwargs['auth_tokens']
388 log_create_user(created_by=cur_user, **kwargs)
391 log_create_user(created_by=cur_user, **kwargs)
389 events.trigger(events.UserPostCreate(user_data))
392 events.trigger(events.UserPostCreate(user_data))
390 return new_user
393 return new_user
391 except (DatabaseError,):
394 except (DatabaseError,):
392 log.error(traceback.format_exc())
395 log.error(traceback.format_exc())
393 raise
396 raise
394
397
395 def create_registration(self, form_data,
398 def create_registration(self, form_data,
396 extern_name='rhodecode', extern_type='rhodecode'):
399 extern_name='rhodecode', extern_type='rhodecode'):
397 from rhodecode.model.notification import NotificationModel
400 from rhodecode.model.notification import NotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
401 from rhodecode.model.notification import EmailNotificationModel
399
402
400 try:
403 try:
401 form_data['admin'] = False
404 form_data['admin'] = False
402 form_data['extern_name'] = extern_name
405 form_data['extern_name'] = extern_name
403 form_data['extern_type'] = extern_type
406 form_data['extern_type'] = extern_type
404 new_user = self.create(form_data)
407 new_user = self.create(form_data)
405
408
406 self.sa.add(new_user)
409 self.sa.add(new_user)
407 self.sa.flush()
410 self.sa.flush()
408
411
409 user_data = new_user.get_dict()
412 user_data = new_user.get_dict()
410 kwargs = {
413 kwargs = {
411 # use SQLALCHEMY safe dump of user data
414 # use SQLALCHEMY safe dump of user data
412 'user': AttributeDict(user_data),
415 'user': AttributeDict(user_data),
413 'date': datetime.datetime.now()
416 'date': datetime.datetime.now()
414 }
417 }
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
418 notification_type = EmailNotificationModel.TYPE_REGISTRATION
416 # pre-generate the subject for notification itself
419 # pre-generate the subject for notification itself
417 (subject,
420 (subject,
418 _h, _e, # we don't care about those
421 _h, _e, # we don't care about those
419 body_plaintext) = EmailNotificationModel().render_email(
422 body_plaintext) = EmailNotificationModel().render_email(
420 notification_type, **kwargs)
423 notification_type, **kwargs)
421
424
422 # create notification objects, and emails
425 # create notification objects, and emails
423 NotificationModel().create(
426 NotificationModel().create(
424 created_by=new_user,
427 created_by=new_user,
425 notification_subject=subject,
428 notification_subject=subject,
426 notification_body=body_plaintext,
429 notification_body=body_plaintext,
427 notification_type=notification_type,
430 notification_type=notification_type,
428 recipients=None, # all admins
431 recipients=None, # all admins
429 email_kwargs=kwargs,
432 email_kwargs=kwargs,
430 )
433 )
431
434
432 return new_user
435 return new_user
433 except Exception:
436 except Exception:
434 log.error(traceback.format_exc())
437 log.error(traceback.format_exc())
435 raise
438 raise
436
439
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
440 def _handle_user_repos(self, username, repositories, handle_mode=None):
438 _superadmin = self.cls.get_first_super_admin()
441 _superadmin = self.cls.get_first_super_admin()
439 left_overs = True
442 left_overs = True
440
443
441 from rhodecode.model.repo import RepoModel
444 from rhodecode.model.repo import RepoModel
442
445
443 if handle_mode == 'detach':
446 if handle_mode == 'detach':
444 for obj in repositories:
447 for obj in repositories:
445 obj.user = _superadmin
448 obj.user = _superadmin
446 # set description we know why we super admin now owns
449 # set description we know why we super admin now owns
447 # additional repositories that were orphaned !
450 # additional repositories that were orphaned !
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
451 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
449 self.sa.add(obj)
452 self.sa.add(obj)
450 left_overs = False
453 left_overs = False
451 elif handle_mode == 'delete':
454 elif handle_mode == 'delete':
452 for obj in repositories:
455 for obj in repositories:
453 RepoModel().delete(obj, forks='detach')
456 RepoModel().delete(obj, forks='detach')
454 left_overs = False
457 left_overs = False
455
458
456 # if nothing is done we have left overs left
459 # if nothing is done we have left overs left
457 return left_overs
460 return left_overs
458
461
459 def _handle_user_repo_groups(self, username, repository_groups,
462 def _handle_user_repo_groups(self, username, repository_groups,
460 handle_mode=None):
463 handle_mode=None):
461 _superadmin = self.cls.get_first_super_admin()
464 _superadmin = self.cls.get_first_super_admin()
462 left_overs = True
465 left_overs = True
463
466
464 from rhodecode.model.repo_group import RepoGroupModel
467 from rhodecode.model.repo_group import RepoGroupModel
465
468
466 if handle_mode == 'detach':
469 if handle_mode == 'detach':
467 for r in repository_groups:
470 for r in repository_groups:
468 r.user = _superadmin
471 r.user = _superadmin
469 # set description we know why we super admin now owns
472 # set description we know why we super admin now owns
470 # additional repositories that were orphaned !
473 # additional repositories that were orphaned !
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
474 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
472 r.personal = False
475 r.personal = False
473 self.sa.add(r)
476 self.sa.add(r)
474 left_overs = False
477 left_overs = False
475 elif handle_mode == 'delete':
478 elif handle_mode == 'delete':
476 for r in repository_groups:
479 for r in repository_groups:
477 RepoGroupModel().delete(r)
480 RepoGroupModel().delete(r)
478 left_overs = False
481 left_overs = False
479
482
480 # if nothing is done we have left overs left
483 # if nothing is done we have left overs left
481 return left_overs
484 return left_overs
482
485
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
486 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
484 _superadmin = self.cls.get_first_super_admin()
487 _superadmin = self.cls.get_first_super_admin()
485 left_overs = True
488 left_overs = True
486
489
487 from rhodecode.model.user_group import UserGroupModel
490 from rhodecode.model.user_group import UserGroupModel
488
491
489 if handle_mode == 'detach':
492 if handle_mode == 'detach':
490 for r in user_groups:
493 for r in user_groups:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
494 for user_user_group_to_perm in r.user_user_group_to_perm:
492 if user_user_group_to_perm.user.username == username:
495 if user_user_group_to_perm.user.username == username:
493 user_user_group_to_perm.user = _superadmin
496 user_user_group_to_perm.user = _superadmin
494 r.user = _superadmin
497 r.user = _superadmin
495 # set description we know why we super admin now owns
498 # set description we know why we super admin now owns
496 # additional repositories that were orphaned !
499 # additional repositories that were orphaned !
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
500 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
498 self.sa.add(r)
501 self.sa.add(r)
499 left_overs = False
502 left_overs = False
500 elif handle_mode == 'delete':
503 elif handle_mode == 'delete':
501 for r in user_groups:
504 for r in user_groups:
502 UserGroupModel().delete(r)
505 UserGroupModel().delete(r)
503 left_overs = False
506 left_overs = False
504
507
505 # if nothing is done we have left overs left
508 # if nothing is done we have left overs left
506 return left_overs
509 return left_overs
507
510
508 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
511 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
509 _superadmin = self.cls.get_first_super_admin()
512 _superadmin = self.cls.get_first_super_admin()
510 left_overs = True
513 left_overs = True
511
514
512 if handle_mode == 'detach':
515 if handle_mode == 'detach':
513 for a in artifacts:
516 for a in artifacts:
514 a.upload_user = _superadmin
517 a.upload_user = _superadmin
515 # set description we know why we super admin now owns
518 # set description we know why we super admin now owns
516 # additional artifacts that were orphaned !
519 # additional artifacts that were orphaned !
517 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
520 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
518 self.sa.add(a)
521 self.sa.add(a)
519 left_overs = False
522 left_overs = False
520 elif handle_mode == 'delete':
523 elif handle_mode == 'delete':
521 from rhodecode.apps.file_store import utils as store_utils
524 from rhodecode.apps.file_store import utils as store_utils
522 storage = store_utils.get_file_storage(self.request.registry.settings)
525 storage = store_utils.get_file_storage(self.request.registry.settings)
523 for a in artifacts:
526 for a in artifacts:
524 file_uid = a.file_uid
527 file_uid = a.file_uid
525 storage.delete(file_uid)
528 storage.delete(file_uid)
526 self.sa.delete(a)
529 self.sa.delete(a)
527
530
528 left_overs = False
531 left_overs = False
529
532
530 # if nothing is done we have left overs left
533 # if nothing is done we have left overs left
531 return left_overs
534 return left_overs
532
535
533 def delete(self, user, cur_user=None, handle_repos=None,
536 def delete(self, user, cur_user=None, handle_repos=None,
534 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
537 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
535 from rhodecode.lib.hooks_base import log_delete_user
538 from rhodecode.lib.hooks_base import log_delete_user
536
539
537 if not cur_user:
540 if not cur_user:
538 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
541 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
539 user = self._get_user(user)
542 user = self._get_user(user)
540
543
541 try:
544 try:
542 if user.username == User.DEFAULT_USER:
545 if user.username == User.DEFAULT_USER:
543 raise DefaultUserException(
546 raise DefaultUserException(
544 u"You can't remove this user since it's"
547 u"You can't remove this user since it's"
545 u" crucial for entire application")
548 u" crucial for entire application")
546
549
547 left_overs = self._handle_user_repos(
550 left_overs = self._handle_user_repos(
548 user.username, user.repositories, handle_repos)
551 user.username, user.repositories, handle_repos)
549 if left_overs and user.repositories:
552 if left_overs and user.repositories:
550 repos = [x.repo_name for x in user.repositories]
553 repos = [x.repo_name for x in user.repositories]
551 raise UserOwnsReposException(
554 raise UserOwnsReposException(
552 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
555 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
553 u'removed. Switch owners or remove those repositories:%(list_repos)s'
556 u'removed. Switch owners or remove those repositories:%(list_repos)s'
554 % {'username': user.username, 'len_repos': len(repos),
557 % {'username': user.username, 'len_repos': len(repos),
555 'list_repos': ', '.join(repos)})
558 'list_repos': ', '.join(repos)})
556
559
557 left_overs = self._handle_user_repo_groups(
560 left_overs = self._handle_user_repo_groups(
558 user.username, user.repository_groups, handle_repo_groups)
561 user.username, user.repository_groups, handle_repo_groups)
559 if left_overs and user.repository_groups:
562 if left_overs and user.repository_groups:
560 repo_groups = [x.group_name for x in user.repository_groups]
563 repo_groups = [x.group_name for x in user.repository_groups]
561 raise UserOwnsRepoGroupsException(
564 raise UserOwnsRepoGroupsException(
562 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
565 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
563 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
566 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
564 % {'username': user.username, 'len_repo_groups': len(repo_groups),
567 % {'username': user.username, 'len_repo_groups': len(repo_groups),
565 'list_repo_groups': ', '.join(repo_groups)})
568 'list_repo_groups': ', '.join(repo_groups)})
566
569
567 left_overs = self._handle_user_user_groups(
570 left_overs = self._handle_user_user_groups(
568 user.username, user.user_groups, handle_user_groups)
571 user.username, user.user_groups, handle_user_groups)
569 if left_overs and user.user_groups:
572 if left_overs and user.user_groups:
570 user_groups = [x.users_group_name for x in user.user_groups]
573 user_groups = [x.users_group_name for x in user.user_groups]
571 raise UserOwnsUserGroupsException(
574 raise UserOwnsUserGroupsException(
572 u'user "%s" still owns %s user groups and cannot be '
575 u'user "%s" still owns %s user groups and cannot be '
573 u'removed. Switch owners or remove those user groups:%s'
576 u'removed. Switch owners or remove those user groups:%s'
574 % (user.username, len(user_groups), ', '.join(user_groups)))
577 % (user.username, len(user_groups), ', '.join(user_groups)))
575
578
576 left_overs = self._handle_user_artifacts(
579 left_overs = self._handle_user_artifacts(
577 user.username, user.artifacts, handle_artifacts)
580 user.username, user.artifacts, handle_artifacts)
578 if left_overs and user.artifacts:
581 if left_overs and user.artifacts:
579 artifacts = [x.file_uid for x in user.artifacts]
582 artifacts = [x.file_uid for x in user.artifacts]
580 raise UserOwnsArtifactsException(
583 raise UserOwnsArtifactsException(
581 u'user "%s" still owns %s artifacts and cannot be '
584 u'user "%s" still owns %s artifacts and cannot be '
582 u'removed. Switch owners or remove those artifacts:%s'
585 u'removed. Switch owners or remove those artifacts:%s'
583 % (user.username, len(artifacts), ', '.join(artifacts)))
586 % (user.username, len(artifacts), ', '.join(artifacts)))
584
587
585 user_data = user.get_dict() # fetch user data before expire
588 user_data = user.get_dict() # fetch user data before expire
586
589
587 # we might change the user data with detach/delete, make sure
590 # we might change the user data with detach/delete, make sure
588 # the object is marked as expired before actually deleting !
591 # the object is marked as expired before actually deleting !
589 self.sa.expire(user)
592 self.sa.expire(user)
590 self.sa.delete(user)
593 self.sa.delete(user)
591
594
592 log_delete_user(deleted_by=cur_user, **user_data)
595 log_delete_user(deleted_by=cur_user, **user_data)
593 except Exception:
596 except Exception:
594 log.error(traceback.format_exc())
597 log.error(traceback.format_exc())
595 raise
598 raise
596
599
597 def reset_password_link(self, data, pwd_reset_url):
600 def reset_password_link(self, data, pwd_reset_url):
598 from rhodecode.lib.celerylib import tasks, run_task
601 from rhodecode.lib.celerylib import tasks, run_task
599 from rhodecode.model.notification import EmailNotificationModel
602 from rhodecode.model.notification import EmailNotificationModel
600 user_email = data['email']
603 user_email = data['email']
601 try:
604 try:
602 user = User.get_by_email(user_email)
605 user = User.get_by_email(user_email)
603 if user:
606 if user:
604 log.debug('password reset user found %s', user)
607 log.debug('password reset user found %s', user)
605
608
606 email_kwargs = {
609 email_kwargs = {
607 'password_reset_url': pwd_reset_url,
610 'password_reset_url': pwd_reset_url,
608 'user': user,
611 'user': user,
609 'email': user_email,
612 'email': user_email,
610 'date': datetime.datetime.now()
613 'date': datetime.datetime.now()
611 }
614 }
612
615
613 (subject, headers, email_body,
616 (subject, headers, email_body,
614 email_body_plaintext) = EmailNotificationModel().render_email(
617 email_body_plaintext) = EmailNotificationModel().render_email(
615 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
618 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
616
619
617 recipients = [user_email]
620 recipients = [user_email]
618
621
619 action_logger_generic(
622 action_logger_generic(
620 'sending password reset email to user: {}'.format(
623 'sending password reset email to user: {}'.format(
621 user), namespace='security.password_reset')
624 user), namespace='security.password_reset')
622
625
623 run_task(tasks.send_email, recipients, subject,
626 run_task(tasks.send_email, recipients, subject,
624 email_body_plaintext, email_body)
627 email_body_plaintext, email_body)
625
628
626 else:
629 else:
627 log.debug("password reset email %s not found", user_email)
630 log.debug("password reset email %s not found", user_email)
628 except Exception:
631 except Exception:
629 log.error(traceback.format_exc())
632 log.error(traceback.format_exc())
630 return False
633 return False
631
634
632 return True
635 return True
633
636
634 def reset_password(self, data):
637 def reset_password(self, data):
635 from rhodecode.lib.celerylib import tasks, run_task
638 from rhodecode.lib.celerylib import tasks, run_task
636 from rhodecode.model.notification import EmailNotificationModel
639 from rhodecode.model.notification import EmailNotificationModel
637 from rhodecode.lib import auth
640 from rhodecode.lib import auth
638 user_email = data['email']
641 user_email = data['email']
639 pre_db = True
642 pre_db = True
640 try:
643 try:
641 user = User.get_by_email(user_email)
644 user = User.get_by_email(user_email)
642 new_passwd = auth.PasswordGenerator().gen_password(
645 new_passwd = auth.PasswordGenerator().gen_password(
643 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
646 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
644 if user:
647 if user:
645 user.password = auth.get_crypt_password(new_passwd)
648 user.password = auth.get_crypt_password(new_passwd)
646 # also force this user to reset his password !
649 # also force this user to reset his password !
647 user.update_userdata(force_password_change=True)
650 user.update_userdata(force_password_change=True)
648
651
649 Session().add(user)
652 Session().add(user)
650
653
651 # now delete the token in question
654 # now delete the token in question
652 UserApiKeys = AuthTokenModel.cls
655 UserApiKeys = AuthTokenModel.cls
653 UserApiKeys().query().filter(
656 UserApiKeys().query().filter(
654 UserApiKeys.api_key == data['token']).delete()
657 UserApiKeys.api_key == data['token']).delete()
655
658
656 Session().commit()
659 Session().commit()
657 log.info('successfully reset password for `%s`', user_email)
660 log.info('successfully reset password for `%s`', user_email)
658
661
659 if new_passwd is None:
662 if new_passwd is None:
660 raise Exception('unable to generate new password')
663 raise Exception('unable to generate new password')
661
664
662 pre_db = False
665 pre_db = False
663
666
664 email_kwargs = {
667 email_kwargs = {
665 'new_password': new_passwd,
668 'new_password': new_passwd,
666 'user': user,
669 'user': user,
667 'email': user_email,
670 'email': user_email,
668 'date': datetime.datetime.now()
671 'date': datetime.datetime.now()
669 }
672 }
670
673
671 (subject, headers, email_body,
674 (subject, headers, email_body,
672 email_body_plaintext) = EmailNotificationModel().render_email(
675 email_body_plaintext) = EmailNotificationModel().render_email(
673 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
676 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
674 **email_kwargs)
677 **email_kwargs)
675
678
676 recipients = [user_email]
679 recipients = [user_email]
677
680
678 action_logger_generic(
681 action_logger_generic(
679 'sent new password to user: {} with email: {}'.format(
682 'sent new password to user: {} with email: {}'.format(
680 user, user_email), namespace='security.password_reset')
683 user, user_email), namespace='security.password_reset')
681
684
682 run_task(tasks.send_email, recipients, subject,
685 run_task(tasks.send_email, recipients, subject,
683 email_body_plaintext, email_body)
686 email_body_plaintext, email_body)
684
687
685 except Exception:
688 except Exception:
686 log.error('Failed to update user password')
689 log.error('Failed to update user password')
687 log.error(traceback.format_exc())
690 log.error(traceback.format_exc())
688 if pre_db:
691 if pre_db:
689 # we rollback only if local db stuff fails. If it goes into
692 # we rollback only if local db stuff fails. If it goes into
690 # run_task, we're pass rollback state this wouldn't work then
693 # run_task, we're pass rollback state this wouldn't work then
691 Session().rollback()
694 Session().rollback()
692
695
693 return True
696 return True
694
697
695 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
698 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
696 """
699 """
697 Fetches auth_user by user_id,or api_key if present.
700 Fetches auth_user by user_id,or api_key if present.
698 Fills auth_user attributes with those taken from database.
701 Fills auth_user attributes with those taken from database.
699 Additionally set's is_authenitated if lookup fails
702 Additionally set's is_authenitated if lookup fails
700 present in database
703 present in database
701
704
702 :param auth_user: instance of user to set attributes
705 :param auth_user: instance of user to set attributes
703 :param user_id: user id to fetch by
706 :param user_id: user id to fetch by
704 :param api_key: api key to fetch by
707 :param api_key: api key to fetch by
705 :param username: username to fetch by
708 :param username: username to fetch by
706 """
709 """
707 def token_obfuscate(token):
710 def token_obfuscate(token):
708 if token:
711 if token:
709 return token[:4] + "****"
712 return token[:4] + "****"
710
713
711 if user_id is None and api_key is None and username is None:
714 if user_id is None and api_key is None and username is None:
712 raise Exception('You need to pass user_id, api_key or username')
715 raise Exception('You need to pass user_id, api_key or username')
713
716
714 log.debug(
717 log.debug(
715 'AuthUser: fill data execution based on: '
718 'AuthUser: fill data execution based on: '
716 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
719 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
717 try:
720 try:
718 dbuser = None
721 dbuser = None
719 if user_id:
722 if user_id:
720 dbuser = self.get(user_id)
723 dbuser = self.get(user_id)
721 elif api_key:
724 elif api_key:
722 dbuser = self.get_by_auth_token(api_key)
725 dbuser = self.get_by_auth_token(api_key)
723 elif username:
726 elif username:
724 dbuser = self.get_by_username(username)
727 dbuser = self.get_by_username(username)
725
728
726 if not dbuser:
729 if not dbuser:
727 log.warning(
730 log.warning(
728 'Unable to lookup user by id:%s api_key:%s username:%s',
731 'Unable to lookup user by id:%s api_key:%s username:%s',
729 user_id, token_obfuscate(api_key), username)
732 user_id, token_obfuscate(api_key), username)
730 return False
733 return False
731 if not dbuser.active:
734 if not dbuser.active:
732 log.debug('User `%s:%s` is inactive, skipping fill data',
735 log.debug('User `%s:%s` is inactive, skipping fill data',
733 username, user_id)
736 username, user_id)
734 return False
737 return False
735
738
736 log.debug('AuthUser: filling found user:%s data', dbuser)
739 log.debug('AuthUser: filling found user:%s data', dbuser)
737
740
738 attrs = {
741 attrs = {
739 'user_id': dbuser.user_id,
742 'user_id': dbuser.user_id,
740 'username': dbuser.username,
743 'username': dbuser.username,
741 'name': dbuser.name,
744 'name': dbuser.name,
742 'first_name': dbuser.first_name,
745 'first_name': dbuser.first_name,
743 'firstname': dbuser.firstname,
746 'firstname': dbuser.firstname,
744 'last_name': dbuser.last_name,
747 'last_name': dbuser.last_name,
745 'lastname': dbuser.lastname,
748 'lastname': dbuser.lastname,
746 'admin': dbuser.admin,
749 'admin': dbuser.admin,
747 'active': dbuser.active,
750 'active': dbuser.active,
748
751
749 'email': dbuser.email,
752 'email': dbuser.email,
750 'emails': dbuser.emails_cached(),
753 'emails': dbuser.emails_cached(),
751 'short_contact': dbuser.short_contact,
754 'short_contact': dbuser.short_contact,
752 'full_contact': dbuser.full_contact,
755 'full_contact': dbuser.full_contact,
753 'full_name': dbuser.full_name,
756 'full_name': dbuser.full_name,
754 'full_name_or_username': dbuser.full_name_or_username,
757 'full_name_or_username': dbuser.full_name_or_username,
755
758
756 '_api_key': dbuser._api_key,
759 '_api_key': dbuser._api_key,
757 '_user_data': dbuser._user_data,
760 '_user_data': dbuser._user_data,
758
761
759 'created_on': dbuser.created_on,
762 'created_on': dbuser.created_on,
760 'extern_name': dbuser.extern_name,
763 'extern_name': dbuser.extern_name,
761 'extern_type': dbuser.extern_type,
764 'extern_type': dbuser.extern_type,
762
765
763 'inherit_default_permissions': dbuser.inherit_default_permissions,
766 'inherit_default_permissions': dbuser.inherit_default_permissions,
764
767
765 'language': dbuser.language,
768 'language': dbuser.language,
766 'last_activity': dbuser.last_activity,
769 'last_activity': dbuser.last_activity,
767 'last_login': dbuser.last_login,
770 'last_login': dbuser.last_login,
768 'password': dbuser.password,
771 'password': dbuser.password,
769 }
772 }
770 auth_user.__dict__.update(attrs)
773 auth_user.__dict__.update(attrs)
771 except Exception:
774 except Exception:
772 log.error(traceback.format_exc())
775 log.error(traceback.format_exc())
773 auth_user.is_authenticated = False
776 auth_user.is_authenticated = False
774 return False
777 return False
775
778
776 return True
779 return True
777
780
778 def has_perm(self, user, perm):
781 def has_perm(self, user, perm):
779 perm = self._get_perm(perm)
782 perm = self._get_perm(perm)
780 user = self._get_user(user)
783 user = self._get_user(user)
781
784
782 return UserToPerm.query().filter(UserToPerm.user == user)\
785 return UserToPerm.query().filter(UserToPerm.user == user)\
783 .filter(UserToPerm.permission == perm).scalar() is not None
786 .filter(UserToPerm.permission == perm).scalar() is not None
784
787
785 def grant_perm(self, user, perm):
788 def grant_perm(self, user, perm):
786 """
789 """
787 Grant user global permissions
790 Grant user global permissions
788
791
789 :param user:
792 :param user:
790 :param perm:
793 :param perm:
791 """
794 """
792 user = self._get_user(user)
795 user = self._get_user(user)
793 perm = self._get_perm(perm)
796 perm = self._get_perm(perm)
794 # if this permission is already granted skip it
797 # if this permission is already granted skip it
795 _perm = UserToPerm.query()\
798 _perm = UserToPerm.query()\
796 .filter(UserToPerm.user == user)\
799 .filter(UserToPerm.user == user)\
797 .filter(UserToPerm.permission == perm)\
800 .filter(UserToPerm.permission == perm)\
798 .scalar()
801 .scalar()
799 if _perm:
802 if _perm:
800 return
803 return
801 new = UserToPerm()
804 new = UserToPerm()
802 new.user = user
805 new.user = user
803 new.permission = perm
806 new.permission = perm
804 self.sa.add(new)
807 self.sa.add(new)
805 return new
808 return new
806
809
807 def revoke_perm(self, user, perm):
810 def revoke_perm(self, user, perm):
808 """
811 """
809 Revoke users global permissions
812 Revoke users global permissions
810
813
811 :param user:
814 :param user:
812 :param perm:
815 :param perm:
813 """
816 """
814 user = self._get_user(user)
817 user = self._get_user(user)
815 perm = self._get_perm(perm)
818 perm = self._get_perm(perm)
816
819
817 obj = UserToPerm.query()\
820 obj = UserToPerm.query()\
818 .filter(UserToPerm.user == user)\
821 .filter(UserToPerm.user == user)\
819 .filter(UserToPerm.permission == perm)\
822 .filter(UserToPerm.permission == perm)\
820 .scalar()
823 .scalar()
821 if obj:
824 if obj:
822 self.sa.delete(obj)
825 self.sa.delete(obj)
823
826
824 def add_extra_email(self, user, email):
827 def add_extra_email(self, user, email):
825 """
828 """
826 Adds email address to UserEmailMap
829 Adds email address to UserEmailMap
827
830
828 :param user:
831 :param user:
829 :param email:
832 :param email:
830 """
833 """
831
834
832 user = self._get_user(user)
835 user = self._get_user(user)
833
836
834 obj = UserEmailMap()
837 obj = UserEmailMap()
835 obj.user = user
838 obj.user = user
836 obj.email = email
839 obj.email = email
837 self.sa.add(obj)
840 self.sa.add(obj)
838 return obj
841 return obj
839
842
840 def delete_extra_email(self, user, email_id):
843 def delete_extra_email(self, user, email_id):
841 """
844 """
842 Removes email address from UserEmailMap
845 Removes email address from UserEmailMap
843
846
844 :param user:
847 :param user:
845 :param email_id:
848 :param email_id:
846 """
849 """
847 user = self._get_user(user)
850 user = self._get_user(user)
848 obj = UserEmailMap.query().get(email_id)
851 obj = UserEmailMap.query().get(email_id)
849 if obj and obj.user_id == user.user_id:
852 if obj and obj.user_id == user.user_id:
850 self.sa.delete(obj)
853 self.sa.delete(obj)
851
854
852 def parse_ip_range(self, ip_range):
855 def parse_ip_range(self, ip_range):
853 ip_list = []
856 ip_list = []
854
857
855 def make_unique(value):
858 def make_unique(value):
856 seen = []
859 seen = []
857 return [c for c in value if not (c in seen or seen.append(c))]
860 return [c for c in value if not (c in seen or seen.append(c))]
858
861
859 # firsts split by commas
862 # firsts split by commas
860 for ip_range in ip_range.split(','):
863 for ip_range in ip_range.split(','):
861 if not ip_range:
864 if not ip_range:
862 continue
865 continue
863 ip_range = ip_range.strip()
866 ip_range = ip_range.strip()
864 if '-' in ip_range:
867 if '-' in ip_range:
865 start_ip, end_ip = ip_range.split('-', 1)
868 start_ip, end_ip = ip_range.split('-', 1)
866 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
869 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
867 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
870 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
868 parsed_ip_range = []
871 parsed_ip_range = []
869
872
870 for index in xrange(int(start_ip), int(end_ip) + 1):
873 for index in xrange(int(start_ip), int(end_ip) + 1):
871 new_ip = ipaddress.ip_address(index)
874 new_ip = ipaddress.ip_address(index)
872 parsed_ip_range.append(str(new_ip))
875 parsed_ip_range.append(str(new_ip))
873 ip_list.extend(parsed_ip_range)
876 ip_list.extend(parsed_ip_range)
874 else:
877 else:
875 ip_list.append(ip_range)
878 ip_list.append(ip_range)
876
879
877 return make_unique(ip_list)
880 return make_unique(ip_list)
878
881
879 def add_extra_ip(self, user, ip, description=None):
882 def add_extra_ip(self, user, ip, description=None):
880 """
883 """
881 Adds ip address to UserIpMap
884 Adds ip address to UserIpMap
882
885
883 :param user:
886 :param user:
884 :param ip:
887 :param ip:
885 """
888 """
886
889
887 user = self._get_user(user)
890 user = self._get_user(user)
888 obj = UserIpMap()
891 obj = UserIpMap()
889 obj.user = user
892 obj.user = user
890 obj.ip_addr = ip
893 obj.ip_addr = ip
891 obj.description = description
894 obj.description = description
892 self.sa.add(obj)
895 self.sa.add(obj)
893 return obj
896 return obj
894
897
895 auth_token_role = AuthTokenModel.cls
898 auth_token_role = AuthTokenModel.cls
896
899
897 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
900 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
898 scope_callback=None):
901 scope_callback=None):
899 """
902 """
900 Add AuthToken for user.
903 Add AuthToken for user.
901
904
902 :param user: username/user_id
905 :param user: username/user_id
903 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
906 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
904 :param role: one of AuthTokenModel.cls.ROLE_*
907 :param role: one of AuthTokenModel.cls.ROLE_*
905 :param description: optional string description
908 :param description: optional string description
906 """
909 """
907
910
908 token = AuthTokenModel().create(
911 token = AuthTokenModel().create(
909 user, description, lifetime_minutes, role)
912 user, description, lifetime_minutes, role)
910 if scope_callback and callable(scope_callback):
913 if scope_callback and callable(scope_callback):
911 # call the callback if we provide, used to attach scope for EE edition
914 # call the callback if we provide, used to attach scope for EE edition
912 scope_callback(token)
915 scope_callback(token)
913 return token
916 return token
914
917
915 def delete_extra_ip(self, user, ip_id):
918 def delete_extra_ip(self, user, ip_id):
916 """
919 """
917 Removes ip address from UserIpMap
920 Removes ip address from UserIpMap
918
921
919 :param user:
922 :param user:
920 :param ip_id:
923 :param ip_id:
921 """
924 """
922 user = self._get_user(user)
925 user = self._get_user(user)
923 obj = UserIpMap.query().get(ip_id)
926 obj = UserIpMap.query().get(ip_id)
924 if obj and obj.user_id == user.user_id:
927 if obj and obj.user_id == user.user_id:
925 self.sa.delete(obj)
928 self.sa.delete(obj)
926
929
927 def get_accounts_in_creation_order(self, current_user=None):
930 def get_accounts_in_creation_order(self, current_user=None):
928 """
931 """
929 Get accounts in order of creation for deactivation for license limits
932 Get accounts in order of creation for deactivation for license limits
930
933
931 pick currently logged in user, and append to the list in position 0
934 pick currently logged in user, and append to the list in position 0
932 pick all super-admins in order of creation date and add it to the list
935 pick all super-admins in order of creation date and add it to the list
933 pick all other accounts in order of creation and add it to the list.
936 pick all other accounts in order of creation and add it to the list.
934
937
935 Based on that list, the last accounts can be disabled as they are
938 Based on that list, the last accounts can be disabled as they are
936 created at the end and don't include any of the super admins as well
939 created at the end and don't include any of the super admins as well
937 as the current user.
940 as the current user.
938
941
939 :param current_user: optionally current user running this operation
942 :param current_user: optionally current user running this operation
940 """
943 """
941
944
942 if not current_user:
945 if not current_user:
943 current_user = get_current_rhodecode_user()
946 current_user = get_current_rhodecode_user()
944 active_super_admins = [
947 active_super_admins = [
945 x.user_id for x in User.query()
948 x.user_id for x in User.query()
946 .filter(User.user_id != current_user.user_id)
949 .filter(User.user_id != current_user.user_id)
947 .filter(User.active == true())
950 .filter(User.active == true())
948 .filter(User.admin == true())
951 .filter(User.admin == true())
949 .order_by(User.created_on.asc())]
952 .order_by(User.created_on.asc())]
950
953
951 active_regular_users = [
954 active_regular_users = [
952 x.user_id for x in User.query()
955 x.user_id for x in User.query()
953 .filter(User.user_id != current_user.user_id)
956 .filter(User.user_id != current_user.user_id)
954 .filter(User.active == true())
957 .filter(User.active == true())
955 .filter(User.admin == false())
958 .filter(User.admin == false())
956 .order_by(User.created_on.asc())]
959 .order_by(User.created_on.asc())]
957
960
958 list_of_accounts = [current_user.user_id]
961 list_of_accounts = [current_user.user_id]
959 list_of_accounts += active_super_admins
962 list_of_accounts += active_super_admins
960 list_of_accounts += active_regular_users
963 list_of_accounts += active_regular_users
961
964
962 return list_of_accounts
965 return list_of_accounts
963
966
964 def deactivate_last_users(self, expected_users, current_user=None):
967 def deactivate_last_users(self, expected_users, current_user=None):
965 """
968 """
966 Deactivate accounts that are over the license limits.
969 Deactivate accounts that are over the license limits.
967 Algorithm of which accounts to disabled is based on the formula:
970 Algorithm of which accounts to disabled is based on the formula:
968
971
969 Get current user, then super admins in creation order, then regular
972 Get current user, then super admins in creation order, then regular
970 active users in creation order.
973 active users in creation order.
971
974
972 Using that list we mark all accounts from the end of it as inactive.
975 Using that list we mark all accounts from the end of it as inactive.
973 This way we block only latest created accounts.
976 This way we block only latest created accounts.
974
977
975 :param expected_users: list of users in special order, we deactivate
978 :param expected_users: list of users in special order, we deactivate
976 the end N amount of users from that list
979 the end N amount of users from that list
977 """
980 """
978
981
979 list_of_accounts = self.get_accounts_in_creation_order(
982 list_of_accounts = self.get_accounts_in_creation_order(
980 current_user=current_user)
983 current_user=current_user)
981
984
982 for acc_id in list_of_accounts[expected_users + 1:]:
985 for acc_id in list_of_accounts[expected_users + 1:]:
983 user = User.get(acc_id)
986 user = User.get(acc_id)
984 log.info('Deactivating account %s for license unlock', user)
987 log.info('Deactivating account %s for license unlock', user)
985 user.active = False
988 user.active = False
986 Session().add(user)
989 Session().add(user)
987 Session().commit()
990 Session().commit()
988
991
989 return
992 return
990
993
991 def get_user_log(self, user, filter_term):
994 def get_user_log(self, user, filter_term):
992 user_log = UserLog.query()\
995 user_log = UserLog.query()\
993 .filter(or_(UserLog.user_id == user.user_id,
996 .filter(or_(UserLog.user_id == user.user_id,
994 UserLog.username == user.username))\
997 UserLog.username == user.username))\
995 .options(joinedload(UserLog.user))\
998 .options(joinedload(UserLog.user))\
996 .options(joinedload(UserLog.repository))\
999 .options(joinedload(UserLog.repository))\
997 .order_by(UserLog.action_date.desc())
1000 .order_by(UserLog.action_date.desc())
998
1001
999 user_log = user_log_filter(user_log, filter_term)
1002 user_log = user_log_filter(user_log, filter_term)
1000 return user_log
1003 return user_log
@@ -1,188 +1,195 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import colander
22 import colander
23
23
24 from rhodecode import forms
24 from rhodecode import forms
25 from rhodecode.model.db import User, UserEmailMap
25 from rhodecode.model.db import User, UserEmailMap
26 from rhodecode.model.validation_schema import types, validators
26 from rhodecode.model.validation_schema import types, validators
27 from rhodecode.translation import _
27 from rhodecode.translation import _
28 from rhodecode.lib.auth import check_password
28 from rhodecode.lib.auth import check_password
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30
30
31
31
32 @colander.deferred
32 @colander.deferred
33 def deferred_user_password_validator(node, kw):
33 def deferred_user_password_validator(node, kw):
34 username = kw.get('username')
34 username = kw.get('username')
35 user = User.get_by_username(username)
35 user = User.get_by_username(username)
36
36
37 def _user_password_validator(node, value):
37 def _user_password_validator(node, value):
38 if not check_password(value, user.password):
38 if not check_password(value, user.password):
39 msg = _('Password is incorrect')
39 msg = _('Password is incorrect')
40 raise colander.Invalid(node, msg)
40 raise colander.Invalid(node, msg)
41 return _user_password_validator
41 return _user_password_validator
42
42
43
43
44
44
45 class ChangePasswordSchema(colander.Schema):
45 class ChangePasswordSchema(colander.Schema):
46
46
47 current_password = colander.SchemaNode(
47 current_password = colander.SchemaNode(
48 colander.String(),
48 colander.String(),
49 missing=colander.required,
49 missing=colander.required,
50 widget=forms.widget.PasswordWidget(redisplay=True),
50 widget=forms.widget.PasswordWidget(redisplay=True),
51 validator=deferred_user_password_validator)
51 validator=deferred_user_password_validator)
52
52
53 new_password = colander.SchemaNode(
53 new_password = colander.SchemaNode(
54 colander.String(),
54 colander.String(),
55 missing=colander.required,
55 missing=colander.required,
56 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
56 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
57 validator=colander.Length(min=6))
57 validator=colander.Length(min=6))
58
58
59 def validator(self, form, values):
59 def validator(self, form, values):
60 if values['current_password'] == values['new_password']:
60 if values['current_password'] == values['new_password']:
61 exc = colander.Invalid(form)
61 exc = colander.Invalid(form)
62 exc['new_password'] = _('New password must be different '
62 exc['new_password'] = _('New password must be different '
63 'to old password')
63 'to old password')
64 raise exc
64 raise exc
65
65
66
66
67 @colander.deferred
67 @colander.deferred
68 def deferred_username_validator(node, kw):
68 def deferred_username_validator(node, kw):
69
69
70 def name_validator(node, value):
70 def name_validator(node, value):
71 msg = _(
71 msg = _(
72 u'Username may only contain alphanumeric characters '
72 u'Username may only contain alphanumeric characters '
73 u'underscores, periods or dashes and must begin with '
73 u'underscores, periods or dashes and must begin with '
74 u'alphanumeric character or underscore')
74 u'alphanumeric character or underscore')
75
75
76 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
76 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
77 raise colander.Invalid(node, msg)
77 raise colander.Invalid(node, msg)
78
78
79 return name_validator
79 return name_validator
80
80
81
81
82 @colander.deferred
82 @colander.deferred
83 def deferred_email_validator(node, kw):
83 def deferred_email_validator(node, kw):
84 # NOTE(marcink): we might provide uniqueness validation later here...
84 # NOTE(marcink): we might provide uniqueness validation later here...
85 return colander.Email()
85 return colander.Email()
86
86
87
87
88 class UserSchema(colander.Schema):
88 class UserSchema(colander.Schema):
89 username = colander.SchemaNode(
89 username = colander.SchemaNode(
90 colander.String(),
90 colander.String(),
91 validator=deferred_username_validator)
91 validator=deferred_username_validator)
92
92
93 email = colander.SchemaNode(
93 email = colander.SchemaNode(
94 colander.String(),
94 colander.String(),
95 validator=deferred_email_validator)
95 validator=deferred_email_validator)
96
96
97 password = colander.SchemaNode(
97 password = colander.SchemaNode(
98 colander.String(), missing='')
98 colander.String(), missing='')
99
99
100 first_name = colander.SchemaNode(
100 first_name = colander.SchemaNode(
101 colander.String(), missing='')
101 colander.String(), missing='')
102
102
103 last_name = colander.SchemaNode(
103 last_name = colander.SchemaNode(
104 colander.String(), missing='')
104 colander.String(), missing='')
105
105
106 active = colander.SchemaNode(
106 active = colander.SchemaNode(
107 types.StringBooleanType(),
107 types.StringBooleanType(),
108 missing=False)
108 missing=False)
109
109
110 admin = colander.SchemaNode(
110 admin = colander.SchemaNode(
111 types.StringBooleanType(),
111 types.StringBooleanType(),
112 missing=False)
112 missing=False)
113
113
114 extern_name = colander.SchemaNode(
114 extern_name = colander.SchemaNode(
115 colander.String(), missing='')
115 colander.String(), missing='')
116
116
117 extern_type = colander.SchemaNode(
117 extern_type = colander.SchemaNode(
118 colander.String(), missing='')
118 colander.String(), missing='')
119
119
120 def deserialize(self, cstruct):
120 def deserialize(self, cstruct):
121 """
121 """
122 Custom deserialize that allows to chain validation, and verify
122 Custom deserialize that allows to chain validation, and verify
123 permissions, and as last step uniqueness
123 permissions, and as last step uniqueness
124 """
124 """
125
125
126 appstruct = super(UserSchema, self).deserialize(cstruct)
126 appstruct = super(UserSchema, self).deserialize(cstruct)
127 return appstruct
127 return appstruct
128
128
129
129
130 @colander.deferred
130 @colander.deferred
131 def deferred_user_email_in_emails_validator(node, kw):
131 def deferred_user_email_in_emails_validator(node, kw):
132 return colander.OneOf(kw.get('user_emails'))
132 return colander.OneOf(kw.get('user_emails'))
133
133
134
134
135 @colander.deferred
135 @colander.deferred
136 def deferred_additional_email_validator(node, kw):
136 def deferred_additional_email_validator(node, kw):
137 emails = kw.get('user_emails')
137 emails = kw.get('user_emails')
138
138
139 def name_validator(node, value):
139 def name_validator(node, value):
140 if value in emails:
140 if value in emails:
141 msg = _('This e-mail address is already taken')
141 msg = _('This e-mail address is already taken')
142 raise colander.Invalid(node, msg)
142 raise colander.Invalid(node, msg)
143 user = User.get_by_email(value, case_insensitive=True)
143 user = User.get_by_email(value, case_insensitive=True)
144 if user:
144 if user:
145 msg = _(u'This e-mail address is already taken')
145 msg = _(u'This e-mail address is already taken')
146 raise colander.Invalid(node, msg)
146 raise colander.Invalid(node, msg)
147 c = colander.Email()
147 c = colander.Email()
148 return c(node, value)
148 return c(node, value)
149 return name_validator
149 return name_validator
150
150
151
151
152 @colander.deferred
152 @colander.deferred
153 def deferred_user_email_in_emails_widget(node, kw):
153 def deferred_user_email_in_emails_widget(node, kw):
154 import deform.widget
154 import deform.widget
155 emails = [(email, email) for email in kw.get('user_emails')]
155 emails = [(email, email) for email in kw.get('user_emails')]
156 return deform.widget.Select2Widget(values=emails)
156 return deform.widget.Select2Widget(values=emails)
157
157
158
158
159 class UserProfileSchema(colander.Schema):
159 class UserProfileSchema(colander.Schema):
160 username = colander.SchemaNode(
160 username = colander.SchemaNode(
161 colander.String(),
161 colander.String(),
162 validator=deferred_username_validator)
162 validator=deferred_username_validator)
163
163
164 firstname = colander.SchemaNode(
164 firstname = colander.SchemaNode(
165 colander.String(), missing='', title='First name')
165 colander.String(), missing='', title='First name')
166
166
167 lastname = colander.SchemaNode(
167 lastname = colander.SchemaNode(
168 colander.String(), missing='', title='Last name')
168 colander.String(), missing='', title='Last name')
169
169
170 description = colander.SchemaNode(
171 colander.String(), missing='', title='Personal Description',
172 widget=forms.widget.TextAreaWidget(),
173 validator=colander.Length(max=250)
174 )
175
170 email = colander.SchemaNode(
176 email = colander.SchemaNode(
171 colander.String(), widget=deferred_user_email_in_emails_widget,
177 colander.String(), widget=deferred_user_email_in_emails_widget,
172 validator=deferred_user_email_in_emails_validator,
178 validator=deferred_user_email_in_emails_validator,
173 description=h.literal(
179 description=h.literal(
174 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
180 _('Additional emails can be specified at <a href="{}">extra emails</a> page.').format(
175 '/_admin/my_account/emails')),
181 '/_admin/my_account/emails')),
176 )
182 )
177
183
178
184
185
179 class AddEmailSchema(colander.Schema):
186 class AddEmailSchema(colander.Schema):
180 current_password = colander.SchemaNode(
187 current_password = colander.SchemaNode(
181 colander.String(),
188 colander.String(),
182 missing=colander.required,
189 missing=colander.required,
183 widget=forms.widget.PasswordWidget(redisplay=True),
190 widget=forms.widget.PasswordWidget(redisplay=True),
184 validator=deferred_user_password_validator)
191 validator=deferred_user_password_validator)
185
192
186 email = colander.SchemaNode(
193 email = colander.SchemaNode(
187 colander.String(), title='New Email',
194 colander.String(), title='New Email',
188 validator=deferred_additional_email_validator)
195 validator=deferred_additional_email_validator)
@@ -1,147 +1,155 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default user-profile">
3 <div class="panel panel-default user-profile">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Profile')}</h3>
5 <h3 class="panel-title">${_('User Profile')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="user-profile-content">
8 <div class="user-profile-content">
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
10 <% readonly = None %>
10 <% readonly = None %>
11 <% disabled = "" %>
11 <% disabled = "" %>
12 %if c.extern_type != 'rhodecode':
12 %if c.extern_type != 'rhodecode':
13 <% readonly = "readonly" %>
13 <% readonly = "readonly" %>
14 <% disabled = " disabled" %>
14 <% disabled = " disabled" %>
15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
17 </div>
17 </div>
18 %endif
18 %endif
19 <div class="form">
19 <div class="form">
20 <div class="fields">
20 <div class="fields">
21 <div class="field">
21 <div class="field">
22 <div class="label photo">
22 <div class="label photo">
23 ${_('Photo')}:
23 ${_('Photo')}:
24 </div>
24 </div>
25 <div class="input profile">
25 <div class="input profile">
26 %if c.visual.use_gravatar:
26 %if c.visual.use_gravatar:
27 ${base.gravatar(c.user.email, 100)}
27 ${base.gravatar(c.user.email, 100)}
28 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
28 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
29 %else:
29 %else:
30 ${base.gravatar(c.user.email, 100)}
30 ${base.gravatar(c.user.email, 100)}
31 %endif
31 %endif
32 </div>
32 </div>
33 </div>
33 </div>
34 <div class="field">
34 <div class="field">
35 <div class="label">
35 <div class="label">
36 ${_('Username')}:
36 ${_('Username')}:
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
39 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="field">
42 <div class="field">
43 <div class="label">
43 <div class="label">
44 <label for="name">${_('First Name')}:</label>
44 <label for="name">${_('First Name')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 ${h.text('firstname', class_="medium")}
47 ${h.text('firstname', class_="medium")}
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class="field">
51 <div class="field">
52 <div class="label">
52 <div class="label">
53 <label for="lastname">${_('Last Name')}:</label>
53 <label for="lastname">${_('Last Name')}:</label>
54 </div>
54 </div>
55 <div class="input">
55 <div class="input">
56 ${h.text('lastname', class_="medium")}
56 ${h.text('lastname', class_="medium")}
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <div class="field">
60 <div class="field">
61 <div class="label">
61 <div class="label">
62 <label for="email">${_('Email')}:</label>
62 <label for="email">${_('Email')}:</label>
63 </div>
63 </div>
64 <div class="input">
64 <div class="input">
65 ## we should be able to edit email !
65 ## we should be able to edit email !
66 ${h.text('email', class_="medium")}
66 ${h.text('email', class_="medium")}
67 </div>
67 </div>
68 </div>
68 </div>
69 <div class="field">
69 <div class="field">
70 <div class="label">
70 <div class="label">
71 <label for="description">${_('Description')}:</label>
72 </div>
73 <div class="input textarea editor">
74 ${h.textarea('description', class_="medium")}
75 </div>
76 </div>
77 <div class="field">
78 <div class="label">
71 ${_('New Password')}:
79 ${_('New Password')}:
72 </div>
80 </div>
73 <div class="input">
81 <div class="input">
74 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
82 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
75 </div>
83 </div>
76 </div>
84 </div>
77 <div class="field">
85 <div class="field">
78 <div class="label">
86 <div class="label">
79 ${_('New Password Confirmation')}:
87 ${_('New Password Confirmation')}:
80 </div>
88 </div>
81 <div class="input">
89 <div class="input">
82 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
90 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
83 </div>
91 </div>
84 </div>
92 </div>
85 <div class="field">
93 <div class="field">
86 <div class="label-text">
94 <div class="label-text">
87 ${_('Active')}:
95 ${_('Active')}:
88 </div>
96 </div>
89 <div class="input user-checkbox">
97 <div class="input user-checkbox">
90 ${h.checkbox('active',value=True)}
98 ${h.checkbox('active',value=True)}
91 </div>
99 </div>
92 </div>
100 </div>
93 <div class="field">
101 <div class="field">
94 <div class="label-text">
102 <div class="label-text">
95 ${_('Super Admin')}:
103 ${_('Super Admin')}:
96 </div>
104 </div>
97 <div class="input user-checkbox">
105 <div class="input user-checkbox">
98 ${h.checkbox('admin',value=True)}
106 ${h.checkbox('admin',value=True)}
99 </div>
107 </div>
100 </div>
108 </div>
101 <div class="field">
109 <div class="field">
102 <div class="label-text">
110 <div class="label-text">
103 ${_('Authentication type')}:
111 ${_('Authentication type')}:
104 </div>
112 </div>
105 <div class="input">
113 <div class="input">
106 ${h.select('extern_type', c.extern_type, c.allowed_extern_types)}
114 ${h.select('extern_type', c.extern_type, c.allowed_extern_types)}
107 <p class="help-block">${_('When user was created using an external source. He is bound to authentication using this method.')}</p>
115 <p class="help-block">${_('When user was created using an external source. He is bound to authentication using this method.')}</p>
108 </div>
116 </div>
109 </div>
117 </div>
110 <div class="field">
118 <div class="field">
111 <div class="label-text">
119 <div class="label-text">
112 ${_('Name in Source of Record')}:
120 ${_('Name in Source of Record')}:
113 </div>
121 </div>
114 <div class="input">
122 <div class="input">
115 <p>${c.extern_name}</p>
123 <p>${c.extern_name}</p>
116 ${h.hidden('extern_name', readonly="readonly")}
124 ${h.hidden('extern_name', readonly="readonly")}
117 </div>
125 </div>
118 </div>
126 </div>
119 <div class="field">
127 <div class="field">
120 <div class="label">
128 <div class="label">
121 ${_('Language')}:
129 ${_('Language')}:
122 </div>
130 </div>
123 <div class="input">
131 <div class="input">
124 ## allowed_languages is defined in the users.py
132 ## allowed_languages is defined in the users.py
125 ## c.language comes from base.py as a default language
133 ## c.language comes from base.py as a default language
126 ${h.select('language', c.language, c.allowed_languages)}
134 ${h.select('language', c.language, c.allowed_languages)}
127 <p class="help-block">${h.literal(_('User interface language. Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
135 <p class="help-block">${h.literal(_('User interface language. Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
128 </div>
136 </div>
129 </div>
137 </div>
130 <div class="buttons">
138 <div class="buttons">
131 ${h.submit('save', _('Save'), class_="btn")}
139 ${h.submit('save', _('Save'), class_="btn")}
132 ${h.reset('reset', _('Reset'), class_="btn")}
140 ${h.reset('reset', _('Reset'), class_="btn")}
133 </div>
141 </div>
134 </div>
142 </div>
135 </div>
143 </div>
136 ${h.end_form()}
144 ${h.end_form()}
137 </div>
145 </div>
138 </div>
146 </div>
139 </div>
147 </div>
140
148
141 <script>
149 <script>
142 $('#language').select2({
150 $('#language').select2({
143 'containerCssClass': "drop-menu",
151 'containerCssClass': "drop-menu",
144 'dropdownCssClass': "drop-menu-dropdown",
152 'dropdownCssClass': "drop-menu-dropdown",
145 'dropdownAutoWidth': true
153 'dropdownAutoWidth': true
146 });
154 });
147 </script>
155 </script>
General Comments 0
You need to be logged in to leave comments. Login now