##// END OF EJS Templates
fix(user-models): added extra protection against model username changes that would create duplicates
super-admin -
r5352:77661e7b default
parent child Browse files
Show More
@@ -1,783 +1,784 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 import datetime
20 import datetime
21 import string
21 import string
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25 import peppercorn
25 import peppercorn
26 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
26 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
27
27
28 from rhodecode.apps._base import BaseAppView, DataGridAppView
28 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode import forms
29 from rhodecode import forms
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib import ext_json
32 from rhodecode.lib import ext_json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, NotAnonymous, CSRFRequired,
34 LoginRequired, NotAnonymous, CSRFRequired,
35 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
35 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
36 from rhodecode.lib.channelstream import (
36 from rhodecode.lib.channelstream import (
37 channelstream_request, ChannelstreamException)
37 channelstream_request, ChannelstreamException)
38 from rhodecode.lib.hash_utils import md5_safe
38 from rhodecode.lib.hash_utils import md5_safe
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 IntegrityError, or_, in_filter_generator,
43 IntegrityError, or_, in_filter_generator,
44 Repository, UserEmailMap, UserApiKeys, UserFollowing,
44 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
45 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.user_group import UserGroupModel
49 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class MyAccountView(BaseAppView, DataGridAppView):
55 class MyAccountView(BaseAppView, DataGridAppView):
56 ALLOW_SCOPED_TOKENS = False
56 ALLOW_SCOPED_TOKENS = False
57 """
57 """
58 This view has alternative version inside EE, if modified please take a look
58 This view has alternative version inside EE, if modified please take a look
59 in there as well.
59 in there as well.
60 """
60 """
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context()
63 c = self._get_local_tmpl_context()
64 c.user = c.auth_user.get_instance()
64 c.user = c.auth_user.get_instance()
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
66 return c
66 return c
67
67
68 @LoginRequired()
68 @LoginRequired()
69 @NotAnonymous()
69 @NotAnonymous()
70 def my_account_profile(self):
70 def my_account_profile(self):
71 c = self.load_default_context()
71 c = self.load_default_context()
72 c.active = 'profile'
72 c.active = 'profile'
73 c.extern_type = c.user.extern_type
73 c.extern_type = c.user.extern_type
74 return self._get_template_context(c)
74 return self._get_template_context(c)
75
75
76 @LoginRequired()
76 @LoginRequired()
77 @NotAnonymous()
77 @NotAnonymous()
78 def my_account_edit(self):
78 def my_account_edit(self):
79 c = self.load_default_context()
79 c = self.load_default_context()
80 c.active = 'profile_edit'
80 c.active = 'profile_edit'
81 c.extern_type = c.user.extern_type
81 c.extern_type = c.user.extern_type
82 c.extern_name = c.user.extern_name
82 c.extern_name = c.user.extern_name
83
83
84 schema = user_schema.UserProfileSchema().bind(
84 schema = user_schema.UserProfileSchema().bind(
85 username=c.user.username, user_emails=c.user.emails)
85 username=c.user.username, user_emails=c.user.emails)
86 appstruct = {
86 appstruct = {
87 'username': c.user.username,
87 'username': c.user.username,
88 'email': c.user.email,
88 'email': c.user.email,
89 'firstname': c.user.firstname,
89 'firstname': c.user.firstname,
90 'lastname': c.user.lastname,
90 'lastname': c.user.lastname,
91 'description': c.user.description,
91 'description': c.user.description,
92 }
92 }
93 c.form = forms.RcForm(
93 c.form = forms.RcForm(
94 schema, appstruct=appstruct,
94 schema, appstruct=appstruct,
95 action=h.route_path('my_account_update'),
95 action=h.route_path('my_account_update'),
96 buttons=(forms.buttons.save, forms.buttons.reset))
96 buttons=(forms.buttons.save, forms.buttons.reset))
97
97
98 return self._get_template_context(c)
98 return self._get_template_context(c)
99
99
100 @LoginRequired()
100 @LoginRequired()
101 @NotAnonymous()
101 @NotAnonymous()
102 @CSRFRequired()
102 @CSRFRequired()
103 def my_account_update(self):
103 def my_account_update(self):
104 _ = self.request.translate
104 _ = self.request.translate
105 c = self.load_default_context()
105 c = self.load_default_context()
106 c.active = 'profile_edit'
106 c.active = 'profile_edit'
107 c.perm_user = c.auth_user
107 c.perm_user = c.auth_user
108 c.extern_type = c.user.extern_type
108 c.extern_type = c.user.extern_type
109 c.extern_name = c.user.extern_name
109 c.extern_name = c.user.extern_name
110
110
111 schema = user_schema.UserProfileSchema().bind(
111 schema = user_schema.UserProfileSchema().bind(
112 username=c.user.username, user_emails=c.user.emails)
112 username=c.user.username, user_emails=c.user.emails)
113 form = forms.RcForm(
113 form = forms.RcForm(
114 schema, buttons=(forms.buttons.save, forms.buttons.reset))
114 schema, buttons=(forms.buttons.save, forms.buttons.reset))
115
115
116 controls = list(self.request.POST.items())
116 controls = list(self.request.POST.items())
117 try:
117 try:
118 valid_data = form.validate(controls)
118 valid_data = form.validate(controls)
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 'new_password', 'password_confirmation']
120 'new_password', 'password_confirmation']
121 if c.extern_type != "rhodecode":
121 if c.extern_type != "rhodecode":
122 # forbid updating username for external accounts
122 # forbid updating username for external accounts
123 skip_attrs.append('username')
123 skip_attrs.append('username')
124 old_email = c.user.email
124 old_email = c.user.email
125 UserModel().update_user(
125 UserModel().update_user(
126 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
126 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
127 **valid_data)
127 **valid_data)
128 if old_email != valid_data['email']:
128 if old_email != valid_data['email']:
129 old = UserEmailMap.query() \
129 old = UserEmailMap.query() \
130 .filter(UserEmailMap.user == c.user)\
130 .filter(UserEmailMap.user == c.user)\
131 .filter(UserEmailMap.email == valid_data['email'])\
131 .filter(UserEmailMap.email == valid_data['email'])\
132 .first()
132 .first()
133 old.email = old_email
133 old.email = old_email
134 h.flash(_('Your account was updated successfully'), category='success')
134 h.flash(_('Your account was updated successfully'), category='success')
135 Session().commit()
135 Session().commit()
136 except forms.ValidationFailure as e:
136 except forms.ValidationFailure as e:
137 c.form = e
137 c.form = e
138 return self._get_template_context(c)
138 return self._get_template_context(c)
139
139 except Exception:
140 except Exception:
140 log.exception("Exception updating user")
141 log.exception("Exception updating user")
141 h.flash(_('Error occurred during update of user'),
142 h.flash(_('Error occurred during update of user'),
142 category='error')
143 category='error')
143 raise HTTPFound(h.route_path('my_account_profile'))
144 raise HTTPFound(h.route_path('my_account_profile'))
144
145
145 @LoginRequired()
146 @LoginRequired()
146 @NotAnonymous()
147 @NotAnonymous()
147 def my_account_password(self):
148 def my_account_password(self):
148 c = self.load_default_context()
149 c = self.load_default_context()
149 c.active = 'password'
150 c.active = 'password'
150 c.extern_type = c.user.extern_type
151 c.extern_type = c.user.extern_type
151
152
152 schema = user_schema.ChangePasswordSchema().bind(
153 schema = user_schema.ChangePasswordSchema().bind(
153 username=c.user.username)
154 username=c.user.username)
154
155
155 form = forms.Form(
156 form = forms.Form(
156 schema,
157 schema,
157 action=h.route_path('my_account_password_update'),
158 action=h.route_path('my_account_password_update'),
158 buttons=(forms.buttons.save, forms.buttons.reset))
159 buttons=(forms.buttons.save, forms.buttons.reset))
159
160
160 c.form = form
161 c.form = form
161 return self._get_template_context(c)
162 return self._get_template_context(c)
162
163
163 @LoginRequired()
164 @LoginRequired()
164 @NotAnonymous()
165 @NotAnonymous()
165 @CSRFRequired()
166 @CSRFRequired()
166 def my_account_password_update(self):
167 def my_account_password_update(self):
167 _ = self.request.translate
168 _ = self.request.translate
168 c = self.load_default_context()
169 c = self.load_default_context()
169 c.active = 'password'
170 c.active = 'password'
170 c.extern_type = c.user.extern_type
171 c.extern_type = c.user.extern_type
171
172
172 schema = user_schema.ChangePasswordSchema().bind(
173 schema = user_schema.ChangePasswordSchema().bind(
173 username=c.user.username)
174 username=c.user.username)
174
175
175 form = forms.Form(
176 form = forms.Form(
176 schema, buttons=(forms.buttons.save, forms.buttons.reset))
177 schema, buttons=(forms.buttons.save, forms.buttons.reset))
177
178
178 if c.extern_type != 'rhodecode':
179 if c.extern_type != 'rhodecode':
179 raise HTTPFound(self.request.route_path('my_account_password'))
180 raise HTTPFound(self.request.route_path('my_account_password'))
180
181
181 controls = list(self.request.POST.items())
182 controls = list(self.request.POST.items())
182 try:
183 try:
183 valid_data = form.validate(controls)
184 valid_data = form.validate(controls)
184 UserModel().update_user(c.user.user_id, **valid_data)
185 UserModel().update_user(c.user.user_id, **valid_data)
185 c.user.update_userdata(force_password_change=False)
186 c.user.update_userdata(force_password_change=False)
186 Session().commit()
187 Session().commit()
187 except forms.ValidationFailure as e:
188 except forms.ValidationFailure as e:
188 c.form = e
189 c.form = e
189 return self._get_template_context(c)
190 return self._get_template_context(c)
190
191
191 except Exception:
192 except Exception:
192 log.exception("Exception updating password")
193 log.exception("Exception updating password")
193 h.flash(_('Error occurred during update of user password'),
194 h.flash(_('Error occurred during update of user password'),
194 category='error')
195 category='error')
195 else:
196 else:
196 instance = c.auth_user.get_instance()
197 instance = c.auth_user.get_instance()
197 self.session.setdefault('rhodecode_user', {}).update(
198 self.session.setdefault('rhodecode_user', {}).update(
198 {'password': md5_safe(instance.password)})
199 {'password': md5_safe(instance.password)})
199 self.session.save()
200 self.session.save()
200 h.flash(_("Successfully updated password"), category='success')
201 h.flash(_("Successfully updated password"), category='success')
201
202
202 raise HTTPFound(self.request.route_path('my_account_password'))
203 raise HTTPFound(self.request.route_path('my_account_password'))
203
204
204 @LoginRequired()
205 @LoginRequired()
205 @NotAnonymous()
206 @NotAnonymous()
206 def my_account_auth_tokens(self):
207 def my_account_auth_tokens(self):
207 _ = self.request.translate
208 _ = self.request.translate
208
209
209 c = self.load_default_context()
210 c = self.load_default_context()
210 c.active = 'auth_tokens'
211 c.active = 'auth_tokens'
211 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
212 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
212 c.role_values = [
213 c.role_values = [
213 (x, AuthTokenModel.cls._get_role_name(x))
214 (x, AuthTokenModel.cls._get_role_name(x))
214 for x in AuthTokenModel.cls.ROLES]
215 for x in AuthTokenModel.cls.ROLES]
215 c.role_options = [(c.role_values, _("Role"))]
216 c.role_options = [(c.role_values, _("Role"))]
216 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
217 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
217 c.user.user_id, show_expired=True)
218 c.user.user_id, show_expired=True)
218 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
219 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
219 return self._get_template_context(c)
220 return self._get_template_context(c)
220
221
221 @LoginRequired()
222 @LoginRequired()
222 @NotAnonymous()
223 @NotAnonymous()
223 @CSRFRequired()
224 @CSRFRequired()
224 def my_account_auth_tokens_view(self):
225 def my_account_auth_tokens_view(self):
225 _ = self.request.translate
226 _ = self.request.translate
226 c = self.load_default_context()
227 c = self.load_default_context()
227
228
228 auth_token_id = self.request.POST.get('auth_token_id')
229 auth_token_id = self.request.POST.get('auth_token_id')
229
230
230 if auth_token_id:
231 if auth_token_id:
231 token = UserApiKeys.get_or_404(auth_token_id)
232 token = UserApiKeys.get_or_404(auth_token_id)
232 if token.user.user_id != c.user.user_id:
233 if token.user.user_id != c.user.user_id:
233 raise HTTPNotFound()
234 raise HTTPNotFound()
234
235
235 return {
236 return {
236 'auth_token': token.api_key
237 'auth_token': token.api_key
237 }
238 }
238
239
239 def maybe_attach_token_scope(self, token):
240 def maybe_attach_token_scope(self, token):
240 # implemented in EE edition
241 # implemented in EE edition
241 pass
242 pass
242
243
243 @LoginRequired()
244 @LoginRequired()
244 @NotAnonymous()
245 @NotAnonymous()
245 @CSRFRequired()
246 @CSRFRequired()
246 def my_account_auth_tokens_add(self):
247 def my_account_auth_tokens_add(self):
247 _ = self.request.translate
248 _ = self.request.translate
248 c = self.load_default_context()
249 c = self.load_default_context()
249
250
250 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
251 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
251 description = self.request.POST.get('description')
252 description = self.request.POST.get('description')
252 role = self.request.POST.get('role')
253 role = self.request.POST.get('role')
253
254
254 token = UserModel().add_auth_token(
255 token = UserModel().add_auth_token(
255 user=c.user.user_id,
256 user=c.user.user_id,
256 lifetime_minutes=lifetime, role=role, description=description,
257 lifetime_minutes=lifetime, role=role, description=description,
257 scope_callback=self.maybe_attach_token_scope)
258 scope_callback=self.maybe_attach_token_scope)
258 token_data = token.get_api_data()
259 token_data = token.get_api_data()
259
260
260 audit_logger.store_web(
261 audit_logger.store_web(
261 'user.edit.token.add', action_data={
262 'user.edit.token.add', action_data={
262 'data': {'token': token_data, 'user': 'self'}},
263 'data': {'token': token_data, 'user': 'self'}},
263 user=self._rhodecode_user, )
264 user=self._rhodecode_user, )
264 Session().commit()
265 Session().commit()
265
266
266 h.flash(_("Auth token successfully created"), category='success')
267 h.flash(_("Auth token successfully created"), category='success')
267 return HTTPFound(h.route_path('my_account_auth_tokens'))
268 return HTTPFound(h.route_path('my_account_auth_tokens'))
268
269
269 @LoginRequired()
270 @LoginRequired()
270 @NotAnonymous()
271 @NotAnonymous()
271 @CSRFRequired()
272 @CSRFRequired()
272 def my_account_auth_tokens_delete(self):
273 def my_account_auth_tokens_delete(self):
273 _ = self.request.translate
274 _ = self.request.translate
274 c = self.load_default_context()
275 c = self.load_default_context()
275
276
276 del_auth_token = self.request.POST.get('del_auth_token')
277 del_auth_token = self.request.POST.get('del_auth_token')
277
278
278 if del_auth_token:
279 if del_auth_token:
279 token = UserApiKeys.get_or_404(del_auth_token)
280 token = UserApiKeys.get_or_404(del_auth_token)
280 token_data = token.get_api_data()
281 token_data = token.get_api_data()
281
282
282 AuthTokenModel().delete(del_auth_token, c.user.user_id)
283 AuthTokenModel().delete(del_auth_token, c.user.user_id)
283 audit_logger.store_web(
284 audit_logger.store_web(
284 'user.edit.token.delete', action_data={
285 'user.edit.token.delete', action_data={
285 'data': {'token': token_data, 'user': 'self'}},
286 'data': {'token': token_data, 'user': 'self'}},
286 user=self._rhodecode_user,)
287 user=self._rhodecode_user,)
287 Session().commit()
288 Session().commit()
288 h.flash(_("Auth token successfully deleted"), category='success')
289 h.flash(_("Auth token successfully deleted"), category='success')
289
290
290 return HTTPFound(h.route_path('my_account_auth_tokens'))
291 return HTTPFound(h.route_path('my_account_auth_tokens'))
291
292
292 @LoginRequired()
293 @LoginRequired()
293 @NotAnonymous()
294 @NotAnonymous()
294 def my_account_emails(self):
295 def my_account_emails(self):
295 _ = self.request.translate
296 _ = self.request.translate
296
297
297 c = self.load_default_context()
298 c = self.load_default_context()
298 c.active = 'emails'
299 c.active = 'emails'
299
300
300 c.user_email_map = UserEmailMap.query()\
301 c.user_email_map = UserEmailMap.query()\
301 .filter(UserEmailMap.user == c.user).all()
302 .filter(UserEmailMap.user == c.user).all()
302
303
303 schema = user_schema.AddEmailSchema().bind(
304 schema = user_schema.AddEmailSchema().bind(
304 username=c.user.username, user_emails=c.user.emails)
305 username=c.user.username, user_emails=c.user.emails)
305
306
306 form = forms.RcForm(schema,
307 form = forms.RcForm(schema,
307 action=h.route_path('my_account_emails_add'),
308 action=h.route_path('my_account_emails_add'),
308 buttons=(forms.buttons.save, forms.buttons.reset))
309 buttons=(forms.buttons.save, forms.buttons.reset))
309
310
310 c.form = form
311 c.form = form
311 return self._get_template_context(c)
312 return self._get_template_context(c)
312
313
313 @LoginRequired()
314 @LoginRequired()
314 @NotAnonymous()
315 @NotAnonymous()
315 @CSRFRequired()
316 @CSRFRequired()
316 def my_account_emails_add(self):
317 def my_account_emails_add(self):
317 _ = self.request.translate
318 _ = self.request.translate
318 c = self.load_default_context()
319 c = self.load_default_context()
319 c.active = 'emails'
320 c.active = 'emails'
320
321
321 schema = user_schema.AddEmailSchema().bind(
322 schema = user_schema.AddEmailSchema().bind(
322 username=c.user.username, user_emails=c.user.emails)
323 username=c.user.username, user_emails=c.user.emails)
323
324
324 form = forms.RcForm(
325 form = forms.RcForm(
325 schema, action=h.route_path('my_account_emails_add'),
326 schema, action=h.route_path('my_account_emails_add'),
326 buttons=(forms.buttons.save, forms.buttons.reset))
327 buttons=(forms.buttons.save, forms.buttons.reset))
327
328
328 controls = list(self.request.POST.items())
329 controls = list(self.request.POST.items())
329 try:
330 try:
330 valid_data = form.validate(controls)
331 valid_data = form.validate(controls)
331 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
332 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
332 audit_logger.store_web(
333 audit_logger.store_web(
333 'user.edit.email.add', action_data={
334 'user.edit.email.add', action_data={
334 'data': {'email': valid_data['email'], 'user': 'self'}},
335 'data': {'email': valid_data['email'], 'user': 'self'}},
335 user=self._rhodecode_user,)
336 user=self._rhodecode_user,)
336 Session().commit()
337 Session().commit()
337 except formencode.Invalid as error:
338 except formencode.Invalid as error:
338 h.flash(h.escape(error.error_dict['email']), category='error')
339 h.flash(h.escape(error.error_dict['email']), category='error')
339 except forms.ValidationFailure as e:
340 except forms.ValidationFailure as e:
340 c.user_email_map = UserEmailMap.query() \
341 c.user_email_map = UserEmailMap.query() \
341 .filter(UserEmailMap.user == c.user).all()
342 .filter(UserEmailMap.user == c.user).all()
342 c.form = e
343 c.form = e
343 return self._get_template_context(c)
344 return self._get_template_context(c)
344 except Exception:
345 except Exception:
345 log.exception("Exception adding email")
346 log.exception("Exception adding email")
346 h.flash(_('Error occurred during adding email'),
347 h.flash(_('Error occurred during adding email'),
347 category='error')
348 category='error')
348 else:
349 else:
349 h.flash(_("Successfully added email"), category='success')
350 h.flash(_("Successfully added email"), category='success')
350
351
351 raise HTTPFound(self.request.route_path('my_account_emails'))
352 raise HTTPFound(self.request.route_path('my_account_emails'))
352
353
353 @LoginRequired()
354 @LoginRequired()
354 @NotAnonymous()
355 @NotAnonymous()
355 @CSRFRequired()
356 @CSRFRequired()
356 def my_account_emails_delete(self):
357 def my_account_emails_delete(self):
357 _ = self.request.translate
358 _ = self.request.translate
358 c = self.load_default_context()
359 c = self.load_default_context()
359
360
360 del_email_id = self.request.POST.get('del_email_id')
361 del_email_id = self.request.POST.get('del_email_id')
361 if del_email_id:
362 if del_email_id:
362 email = UserEmailMap.get_or_404(del_email_id).email
363 email = UserEmailMap.get_or_404(del_email_id).email
363 UserModel().delete_extra_email(c.user.user_id, del_email_id)
364 UserModel().delete_extra_email(c.user.user_id, del_email_id)
364 audit_logger.store_web(
365 audit_logger.store_web(
365 'user.edit.email.delete', action_data={
366 'user.edit.email.delete', action_data={
366 'data': {'email': email, 'user': 'self'}},
367 'data': {'email': email, 'user': 'self'}},
367 user=self._rhodecode_user,)
368 user=self._rhodecode_user,)
368 Session().commit()
369 Session().commit()
369 h.flash(_("Email successfully deleted"),
370 h.flash(_("Email successfully deleted"),
370 category='success')
371 category='success')
371 return HTTPFound(h.route_path('my_account_emails'))
372 return HTTPFound(h.route_path('my_account_emails'))
372
373
373 @LoginRequired()
374 @LoginRequired()
374 @NotAnonymous()
375 @NotAnonymous()
375 @CSRFRequired()
376 @CSRFRequired()
376 def my_account_notifications_test_channelstream(self):
377 def my_account_notifications_test_channelstream(self):
377 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
378 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
378 self._rhodecode_user.username, datetime.datetime.now())
379 self._rhodecode_user.username, datetime.datetime.now())
379 payload = {
380 payload = {
380 # 'channel': 'broadcast',
381 # 'channel': 'broadcast',
381 'type': 'message',
382 'type': 'message',
382 'timestamp': datetime.datetime.utcnow(),
383 'timestamp': datetime.datetime.utcnow(),
383 'user': 'system',
384 'user': 'system',
384 'pm_users': [self._rhodecode_user.username],
385 'pm_users': [self._rhodecode_user.username],
385 'message': {
386 'message': {
386 'message': message,
387 'message': message,
387 'level': 'info',
388 'level': 'info',
388 'topic': '/notifications'
389 'topic': '/notifications'
389 }
390 }
390 }
391 }
391
392
392 registry = self.request.registry
393 registry = self.request.registry
393 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
394 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
394 channelstream_config = rhodecode_plugins.get('channelstream', {})
395 channelstream_config = rhodecode_plugins.get('channelstream', {})
395
396
396 try:
397 try:
397 channelstream_request(channelstream_config, [payload], '/message')
398 channelstream_request(channelstream_config, [payload], '/message')
398 except ChannelstreamException as e:
399 except ChannelstreamException as e:
399 log.exception('Failed to send channelstream data')
400 log.exception('Failed to send channelstream data')
400 return {"response": f'ERROR: {e.__class__.__name__}'}
401 return {"response": f'ERROR: {e.__class__.__name__}'}
401 return {"response": 'Channelstream data sent. '
402 return {"response": 'Channelstream data sent. '
402 'You should see a new live message now.'}
403 'You should see a new live message now.'}
403
404
404 def _load_my_repos_data(self, watched=False):
405 def _load_my_repos_data(self, watched=False):
405
406
406 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
407 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
407
408
408 if watched:
409 if watched:
409 # repos user watch
410 # repos user watch
410 repo_list = Session().query(
411 repo_list = Session().query(
411 Repository
412 Repository
412 ) \
413 ) \
413 .join(
414 .join(
414 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
415 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
415 ) \
416 ) \
416 .filter(
417 .filter(
417 UserFollowing.user_id == self._rhodecode_user.user_id
418 UserFollowing.user_id == self._rhodecode_user.user_id
418 ) \
419 ) \
419 .filter(or_(
420 .filter(or_(
420 # generate multiple IN to fix limitation problems
421 # generate multiple IN to fix limitation problems
421 *in_filter_generator(Repository.repo_id, allowed_ids))
422 *in_filter_generator(Repository.repo_id, allowed_ids))
422 ) \
423 ) \
423 .order_by(Repository.repo_name) \
424 .order_by(Repository.repo_name) \
424 .all()
425 .all()
425
426
426 else:
427 else:
427 # repos user is owner of
428 # repos user is owner of
428 repo_list = Session().query(
429 repo_list = Session().query(
429 Repository
430 Repository
430 ) \
431 ) \
431 .filter(
432 .filter(
432 Repository.user_id == self._rhodecode_user.user_id
433 Repository.user_id == self._rhodecode_user.user_id
433 ) \
434 ) \
434 .filter(or_(
435 .filter(or_(
435 # generate multiple IN to fix limitation problems
436 # generate multiple IN to fix limitation problems
436 *in_filter_generator(Repository.repo_id, allowed_ids))
437 *in_filter_generator(Repository.repo_id, allowed_ids))
437 ) \
438 ) \
438 .order_by(Repository.repo_name) \
439 .order_by(Repository.repo_name) \
439 .all()
440 .all()
440
441
441 _render = self.request.get_partial_renderer(
442 _render = self.request.get_partial_renderer(
442 'rhodecode:templates/data_table/_dt_elements.mako')
443 'rhodecode:templates/data_table/_dt_elements.mako')
443
444
444 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
445 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
445 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
446 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
446 short_name=False, admin=False)
447 short_name=False, admin=False)
447
448
448 repos_data = []
449 repos_data = []
449 for repo in repo_list:
450 for repo in repo_list:
450 row = {
451 row = {
451 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
452 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
452 repo.private, repo.archived, repo.fork),
453 repo.private, repo.archived, repo.fork),
453 "name_raw": repo.repo_name.lower(),
454 "name_raw": repo.repo_name.lower(),
454 }
455 }
455
456
456 repos_data.append(row)
457 repos_data.append(row)
457
458
458 # json used to render the grid
459 # json used to render the grid
459 return ext_json.str_json(repos_data)
460 return ext_json.str_json(repos_data)
460
461
461 @LoginRequired()
462 @LoginRequired()
462 @NotAnonymous()
463 @NotAnonymous()
463 def my_account_repos(self):
464 def my_account_repos(self):
464 c = self.load_default_context()
465 c = self.load_default_context()
465 c.active = 'repos'
466 c.active = 'repos'
466
467
467 # json used to render the grid
468 # json used to render the grid
468 c.data = self._load_my_repos_data()
469 c.data = self._load_my_repos_data()
469 return self._get_template_context(c)
470 return self._get_template_context(c)
470
471
471 @LoginRequired()
472 @LoginRequired()
472 @NotAnonymous()
473 @NotAnonymous()
473 def my_account_watched(self):
474 def my_account_watched(self):
474 c = self.load_default_context()
475 c = self.load_default_context()
475 c.active = 'watched'
476 c.active = 'watched'
476
477
477 # json used to render the grid
478 # json used to render the grid
478 c.data = self._load_my_repos_data(watched=True)
479 c.data = self._load_my_repos_data(watched=True)
479 return self._get_template_context(c)
480 return self._get_template_context(c)
480
481
481 @LoginRequired()
482 @LoginRequired()
482 @NotAnonymous()
483 @NotAnonymous()
483 def my_account_bookmarks(self):
484 def my_account_bookmarks(self):
484 c = self.load_default_context()
485 c = self.load_default_context()
485 c.active = 'bookmarks'
486 c.active = 'bookmarks'
486 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
487 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
487 self._rhodecode_db_user.user_id, cache=False)
488 self._rhodecode_db_user.user_id, cache=False)
488 return self._get_template_context(c)
489 return self._get_template_context(c)
489
490
490 def _process_bookmark_entry(self, entry, user_id):
491 def _process_bookmark_entry(self, entry, user_id):
491 position = safe_int(entry.get('position'))
492 position = safe_int(entry.get('position'))
492 cur_position = safe_int(entry.get('cur_position'))
493 cur_position = safe_int(entry.get('cur_position'))
493 if position is None:
494 if position is None:
494 return
495 return
495
496
496 # check if this is an existing entry
497 # check if this is an existing entry
497 is_new = False
498 is_new = False
498 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
499 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
499
500
500 if db_entry and str2bool(entry.get('remove')):
501 if db_entry and str2bool(entry.get('remove')):
501 log.debug('Marked bookmark %s for deletion', db_entry)
502 log.debug('Marked bookmark %s for deletion', db_entry)
502 Session().delete(db_entry)
503 Session().delete(db_entry)
503 return
504 return
504
505
505 if not db_entry:
506 if not db_entry:
506 # new
507 # new
507 db_entry = UserBookmark()
508 db_entry = UserBookmark()
508 is_new = True
509 is_new = True
509
510
510 should_save = False
511 should_save = False
511 default_redirect_url = ''
512 default_redirect_url = ''
512
513
513 # save repo
514 # save repo
514 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
515 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
515 repo = Repository.get(entry['bookmark_repo'])
516 repo = Repository.get(entry['bookmark_repo'])
516 perm_check = HasRepoPermissionAny(
517 perm_check = HasRepoPermissionAny(
517 'repository.read', 'repository.write', 'repository.admin')
518 'repository.read', 'repository.write', 'repository.admin')
518 if repo and perm_check(repo_name=repo.repo_name):
519 if repo and perm_check(repo_name=repo.repo_name):
519 db_entry.repository = repo
520 db_entry.repository = repo
520 should_save = True
521 should_save = True
521 default_redirect_url = '${repo_url}'
522 default_redirect_url = '${repo_url}'
522 # save repo group
523 # save repo group
523 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
524 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
524 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
525 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
525 perm_check = HasRepoGroupPermissionAny(
526 perm_check = HasRepoGroupPermissionAny(
526 'group.read', 'group.write', 'group.admin')
527 'group.read', 'group.write', 'group.admin')
527
528
528 if repo_group and perm_check(group_name=repo_group.group_name):
529 if repo_group and perm_check(group_name=repo_group.group_name):
529 db_entry.repository_group = repo_group
530 db_entry.repository_group = repo_group
530 should_save = True
531 should_save = True
531 default_redirect_url = '${repo_group_url}'
532 default_redirect_url = '${repo_group_url}'
532 # save generic info
533 # save generic info
533 elif entry.get('title') and entry.get('redirect_url'):
534 elif entry.get('title') and entry.get('redirect_url'):
534 should_save = True
535 should_save = True
535
536
536 if should_save:
537 if should_save:
537 # mark user and position
538 # mark user and position
538 db_entry.user_id = user_id
539 db_entry.user_id = user_id
539 db_entry.position = position
540 db_entry.position = position
540 db_entry.title = entry.get('title')
541 db_entry.title = entry.get('title')
541 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
542 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
542 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
543 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
543
544
544 Session().add(db_entry)
545 Session().add(db_entry)
545
546
546 @LoginRequired()
547 @LoginRequired()
547 @NotAnonymous()
548 @NotAnonymous()
548 @CSRFRequired()
549 @CSRFRequired()
549 def my_account_bookmarks_update(self):
550 def my_account_bookmarks_update(self):
550 _ = self.request.translate
551 _ = self.request.translate
551 c = self.load_default_context()
552 c = self.load_default_context()
552 c.active = 'bookmarks'
553 c.active = 'bookmarks'
553
554
554 controls = peppercorn.parse(self.request.POST.items())
555 controls = peppercorn.parse(self.request.POST.items())
555 user_id = c.user.user_id
556 user_id = c.user.user_id
556
557
557 # validate positions
558 # validate positions
558 positions = {}
559 positions = {}
559 for entry in controls.get('bookmarks', []):
560 for entry in controls.get('bookmarks', []):
560 position = safe_int(entry['position'])
561 position = safe_int(entry['position'])
561 if position is None:
562 if position is None:
562 continue
563 continue
563
564
564 if position in positions:
565 if position in positions:
565 h.flash(_("Position {} is defined twice. "
566 h.flash(_("Position {} is defined twice. "
566 "Please correct this error.").format(position), category='error')
567 "Please correct this error.").format(position), category='error')
567 return HTTPFound(h.route_path('my_account_bookmarks'))
568 return HTTPFound(h.route_path('my_account_bookmarks'))
568
569
569 entry['position'] = position
570 entry['position'] = position
570 entry['cur_position'] = safe_int(entry.get('cur_position'))
571 entry['cur_position'] = safe_int(entry.get('cur_position'))
571 positions[position] = entry
572 positions[position] = entry
572
573
573 try:
574 try:
574 for entry in positions.values():
575 for entry in positions.values():
575 self._process_bookmark_entry(entry, user_id)
576 self._process_bookmark_entry(entry, user_id)
576
577
577 Session().commit()
578 Session().commit()
578 h.flash(_("Update Bookmarks"), category='success')
579 h.flash(_("Update Bookmarks"), category='success')
579 except IntegrityError:
580 except IntegrityError:
580 h.flash(_("Failed to update bookmarks. "
581 h.flash(_("Failed to update bookmarks. "
581 "Make sure an unique position is used."), category='error')
582 "Make sure an unique position is used."), category='error')
582
583
583 return HTTPFound(h.route_path('my_account_bookmarks'))
584 return HTTPFound(h.route_path('my_account_bookmarks'))
584
585
585 @LoginRequired()
586 @LoginRequired()
586 @NotAnonymous()
587 @NotAnonymous()
587 def my_account_goto_bookmark(self):
588 def my_account_goto_bookmark(self):
588
589
589 bookmark_id = self.request.matchdict['bookmark_id']
590 bookmark_id = self.request.matchdict['bookmark_id']
590 user_bookmark = UserBookmark().query()\
591 user_bookmark = UserBookmark().query()\
591 .filter(UserBookmark.user_id == self.request.user.user_id) \
592 .filter(UserBookmark.user_id == self.request.user.user_id) \
592 .filter(UserBookmark.position == bookmark_id).scalar()
593 .filter(UserBookmark.position == bookmark_id).scalar()
593
594
594 redirect_url = h.route_path('my_account_bookmarks')
595 redirect_url = h.route_path('my_account_bookmarks')
595 if not user_bookmark:
596 if not user_bookmark:
596 raise HTTPFound(redirect_url)
597 raise HTTPFound(redirect_url)
597
598
598 # repository set
599 # repository set
599 if user_bookmark.repository:
600 if user_bookmark.repository:
600 repo_name = user_bookmark.repository.repo_name
601 repo_name = user_bookmark.repository.repo_name
601 base_redirect_url = h.route_path(
602 base_redirect_url = h.route_path(
602 'repo_summary', repo_name=repo_name)
603 'repo_summary', repo_name=repo_name)
603 if user_bookmark.redirect_url and \
604 if user_bookmark.redirect_url and \
604 '${repo_url}' in user_bookmark.redirect_url:
605 '${repo_url}' in user_bookmark.redirect_url:
605 redirect_url = string.Template(user_bookmark.redirect_url)\
606 redirect_url = string.Template(user_bookmark.redirect_url)\
606 .safe_substitute({'repo_url': base_redirect_url})
607 .safe_substitute({'repo_url': base_redirect_url})
607 else:
608 else:
608 redirect_url = base_redirect_url
609 redirect_url = base_redirect_url
609 # repository group set
610 # repository group set
610 elif user_bookmark.repository_group:
611 elif user_bookmark.repository_group:
611 repo_group_name = user_bookmark.repository_group.group_name
612 repo_group_name = user_bookmark.repository_group.group_name
612 base_redirect_url = h.route_path(
613 base_redirect_url = h.route_path(
613 'repo_group_home', repo_group_name=repo_group_name)
614 'repo_group_home', repo_group_name=repo_group_name)
614 if user_bookmark.redirect_url and \
615 if user_bookmark.redirect_url and \
615 '${repo_group_url}' in user_bookmark.redirect_url:
616 '${repo_group_url}' in user_bookmark.redirect_url:
616 redirect_url = string.Template(user_bookmark.redirect_url)\
617 redirect_url = string.Template(user_bookmark.redirect_url)\
617 .safe_substitute({'repo_group_url': base_redirect_url})
618 .safe_substitute({'repo_group_url': base_redirect_url})
618 else:
619 else:
619 redirect_url = base_redirect_url
620 redirect_url = base_redirect_url
620 # custom URL set
621 # custom URL set
621 elif user_bookmark.redirect_url:
622 elif user_bookmark.redirect_url:
622 server_url = h.route_url('home').rstrip('/')
623 server_url = h.route_url('home').rstrip('/')
623 redirect_url = string.Template(user_bookmark.redirect_url) \
624 redirect_url = string.Template(user_bookmark.redirect_url) \
624 .safe_substitute({'server_url': server_url})
625 .safe_substitute({'server_url': server_url})
625
626
626 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
627 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
627 raise HTTPFound(redirect_url)
628 raise HTTPFound(redirect_url)
628
629
629 @LoginRequired()
630 @LoginRequired()
630 @NotAnonymous()
631 @NotAnonymous()
631 def my_account_perms(self):
632 def my_account_perms(self):
632 c = self.load_default_context()
633 c = self.load_default_context()
633 c.active = 'perms'
634 c.active = 'perms'
634
635
635 c.perm_user = c.auth_user
636 c.perm_user = c.auth_user
636 return self._get_template_context(c)
637 return self._get_template_context(c)
637
638
638 @LoginRequired()
639 @LoginRequired()
639 @NotAnonymous()
640 @NotAnonymous()
640 def my_notifications(self):
641 def my_notifications(self):
641 c = self.load_default_context()
642 c = self.load_default_context()
642 c.active = 'notifications'
643 c.active = 'notifications'
643
644
644 return self._get_template_context(c)
645 return self._get_template_context(c)
645
646
646 @LoginRequired()
647 @LoginRequired()
647 @NotAnonymous()
648 @NotAnonymous()
648 @CSRFRequired()
649 @CSRFRequired()
649 def my_notifications_toggle_visibility(self):
650 def my_notifications_toggle_visibility(self):
650 user = self._rhodecode_db_user
651 user = self._rhodecode_db_user
651 new_status = not user.user_data.get('notification_status', True)
652 new_status = not user.user_data.get('notification_status', True)
652 user.update_userdata(notification_status=new_status)
653 user.update_userdata(notification_status=new_status)
653 Session().commit()
654 Session().commit()
654 return user.user_data['notification_status']
655 return user.user_data['notification_status']
655
656
656 def _get_pull_requests_list(self, statuses, filter_type=None):
657 def _get_pull_requests_list(self, statuses, filter_type=None):
657 draw, start, limit = self._extract_chunk(self.request)
658 draw, start, limit = self._extract_chunk(self.request)
658 search_q, order_by, order_dir = self._extract_ordering(self.request)
659 search_q, order_by, order_dir = self._extract_ordering(self.request)
659
660
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 if filter_type == 'awaiting_my_review':
664 if filter_type == 'awaiting_my_review':
664 pull_requests = PullRequestModel().get_im_participating_in_for_review(
665 pull_requests = PullRequestModel().get_im_participating_in_for_review(
665 user_id=self._rhodecode_user.user_id,
666 user_id=self._rhodecode_user.user_id,
666 statuses=statuses, query=search_q,
667 statuses=statuses, query=search_q,
667 offset=start, length=limit, order_by=order_by,
668 offset=start, length=limit, order_by=order_by,
668 order_dir=order_dir)
669 order_dir=order_dir)
669
670
670 pull_requests_total_count = PullRequestModel().count_im_participating_in_for_review(
671 pull_requests_total_count = PullRequestModel().count_im_participating_in_for_review(
671 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
672 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
672 else:
673 else:
673 pull_requests = PullRequestModel().get_im_participating_in(
674 pull_requests = PullRequestModel().get_im_participating_in(
674 user_id=self._rhodecode_user.user_id,
675 user_id=self._rhodecode_user.user_id,
675 statuses=statuses, query=search_q,
676 statuses=statuses, query=search_q,
676 offset=start, length=limit, order_by=order_by,
677 offset=start, length=limit, order_by=order_by,
677 order_dir=order_dir)
678 order_dir=order_dir)
678
679
679 pull_requests_total_count = PullRequestModel().count_im_participating_in(
680 pull_requests_total_count = PullRequestModel().count_im_participating_in(
680 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
681 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
681
682
682 data = []
683 data = []
683 comments_model = CommentsModel()
684 comments_model = CommentsModel()
684 for pr in pull_requests:
685 for pr in pull_requests:
685 repo_id = pr.target_repo_id
686 repo_id = pr.target_repo_id
686 comments_count = comments_model.get_all_comments(
687 comments_count = comments_model.get_all_comments(
687 repo_id, pull_request=pr, include_drafts=False, count_only=True)
688 repo_id, pull_request=pr, include_drafts=False, count_only=True)
688 owned = pr.user_id == self._rhodecode_user.user_id
689 owned = pr.user_id == self._rhodecode_user.user_id
689
690
690 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
691 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
691 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
692 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
692 if review_statuses and review_statuses[4]:
693 if review_statuses and review_statuses[4]:
693 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
694 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
694 my_review_status = statuses[0][1].status
695 my_review_status = statuses[0][1].status
695
696
696 data.append({
697 data.append({
697 'target_repo': _render('pullrequest_target_repo',
698 'target_repo': _render('pullrequest_target_repo',
698 pr.target_repo.repo_name),
699 pr.target_repo.repo_name),
699 'name': _render('pullrequest_name',
700 'name': _render('pullrequest_name',
700 pr.pull_request_id, pr.pull_request_state,
701 pr.pull_request_id, pr.pull_request_state,
701 pr.work_in_progress, pr.target_repo.repo_name,
702 pr.work_in_progress, pr.target_repo.repo_name,
702 short=True),
703 short=True),
703 'name_raw': pr.pull_request_id,
704 'name_raw': pr.pull_request_id,
704 'status': _render('pullrequest_status',
705 'status': _render('pullrequest_status',
705 pr.calculated_review_status()),
706 pr.calculated_review_status()),
706 'my_status': _render('pullrequest_status',
707 'my_status': _render('pullrequest_status',
707 my_review_status),
708 my_review_status),
708 'title': _render('pullrequest_title', pr.title, pr.description),
709 'title': _render('pullrequest_title', pr.title, pr.description),
709 'pr_flow': _render('pullrequest_commit_flow', pr),
710 'pr_flow': _render('pullrequest_commit_flow', pr),
710 'description': h.escape(pr.description),
711 'description': h.escape(pr.description),
711 'updated_on': _render('pullrequest_updated_on',
712 'updated_on': _render('pullrequest_updated_on',
712 h.datetime_to_time(pr.updated_on),
713 h.datetime_to_time(pr.updated_on),
713 pr.versions_count),
714 pr.versions_count),
714 'updated_on_raw': h.datetime_to_time(pr.updated_on),
715 'updated_on_raw': h.datetime_to_time(pr.updated_on),
715 'created_on': _render('pullrequest_updated_on',
716 'created_on': _render('pullrequest_updated_on',
716 h.datetime_to_time(pr.created_on)),
717 h.datetime_to_time(pr.created_on)),
717 'created_on_raw': h.datetime_to_time(pr.created_on),
718 'created_on_raw': h.datetime_to_time(pr.created_on),
718 'state': pr.pull_request_state,
719 'state': pr.pull_request_state,
719 'author': _render('pullrequest_author',
720 'author': _render('pullrequest_author',
720 pr.author.full_contact, ),
721 pr.author.full_contact, ),
721 'author_raw': pr.author.full_name,
722 'author_raw': pr.author.full_name,
722 'comments': _render('pullrequest_comments', comments_count),
723 'comments': _render('pullrequest_comments', comments_count),
723 'comments_raw': comments_count,
724 'comments_raw': comments_count,
724 'closed': pr.is_closed(),
725 'closed': pr.is_closed(),
725 'owned': owned
726 'owned': owned
726 })
727 })
727
728
728 # json used to render the grid
729 # json used to render the grid
729 data = ({
730 data = ({
730 'draw': draw,
731 'draw': draw,
731 'data': data,
732 'data': data,
732 'recordsTotal': pull_requests_total_count,
733 'recordsTotal': pull_requests_total_count,
733 'recordsFiltered': pull_requests_total_count,
734 'recordsFiltered': pull_requests_total_count,
734 })
735 })
735 return data
736 return data
736
737
737 @LoginRequired()
738 @LoginRequired()
738 @NotAnonymous()
739 @NotAnonymous()
739 def my_account_pullrequests(self):
740 def my_account_pullrequests(self):
740 c = self.load_default_context()
741 c = self.load_default_context()
741 c.active = 'pullrequests'
742 c.active = 'pullrequests'
742 req_get = self.request.GET
743 req_get = self.request.GET
743
744
744 c.closed = str2bool(req_get.get('closed'))
745 c.closed = str2bool(req_get.get('closed'))
745 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
746 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
746
747
747 c.selected_filter = 'all'
748 c.selected_filter = 'all'
748 if c.closed:
749 if c.closed:
749 c.selected_filter = 'all_closed'
750 c.selected_filter = 'all_closed'
750 if c.awaiting_my_review:
751 if c.awaiting_my_review:
751 c.selected_filter = 'awaiting_my_review'
752 c.selected_filter = 'awaiting_my_review'
752
753
753 return self._get_template_context(c)
754 return self._get_template_context(c)
754
755
755 @LoginRequired()
756 @LoginRequired()
756 @NotAnonymous()
757 @NotAnonymous()
757 def my_account_pullrequests_data(self):
758 def my_account_pullrequests_data(self):
758 self.load_default_context()
759 self.load_default_context()
759 req_get = self.request.GET
760 req_get = self.request.GET
760
761
761 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
762 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
762 closed = str2bool(req_get.get('closed'))
763 closed = str2bool(req_get.get('closed'))
763
764
764 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
765 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
765 if closed:
766 if closed:
766 statuses += [PullRequest.STATUS_CLOSED]
767 statuses += [PullRequest.STATUS_CLOSED]
767
768
768 filter_type = \
769 filter_type = \
769 'awaiting_my_review' if awaiting_my_review \
770 'awaiting_my_review' if awaiting_my_review \
770 else None
771 else None
771
772
772 data = self._get_pull_requests_list(statuses=statuses, filter_type=filter_type)
773 data = self._get_pull_requests_list(statuses=statuses, filter_type=filter_type)
773 return data
774 return data
774
775
775 @LoginRequired()
776 @LoginRequired()
776 @NotAnonymous()
777 @NotAnonymous()
777 def my_account_user_group_membership(self):
778 def my_account_user_group_membership(self):
778 c = self.load_default_context()
779 c = self.load_default_context()
779 c.active = 'user_group_membership'
780 c.active = 'user_group_membership'
780 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
781 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
781 for group in self._rhodecode_db_user.group_member]
782 for group in self._rhodecode_db_user.group_member]
782 c.user_groups = ext_json.str_json(groups)
783 c.user_groups = ext_json.str_json(groups)
783 return self._get_template_context(c)
784 return self._get_template_context(c)
@@ -1,201 +1,205 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Set of custom exceptions used in RhodeCode
20 Set of custom exceptions used in RhodeCode
21 """
21 """
22
22
23 from webob.exc import HTTPClientError
23 from webob.exc import HTTPClientError
24 from pyramid.httpexceptions import HTTPBadGateway
24 from pyramid.httpexceptions import HTTPBadGateway
25
25
26
26
27 class LdapUsernameError(Exception):
27 class LdapUsernameError(Exception):
28 pass
28 pass
29
29
30
30
31 class LdapPasswordError(Exception):
31 class LdapPasswordError(Exception):
32 pass
32 pass
33
33
34
34
35 class LdapConnectionError(Exception):
35 class LdapConnectionError(Exception):
36 pass
36 pass
37
37
38
38
39 class LdapImportError(Exception):
39 class LdapImportError(Exception):
40 pass
40 pass
41
41
42
42
43 class DefaultUserException(Exception):
43 class DefaultUserException(Exception):
44 pass
44 pass
45
45
46
46
47 class UserOwnsReposException(Exception):
47 class UserOwnsReposException(Exception):
48 pass
48 pass
49
49
50
50
51 class UserOwnsRepoGroupsException(Exception):
51 class UserOwnsRepoGroupsException(Exception):
52 pass
52 pass
53
53
54
54
55 class UserOwnsUserGroupsException(Exception):
55 class UserOwnsUserGroupsException(Exception):
56 pass
56 pass
57
57
58
58
59 class UserOwnsPullRequestsException(Exception):
59 class UserOwnsPullRequestsException(Exception):
60 pass
60 pass
61
61
62
62
63 class UserOwnsArtifactsException(Exception):
63 class UserOwnsArtifactsException(Exception):
64 pass
64 pass
65
65
66
66
67 class UserGroupAssignedException(Exception):
67 class UserGroupAssignedException(Exception):
68 pass
68 pass
69
69
70
70
71 class StatusChangeOnClosedPullRequestError(Exception):
71 class StatusChangeOnClosedPullRequestError(Exception):
72 pass
72 pass
73
73
74
74
75 class AttachedForksError(Exception):
75 class AttachedForksError(Exception):
76 pass
76 pass
77
77
78
78
79 class AttachedPullRequestsError(Exception):
79 class AttachedPullRequestsError(Exception):
80 pass
80 pass
81
81
82
82
83 class RepoGroupAssignmentError(Exception):
83 class RepoGroupAssignmentError(Exception):
84 pass
84 pass
85
85
86
86
87 class NonRelativePathError(Exception):
87 class NonRelativePathError(Exception):
88 pass
88 pass
89
89
90
90
91 class HTTPRequirementError(HTTPClientError):
91 class HTTPRequirementError(HTTPClientError):
92 title = explanation = 'Repository Requirement Missing'
92 title = explanation = 'Repository Requirement Missing'
93 reason = None
93 reason = None
94
94
95 def __init__(self, message, *args, **kwargs):
95 def __init__(self, message, *args, **kwargs):
96 self.title = self.explanation = message
96 self.title = self.explanation = message
97 super().__init__(*args, **kwargs)
97 super().__init__(*args, **kwargs)
98 self.args = (message, )
98 self.args = (message, )
99
99
100
100
101 class HTTPLockedRC(HTTPClientError):
101 class HTTPLockedRC(HTTPClientError):
102 """
102 """
103 Special Exception For locked Repos in RhodeCode, the return code can
103 Special Exception For locked Repos in RhodeCode, the return code can
104 be overwritten by _code keyword argument passed into constructors
104 be overwritten by _code keyword argument passed into constructors
105 """
105 """
106 code = 423
106 code = 423
107 title = explanation = 'Repository Locked'
107 title = explanation = 'Repository Locked'
108 reason = None
108 reason = None
109
109
110 def __init__(self, message, *args, **kwargs):
110 def __init__(self, message, *args, **kwargs):
111 import rhodecode
111 import rhodecode
112
112
113 self.code = rhodecode.ConfigGet().get_int('lock_ret_code', missing=self.code)
113 self.code = rhodecode.ConfigGet().get_int('lock_ret_code', missing=self.code)
114
114
115 self.title = self.explanation = message
115 self.title = self.explanation = message
116 super().__init__(*args, **kwargs)
116 super().__init__(*args, **kwargs)
117 self.args = (message, )
117 self.args = (message, )
118
118
119
119
120 class HTTPBranchProtected(HTTPClientError):
120 class HTTPBranchProtected(HTTPClientError):
121 """
121 """
122 Special Exception For Indicating that branch is protected in RhodeCode, the
122 Special Exception For Indicating that branch is protected in RhodeCode, the
123 return code can be overwritten by _code keyword argument passed into constructors
123 return code can be overwritten by _code keyword argument passed into constructors
124 """
124 """
125 code = 403
125 code = 403
126 title = explanation = 'Branch Protected'
126 title = explanation = 'Branch Protected'
127 reason = None
127 reason = None
128
128
129 def __init__(self, message, *args, **kwargs):
129 def __init__(self, message, *args, **kwargs):
130 self.title = self.explanation = message
130 self.title = self.explanation = message
131 super().__init__(*args, **kwargs)
131 super().__init__(*args, **kwargs)
132 self.args = (message, )
132 self.args = (message, )
133
133
134
134
135 class IMCCommitError(Exception):
135 class IMCCommitError(Exception):
136 pass
136 pass
137
137
138
138
139 class UserCreationError(Exception):
139 class UserCreationError(Exception):
140 pass
140 pass
141
141
142
142
143 class NotAllowedToCreateUserError(Exception):
143 class NotAllowedToCreateUserError(Exception):
144 pass
144 pass
145
145
146
146
147 class DuplicateUpdateUserError(Exception):
148 pass
149
150
147 class RepositoryCreationError(Exception):
151 class RepositoryCreationError(Exception):
148 pass
152 pass
149
153
150
154
151 class VCSServerUnavailable(HTTPBadGateway):
155 class VCSServerUnavailable(HTTPBadGateway):
152 """ HTTP Exception class for VCS Server errors """
156 """ HTTP Exception class for VCS Server errors """
153 code = 502
157 code = 502
154 title = 'VCS Server Error'
158 title = 'VCS Server Error'
155 causes = [
159 causes = [
156 'VCS Server is not running',
160 'VCS Server is not running',
157 'Incorrect vcs.server=host:port',
161 'Incorrect vcs.server=host:port',
158 'Incorrect vcs.server.protocol',
162 'Incorrect vcs.server.protocol',
159 ]
163 ]
160
164
161 def __init__(self, message=''):
165 def __init__(self, message=''):
162 self.explanation = 'Could not connect to VCS Server'
166 self.explanation = 'Could not connect to VCS Server'
163 if message:
167 if message:
164 self.explanation += ': ' + message
168 self.explanation += ': ' + message
165 super().__init__()
169 super().__init__()
166
170
167
171
168 class ArtifactMetadataDuplicate(ValueError):
172 class ArtifactMetadataDuplicate(ValueError):
169
173
170 def __init__(self, *args, **kwargs):
174 def __init__(self, *args, **kwargs):
171 self.err_section = kwargs.pop('err_section', None)
175 self.err_section = kwargs.pop('err_section', None)
172 self.err_key = kwargs.pop('err_key', None)
176 self.err_key = kwargs.pop('err_key', None)
173 super().__init__(*args, **kwargs)
177 super().__init__(*args, **kwargs)
174
178
175
179
176 class ArtifactMetadataBadValueType(ValueError):
180 class ArtifactMetadataBadValueType(ValueError):
177 pass
181 pass
178
182
179
183
180 class CommentVersionMismatch(ValueError):
184 class CommentVersionMismatch(ValueError):
181 pass
185 pass
182
186
183
187
184 class SignatureVerificationError(ValueError):
188 class SignatureVerificationError(ValueError):
185 pass
189 pass
186
190
187
191
188 def signature_verification_error(msg):
192 def signature_verification_error(msg):
189 details = """
193 details = """
190 Encryption signature verification failed.
194 Encryption signature verification failed.
191 Please check your value of secret key, and/or encrypted value stored.
195 Please check your value of secret key, and/or encrypted value stored.
192 Secret key stored inside .ini file:
196 Secret key stored inside .ini file:
193 `rhodecode.encrypted_values.secret` or defaults to
197 `rhodecode.encrypted_values.secret` or defaults to
194 `beaker.session.secret`
198 `beaker.session.secret`
195
199
196 Probably the stored values were encrypted using a different secret then currently set in .ini file
200 Probably the stored values were encrypted using a different secret then currently set in .ini file
197 """
201 """
198
202
199 final_msg = f'{msg}\n{details}'
203 final_msg = f'{msg}\n{details}'
200 return SignatureVerificationError(final_msg)
204 return SignatureVerificationError(final_msg)
201
205
@@ -1,1046 +1,1050 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 users model for RhodeCode
20 users model for RhodeCode
21 """
21 """
22
22
23 import logging
23 import logging
24 import traceback
24 import traceback
25 import datetime
25 import datetime
26 import ipaddress
26 import ipaddress
27
27
28 from pyramid.threadlocal import get_current_request
28 from pyramid.threadlocal import get_current_request
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib.user_log_filter import user_log_filter
32 from rhodecode.lib.user_log_filter import user_log_filter
33 from rhodecode.lib.utils2 import (
33 from rhodecode.lib.utils2 import (
34 get_current_rhodecode_user, action_logger_generic,
34 get_current_rhodecode_user, action_logger_generic,
35 AttributeDict, str2bool)
35 AttributeDict, str2bool)
36 from rhodecode.lib.str_utils import safe_str
36 from rhodecode.lib.str_utils import safe_str
37 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
38 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
38 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
39 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
40 UserOwnsPullRequestsException, UserOwnsArtifactsException)
40 UserOwnsPullRequestsException, UserOwnsArtifactsException, DuplicateUpdateUserError)
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.db import (
43 from rhodecode.model.db import (
44 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
44 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
45 UserEmailMap, UserIpMap, UserLog)
45 UserEmailMap, UserIpMap, UserLog)
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class UserModel(BaseModel):
53 class UserModel(BaseModel):
54 cls = User
54 cls = User
55
55
56 def get(self, user_id, cache=False):
56 def get(self, user_id, cache=False):
57 user = self.sa.query(User)
57 user = self.sa.query(User)
58 if cache:
58 if cache:
59 user = user.options(
59 user = user.options(
60 FromCache("sql_cache_short", f"get_user_{user_id}"))
60 FromCache("sql_cache_short", f"get_user_{user_id}"))
61 return user.get(user_id)
61 return user.get(user_id)
62
62
63 def get_user(self, user):
63 def get_user(self, user):
64 return self._get_user(user)
64 return self._get_user(user)
65
65
66 def _serialize_user(self, user):
66 def _serialize_user(self, user):
67 import rhodecode.lib.helpers as h
67 import rhodecode.lib.helpers as h
68
68
69 return {
69 return {
70 'id': user.user_id,
70 'id': user.user_id,
71 'first_name': user.first_name,
71 'first_name': user.first_name,
72 'last_name': user.last_name,
72 'last_name': user.last_name,
73 'username': user.username,
73 'username': user.username,
74 'email': user.email,
74 'email': user.email,
75 'icon_link': h.gravatar_url(user.email, 30),
75 'icon_link': h.gravatar_url(user.email, 30),
76 'profile_link': h.link_to_user(user),
76 'profile_link': h.link_to_user(user),
77 'value_display': h.escape(h.person(user)),
77 'value_display': h.escape(h.person(user)),
78 'value': user.username,
78 'value': user.username,
79 'value_type': 'user',
79 'value_type': 'user',
80 'active': user.active,
80 'active': user.active,
81 }
81 }
82
82
83 def get_users(self, name_contains=None, limit=20, only_active=True):
83 def get_users(self, name_contains=None, limit=20, only_active=True):
84
84
85 query = self.sa.query(User)
85 query = self.sa.query(User)
86 if only_active:
86 if only_active:
87 query = query.filter(User.active == true())
87 query = query.filter(User.active == true())
88
88
89 if name_contains:
89 if name_contains:
90 ilike_expression = f'%{safe_str(name_contains)}%'
90 ilike_expression = f'%{safe_str(name_contains)}%'
91 query = query.filter(
91 query = query.filter(
92 or_(
92 or_(
93 User.name.ilike(ilike_expression),
93 User.name.ilike(ilike_expression),
94 User.lastname.ilike(ilike_expression),
94 User.lastname.ilike(ilike_expression),
95 User.username.ilike(ilike_expression)
95 User.username.ilike(ilike_expression)
96 )
96 )
97 )
97 )
98 # sort by len to have top most matches first
98 # sort by len to have top most matches first
99 query = query.order_by(func.length(User.username))\
99 query = query.order_by(func.length(User.username))\
100 .order_by(User.username)
100 .order_by(User.username)
101 query = query.limit(limit)
101 query = query.limit(limit)
102
102
103 users = query.all()
103 users = query.all()
104
104
105 _users = [
105 _users = [
106 self._serialize_user(user) for user in users
106 self._serialize_user(user) for user in users
107 ]
107 ]
108 return _users
108 return _users
109
109
110 def get_by_username(self, username, cache=False, case_insensitive=False):
110 def get_by_username(self, username, cache=False, case_insensitive=False):
111
111
112 if case_insensitive:
112 if case_insensitive:
113 user = self.sa.query(User).filter(User.username.ilike(username))
113 user = self.sa.query(User).filter(User.username.ilike(username))
114 else:
114 else:
115 user = self.sa.query(User)\
115 user = self.sa.query(User)\
116 .filter(User.username == username)
116 .filter(User.username == username)
117 if cache:
117 if cache:
118 name_key = _hash_key(username)
118 name_key = _hash_key(username)
119 user = user.options(
119 user = user.options(
120 FromCache("sql_cache_short", f"get_user_{name_key}"))
120 FromCache("sql_cache_short", f"get_user_{name_key}"))
121 return user.scalar()
121 return user.scalar()
122
122
123 def get_by_email(self, email, cache=False, case_insensitive=False):
123 def get_by_email(self, email, cache=False, case_insensitive=False):
124 return User.get_by_email(email, case_insensitive, cache)
124 return User.get_by_email(email, case_insensitive, cache)
125
125
126 def get_by_auth_token(self, auth_token, cache=False):
126 def get_by_auth_token(self, auth_token, cache=False):
127 return User.get_by_auth_token(auth_token, cache)
127 return User.get_by_auth_token(auth_token, cache)
128
128
129 def get_active_user_count(self, cache=False):
129 def get_active_user_count(self, cache=False):
130 qry = User.query().filter(
130 qry = User.query().filter(
131 User.active == true()).filter(
131 User.active == true()).filter(
132 User.username != User.DEFAULT_USER)
132 User.username != User.DEFAULT_USER)
133 if cache:
133 if cache:
134 qry = qry.options(
134 qry = qry.options(
135 FromCache("sql_cache_short", "get_active_users"))
135 FromCache("sql_cache_short", "get_active_users"))
136 return qry.count()
136 return qry.count()
137
137
138 def create(self, form_data, cur_user=None):
138 def create(self, form_data, cur_user=None):
139 if not cur_user:
139 if not cur_user:
140 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
140 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
141
141
142 user_data = {
142 user_data = {
143 'username': form_data['username'],
143 'username': form_data['username'],
144 'password': form_data['password'],
144 'password': form_data['password'],
145 'email': form_data['email'],
145 'email': form_data['email'],
146 'firstname': form_data['firstname'],
146 'firstname': form_data['firstname'],
147 'lastname': form_data['lastname'],
147 'lastname': form_data['lastname'],
148 'active': form_data['active'],
148 'active': form_data['active'],
149 'extern_type': form_data['extern_type'],
149 'extern_type': form_data['extern_type'],
150 'extern_name': form_data['extern_name'],
150 'extern_name': form_data['extern_name'],
151 'admin': False,
151 'admin': False,
152 'cur_user': cur_user
152 'cur_user': cur_user
153 }
153 }
154
154
155 if 'create_repo_group' in form_data:
155 if 'create_repo_group' in form_data:
156 user_data['create_repo_group'] = str2bool(
156 user_data['create_repo_group'] = str2bool(
157 form_data.get('create_repo_group'))
157 form_data.get('create_repo_group'))
158
158
159 try:
159 try:
160 if form_data.get('password_change'):
160 if form_data.get('password_change'):
161 user_data['force_password_change'] = True
161 user_data['force_password_change'] = True
162 return UserModel().create_or_update(**user_data)
162 return UserModel().create_or_update(**user_data)
163 except Exception:
163 except Exception:
164 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
165 raise
165 raise
166
166
167 def update_user(self, user, skip_attrs=None, **kwargs):
167 def update_user(self, user, skip_attrs=None, **kwargs):
168 from rhodecode.lib.auth import get_crypt_password
168 from rhodecode.lib.auth import get_crypt_password
169
169
170 user = self._get_user(user)
170 user = self._get_user(user)
171 if user.username == User.DEFAULT_USER:
171 if user.username == User.DEFAULT_USER:
172 raise DefaultUserException(
172 raise DefaultUserException(
173 "You can't edit this user (`%(username)s`) since it's "
173 "You can't edit this user (`%(username)s`) since it's "
174 "crucial for entire application" % {
174 "crucial for entire application" % {
175 'username': user.username})
175 'username': user.username})
176
176
177 # first store only defaults
177 # first store only defaults
178 user_attrs = {
178 user_attrs = {
179 'updating_user_id': user.user_id,
179 'updating_user_id': user.user_id,
180 'username': user.username,
180 'username': user.username,
181 'password': user.password,
181 'password': user.password,
182 'email': user.email,
182 'email': user.email,
183 'firstname': user.name,
183 'firstname': user.name,
184 'lastname': user.lastname,
184 'lastname': user.lastname,
185 'description': user.description,
185 'description': user.description,
186 'active': user.active,
186 'active': user.active,
187 'admin': user.admin,
187 'admin': user.admin,
188 'extern_name': user.extern_name,
188 'extern_name': user.extern_name,
189 'extern_type': user.extern_type,
189 'extern_type': user.extern_type,
190 'language': user.user_data.get('language')
190 'language': user.user_data.get('language')
191 }
191 }
192
192
193 # in case there's new_password, that comes from form, use it to
193 # in case there's new_password, that comes from form, use it to
194 # store password
194 # store password
195 if kwargs.get('new_password'):
195 if kwargs.get('new_password'):
196 kwargs['password'] = kwargs['new_password']
196 kwargs['password'] = kwargs['new_password']
197
197
198 # cleanups, my_account password change form
198 # cleanups, my_account password change form
199 kwargs.pop('current_password', None)
199 kwargs.pop('current_password', None)
200 kwargs.pop('new_password', None)
200 kwargs.pop('new_password', None)
201
201
202 # cleanups, user edit password change form
202 # cleanups, user edit password change form
203 kwargs.pop('password_confirmation', None)
203 kwargs.pop('password_confirmation', None)
204 kwargs.pop('password_change', None)
204 kwargs.pop('password_change', None)
205
205
206 # create repo group on user creation
206 # create repo group on user creation
207 kwargs.pop('create_repo_group', None)
207 kwargs.pop('create_repo_group', None)
208
208
209 # legacy forms send name, which is the firstname
209 # legacy forms send name, which is the firstname
210 firstname = kwargs.pop('name', None)
210 firstname = kwargs.pop('name', None)
211 if firstname:
211 if firstname:
212 kwargs['firstname'] = firstname
212 kwargs['firstname'] = firstname
213
213
214 for k, v in kwargs.items():
214 for k, v in kwargs.items():
215 # skip if we don't want to update this
215 # skip if we don't want to update this
216 if skip_attrs and k in skip_attrs:
216 if skip_attrs and k in skip_attrs:
217 continue
217 continue
218
218
219 user_attrs[k] = v
219 user_attrs[k] = v
220
220
221 try:
221 try:
222 return self.create_or_update(**user_attrs)
222 return self.create_or_update(**user_attrs)
223 except Exception:
223 except Exception:
224 log.error(traceback.format_exc())
224 log.error(traceback.format_exc())
225 raise
225 raise
226
226
227 def create_or_update(
227 def create_or_update(
228 self, username, password, email, firstname='', lastname='',
228 self, username, password, email, firstname='', lastname='',
229 active=True, admin=False, extern_type=None, extern_name=None,
229 active=True, admin=False, extern_type=None, extern_name=None,
230 cur_user=None, plugin=None, force_password_change=False,
230 cur_user=None, plugin=None, force_password_change=False,
231 allow_to_create_user=True, create_repo_group=None,
231 allow_to_create_user=True, create_repo_group=None,
232 updating_user_id=None, language=None, description='',
232 updating_user_id=None, language=None, description='',
233 strict_creation_check=True):
233 strict_creation_check=True):
234 """
234 """
235 Creates a new instance if not found, or updates current one
235 Creates a new instance if not found, or updates current one
236
236
237 :param username:
237 :param username:
238 :param password:
238 :param password:
239 :param email:
239 :param email:
240 :param firstname:
240 :param firstname:
241 :param lastname:
241 :param lastname:
242 :param active:
242 :param active:
243 :param admin:
243 :param admin:
244 :param extern_type:
244 :param extern_type:
245 :param extern_name:
245 :param extern_name:
246 :param cur_user:
246 :param cur_user:
247 :param plugin: optional plugin this method was called from
247 :param plugin: optional plugin this method was called from
248 :param force_password_change: toggles new or existing user flag
248 :param force_password_change: toggles new or existing user flag
249 for password change
249 for password change
250 :param allow_to_create_user: Defines if the method can actually create
250 :param allow_to_create_user: Defines if the method can actually create
251 new users
251 new users
252 :param create_repo_group: Defines if the method should also
252 :param create_repo_group: Defines if the method should also
253 create an repo group with user name, and owner
253 create an repo group with user name, and owner
254 :param updating_user_id: if we set it up this is the user we want to
254 :param updating_user_id: if we set it up this is the user we want to
255 update this allows to editing username.
255 update this allows to editing username.
256 :param language: language of user from interface.
256 :param language: language of user from interface.
257 :param description: user description
257 :param description: user description
258 :param strict_creation_check: checks for allowed creation license wise etc.
258 :param strict_creation_check: checks for allowed creation license wise etc.
259
259
260 :returns: new User object with injected `is_new_user` attribute.
260 :returns: new User object with injected `is_new_user` attribute.
261 """
261 """
262
262
263 if not cur_user:
263 if not cur_user:
264 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
264 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
265
265
266 from rhodecode.lib.auth import (
266 from rhodecode.lib.auth import (
267 get_crypt_password, check_password)
267 get_crypt_password, check_password)
268 from rhodecode.lib import hooks_base
268 from rhodecode.lib import hooks_base
269
269
270 def _password_change(new_user, password):
270 def _password_change(new_user, password):
271 old_password = new_user.password or ''
271 old_password = new_user.password or ''
272 # empty password
272 # empty password
273 if not old_password:
273 if not old_password:
274 return False
274 return False
275
275
276 # password check is only needed for RhodeCode internal auth calls
276 # password check is only needed for RhodeCode internal auth calls
277 # in case it's a plugin we don't care
277 # in case it's a plugin we don't care
278 if not plugin:
278 if not plugin:
279
279
280 # first check if we gave crypted password back, and if it
280 # first check if we gave crypted password back, and if it
281 # matches it's not password change
281 # matches it's not password change
282 if new_user.password == password:
282 if new_user.password == password:
283 return False
283 return False
284
284
285 password_match = check_password(password, old_password)
285 password_match = check_password(password, old_password)
286 if not password_match:
286 if not password_match:
287 return True
287 return True
288
288
289 return False
289 return False
290
290
291 # read settings on default personal repo group creation
291 # read settings on default personal repo group creation
292 if create_repo_group is None:
292 if create_repo_group is None:
293 default_create_repo_group = RepoGroupModel()\
293 default_create_repo_group = RepoGroupModel()\
294 .get_default_create_personal_repo_group()
294 .get_default_create_personal_repo_group()
295 create_repo_group = default_create_repo_group
295 create_repo_group = default_create_repo_group
296
296
297 user_data = {
297 user_data = {
298 'username': username,
298 'username': username,
299 'password': password,
299 'password': password,
300 'email': email,
300 'email': email,
301 'firstname': firstname,
301 'firstname': firstname,
302 'lastname': lastname,
302 'lastname': lastname,
303 'active': active,
303 'active': active,
304 'admin': admin
304 'admin': admin
305 }
305 }
306
306
307 if updating_user_id:
307 if updating_user_id:
308 log.debug('Checking for existing account in RhodeCode '
308 log.debug('Checking for existing account in RhodeCode '
309 'database with user_id `%s` ', updating_user_id)
309 'database with user_id `%s` ', updating_user_id)
310 user = User.get(updating_user_id)
310 user = User.get(updating_user_id)
311 # now also validate if USERNAME belongs to potentially other user
312 maybe_other_user = User.get_by_username(username, case_insensitive=True)
313 if maybe_other_user and maybe_other_user.user_id != updating_user_id:
314 raise DuplicateUpdateUserError(f'different user exists with the {username} username')
311 else:
315 else:
312 log.debug('Checking for existing account in RhodeCode '
316 log.debug('Checking for existing account in RhodeCode '
313 'database with username `%s` ', username)
317 'database with username `%s` ', username)
314 user = User.get_by_username(username, case_insensitive=True)
318 user = User.get_by_username(username, case_insensitive=True)
315
319
316 if user is None:
320 if user is None:
317 # we check internal flag if this method is actually allowed to
321 # we check internal flag if this method is actually allowed to
318 # create new user
322 # create new user
319 if not allow_to_create_user:
323 if not allow_to_create_user:
320 msg = ('Method wants to create new user, but it is not '
324 msg = ('Method wants to create new user, but it is not '
321 'allowed to do so')
325 'allowed to do so')
322 log.warning(msg)
326 log.warning(msg)
323 raise NotAllowedToCreateUserError(msg)
327 raise NotAllowedToCreateUserError(msg)
324
328
325 log.debug('Creating new user %s', username)
329 log.debug('Creating new user %s', username)
326
330
327 # only if we create user that is active
331 # only if we create user that is active
328 new_active_user = active
332 new_active_user = active
329 if new_active_user and strict_creation_check:
333 if new_active_user and strict_creation_check:
330 # raises UserCreationError if it's not allowed for any reason to
334 # raises UserCreationError if it's not allowed for any reason to
331 # create new active user, this also executes pre-create hooks
335 # create new active user, this also executes pre-create hooks
332 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
336 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
333 events.trigger(events.UserPreCreate(user_data))
337 events.trigger(events.UserPreCreate(user_data))
334 new_user = User()
338 new_user = User()
335 edit = False
339 edit = False
336 else:
340 else:
337 log.debug('updating user `%s`', username)
341 log.debug('updating user `%s`', username)
338 events.trigger(events.UserPreUpdate(user, user_data))
342 events.trigger(events.UserPreUpdate(user, user_data))
339 new_user = user
343 new_user = user
340 edit = True
344 edit = True
341
345
342 # we're not allowed to edit default user
346 # we're not allowed to edit default user
343 if user.username == User.DEFAULT_USER:
347 if user.username == User.DEFAULT_USER:
344 raise DefaultUserException(
348 raise DefaultUserException(
345 "You can't edit this user (`%(username)s`) since it's "
349 "You can't edit this user (`%(username)s`) since it's "
346 "crucial for entire application"
350 "crucial for entire application"
347 % {'username': user.username})
351 % {'username': user.username})
348
352
349 # inject special attribute that will tell us if User is new or old
353 # inject special attribute that will tell us if User is new or old
350 new_user.is_new_user = not edit
354 new_user.is_new_user = not edit
351 # for users that didn's specify auth type, we use RhodeCode built in
355 # for users that didn's specify auth type, we use RhodeCode built in
352 from rhodecode.authentication.plugins import auth_rhodecode
356 from rhodecode.authentication.plugins import auth_rhodecode
353 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
357 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
354 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
358 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
355
359
356 try:
360 try:
357 new_user.username = username
361 new_user.username = username
358 new_user.admin = admin
362 new_user.admin = admin
359 new_user.email = email
363 new_user.email = email
360 new_user.active = active
364 new_user.active = active
361 new_user.extern_name = safe_str(extern_name)
365 new_user.extern_name = safe_str(extern_name)
362 new_user.extern_type = safe_str(extern_type)
366 new_user.extern_type = safe_str(extern_type)
363 new_user.name = firstname
367 new_user.name = firstname
364 new_user.lastname = lastname
368 new_user.lastname = lastname
365 new_user.description = description
369 new_user.description = description
366
370
367 # set password only if creating an user or password is changed
371 # set password only if creating an user or password is changed
368 if not edit or _password_change(new_user, password):
372 if not edit or _password_change(new_user, password):
369 reason = 'new password' if edit else 'new user'
373 reason = 'new password' if edit else 'new user'
370 log.debug('Updating password reason=>%s', reason)
374 log.debug('Updating password reason=>%s', reason)
371 new_user.password = get_crypt_password(password) if password else None
375 new_user.password = get_crypt_password(password) if password else None
372
376
373 if force_password_change:
377 if force_password_change:
374 new_user.update_userdata(force_password_change=True)
378 new_user.update_userdata(force_password_change=True)
375 if language:
379 if language:
376 new_user.update_userdata(language=language)
380 new_user.update_userdata(language=language)
377 new_user.update_userdata(notification_status=True)
381 new_user.update_userdata(notification_status=True)
378
382
379 self.sa.add(new_user)
383 self.sa.add(new_user)
380
384
381 if not edit and create_repo_group:
385 if not edit and create_repo_group:
382 RepoGroupModel().create_personal_repo_group(
386 RepoGroupModel().create_personal_repo_group(
383 new_user, commit_early=False)
387 new_user, commit_early=False)
384
388
385 if not edit:
389 if not edit:
386 # add the RSS token
390 # add the RSS token
387 self.add_auth_token(
391 self.add_auth_token(
388 user=username, lifetime_minutes=-1,
392 user=username, lifetime_minutes=-1,
389 role=self.auth_token_role.ROLE_FEED,
393 role=self.auth_token_role.ROLE_FEED,
390 description='Generated feed token')
394 description='Generated feed token')
391
395
392 kwargs = new_user.get_dict()
396 kwargs = new_user.get_dict()
393 # backward compat, require api_keys present
397 # backward compat, require api_keys present
394 kwargs['api_keys'] = kwargs['auth_tokens']
398 kwargs['api_keys'] = kwargs['auth_tokens']
395 hooks_base.create_user(created_by=cur_user, **kwargs)
399 hooks_base.create_user(created_by=cur_user, **kwargs)
396 events.trigger(events.UserPostCreate(user_data))
400 events.trigger(events.UserPostCreate(user_data))
397 return new_user
401 return new_user
398 except (DatabaseError,):
402 except (DatabaseError,):
399 log.error(traceback.format_exc())
403 log.error(traceback.format_exc())
400 raise
404 raise
401
405
402 def create_registration(self, form_data,
406 def create_registration(self, form_data,
403 extern_name='rhodecode', extern_type='rhodecode'):
407 extern_name='rhodecode', extern_type='rhodecode'):
404 from rhodecode.model.notification import NotificationModel
408 from rhodecode.model.notification import NotificationModel
405 from rhodecode.model.notification import EmailNotificationModel
409 from rhodecode.model.notification import EmailNotificationModel
406
410
407 try:
411 try:
408 form_data['admin'] = False
412 form_data['admin'] = False
409 form_data['extern_name'] = extern_name
413 form_data['extern_name'] = extern_name
410 form_data['extern_type'] = extern_type
414 form_data['extern_type'] = extern_type
411 new_user = self.create(form_data)
415 new_user = self.create(form_data)
412
416
413 self.sa.add(new_user)
417 self.sa.add(new_user)
414 self.sa.flush()
418 self.sa.flush()
415
419
416 user_data = new_user.get_dict()
420 user_data = new_user.get_dict()
417 user_data.update({
421 user_data.update({
418 'first_name': user_data.get('firstname'),
422 'first_name': user_data.get('firstname'),
419 'last_name': user_data.get('lastname'),
423 'last_name': user_data.get('lastname'),
420 })
424 })
421 kwargs = {
425 kwargs = {
422 # use SQLALCHEMY safe dump of user data
426 # use SQLALCHEMY safe dump of user data
423 'user': AttributeDict(user_data),
427 'user': AttributeDict(user_data),
424 'date': datetime.datetime.now()
428 'date': datetime.datetime.now()
425 }
429 }
426 notification_type = EmailNotificationModel.TYPE_REGISTRATION
430 notification_type = EmailNotificationModel.TYPE_REGISTRATION
427
431
428 # create notification objects, and emails
432 # create notification objects, and emails
429 NotificationModel().create(
433 NotificationModel().create(
430 created_by=new_user,
434 created_by=new_user,
431 notification_subject='', # Filled in based on the notification_type
435 notification_subject='', # Filled in based on the notification_type
432 notification_body='', # Filled in based on the notification_type
436 notification_body='', # Filled in based on the notification_type
433 notification_type=notification_type,
437 notification_type=notification_type,
434 recipients=None, # all admins
438 recipients=None, # all admins
435 email_kwargs=kwargs,
439 email_kwargs=kwargs,
436 )
440 )
437
441
438 return new_user
442 return new_user
439 except Exception:
443 except Exception:
440 log.error(traceback.format_exc())
444 log.error(traceback.format_exc())
441 raise
445 raise
442
446
443 def _handle_user_repos(self, username, repositories, handle_user,
447 def _handle_user_repos(self, username, repositories, handle_user,
444 handle_mode=None):
448 handle_mode=None):
445
449
446 left_overs = True
450 left_overs = True
447
451
448 from rhodecode.model.repo import RepoModel
452 from rhodecode.model.repo import RepoModel
449
453
450 if handle_mode == 'detach':
454 if handle_mode == 'detach':
451 for obj in repositories:
455 for obj in repositories:
452 obj.user = handle_user
456 obj.user = handle_user
453 # set description we know why we super admin now owns
457 # set description we know why we super admin now owns
454 # additional repositories that were orphaned !
458 # additional repositories that were orphaned !
455 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
459 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
456 self.sa.add(obj)
460 self.sa.add(obj)
457 left_overs = False
461 left_overs = False
458 elif handle_mode == 'delete':
462 elif handle_mode == 'delete':
459 for obj in repositories:
463 for obj in repositories:
460 RepoModel().delete(obj, forks='detach')
464 RepoModel().delete(obj, forks='detach')
461 left_overs = False
465 left_overs = False
462
466
463 # if nothing is done we have left overs left
467 # if nothing is done we have left overs left
464 return left_overs
468 return left_overs
465
469
466 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
470 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
467 handle_mode=None):
471 handle_mode=None):
468
472
469 left_overs = True
473 left_overs = True
470
474
471 from rhodecode.model.repo_group import RepoGroupModel
475 from rhodecode.model.repo_group import RepoGroupModel
472
476
473 if handle_mode == 'detach':
477 if handle_mode == 'detach':
474 for r in repository_groups:
478 for r in repository_groups:
475 r.user = handle_user
479 r.user = handle_user
476 # set description we know why we super admin now owns
480 # set description we know why we super admin now owns
477 # additional repositories that were orphaned !
481 # additional repositories that were orphaned !
478 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
482 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
479 r.personal = False
483 r.personal = False
480 self.sa.add(r)
484 self.sa.add(r)
481 left_overs = False
485 left_overs = False
482 elif handle_mode == 'delete':
486 elif handle_mode == 'delete':
483 for r in repository_groups:
487 for r in repository_groups:
484 RepoGroupModel().delete(r)
488 RepoGroupModel().delete(r)
485 left_overs = False
489 left_overs = False
486
490
487 # if nothing is done we have left overs left
491 # if nothing is done we have left overs left
488 return left_overs
492 return left_overs
489
493
490 def _handle_user_user_groups(self, username, user_groups, handle_user,
494 def _handle_user_user_groups(self, username, user_groups, handle_user,
491 handle_mode=None):
495 handle_mode=None):
492
496
493 left_overs = True
497 left_overs = True
494
498
495 from rhodecode.model.user_group import UserGroupModel
499 from rhodecode.model.user_group import UserGroupModel
496
500
497 if handle_mode == 'detach':
501 if handle_mode == 'detach':
498 for r in user_groups:
502 for r in user_groups:
499 for user_user_group_to_perm in r.user_user_group_to_perm:
503 for user_user_group_to_perm in r.user_user_group_to_perm:
500 if user_user_group_to_perm.user.username == username:
504 if user_user_group_to_perm.user.username == username:
501 user_user_group_to_perm.user = handle_user
505 user_user_group_to_perm.user = handle_user
502 r.user = handle_user
506 r.user = handle_user
503 # set description we know why we super admin now owns
507 # set description we know why we super admin now owns
504 # additional repositories that were orphaned !
508 # additional repositories that were orphaned !
505 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
509 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
506 self.sa.add(r)
510 self.sa.add(r)
507 left_overs = False
511 left_overs = False
508 elif handle_mode == 'delete':
512 elif handle_mode == 'delete':
509 for r in user_groups:
513 for r in user_groups:
510 UserGroupModel().delete(r)
514 UserGroupModel().delete(r)
511 left_overs = False
515 left_overs = False
512
516
513 # if nothing is done we have left overs left
517 # if nothing is done we have left overs left
514 return left_overs
518 return left_overs
515
519
516 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
520 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
517 handle_mode=None):
521 handle_mode=None):
518 left_overs = True
522 left_overs = True
519
523
520 from rhodecode.model.pull_request import PullRequestModel
524 from rhodecode.model.pull_request import PullRequestModel
521
525
522 if handle_mode == 'detach':
526 if handle_mode == 'detach':
523 for pr in pull_requests:
527 for pr in pull_requests:
524 pr.user_id = handle_user.user_id
528 pr.user_id = handle_user.user_id
525 # set description we know why we super admin now owns
529 # set description we know why we super admin now owns
526 # additional repositories that were orphaned !
530 # additional repositories that were orphaned !
527 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
531 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
528 self.sa.add(pr)
532 self.sa.add(pr)
529 left_overs = False
533 left_overs = False
530 elif handle_mode == 'delete':
534 elif handle_mode == 'delete':
531 for pr in pull_requests:
535 for pr in pull_requests:
532 PullRequestModel().delete(pr)
536 PullRequestModel().delete(pr)
533
537
534 left_overs = False
538 left_overs = False
535
539
536 # if nothing is done we have leftovers left
540 # if nothing is done we have leftovers left
537 return left_overs
541 return left_overs
538
542
539 def _handle_user_artifacts(self, username, artifacts, handle_user,
543 def _handle_user_artifacts(self, username, artifacts, handle_user,
540 handle_mode=None):
544 handle_mode=None):
541
545
542 left_overs = True
546 left_overs = True
543
547
544 if handle_mode == 'detach':
548 if handle_mode == 'detach':
545 for a in artifacts:
549 for a in artifacts:
546 a.upload_user = handle_user
550 a.upload_user = handle_user
547 # set description we know why we super admin now owns
551 # set description we know why we super admin now owns
548 # additional artifacts that were orphaned !
552 # additional artifacts that were orphaned !
549 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
553 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
550 self.sa.add(a)
554 self.sa.add(a)
551 left_overs = False
555 left_overs = False
552 elif handle_mode == 'delete':
556 elif handle_mode == 'delete':
553 from rhodecode.apps.file_store import utils as store_utils
557 from rhodecode.apps.file_store import utils as store_utils
554 request = get_current_request()
558 request = get_current_request()
555 storage = store_utils.get_file_storage(request.registry.settings)
559 storage = store_utils.get_file_storage(request.registry.settings)
556 for a in artifacts:
560 for a in artifacts:
557 file_uid = a.file_uid
561 file_uid = a.file_uid
558 storage.delete(file_uid)
562 storage.delete(file_uid)
559 self.sa.delete(a)
563 self.sa.delete(a)
560
564
561 left_overs = False
565 left_overs = False
562
566
563 # if nothing is done we have left overs left
567 # if nothing is done we have left overs left
564 return left_overs
568 return left_overs
565
569
566 def delete(self, user, cur_user=None, handle_repos=None,
570 def delete(self, user, cur_user=None, handle_repos=None,
567 handle_repo_groups=None, handle_user_groups=None,
571 handle_repo_groups=None, handle_user_groups=None,
568 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
572 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
569 from rhodecode.lib import hooks_base
573 from rhodecode.lib import hooks_base
570
574
571 if not cur_user:
575 if not cur_user:
572 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
576 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
573
577
574 user = self._get_user(user)
578 user = self._get_user(user)
575
579
576 try:
580 try:
577 if user.username == User.DEFAULT_USER:
581 if user.username == User.DEFAULT_USER:
578 raise DefaultUserException(
582 raise DefaultUserException(
579 "You can't remove this user since it's"
583 "You can't remove this user since it's"
580 " crucial for entire application")
584 " crucial for entire application")
581 handle_user = handle_new_owner or self.cls.get_first_super_admin()
585 handle_user = handle_new_owner or self.cls.get_first_super_admin()
582 log.debug('New detached objects owner %s', handle_user)
586 log.debug('New detached objects owner %s', handle_user)
583
587
584 left_overs = self._handle_user_repos(
588 left_overs = self._handle_user_repos(
585 user.username, user.repositories, handle_user, handle_repos)
589 user.username, user.repositories, handle_user, handle_repos)
586 if left_overs and user.repositories:
590 if left_overs and user.repositories:
587 repos = [x.repo_name for x in user.repositories]
591 repos = [x.repo_name for x in user.repositories]
588 raise UserOwnsReposException(
592 raise UserOwnsReposException(
589 'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
593 'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
590 'removed. Switch owners or remove those repositories:%(list_repos)s'
594 'removed. Switch owners or remove those repositories:%(list_repos)s'
591 % {'username': user.username, 'len_repos': len(repos),
595 % {'username': user.username, 'len_repos': len(repos),
592 'list_repos': ', '.join(repos)})
596 'list_repos': ', '.join(repos)})
593
597
594 left_overs = self._handle_user_repo_groups(
598 left_overs = self._handle_user_repo_groups(
595 user.username, user.repository_groups, handle_user, handle_repo_groups)
599 user.username, user.repository_groups, handle_user, handle_repo_groups)
596 if left_overs and user.repository_groups:
600 if left_overs and user.repository_groups:
597 repo_groups = [x.group_name for x in user.repository_groups]
601 repo_groups = [x.group_name for x in user.repository_groups]
598 raise UserOwnsRepoGroupsException(
602 raise UserOwnsRepoGroupsException(
599 'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
603 'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
600 'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
604 'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
601 % {'username': user.username, 'len_repo_groups': len(repo_groups),
605 % {'username': user.username, 'len_repo_groups': len(repo_groups),
602 'list_repo_groups': ', '.join(repo_groups)})
606 'list_repo_groups': ', '.join(repo_groups)})
603
607
604 left_overs = self._handle_user_user_groups(
608 left_overs = self._handle_user_user_groups(
605 user.username, user.user_groups, handle_user, handle_user_groups)
609 user.username, user.user_groups, handle_user, handle_user_groups)
606 if left_overs and user.user_groups:
610 if left_overs and user.user_groups:
607 user_groups = [x.users_group_name for x in user.user_groups]
611 user_groups = [x.users_group_name for x in user.user_groups]
608 raise UserOwnsUserGroupsException(
612 raise UserOwnsUserGroupsException(
609 'user "%s" still owns %s user groups and cannot be '
613 'user "%s" still owns %s user groups and cannot be '
610 'removed. Switch owners or remove those user groups:%s'
614 'removed. Switch owners or remove those user groups:%s'
611 % (user.username, len(user_groups), ', '.join(user_groups)))
615 % (user.username, len(user_groups), ', '.join(user_groups)))
612
616
613 left_overs = self._handle_user_pull_requests(
617 left_overs = self._handle_user_pull_requests(
614 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
618 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
615 if left_overs and user.user_pull_requests:
619 if left_overs and user.user_pull_requests:
616 pull_requests = [f'!{x.pull_request_id}' for x in user.user_pull_requests]
620 pull_requests = [f'!{x.pull_request_id}' for x in user.user_pull_requests]
617 raise UserOwnsPullRequestsException(
621 raise UserOwnsPullRequestsException(
618 'user "%s" still owns %s pull requests and cannot be '
622 'user "%s" still owns %s pull requests and cannot be '
619 'removed. Switch owners or remove those pull requests:%s'
623 'removed. Switch owners or remove those pull requests:%s'
620 % (user.username, len(pull_requests), ', '.join(pull_requests)))
624 % (user.username, len(pull_requests), ', '.join(pull_requests)))
621
625
622 left_overs = self._handle_user_artifacts(
626 left_overs = self._handle_user_artifacts(
623 user.username, user.artifacts, handle_user, handle_artifacts)
627 user.username, user.artifacts, handle_user, handle_artifacts)
624 if left_overs and user.artifacts:
628 if left_overs and user.artifacts:
625 artifacts = [x.file_uid for x in user.artifacts]
629 artifacts = [x.file_uid for x in user.artifacts]
626 raise UserOwnsArtifactsException(
630 raise UserOwnsArtifactsException(
627 'user "%s" still owns %s artifacts and cannot be '
631 'user "%s" still owns %s artifacts and cannot be '
628 'removed. Switch owners or remove those artifacts:%s'
632 'removed. Switch owners or remove those artifacts:%s'
629 % (user.username, len(artifacts), ', '.join(artifacts)))
633 % (user.username, len(artifacts), ', '.join(artifacts)))
630
634
631 user_data = user.get_dict() # fetch user data before expire
635 user_data = user.get_dict() # fetch user data before expire
632
636
633 # we might change the user data with detach/delete, make sure
637 # we might change the user data with detach/delete, make sure
634 # the object is marked as expired before actually deleting !
638 # the object is marked as expired before actually deleting !
635 self.sa.expire(user)
639 self.sa.expire(user)
636 self.sa.delete(user)
640 self.sa.delete(user)
637
641
638 hooks_base.delete_user(deleted_by=cur_user, **user_data)
642 hooks_base.delete_user(deleted_by=cur_user, **user_data)
639 except Exception:
643 except Exception:
640 log.error(traceback.format_exc())
644 log.error(traceback.format_exc())
641 raise
645 raise
642
646
643 def reset_password_link(self, data, pwd_reset_url):
647 def reset_password_link(self, data, pwd_reset_url):
644 from rhodecode.lib.celerylib import tasks, run_task
648 from rhodecode.lib.celerylib import tasks, run_task
645 from rhodecode.model.notification import EmailNotificationModel
649 from rhodecode.model.notification import EmailNotificationModel
646 user_email = data['email']
650 user_email = data['email']
647 try:
651 try:
648 user = User.get_by_email(user_email)
652 user = User.get_by_email(user_email)
649 if user:
653 if user:
650 log.debug('password reset user found %s', user)
654 log.debug('password reset user found %s', user)
651
655
652 email_kwargs = {
656 email_kwargs = {
653 'password_reset_url': pwd_reset_url,
657 'password_reset_url': pwd_reset_url,
654 'user': user,
658 'user': user,
655 'email': user_email,
659 'email': user_email,
656 'date': datetime.datetime.now(),
660 'date': datetime.datetime.now(),
657 'first_admin_email': User.get_first_super_admin().email
661 'first_admin_email': User.get_first_super_admin().email
658 }
662 }
659
663
660 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
664 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
661 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
665 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
662
666
663 recipients = [user_email]
667 recipients = [user_email]
664
668
665 action_logger_generic(
669 action_logger_generic(
666 'sending password reset email to user: {}'.format(
670 'sending password reset email to user: {}'.format(
667 user), namespace='security.password_reset')
671 user), namespace='security.password_reset')
668
672
669 run_task(tasks.send_email, recipients, subject,
673 run_task(tasks.send_email, recipients, subject,
670 email_body_plaintext, email_body)
674 email_body_plaintext, email_body)
671
675
672 else:
676 else:
673 log.debug("password reset email %s not found", user_email)
677 log.debug("password reset email %s not found", user_email)
674 except Exception:
678 except Exception:
675 log.error(traceback.format_exc())
679 log.error(traceback.format_exc())
676 return False
680 return False
677
681
678 return True
682 return True
679
683
680 def reset_password(self, data):
684 def reset_password(self, data):
681 from rhodecode.lib.celerylib import tasks, run_task
685 from rhodecode.lib.celerylib import tasks, run_task
682 from rhodecode.model.notification import EmailNotificationModel
686 from rhodecode.model.notification import EmailNotificationModel
683 from rhodecode.lib import auth
687 from rhodecode.lib import auth
684 user_email = data['email']
688 user_email = data['email']
685 pre_db = True
689 pre_db = True
686 try:
690 try:
687 user = User.get_by_email(user_email)
691 user = User.get_by_email(user_email)
688 new_passwd = auth.PasswordGenerator().gen_password(
692 new_passwd = auth.PasswordGenerator().gen_password(
689 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
693 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
690 if user:
694 if user:
691 user.password = auth.get_crypt_password(new_passwd)
695 user.password = auth.get_crypt_password(new_passwd)
692 # also force this user to reset his password !
696 # also force this user to reset his password !
693 user.update_userdata(force_password_change=True)
697 user.update_userdata(force_password_change=True)
694
698
695 Session().add(user)
699 Session().add(user)
696
700
697 # now delete the token in question
701 # now delete the token in question
698 UserApiKeys = AuthTokenModel.cls
702 UserApiKeys = AuthTokenModel.cls
699 UserApiKeys().query().filter(
703 UserApiKeys().query().filter(
700 UserApiKeys.api_key == data['token']).delete()
704 UserApiKeys.api_key == data['token']).delete()
701
705
702 Session().commit()
706 Session().commit()
703 log.info('successfully reset password for `%s`', user_email)
707 log.info('successfully reset password for `%s`', user_email)
704
708
705 if new_passwd is None:
709 if new_passwd is None:
706 raise Exception('unable to generate new password')
710 raise Exception('unable to generate new password')
707
711
708 pre_db = False
712 pre_db = False
709
713
710 email_kwargs = {
714 email_kwargs = {
711 'new_password': new_passwd,
715 'new_password': new_passwd,
712 'user': user,
716 'user': user,
713 'email': user_email,
717 'email': user_email,
714 'date': datetime.datetime.now(),
718 'date': datetime.datetime.now(),
715 'first_admin_email': User.get_first_super_admin().email
719 'first_admin_email': User.get_first_super_admin().email
716 }
720 }
717
721
718 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
722 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
719 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
723 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
720 **email_kwargs)
724 **email_kwargs)
721
725
722 recipients = [user_email]
726 recipients = [user_email]
723
727
724 action_logger_generic(
728 action_logger_generic(
725 'sent new password to user: {} with email: {}'.format(
729 'sent new password to user: {} with email: {}'.format(
726 user, user_email), namespace='security.password_reset')
730 user, user_email), namespace='security.password_reset')
727
731
728 run_task(tasks.send_email, recipients, subject,
732 run_task(tasks.send_email, recipients, subject,
729 email_body_plaintext, email_body)
733 email_body_plaintext, email_body)
730
734
731 except Exception:
735 except Exception:
732 log.error('Failed to update user password')
736 log.error('Failed to update user password')
733 log.error(traceback.format_exc())
737 log.error(traceback.format_exc())
734 if pre_db:
738 if pre_db:
735 # we rollback only if local db stuff fails. If it goes into
739 # we rollback only if local db stuff fails. If it goes into
736 # run_task, we're pass rollback state this wouldn't work then
740 # run_task, we're pass rollback state this wouldn't work then
737 Session().rollback()
741 Session().rollback()
738
742
739 return True
743 return True
740
744
741 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
745 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
742 """
746 """
743 Fetches auth_user by user_id,or api_key if present.
747 Fetches auth_user by user_id,or api_key if present.
744 Fills auth_user attributes with those taken from database.
748 Fills auth_user attributes with those taken from database.
745 Additionally set's is_authenitated if lookup fails
749 Additionally set's is_authenitated if lookup fails
746 present in database
750 present in database
747
751
748 :param auth_user: instance of user to set attributes
752 :param auth_user: instance of user to set attributes
749 :param user_id: user id to fetch by
753 :param user_id: user id to fetch by
750 :param api_key: api key to fetch by
754 :param api_key: api key to fetch by
751 :param username: username to fetch by
755 :param username: username to fetch by
752 """
756 """
753 def token_obfuscate(token):
757 def token_obfuscate(token):
754 if token:
758 if token:
755 return token[:4] + "****"
759 return token[:4] + "****"
756
760
757 if user_id is None and api_key is None and username is None:
761 if user_id is None and api_key is None and username is None:
758 raise Exception('You need to pass user_id, api_key or username')
762 raise Exception('You need to pass user_id, api_key or username')
759
763
760 log.debug(
764 log.debug(
761 'AuthUser: fill data execution based on: '
765 'AuthUser: fill data execution based on: '
762 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
766 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
763 try:
767 try:
764 dbuser = None
768 dbuser = None
765 if user_id:
769 if user_id:
766 dbuser = self.get(user_id)
770 dbuser = self.get(user_id)
767 elif api_key:
771 elif api_key:
768 dbuser = self.get_by_auth_token(api_key)
772 dbuser = self.get_by_auth_token(api_key)
769 elif username:
773 elif username:
770 dbuser = self.get_by_username(username)
774 dbuser = self.get_by_username(username)
771
775
772 if not dbuser:
776 if not dbuser:
773 log.warning(
777 log.warning(
774 'Unable to lookup user by id:%s api_key:%s username:%s',
778 'Unable to lookup user by id:%s api_key:%s username:%s',
775 user_id, token_obfuscate(api_key), username)
779 user_id, token_obfuscate(api_key), username)
776 return False
780 return False
777 if not dbuser.active:
781 if not dbuser.active:
778 log.debug('User `%s:%s` is inactive, skipping fill data',
782 log.debug('User `%s:%s` is inactive, skipping fill data',
779 username, user_id)
783 username, user_id)
780 return False
784 return False
781
785
782 log.debug('AuthUser: filling found user:%s data', dbuser)
786 log.debug('AuthUser: filling found user:%s data', dbuser)
783
787
784 attrs = {
788 attrs = {
785 'user_id': dbuser.user_id,
789 'user_id': dbuser.user_id,
786 'username': dbuser.username,
790 'username': dbuser.username,
787 'name': dbuser.name,
791 'name': dbuser.name,
788 'first_name': dbuser.first_name,
792 'first_name': dbuser.first_name,
789 'firstname': dbuser.firstname,
793 'firstname': dbuser.firstname,
790 'last_name': dbuser.last_name,
794 'last_name': dbuser.last_name,
791 'lastname': dbuser.lastname,
795 'lastname': dbuser.lastname,
792 'admin': dbuser.admin,
796 'admin': dbuser.admin,
793 'active': dbuser.active,
797 'active': dbuser.active,
794
798
795 'email': dbuser.email,
799 'email': dbuser.email,
796 'emails': dbuser.emails_cached(),
800 'emails': dbuser.emails_cached(),
797 'short_contact': dbuser.short_contact,
801 'short_contact': dbuser.short_contact,
798 'full_contact': dbuser.full_contact,
802 'full_contact': dbuser.full_contact,
799 'full_name': dbuser.full_name,
803 'full_name': dbuser.full_name,
800 'full_name_or_username': dbuser.full_name_or_username,
804 'full_name_or_username': dbuser.full_name_or_username,
801
805
802 '_api_key': dbuser._api_key,
806 '_api_key': dbuser._api_key,
803 '_user_data': dbuser._user_data,
807 '_user_data': dbuser._user_data,
804
808
805 'created_on': dbuser.created_on,
809 'created_on': dbuser.created_on,
806 'extern_name': dbuser.extern_name,
810 'extern_name': dbuser.extern_name,
807 'extern_type': dbuser.extern_type,
811 'extern_type': dbuser.extern_type,
808
812
809 'inherit_default_permissions': dbuser.inherit_default_permissions,
813 'inherit_default_permissions': dbuser.inherit_default_permissions,
810
814
811 'language': dbuser.language,
815 'language': dbuser.language,
812 'last_activity': dbuser.last_activity,
816 'last_activity': dbuser.last_activity,
813 'last_login': dbuser.last_login,
817 'last_login': dbuser.last_login,
814 'password': dbuser.password,
818 'password': dbuser.password,
815 }
819 }
816 auth_user.__dict__.update(attrs)
820 auth_user.__dict__.update(attrs)
817 except Exception:
821 except Exception:
818 log.error(traceback.format_exc())
822 log.error(traceback.format_exc())
819 auth_user.is_authenticated = False
823 auth_user.is_authenticated = False
820 return False
824 return False
821
825
822 return True
826 return True
823
827
824 def has_perm(self, user, perm):
828 def has_perm(self, user, perm):
825 perm = self._get_perm(perm)
829 perm = self._get_perm(perm)
826 user = self._get_user(user)
830 user = self._get_user(user)
827
831
828 return UserToPerm.query().filter(UserToPerm.user == user)\
832 return UserToPerm.query().filter(UserToPerm.user == user)\
829 .filter(UserToPerm.permission == perm).scalar() is not None
833 .filter(UserToPerm.permission == perm).scalar() is not None
830
834
831 def grant_perm(self, user, perm):
835 def grant_perm(self, user, perm):
832 """
836 """
833 Grant user global permissions
837 Grant user global permissions
834
838
835 :param user:
839 :param user:
836 :param perm:
840 :param perm:
837 """
841 """
838 user = self._get_user(user)
842 user = self._get_user(user)
839 perm = self._get_perm(perm)
843 perm = self._get_perm(perm)
840 # if this permission is already granted skip it
844 # if this permission is already granted skip it
841 _perm = UserToPerm.query()\
845 _perm = UserToPerm.query()\
842 .filter(UserToPerm.user == user)\
846 .filter(UserToPerm.user == user)\
843 .filter(UserToPerm.permission == perm)\
847 .filter(UserToPerm.permission == perm)\
844 .scalar()
848 .scalar()
845 if _perm:
849 if _perm:
846 return
850 return
847 new = UserToPerm()
851 new = UserToPerm()
848 new.user = user
852 new.user = user
849 new.permission = perm
853 new.permission = perm
850 self.sa.add(new)
854 self.sa.add(new)
851 return new
855 return new
852
856
853 def revoke_perm(self, user, perm):
857 def revoke_perm(self, user, perm):
854 """
858 """
855 Revoke users global permissions
859 Revoke users global permissions
856
860
857 :param user:
861 :param user:
858 :param perm:
862 :param perm:
859 """
863 """
860 user = self._get_user(user)
864 user = self._get_user(user)
861 perm = self._get_perm(perm)
865 perm = self._get_perm(perm)
862
866
863 obj = UserToPerm.query()\
867 obj = UserToPerm.query()\
864 .filter(UserToPerm.user == user)\
868 .filter(UserToPerm.user == user)\
865 .filter(UserToPerm.permission == perm)\
869 .filter(UserToPerm.permission == perm)\
866 .scalar()
870 .scalar()
867 if obj:
871 if obj:
868 self.sa.delete(obj)
872 self.sa.delete(obj)
869
873
870 def add_extra_email(self, user, email):
874 def add_extra_email(self, user, email):
871 """
875 """
872 Adds email address to UserEmailMap
876 Adds email address to UserEmailMap
873
877
874 :param user:
878 :param user:
875 :param email:
879 :param email:
876 """
880 """
877
881
878 user = self._get_user(user)
882 user = self._get_user(user)
879
883
880 obj = UserEmailMap()
884 obj = UserEmailMap()
881 obj.user = user
885 obj.user = user
882 obj.email = email
886 obj.email = email
883 self.sa.add(obj)
887 self.sa.add(obj)
884 return obj
888 return obj
885
889
886 def delete_extra_email(self, user, email_id):
890 def delete_extra_email(self, user, email_id):
887 """
891 """
888 Removes email address from UserEmailMap
892 Removes email address from UserEmailMap
889
893
890 :param user:
894 :param user:
891 :param email_id:
895 :param email_id:
892 """
896 """
893 user = self._get_user(user)
897 user = self._get_user(user)
894 obj = UserEmailMap.query().get(email_id)
898 obj = UserEmailMap.query().get(email_id)
895 if obj and obj.user_id == user.user_id:
899 if obj and obj.user_id == user.user_id:
896 self.sa.delete(obj)
900 self.sa.delete(obj)
897
901
898 def parse_ip_range(self, ip_range):
902 def parse_ip_range(self, ip_range):
899 ip_list = []
903 ip_list = []
900
904
901 def make_unique(value):
905 def make_unique(value):
902 seen = []
906 seen = []
903 return [c for c in value if not (c in seen or seen.append(c))]
907 return [c for c in value if not (c in seen or seen.append(c))]
904
908
905 # firsts split by commas
909 # firsts split by commas
906 for ip_range in ip_range.split(','):
910 for ip_range in ip_range.split(','):
907 if not ip_range:
911 if not ip_range:
908 continue
912 continue
909 ip_range = ip_range.strip()
913 ip_range = ip_range.strip()
910 if '-' in ip_range:
914 if '-' in ip_range:
911 start_ip, end_ip = ip_range.split('-', 1)
915 start_ip, end_ip = ip_range.split('-', 1)
912 start_ip = ipaddress.ip_address(safe_str(start_ip.strip()))
916 start_ip = ipaddress.ip_address(safe_str(start_ip.strip()))
913 end_ip = ipaddress.ip_address(safe_str(end_ip.strip()))
917 end_ip = ipaddress.ip_address(safe_str(end_ip.strip()))
914 parsed_ip_range = []
918 parsed_ip_range = []
915
919
916 for index in range(int(start_ip), int(end_ip) + 1):
920 for index in range(int(start_ip), int(end_ip) + 1):
917 new_ip = ipaddress.ip_address(index)
921 new_ip = ipaddress.ip_address(index)
918 parsed_ip_range.append(str(new_ip))
922 parsed_ip_range.append(str(new_ip))
919 ip_list.extend(parsed_ip_range)
923 ip_list.extend(parsed_ip_range)
920 else:
924 else:
921 ip_list.append(ip_range)
925 ip_list.append(ip_range)
922
926
923 return make_unique(ip_list)
927 return make_unique(ip_list)
924
928
925 def add_extra_ip(self, user, ip, description=None):
929 def add_extra_ip(self, user, ip, description=None):
926 """
930 """
927 Adds ip address to UserIpMap
931 Adds ip address to UserIpMap
928
932
929 :param user:
933 :param user:
930 :param ip:
934 :param ip:
931 """
935 """
932
936
933 user = self._get_user(user)
937 user = self._get_user(user)
934 obj = UserIpMap()
938 obj = UserIpMap()
935 obj.user = user
939 obj.user = user
936 obj.ip_addr = ip
940 obj.ip_addr = ip
937 obj.description = description
941 obj.description = description
938 self.sa.add(obj)
942 self.sa.add(obj)
939 return obj
943 return obj
940
944
941 auth_token_role = AuthTokenModel.cls
945 auth_token_role = AuthTokenModel.cls
942
946
943 def add_auth_token(self, user, lifetime_minutes, role, description='',
947 def add_auth_token(self, user, lifetime_minutes, role, description='',
944 scope_callback=None):
948 scope_callback=None):
945 """
949 """
946 Add AuthToken for user.
950 Add AuthToken for user.
947
951
948 :param user: username/user_id
952 :param user: username/user_id
949 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
953 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
950 :param role: one of AuthTokenModel.cls.ROLE_*
954 :param role: one of AuthTokenModel.cls.ROLE_*
951 :param description: optional string description
955 :param description: optional string description
952 """
956 """
953
957
954 token = AuthTokenModel().create(
958 token = AuthTokenModel().create(
955 user, description, lifetime_minutes, role)
959 user, description, lifetime_minutes, role)
956 if scope_callback and callable(scope_callback):
960 if scope_callback and callable(scope_callback):
957 # call the callback if we provide, used to attach scope for EE edition
961 # call the callback if we provide, used to attach scope for EE edition
958 scope_callback(token)
962 scope_callback(token)
959 return token
963 return token
960
964
961 def delete_extra_ip(self, user, ip_id):
965 def delete_extra_ip(self, user, ip_id):
962 """
966 """
963 Removes ip address from UserIpMap
967 Removes ip address from UserIpMap
964
968
965 :param user:
969 :param user:
966 :param ip_id:
970 :param ip_id:
967 """
971 """
968 user = self._get_user(user)
972 user = self._get_user(user)
969 obj = UserIpMap.query().get(ip_id)
973 obj = UserIpMap.query().get(ip_id)
970 if obj and obj.user_id == user.user_id:
974 if obj and obj.user_id == user.user_id:
971 self.sa.delete(obj)
975 self.sa.delete(obj)
972
976
973 def get_accounts_in_creation_order(self, current_user=None):
977 def get_accounts_in_creation_order(self, current_user=None):
974 """
978 """
975 Get accounts in order of creation for deactivation for license limits
979 Get accounts in order of creation for deactivation for license limits
976
980
977 pick currently logged in user, and append to the list in position 0
981 pick currently logged in user, and append to the list in position 0
978 pick all super-admins in order of creation date and add it to the list
982 pick all super-admins in order of creation date and add it to the list
979 pick all other accounts in order of creation and add it to the list.
983 pick all other accounts in order of creation and add it to the list.
980
984
981 Based on that list, the last accounts can be disabled as they are
985 Based on that list, the last accounts can be disabled as they are
982 created at the end and don't include any of the super admins as well
986 created at the end and don't include any of the super admins as well
983 as the current user.
987 as the current user.
984
988
985 :param current_user: optionally current user running this operation
989 :param current_user: optionally current user running this operation
986 """
990 """
987
991
988 if not current_user:
992 if not current_user:
989 current_user = get_current_rhodecode_user()
993 current_user = get_current_rhodecode_user()
990 active_super_admins = [
994 active_super_admins = [
991 x.user_id for x in User.query()
995 x.user_id for x in User.query()
992 .filter(User.user_id != current_user.user_id)
996 .filter(User.user_id != current_user.user_id)
993 .filter(User.active == true())
997 .filter(User.active == true())
994 .filter(User.admin == true())
998 .filter(User.admin == true())
995 .order_by(User.created_on.asc())]
999 .order_by(User.created_on.asc())]
996
1000
997 active_regular_users = [
1001 active_regular_users = [
998 x.user_id for x in User.query()
1002 x.user_id for x in User.query()
999 .filter(User.user_id != current_user.user_id)
1003 .filter(User.user_id != current_user.user_id)
1000 .filter(User.active == true())
1004 .filter(User.active == true())
1001 .filter(User.admin == false())
1005 .filter(User.admin == false())
1002 .order_by(User.created_on.asc())]
1006 .order_by(User.created_on.asc())]
1003
1007
1004 list_of_accounts = [current_user.user_id]
1008 list_of_accounts = [current_user.user_id]
1005 list_of_accounts += active_super_admins
1009 list_of_accounts += active_super_admins
1006 list_of_accounts += active_regular_users
1010 list_of_accounts += active_regular_users
1007
1011
1008 return list_of_accounts
1012 return list_of_accounts
1009
1013
1010 def deactivate_last_users(self, expected_users, current_user=None):
1014 def deactivate_last_users(self, expected_users, current_user=None):
1011 """
1015 """
1012 Deactivate accounts that are over the license limits.
1016 Deactivate accounts that are over the license limits.
1013 Algorithm of which accounts to disabled is based on the formula:
1017 Algorithm of which accounts to disabled is based on the formula:
1014
1018
1015 Get current user, then super admins in creation order, then regular
1019 Get current user, then super admins in creation order, then regular
1016 active users in creation order.
1020 active users in creation order.
1017
1021
1018 Using that list we mark all accounts from the end of it as inactive.
1022 Using that list we mark all accounts from the end of it as inactive.
1019 This way we block only latest created accounts.
1023 This way we block only latest created accounts.
1020
1024
1021 :param expected_users: list of users in special order, we deactivate
1025 :param expected_users: list of users in special order, we deactivate
1022 the end N amount of users from that list
1026 the end N amount of users from that list
1023 """
1027 """
1024
1028
1025 list_of_accounts = self.get_accounts_in_creation_order(
1029 list_of_accounts = self.get_accounts_in_creation_order(
1026 current_user=current_user)
1030 current_user=current_user)
1027
1031
1028 for acc_id in list_of_accounts[expected_users + 1:]:
1032 for acc_id in list_of_accounts[expected_users + 1:]:
1029 user = User.get(acc_id)
1033 user = User.get(acc_id)
1030 log.info('Deactivating account %s for license unlock', user)
1034 log.info('Deactivating account %s for license unlock', user)
1031 user.active = False
1035 user.active = False
1032 Session().add(user)
1036 Session().add(user)
1033 Session().commit()
1037 Session().commit()
1034
1038
1035 return
1039 return
1036
1040
1037 def get_user_log(self, user, filter_term):
1041 def get_user_log(self, user, filter_term):
1038 user_log = UserLog.query()\
1042 user_log = UserLog.query()\
1039 .filter(or_(UserLog.user_id == user.user_id,
1043 .filter(or_(UserLog.user_id == user.user_id,
1040 UserLog.username == user.username))\
1044 UserLog.username == user.username))\
1041 .options(joinedload(UserLog.user))\
1045 .options(joinedload(UserLog.user))\
1042 .options(joinedload(UserLog.repository))\
1046 .options(joinedload(UserLog.repository))\
1043 .order_by(UserLog.action_date.desc())
1047 .order_by(UserLog.action_date.desc())
1044
1048
1045 user_log = user_log_filter(user_log, filter_term)
1049 user_log = user_log_filter(user_log, filter_term)
1046 return user_log
1050 return user_log
General Comments 0
You need to be logged in to leave comments. Login now