##// END OF EJS Templates
my-account: owner/watched repos are now loaded only using DB queries....
marcink -
r4153:4c7da78c default
parent child Browse files
Show More
@@ -1,765 +1,800 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import string
24 24
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import peppercorn
28 28 from pyramid.httpexceptions import HTTPFound
29 29 from pyramid.view import view_config
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode import forms
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
36 from rhodecode.lib.auth import (
37 LoginRequired, NotAnonymous, CSRFRequired,
38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
38 39 from rhodecode.lib.channelstream import (
39 40 channelstream_request, ChannelstreamException)
40 41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 42 from rhodecode.model.auth_token import AuthTokenModel
42 43 from rhodecode.model.comment import CommentsModel
43 44 from rhodecode.model.db import (
44 IntegrityError, joinedload,
45 IntegrityError, or_, in_filter_generator,
45 46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 47 PullRequest, UserBookmark, RepoGroup)
47 48 from rhodecode.model.meta import Session
48 49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.scm import RepoList
50 50 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
52 51 from rhodecode.model.user_group import UserGroupModel
53 52 from rhodecode.model.validation_schema.schemas import user_schema
54 53
55 54 log = logging.getLogger(__name__)
56 55
57 56
58 57 class MyAccountView(BaseAppView, DataGridAppView):
59 58 ALLOW_SCOPED_TOKENS = False
60 59 """
61 60 This view has alternative version inside EE, if modified please take a look
62 61 in there as well.
63 62 """
64 63
65 64 def load_default_context(self):
66 65 c = self._get_local_tmpl_context()
67 66 c.user = c.auth_user.get_instance()
68 67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69 68
70 69 return c
71 70
72 71 @LoginRequired()
73 72 @NotAnonymous()
74 73 @view_config(
75 74 route_name='my_account_profile', request_method='GET',
76 75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 76 def my_account_profile(self):
78 77 c = self.load_default_context()
79 78 c.active = 'profile'
80 79 return self._get_template_context(c)
81 80
82 81 @LoginRequired()
83 82 @NotAnonymous()
84 83 @view_config(
85 84 route_name='my_account_password', request_method='GET',
86 85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 86 def my_account_password(self):
88 87 c = self.load_default_context()
89 88 c.active = 'password'
90 89 c.extern_type = c.user.extern_type
91 90
92 91 schema = user_schema.ChangePasswordSchema().bind(
93 92 username=c.user.username)
94 93
95 94 form = forms.Form(
96 95 schema,
97 96 action=h.route_path('my_account_password_update'),
98 97 buttons=(forms.buttons.save, forms.buttons.reset))
99 98
100 99 c.form = form
101 100 return self._get_template_context(c)
102 101
103 102 @LoginRequired()
104 103 @NotAnonymous()
105 104 @CSRFRequired()
106 105 @view_config(
107 106 route_name='my_account_password_update', request_method='POST',
108 107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 108 def my_account_password_update(self):
110 109 _ = self.request.translate
111 110 c = self.load_default_context()
112 111 c.active = 'password'
113 112 c.extern_type = c.user.extern_type
114 113
115 114 schema = user_schema.ChangePasswordSchema().bind(
116 115 username=c.user.username)
117 116
118 117 form = forms.Form(
119 118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120 119
121 120 if c.extern_type != 'rhodecode':
122 121 raise HTTPFound(self.request.route_path('my_account_password'))
123 122
124 123 controls = self.request.POST.items()
125 124 try:
126 125 valid_data = form.validate(controls)
127 126 UserModel().update_user(c.user.user_id, **valid_data)
128 127 c.user.update_userdata(force_password_change=False)
129 128 Session().commit()
130 129 except forms.ValidationFailure as e:
131 130 c.form = e
132 131 return self._get_template_context(c)
133 132
134 133 except Exception:
135 134 log.exception("Exception updating password")
136 135 h.flash(_('Error occurred during update of user password'),
137 136 category='error')
138 137 else:
139 138 instance = c.auth_user.get_instance()
140 139 self.session.setdefault('rhodecode_user', {}).update(
141 140 {'password': md5(instance.password)})
142 141 self.session.save()
143 142 h.flash(_("Successfully updated password"), category='success')
144 143
145 144 raise HTTPFound(self.request.route_path('my_account_password'))
146 145
147 146 @LoginRequired()
148 147 @NotAnonymous()
149 148 @view_config(
150 149 route_name='my_account_auth_tokens', request_method='GET',
151 150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 151 def my_account_auth_tokens(self):
153 152 _ = self.request.translate
154 153
155 154 c = self.load_default_context()
156 155 c.active = 'auth_tokens'
157 156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 157 c.role_values = [
159 158 (x, AuthTokenModel.cls._get_role_name(x))
160 159 for x in AuthTokenModel.cls.ROLES]
161 160 c.role_options = [(c.role_values, _("Role"))]
162 161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 162 c.user.user_id, show_expired=True)
164 163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 164 return self._get_template_context(c)
166 165
167 166 def maybe_attach_token_scope(self, token):
168 167 # implemented in EE edition
169 168 pass
170 169
171 170 @LoginRequired()
172 171 @NotAnonymous()
173 172 @CSRFRequired()
174 173 @view_config(
175 174 route_name='my_account_auth_tokens_add', request_method='POST',)
176 175 def my_account_auth_tokens_add(self):
177 176 _ = self.request.translate
178 177 c = self.load_default_context()
179 178
180 179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 180 description = self.request.POST.get('description')
182 181 role = self.request.POST.get('role')
183 182
184 183 token = UserModel().add_auth_token(
185 184 user=c.user.user_id,
186 185 lifetime_minutes=lifetime, role=role, description=description,
187 186 scope_callback=self.maybe_attach_token_scope)
188 187 token_data = token.get_api_data()
189 188
190 189 audit_logger.store_web(
191 190 'user.edit.token.add', action_data={
192 191 'data': {'token': token_data, 'user': 'self'}},
193 192 user=self._rhodecode_user, )
194 193 Session().commit()
195 194
196 195 h.flash(_("Auth token successfully created"), category='success')
197 196 return HTTPFound(h.route_path('my_account_auth_tokens'))
198 197
199 198 @LoginRequired()
200 199 @NotAnonymous()
201 200 @CSRFRequired()
202 201 @view_config(
203 202 route_name='my_account_auth_tokens_delete', request_method='POST')
204 203 def my_account_auth_tokens_delete(self):
205 204 _ = self.request.translate
206 205 c = self.load_default_context()
207 206
208 207 del_auth_token = self.request.POST.get('del_auth_token')
209 208
210 209 if del_auth_token:
211 210 token = UserApiKeys.get_or_404(del_auth_token)
212 211 token_data = token.get_api_data()
213 212
214 213 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 214 audit_logger.store_web(
216 215 'user.edit.token.delete', action_data={
217 216 'data': {'token': token_data, 'user': 'self'}},
218 217 user=self._rhodecode_user,)
219 218 Session().commit()
220 219 h.flash(_("Auth token successfully deleted"), category='success')
221 220
222 221 return HTTPFound(h.route_path('my_account_auth_tokens'))
223 222
224 223 @LoginRequired()
225 224 @NotAnonymous()
226 225 @view_config(
227 226 route_name='my_account_emails', request_method='GET',
228 227 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 228 def my_account_emails(self):
230 229 _ = self.request.translate
231 230
232 231 c = self.load_default_context()
233 232 c.active = 'emails'
234 233
235 234 c.user_email_map = UserEmailMap.query()\
236 235 .filter(UserEmailMap.user == c.user).all()
237 236
238 237 schema = user_schema.AddEmailSchema().bind(
239 238 username=c.user.username, user_emails=c.user.emails)
240 239
241 240 form = forms.RcForm(schema,
242 241 action=h.route_path('my_account_emails_add'),
243 242 buttons=(forms.buttons.save, forms.buttons.reset))
244 243
245 244 c.form = form
246 245 return self._get_template_context(c)
247 246
248 247 @LoginRequired()
249 248 @NotAnonymous()
250 249 @CSRFRequired()
251 250 @view_config(
252 251 route_name='my_account_emails_add', request_method='POST',
253 252 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 253 def my_account_emails_add(self):
255 254 _ = self.request.translate
256 255 c = self.load_default_context()
257 256 c.active = 'emails'
258 257
259 258 schema = user_schema.AddEmailSchema().bind(
260 259 username=c.user.username, user_emails=c.user.emails)
261 260
262 261 form = forms.RcForm(
263 262 schema, action=h.route_path('my_account_emails_add'),
264 263 buttons=(forms.buttons.save, forms.buttons.reset))
265 264
266 265 controls = self.request.POST.items()
267 266 try:
268 267 valid_data = form.validate(controls)
269 268 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 269 audit_logger.store_web(
271 270 'user.edit.email.add', action_data={
272 271 'data': {'email': valid_data['email'], 'user': 'self'}},
273 272 user=self._rhodecode_user,)
274 273 Session().commit()
275 274 except formencode.Invalid as error:
276 275 h.flash(h.escape(error.error_dict['email']), category='error')
277 276 except forms.ValidationFailure as e:
278 277 c.user_email_map = UserEmailMap.query() \
279 278 .filter(UserEmailMap.user == c.user).all()
280 279 c.form = e
281 280 return self._get_template_context(c)
282 281 except Exception:
283 282 log.exception("Exception adding email")
284 283 h.flash(_('Error occurred during adding email'),
285 284 category='error')
286 285 else:
287 286 h.flash(_("Successfully added email"), category='success')
288 287
289 288 raise HTTPFound(self.request.route_path('my_account_emails'))
290 289
291 290 @LoginRequired()
292 291 @NotAnonymous()
293 292 @CSRFRequired()
294 293 @view_config(
295 294 route_name='my_account_emails_delete', request_method='POST')
296 295 def my_account_emails_delete(self):
297 296 _ = self.request.translate
298 297 c = self.load_default_context()
299 298
300 299 del_email_id = self.request.POST.get('del_email_id')
301 300 if del_email_id:
302 301 email = UserEmailMap.get_or_404(del_email_id).email
303 302 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 303 audit_logger.store_web(
305 304 'user.edit.email.delete', action_data={
306 305 'data': {'email': email, 'user': 'self'}},
307 306 user=self._rhodecode_user,)
308 307 Session().commit()
309 308 h.flash(_("Email successfully deleted"),
310 309 category='success')
311 310 return HTTPFound(h.route_path('my_account_emails'))
312 311
313 312 @LoginRequired()
314 313 @NotAnonymous()
315 314 @CSRFRequired()
316 315 @view_config(
317 316 route_name='my_account_notifications_test_channelstream',
318 317 request_method='POST', renderer='json_ext')
319 318 def my_account_notifications_test_channelstream(self):
320 319 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 320 self._rhodecode_user.username, datetime.datetime.now())
322 321 payload = {
323 322 # 'channel': 'broadcast',
324 323 'type': 'message',
325 324 'timestamp': datetime.datetime.utcnow(),
326 325 'user': 'system',
327 326 'pm_users': [self._rhodecode_user.username],
328 327 'message': {
329 328 'message': message,
330 329 'level': 'info',
331 330 'topic': '/notifications'
332 331 }
333 332 }
334 333
335 334 registry = self.request.registry
336 335 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 336 channelstream_config = rhodecode_plugins.get('channelstream', {})
338 337
339 338 try:
340 339 channelstream_request(channelstream_config, [payload], '/message')
341 340 except ChannelstreamException as e:
342 341 log.exception('Failed to send channelstream data')
343 342 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 343 return {"response": 'Channelstream data sent. '
345 344 'You should see a new live message now.'}
346 345
347 346 def _load_my_repos_data(self, watched=False):
347
348 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
349
348 350 if watched:
349 admin = False
350 follows_repos = Session().query(UserFollowing)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 .options(joinedload(UserFollowing.follows_repository))\
351 # repos user watch
352 repo_list = Session().query(
353 Repository
354 ) \
355 .join(
356 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
357 ) \
358 .filter(
359 UserFollowing.user_id == self._rhodecode_user.user_id
360 ) \
361 .filter(or_(
362 # generate multiple IN to fix limitation problems
363 *in_filter_generator(Repository.repo_id, allowed_ids))
364 ) \
365 .order_by(Repository.repo_name) \
353 366 .all()
354 repo_list = [x.follows_repository for x in follows_repos]
367
355 368 else:
356 admin = True
357 repo_list = Repository.get_all_repos(
358 user_id=self._rhodecode_user.user_id)
359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'],
361 extra_kwargs=dict(user=self._rhodecode_user))
369 # repos user is owner of
370 repo_list = Session().query(
371 Repository
372 ) \
373 .filter(
374 Repository.user_id == self._rhodecode_user.user_id
375 ) \
376 .filter(or_(
377 # generate multiple IN to fix limitation problems
378 *in_filter_generator(Repository.repo_id, allowed_ids))
379 ) \
380 .order_by(Repository.repo_name) \
381 .all()
362 382
363 repos_data = RepoModel().get_repos_as_dict(
364 repo_list=repo_list, admin=admin, short_name=False)
383 _render = self.request.get_partial_renderer(
384 'rhodecode:templates/data_table/_dt_elements.mako')
385
386 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
387 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
388 short_name=False, admin=False)
389
390 repos_data = []
391 for repo in repo_list:
392 row = {
393 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
394 repo.private, repo.archived, repo.fork),
395 "name_raw": repo.repo_name.lower(),
396 }
397
398 repos_data.append(row)
399
365 400 # json used to render the grid
366 401 return json.dumps(repos_data)
367 402
368 403 @LoginRequired()
369 404 @NotAnonymous()
370 405 @view_config(
371 406 route_name='my_account_repos', request_method='GET',
372 407 renderer='rhodecode:templates/admin/my_account/my_account.mako')
373 408 def my_account_repos(self):
374 409 c = self.load_default_context()
375 410 c.active = 'repos'
376 411
377 412 # json used to render the grid
378 413 c.data = self._load_my_repos_data()
379 414 return self._get_template_context(c)
380 415
381 416 @LoginRequired()
382 417 @NotAnonymous()
383 418 @view_config(
384 419 route_name='my_account_watched', request_method='GET',
385 420 renderer='rhodecode:templates/admin/my_account/my_account.mako')
386 421 def my_account_watched(self):
387 422 c = self.load_default_context()
388 423 c.active = 'watched'
389 424
390 425 # json used to render the grid
391 426 c.data = self._load_my_repos_data(watched=True)
392 427 return self._get_template_context(c)
393 428
394 429 @LoginRequired()
395 430 @NotAnonymous()
396 431 @view_config(
397 432 route_name='my_account_bookmarks', request_method='GET',
398 433 renderer='rhodecode:templates/admin/my_account/my_account.mako')
399 434 def my_account_bookmarks(self):
400 435 c = self.load_default_context()
401 436 c.active = 'bookmarks'
402 437 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
403 438 self._rhodecode_db_user.user_id, cache=False)
404 439 return self._get_template_context(c)
405 440
406 441 def _process_bookmark_entry(self, entry, user_id):
407 442 position = safe_int(entry.get('position'))
408 443 cur_position = safe_int(entry.get('cur_position'))
409 444 if position is None:
410 445 return
411 446
412 447 # check if this is an existing entry
413 448 is_new = False
414 449 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
415 450
416 451 if db_entry and str2bool(entry.get('remove')):
417 452 log.debug('Marked bookmark %s for deletion', db_entry)
418 453 Session().delete(db_entry)
419 454 return
420 455
421 456 if not db_entry:
422 457 # new
423 458 db_entry = UserBookmark()
424 459 is_new = True
425 460
426 461 should_save = False
427 462 default_redirect_url = ''
428 463
429 464 # save repo
430 465 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
431 466 repo = Repository.get(entry['bookmark_repo'])
432 467 perm_check = HasRepoPermissionAny(
433 468 'repository.read', 'repository.write', 'repository.admin')
434 469 if repo and perm_check(repo_name=repo.repo_name):
435 470 db_entry.repository = repo
436 471 should_save = True
437 472 default_redirect_url = '${repo_url}'
438 473 # save repo group
439 474 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
440 475 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
441 476 perm_check = HasRepoGroupPermissionAny(
442 477 'group.read', 'group.write', 'group.admin')
443 478
444 479 if repo_group and perm_check(group_name=repo_group.group_name):
445 480 db_entry.repository_group = repo_group
446 481 should_save = True
447 482 default_redirect_url = '${repo_group_url}'
448 483 # save generic info
449 484 elif entry.get('title') and entry.get('redirect_url'):
450 485 should_save = True
451 486
452 487 if should_save:
453 488 # mark user and position
454 489 db_entry.user_id = user_id
455 490 db_entry.position = position
456 491 db_entry.title = entry.get('title')
457 492 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
458 493 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
459 494
460 495 Session().add(db_entry)
461 496
462 497 @LoginRequired()
463 498 @NotAnonymous()
464 499 @CSRFRequired()
465 500 @view_config(
466 501 route_name='my_account_bookmarks_update', request_method='POST')
467 502 def my_account_bookmarks_update(self):
468 503 _ = self.request.translate
469 504 c = self.load_default_context()
470 505 c.active = 'bookmarks'
471 506
472 507 controls = peppercorn.parse(self.request.POST.items())
473 508 user_id = c.user.user_id
474 509
475 510 # validate positions
476 511 positions = {}
477 512 for entry in controls.get('bookmarks', []):
478 513 position = safe_int(entry['position'])
479 514 if position is None:
480 515 continue
481 516
482 517 if position in positions:
483 518 h.flash(_("Position {} is defined twice. "
484 519 "Please correct this error.").format(position), category='error')
485 520 return HTTPFound(h.route_path('my_account_bookmarks'))
486 521
487 522 entry['position'] = position
488 523 entry['cur_position'] = safe_int(entry.get('cur_position'))
489 524 positions[position] = entry
490 525
491 526 try:
492 527 for entry in positions.values():
493 528 self._process_bookmark_entry(entry, user_id)
494 529
495 530 Session().commit()
496 531 h.flash(_("Update Bookmarks"), category='success')
497 532 except IntegrityError:
498 533 h.flash(_("Failed to update bookmarks. "
499 534 "Make sure an unique position is used."), category='error')
500 535
501 536 return HTTPFound(h.route_path('my_account_bookmarks'))
502 537
503 538 @LoginRequired()
504 539 @NotAnonymous()
505 540 @view_config(
506 541 route_name='my_account_goto_bookmark', request_method='GET',
507 542 renderer='rhodecode:templates/admin/my_account/my_account.mako')
508 543 def my_account_goto_bookmark(self):
509 544
510 545 bookmark_id = self.request.matchdict['bookmark_id']
511 546 user_bookmark = UserBookmark().query()\
512 547 .filter(UserBookmark.user_id == self.request.user.user_id) \
513 548 .filter(UserBookmark.position == bookmark_id).scalar()
514 549
515 550 redirect_url = h.route_path('my_account_bookmarks')
516 551 if not user_bookmark:
517 552 raise HTTPFound(redirect_url)
518 553
519 554 # repository set
520 555 if user_bookmark.repository:
521 556 repo_name = user_bookmark.repository.repo_name
522 557 base_redirect_url = h.route_path(
523 558 'repo_summary', repo_name=repo_name)
524 559 if user_bookmark.redirect_url and \
525 560 '${repo_url}' in user_bookmark.redirect_url:
526 561 redirect_url = string.Template(user_bookmark.redirect_url)\
527 562 .safe_substitute({'repo_url': base_redirect_url})
528 563 else:
529 564 redirect_url = base_redirect_url
530 565 # repository group set
531 566 elif user_bookmark.repository_group:
532 567 repo_group_name = user_bookmark.repository_group.group_name
533 568 base_redirect_url = h.route_path(
534 569 'repo_group_home', repo_group_name=repo_group_name)
535 570 if user_bookmark.redirect_url and \
536 571 '${repo_group_url}' in user_bookmark.redirect_url:
537 572 redirect_url = string.Template(user_bookmark.redirect_url)\
538 573 .safe_substitute({'repo_group_url': base_redirect_url})
539 574 else:
540 575 redirect_url = base_redirect_url
541 576 # custom URL set
542 577 elif user_bookmark.redirect_url:
543 578 server_url = h.route_url('home').rstrip('/')
544 579 redirect_url = string.Template(user_bookmark.redirect_url) \
545 580 .safe_substitute({'server_url': server_url})
546 581
547 582 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
548 583 raise HTTPFound(redirect_url)
549 584
550 585 @LoginRequired()
551 586 @NotAnonymous()
552 587 @view_config(
553 588 route_name='my_account_perms', request_method='GET',
554 589 renderer='rhodecode:templates/admin/my_account/my_account.mako')
555 590 def my_account_perms(self):
556 591 c = self.load_default_context()
557 592 c.active = 'perms'
558 593
559 594 c.perm_user = c.auth_user
560 595 return self._get_template_context(c)
561 596
562 597 @LoginRequired()
563 598 @NotAnonymous()
564 599 @view_config(
565 600 route_name='my_account_notifications', request_method='GET',
566 601 renderer='rhodecode:templates/admin/my_account/my_account.mako')
567 602 def my_notifications(self):
568 603 c = self.load_default_context()
569 604 c.active = 'notifications'
570 605
571 606 return self._get_template_context(c)
572 607
573 608 @LoginRequired()
574 609 @NotAnonymous()
575 610 @CSRFRequired()
576 611 @view_config(
577 612 route_name='my_account_notifications_toggle_visibility',
578 613 request_method='POST', renderer='json_ext')
579 614 def my_notifications_toggle_visibility(self):
580 615 user = self._rhodecode_db_user
581 616 new_status = not user.user_data.get('notification_status', True)
582 617 user.update_userdata(notification_status=new_status)
583 618 Session().commit()
584 619 return user.user_data['notification_status']
585 620
586 621 @LoginRequired()
587 622 @NotAnonymous()
588 623 @view_config(
589 624 route_name='my_account_edit',
590 625 request_method='GET',
591 626 renderer='rhodecode:templates/admin/my_account/my_account.mako')
592 627 def my_account_edit(self):
593 628 c = self.load_default_context()
594 629 c.active = 'profile_edit'
595 630 c.extern_type = c.user.extern_type
596 631 c.extern_name = c.user.extern_name
597 632
598 633 schema = user_schema.UserProfileSchema().bind(
599 634 username=c.user.username, user_emails=c.user.emails)
600 635 appstruct = {
601 636 'username': c.user.username,
602 637 'email': c.user.email,
603 638 'firstname': c.user.firstname,
604 639 'lastname': c.user.lastname,
605 640 'description': c.user.description,
606 641 }
607 642 c.form = forms.RcForm(
608 643 schema, appstruct=appstruct,
609 644 action=h.route_path('my_account_update'),
610 645 buttons=(forms.buttons.save, forms.buttons.reset))
611 646
612 647 return self._get_template_context(c)
613 648
614 649 @LoginRequired()
615 650 @NotAnonymous()
616 651 @CSRFRequired()
617 652 @view_config(
618 653 route_name='my_account_update',
619 654 request_method='POST',
620 655 renderer='rhodecode:templates/admin/my_account/my_account.mako')
621 656 def my_account_update(self):
622 657 _ = self.request.translate
623 658 c = self.load_default_context()
624 659 c.active = 'profile_edit'
625 660 c.perm_user = c.auth_user
626 661 c.extern_type = c.user.extern_type
627 662 c.extern_name = c.user.extern_name
628 663
629 664 schema = user_schema.UserProfileSchema().bind(
630 665 username=c.user.username, user_emails=c.user.emails)
631 666 form = forms.RcForm(
632 667 schema, buttons=(forms.buttons.save, forms.buttons.reset))
633 668
634 669 controls = self.request.POST.items()
635 670 try:
636 671 valid_data = form.validate(controls)
637 672 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
638 673 'new_password', 'password_confirmation']
639 674 if c.extern_type != "rhodecode":
640 675 # forbid updating username for external accounts
641 676 skip_attrs.append('username')
642 677 old_email = c.user.email
643 678 UserModel().update_user(
644 679 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
645 680 **valid_data)
646 681 if old_email != valid_data['email']:
647 682 old = UserEmailMap.query() \
648 683 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
649 684 old.email = old_email
650 685 h.flash(_('Your account was updated successfully'), category='success')
651 686 Session().commit()
652 687 except forms.ValidationFailure as e:
653 688 c.form = e
654 689 return self._get_template_context(c)
655 690 except Exception:
656 691 log.exception("Exception updating user")
657 692 h.flash(_('Error occurred during update of user'),
658 693 category='error')
659 694 raise HTTPFound(h.route_path('my_account_profile'))
660 695
661 696 def _get_pull_requests_list(self, statuses):
662 697 draw, start, limit = self._extract_chunk(self.request)
663 698 search_q, order_by, order_dir = self._extract_ordering(self.request)
664 699 _render = self.request.get_partial_renderer(
665 700 'rhodecode:templates/data_table/_dt_elements.mako')
666 701
667 702 pull_requests = PullRequestModel().get_im_participating_in(
668 703 user_id=self._rhodecode_user.user_id,
669 704 statuses=statuses,
670 705 offset=start, length=limit, order_by=order_by,
671 706 order_dir=order_dir)
672 707
673 708 pull_requests_total_count = PullRequestModel().count_im_participating_in(
674 709 user_id=self._rhodecode_user.user_id, statuses=statuses)
675 710
676 711 data = []
677 712 comments_model = CommentsModel()
678 713 for pr in pull_requests:
679 714 repo_id = pr.target_repo_id
680 715 comments = comments_model.get_all_comments(
681 716 repo_id, pull_request=pr)
682 717 owned = pr.user_id == self._rhodecode_user.user_id
683 718
684 719 data.append({
685 720 'target_repo': _render('pullrequest_target_repo',
686 721 pr.target_repo.repo_name),
687 722 'name': _render('pullrequest_name',
688 723 pr.pull_request_id, pr.pull_request_state,
689 724 pr.work_in_progress, pr.target_repo.repo_name,
690 725 short=True),
691 726 'name_raw': pr.pull_request_id,
692 727 'status': _render('pullrequest_status',
693 728 pr.calculated_review_status()),
694 729 'title': _render('pullrequest_title', pr.title, pr.description),
695 730 'description': h.escape(pr.description),
696 731 'updated_on': _render('pullrequest_updated_on',
697 732 h.datetime_to_time(pr.updated_on)),
698 733 'updated_on_raw': h.datetime_to_time(pr.updated_on),
699 734 'created_on': _render('pullrequest_updated_on',
700 735 h.datetime_to_time(pr.created_on)),
701 736 'created_on_raw': h.datetime_to_time(pr.created_on),
702 737 'state': pr.pull_request_state,
703 738 'author': _render('pullrequest_author',
704 739 pr.author.full_contact, ),
705 740 'author_raw': pr.author.full_name,
706 741 'comments': _render('pullrequest_comments', len(comments)),
707 742 'comments_raw': len(comments),
708 743 'closed': pr.is_closed(),
709 744 'owned': owned
710 745 })
711 746
712 747 # json used to render the grid
713 748 data = ({
714 749 'draw': draw,
715 750 'data': data,
716 751 'recordsTotal': pull_requests_total_count,
717 752 'recordsFiltered': pull_requests_total_count,
718 753 })
719 754 return data
720 755
721 756 @LoginRequired()
722 757 @NotAnonymous()
723 758 @view_config(
724 759 route_name='my_account_pullrequests',
725 760 request_method='GET',
726 761 renderer='rhodecode:templates/admin/my_account/my_account.mako')
727 762 def my_account_pullrequests(self):
728 763 c = self.load_default_context()
729 764 c.active = 'pullrequests'
730 765 req_get = self.request.GET
731 766
732 767 c.closed = str2bool(req_get.get('pr_show_closed'))
733 768
734 769 return self._get_template_context(c)
735 770
736 771 @LoginRequired()
737 772 @NotAnonymous()
738 773 @view_config(
739 774 route_name='my_account_pullrequests_data',
740 775 request_method='GET', renderer='json_ext')
741 776 def my_account_pullrequests_data(self):
742 777 self.load_default_context()
743 778 req_get = self.request.GET
744 779 closed = str2bool(req_get.get('closed'))
745 780
746 781 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
747 782 if closed:
748 783 statuses += [PullRequest.STATUS_CLOSED]
749 784
750 785 data = self._get_pull_requests_list(statuses=statuses)
751 786 return data
752 787
753 788 @LoginRequired()
754 789 @NotAnonymous()
755 790 @view_config(
756 791 route_name='my_account_user_group_membership',
757 792 request_method='GET',
758 793 renderer='rhodecode:templates/admin/my_account/my_account.mako')
759 794 def my_account_user_group_membership(self):
760 795 c = self.load_default_context()
761 796 c.active = 'user_group_membership'
762 797 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
763 798 for group in self._rhodecode_db_user.group_member]
764 799 c.user_groups = json.dumps(groups)
765 800 return self._get_template_context(c)
@@ -1,2410 +1,2412 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default', 1
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin', 2
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin, obj_id)):
370 370 self.perm_origin_stack.setdefault(key, []).append(
371 371 (perm, origin, obj_id))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, (pattern, perm, origin)):
390 390
391 391 self.perm_origin_stack.setdefault(key, {}) \
392 392 .setdefault(pattern, []).append((perm, origin))
393 393
394 394 if key in self:
395 395 self[key].__setitem__(pattern, perm)
396 396 else:
397 397 patterns = collections.OrderedDict()
398 398 patterns[pattern] = perm
399 399 dict.__setitem__(self, key, patterns)
400 400
401 401
402 402 class PermissionCalculator(object):
403 403
404 404 def __init__(
405 405 self, user_id, scope, user_is_admin,
406 406 user_inherit_default_permissions, explicit, algo,
407 407 calculate_super_admin_as_user=False):
408 408
409 409 self.user_id = user_id
410 410 self.user_is_admin = user_is_admin
411 411 self.inherit_default_permissions = user_inherit_default_permissions
412 412 self.explicit = explicit
413 413 self.algo = algo
414 414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 415
416 416 scope = scope or {}
417 417 self.scope_repo_id = scope.get('repo_id')
418 418 self.scope_repo_group_id = scope.get('repo_group_id')
419 419 self.scope_user_group_id = scope.get('user_group_id')
420 420
421 421 self.default_user_id = User.get_default_user(cache=True).user_id
422 422
423 423 self.permissions_repositories = PermOriginDict()
424 424 self.permissions_repository_groups = PermOriginDict()
425 425 self.permissions_user_groups = PermOriginDict()
426 426 self.permissions_repository_branches = BranchPermOriginDict()
427 427 self.permissions_global = set()
428 428
429 429 self.default_repo_perms = Permission.get_default_repo_perms(
430 430 self.default_user_id, self.scope_repo_id)
431 431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 432 self.default_user_id, self.scope_repo_group_id)
433 433 self.default_user_group_perms = \
434 434 Permission.get_default_user_group_perms(
435 435 self.default_user_id, self.scope_user_group_id)
436 436
437 437 # default branch perms
438 438 self.default_branch_repo_perms = \
439 439 Permission.get_default_repo_branch_perms(
440 440 self.default_user_id, self.scope_repo_id)
441 441
442 442 def calculate(self):
443 443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 444 return self._calculate_admin_permissions()
445 445
446 446 self._calculate_global_default_permissions()
447 447 self._calculate_global_permissions()
448 448 self._calculate_default_permissions()
449 449 self._calculate_repository_permissions()
450 450 self._calculate_repository_branch_permissions()
451 451 self._calculate_repository_group_permissions()
452 452 self._calculate_user_group_permissions()
453 453 return self._permission_structure()
454 454
455 455 def _calculate_admin_permissions(self):
456 456 """
457 457 admin user have all default rights for repositories
458 458 and groups set to admin
459 459 """
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 # repositories
464 464 for perm in self.default_repo_perms:
465 465 r_k = perm.UserRepoToPerm.repository.repo_name
466 466 obj_id = perm.UserRepoToPerm.repository.repo_id
467 467 archived = perm.UserRepoToPerm.repository.archived
468 468 p = 'repository.admin'
469 469 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
470 470 # special case for archived repositories, which we block still even for
471 471 # super admins
472 472 if archived:
473 473 p = 'repository.read'
474 474 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
475 475
476 476 # repository groups
477 477 for perm in self.default_repo_groups_perms:
478 478 rg_k = perm.UserRepoGroupToPerm.group.group_name
479 479 obj_id = perm.UserRepoGroupToPerm.group.group_id
480 480 p = 'group.admin'
481 481 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
482 482
483 483 # user groups
484 484 for perm in self.default_user_group_perms:
485 485 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
486 486 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
487 487 p = 'usergroup.admin'
488 488 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
489 489
490 490 # branch permissions
491 491 # since super-admin also can have custom rule permissions
492 492 # we *always* need to calculate those inherited from default, and also explicit
493 493 self._calculate_default_permissions_repository_branches(
494 494 user_inherit_object_permissions=False)
495 495 self._calculate_repository_branch_permissions()
496 496
497 497 return self._permission_structure()
498 498
499 499 def _calculate_global_default_permissions(self):
500 500 """
501 501 global permissions taken from the default user
502 502 """
503 503 default_global_perms = UserToPerm.query()\
504 504 .filter(UserToPerm.user_id == self.default_user_id)\
505 505 .options(joinedload(UserToPerm.permission))
506 506
507 507 for perm in default_global_perms:
508 508 self.permissions_global.add(perm.permission.permission_name)
509 509
510 510 if self.user_is_admin:
511 511 self.permissions_global.add('hg.admin')
512 512 self.permissions_global.add('hg.create.write_on_repogroup.true')
513 513
514 514 def _calculate_global_permissions(self):
515 515 """
516 516 Set global system permissions with user permissions or permissions
517 517 taken from the user groups of the current user.
518 518
519 519 The permissions include repo creating, repo group creating, forking
520 520 etc.
521 521 """
522 522
523 523 # now we read the defined permissions and overwrite what we have set
524 524 # before those can be configured from groups or users explicitly.
525 525
526 526 # In case we want to extend this list we should make sure
527 527 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
528 528 _configurable = frozenset([
529 529 'hg.fork.none', 'hg.fork.repository',
530 530 'hg.create.none', 'hg.create.repository',
531 531 'hg.usergroup.create.false', 'hg.usergroup.create.true',
532 532 'hg.repogroup.create.false', 'hg.repogroup.create.true',
533 533 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
534 534 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
535 535 ])
536 536
537 537 # USER GROUPS comes first user group global permissions
538 538 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
539 539 .options(joinedload(UserGroupToPerm.permission))\
540 540 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
541 541 UserGroupMember.users_group_id))\
542 542 .filter(UserGroupMember.user_id == self.user_id)\
543 543 .order_by(UserGroupToPerm.users_group_id)\
544 544 .all()
545 545
546 546 # need to group here by groups since user can be in more than
547 547 # one group, so we get all groups
548 548 _explicit_grouped_perms = [
549 549 [x, list(y)] for x, y in
550 550 itertools.groupby(user_perms_from_users_groups,
551 551 lambda _x: _x.users_group)]
552 552
553 553 for gr, perms in _explicit_grouped_perms:
554 554 # since user can be in multiple groups iterate over them and
555 555 # select the lowest permissions first (more explicit)
556 556 # TODO(marcink): do this^^
557 557
558 558 # group doesn't inherit default permissions so we actually set them
559 559 if not gr.inherit_default_permissions:
560 560 # NEED TO IGNORE all previously set configurable permissions
561 561 # and replace them with explicitly set from this user
562 562 # group permissions
563 563 self.permissions_global = self.permissions_global.difference(
564 564 _configurable)
565 565 for perm in perms:
566 566 self.permissions_global.add(perm.permission.permission_name)
567 567
568 568 # user explicit global permissions
569 569 user_perms = Session().query(UserToPerm)\
570 570 .options(joinedload(UserToPerm.permission))\
571 571 .filter(UserToPerm.user_id == self.user_id).all()
572 572
573 573 if not self.inherit_default_permissions:
574 574 # NEED TO IGNORE all configurable permissions and
575 575 # replace them with explicitly set from this user permissions
576 576 self.permissions_global = self.permissions_global.difference(
577 577 _configurable)
578 578 for perm in user_perms:
579 579 self.permissions_global.add(perm.permission.permission_name)
580 580
581 581 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
582 582 for perm in self.default_repo_perms:
583 583 r_k = perm.UserRepoToPerm.repository.repo_name
584 584 obj_id = perm.UserRepoToPerm.repository.repo_id
585 585 archived = perm.UserRepoToPerm.repository.archived
586 586 p = perm.Permission.permission_name
587 587 o = PermOrigin.REPO_DEFAULT
588 588 self.permissions_repositories[r_k] = p, o, obj_id
589 589
590 590 # if we decide this user isn't inheriting permissions from
591 591 # default user we set him to .none so only explicit
592 592 # permissions work
593 593 if not user_inherit_object_permissions:
594 594 p = 'repository.none'
595 595 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
596 596 self.permissions_repositories[r_k] = p, o, obj_id
597 597
598 598 if perm.Repository.private and not (
599 599 perm.Repository.user_id == self.user_id):
600 600 # disable defaults for private repos,
601 601 p = 'repository.none'
602 602 o = PermOrigin.REPO_PRIVATE
603 603 self.permissions_repositories[r_k] = p, o, obj_id
604 604
605 605 elif perm.Repository.user_id == self.user_id:
606 606 # set admin if owner
607 607 p = 'repository.admin'
608 608 o = PermOrigin.REPO_OWNER
609 609 self.permissions_repositories[r_k] = p, o, obj_id
610 610
611 611 if self.user_is_admin:
612 612 p = 'repository.admin'
613 613 o = PermOrigin.SUPER_ADMIN
614 614 self.permissions_repositories[r_k] = p, o, obj_id
615 615
616 616 # finally in case of archived repositories, we downgrade higher
617 617 # permissions to read
618 618 if archived:
619 619 current_perm = self.permissions_repositories[r_k]
620 620 if current_perm in ['repository.write', 'repository.admin']:
621 621 p = 'repository.read'
622 622 o = PermOrigin.ARCHIVED
623 623 self.permissions_repositories[r_k] = p, o, obj_id
624 624
625 625 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
626 626 for perm in self.default_branch_repo_perms:
627 627
628 628 r_k = perm.UserRepoToPerm.repository.repo_name
629 629 p = perm.Permission.permission_name
630 630 pattern = perm.UserToRepoBranchPermission.branch_pattern
631 631 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
632 632
633 633 if not self.explicit:
634 634 cur_perm = self.permissions_repository_branches.get(r_k)
635 635 if cur_perm:
636 636 cur_perm = cur_perm[pattern]
637 637 cur_perm = cur_perm or 'branch.none'
638 638
639 639 p = self._choose_permission(p, cur_perm)
640 640
641 641 # NOTE(marcink): register all pattern/perm instances in this
642 642 # special dict that aggregates entries
643 643 self.permissions_repository_branches[r_k] = pattern, p, o
644 644
645 645 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
646 646 for perm in self.default_repo_groups_perms:
647 647 rg_k = perm.UserRepoGroupToPerm.group.group_name
648 648 obj_id = perm.UserRepoGroupToPerm.group.group_id
649 649 p = perm.Permission.permission_name
650 650 o = PermOrigin.REPOGROUP_DEFAULT
651 651 self.permissions_repository_groups[rg_k] = p, o, obj_id
652 652
653 653 # if we decide this user isn't inheriting permissions from default
654 654 # user we set him to .none so only explicit permissions work
655 655 if not user_inherit_object_permissions:
656 656 p = 'group.none'
657 657 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
658 658 self.permissions_repository_groups[rg_k] = p, o, obj_id
659 659
660 660 if perm.RepoGroup.user_id == self.user_id:
661 661 # set admin if owner
662 662 p = 'group.admin'
663 663 o = PermOrigin.REPOGROUP_OWNER
664 664 self.permissions_repository_groups[rg_k] = p, o, obj_id
665 665
666 666 if self.user_is_admin:
667 667 p = 'group.admin'
668 668 o = PermOrigin.SUPER_ADMIN
669 669 self.permissions_repository_groups[rg_k] = p, o, obj_id
670 670
671 671 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
672 672 for perm in self.default_user_group_perms:
673 673 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
674 674 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
675 675 p = perm.Permission.permission_name
676 676 o = PermOrigin.USERGROUP_DEFAULT
677 677 self.permissions_user_groups[u_k] = p, o, obj_id
678 678
679 679 # if we decide this user isn't inheriting permissions from default
680 680 # user we set him to .none so only explicit permissions work
681 681 if not user_inherit_object_permissions:
682 682 p = 'usergroup.none'
683 683 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
684 684 self.permissions_user_groups[u_k] = p, o, obj_id
685 685
686 686 if perm.UserGroup.user_id == self.user_id:
687 687 # set admin if owner
688 688 p = 'usergroup.admin'
689 689 o = PermOrigin.USERGROUP_OWNER
690 690 self.permissions_user_groups[u_k] = p, o, obj_id
691 691
692 692 if self.user_is_admin:
693 693 p = 'usergroup.admin'
694 694 o = PermOrigin.SUPER_ADMIN
695 695 self.permissions_user_groups[u_k] = p, o, obj_id
696 696
697 697 def _calculate_default_permissions(self):
698 698 """
699 699 Set default user permissions for repositories, repository branches,
700 700 repository groups, user groups taken from the default user.
701 701
702 702 Calculate inheritance of object permissions based on what we have now
703 703 in GLOBAL permissions. We check if .false is in GLOBAL since this is
704 704 explicitly set. Inherit is the opposite of .false being there.
705 705
706 706 .. note::
707 707
708 708 the syntax is little bit odd but what we need to check here is
709 709 the opposite of .false permission being in the list so even for
710 710 inconsistent state when both .true/.false is there
711 711 .false is more important
712 712
713 713 """
714 714 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
715 715 in self.permissions_global)
716 716
717 717 # default permissions inherited from `default` user permissions
718 718 self._calculate_default_permissions_repositories(
719 719 user_inherit_object_permissions)
720 720
721 721 self._calculate_default_permissions_repository_branches(
722 722 user_inherit_object_permissions)
723 723
724 724 self._calculate_default_permissions_repository_groups(
725 725 user_inherit_object_permissions)
726 726
727 727 self._calculate_default_permissions_user_groups(
728 728 user_inherit_object_permissions)
729 729
730 730 def _calculate_repository_permissions(self):
731 731 """
732 732 Repository access permissions for the current user.
733 733
734 734 Check if the user is part of user groups for this repository and
735 735 fill in the permission from it. `_choose_permission` decides of which
736 736 permission should be selected based on selected method.
737 737 """
738 738
739 739 # user group for repositories permissions
740 740 user_repo_perms_from_user_group = Permission\
741 741 .get_default_repo_perms_from_user_group(
742 742 self.user_id, self.scope_repo_id)
743 743
744 744 multiple_counter = collections.defaultdict(int)
745 745 for perm in user_repo_perms_from_user_group:
746 746 r_k = perm.UserGroupRepoToPerm.repository.repo_name
747 747 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
748 748 multiple_counter[r_k] += 1
749 749 p = perm.Permission.permission_name
750 750 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
751 751 .users_group.users_group_name
752 752
753 753 if multiple_counter[r_k] > 1:
754 754 cur_perm = self.permissions_repositories[r_k]
755 755 p = self._choose_permission(p, cur_perm)
756 756
757 757 self.permissions_repositories[r_k] = p, o, obj_id
758 758
759 759 if perm.Repository.user_id == self.user_id:
760 760 # set admin if owner
761 761 p = 'repository.admin'
762 762 o = PermOrigin.REPO_OWNER
763 763 self.permissions_repositories[r_k] = p, o, obj_id
764 764
765 765 if self.user_is_admin:
766 766 p = 'repository.admin'
767 767 o = PermOrigin.SUPER_ADMIN
768 768 self.permissions_repositories[r_k] = p, o, obj_id
769 769
770 770 # user explicit permissions for repositories, overrides any specified
771 771 # by the group permission
772 772 user_repo_perms = Permission.get_default_repo_perms(
773 773 self.user_id, self.scope_repo_id)
774 774 for perm in user_repo_perms:
775 775 r_k = perm.UserRepoToPerm.repository.repo_name
776 776 obj_id = perm.UserRepoToPerm.repository.repo_id
777 777 p = perm.Permission.permission_name
778 778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
779 779
780 780 if not self.explicit:
781 781 cur_perm = self.permissions_repositories.get(
782 782 r_k, 'repository.none')
783 783 p = self._choose_permission(p, cur_perm)
784 784
785 785 self.permissions_repositories[r_k] = p, o, obj_id
786 786
787 787 if perm.Repository.user_id == self.user_id:
788 788 # set admin if owner
789 789 p = 'repository.admin'
790 790 o = PermOrigin.REPO_OWNER
791 791 self.permissions_repositories[r_k] = p, o, obj_id
792 792
793 793 if self.user_is_admin:
794 794 p = 'repository.admin'
795 795 o = PermOrigin.SUPER_ADMIN
796 796 self.permissions_repositories[r_k] = p, o, obj_id
797 797
798 798 def _calculate_repository_branch_permissions(self):
799 799 # user group for repositories permissions
800 800 user_repo_branch_perms_from_user_group = Permission\
801 801 .get_default_repo_branch_perms_from_user_group(
802 802 self.user_id, self.scope_repo_id)
803 803
804 804 multiple_counter = collections.defaultdict(int)
805 805 for perm in user_repo_branch_perms_from_user_group:
806 806 r_k = perm.UserGroupRepoToPerm.repository.repo_name
807 807 p = perm.Permission.permission_name
808 808 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
809 809 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
810 810 .users_group.users_group_name
811 811
812 812 multiple_counter[r_k] += 1
813 813 if multiple_counter[r_k] > 1:
814 814 cur_perm = self.permissions_repository_branches[r_k][pattern]
815 815 p = self._choose_permission(p, cur_perm)
816 816
817 817 self.permissions_repository_branches[r_k] = pattern, p, o
818 818
819 819 # user explicit branch permissions for repositories, overrides
820 820 # any specified by the group permission
821 821 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
822 822 self.user_id, self.scope_repo_id)
823 823
824 824 for perm in user_repo_branch_perms:
825 825
826 826 r_k = perm.UserRepoToPerm.repository.repo_name
827 827 p = perm.Permission.permission_name
828 828 pattern = perm.UserToRepoBranchPermission.branch_pattern
829 829 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
830 830
831 831 if not self.explicit:
832 832 cur_perm = self.permissions_repository_branches.get(r_k)
833 833 if cur_perm:
834 834 cur_perm = cur_perm[pattern]
835 835 cur_perm = cur_perm or 'branch.none'
836 836 p = self._choose_permission(p, cur_perm)
837 837
838 838 # NOTE(marcink): register all pattern/perm instances in this
839 839 # special dict that aggregates entries
840 840 self.permissions_repository_branches[r_k] = pattern, p, o
841 841
842 842 def _calculate_repository_group_permissions(self):
843 843 """
844 844 Repository group permissions for the current user.
845 845
846 846 Check if the user is part of user groups for repository groups and
847 847 fill in the permissions from it. `_choose_permission` decides of which
848 848 permission should be selected based on selected method.
849 849 """
850 850 # user group for repo groups permissions
851 851 user_repo_group_perms_from_user_group = Permission\
852 852 .get_default_group_perms_from_user_group(
853 853 self.user_id, self.scope_repo_group_id)
854 854
855 855 multiple_counter = collections.defaultdict(int)
856 856 for perm in user_repo_group_perms_from_user_group:
857 857 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
858 858 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
859 859 multiple_counter[rg_k] += 1
860 860 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
861 861 .users_group.users_group_name
862 862 p = perm.Permission.permission_name
863 863
864 864 if multiple_counter[rg_k] > 1:
865 865 cur_perm = self.permissions_repository_groups[rg_k]
866 866 p = self._choose_permission(p, cur_perm)
867 867 self.permissions_repository_groups[rg_k] = p, o, obj_id
868 868
869 869 if perm.RepoGroup.user_id == self.user_id:
870 870 # set admin if owner, even for member of other user group
871 871 p = 'group.admin'
872 872 o = PermOrigin.REPOGROUP_OWNER
873 873 self.permissions_repository_groups[rg_k] = p, o, obj_id
874 874
875 875 if self.user_is_admin:
876 876 p = 'group.admin'
877 877 o = PermOrigin.SUPER_ADMIN
878 878 self.permissions_repository_groups[rg_k] = p, o, obj_id
879 879
880 880 # user explicit permissions for repository groups
881 881 user_repo_groups_perms = Permission.get_default_group_perms(
882 882 self.user_id, self.scope_repo_group_id)
883 883 for perm in user_repo_groups_perms:
884 884 rg_k = perm.UserRepoGroupToPerm.group.group_name
885 885 obj_id = perm.UserRepoGroupToPerm.group.group_id
886 886 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
887 887 .user.username
888 888 p = perm.Permission.permission_name
889 889
890 890 if not self.explicit:
891 891 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
892 892 p = self._choose_permission(p, cur_perm)
893 893
894 894 self.permissions_repository_groups[rg_k] = p, o, obj_id
895 895
896 896 if perm.RepoGroup.user_id == self.user_id:
897 897 # set admin if owner
898 898 p = 'group.admin'
899 899 o = PermOrigin.REPOGROUP_OWNER
900 900 self.permissions_repository_groups[rg_k] = p, o, obj_id
901 901
902 902 if self.user_is_admin:
903 903 p = 'group.admin'
904 904 o = PermOrigin.SUPER_ADMIN
905 905 self.permissions_repository_groups[rg_k] = p, o, obj_id
906 906
907 907 def _calculate_user_group_permissions(self):
908 908 """
909 909 User group permissions for the current user.
910 910 """
911 911 # user group for user group permissions
912 912 user_group_from_user_group = Permission\
913 913 .get_default_user_group_perms_from_user_group(
914 914 self.user_id, self.scope_user_group_id)
915 915
916 916 multiple_counter = collections.defaultdict(int)
917 917 for perm in user_group_from_user_group:
918 918 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
919 919 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
920 920 multiple_counter[ug_k] += 1
921 921 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
922 922 .user_group.users_group_name
923 923 p = perm.Permission.permission_name
924 924
925 925 if multiple_counter[ug_k] > 1:
926 926 cur_perm = self.permissions_user_groups[ug_k]
927 927 p = self._choose_permission(p, cur_perm)
928 928
929 929 self.permissions_user_groups[ug_k] = p, o, obj_id
930 930
931 931 if perm.UserGroup.user_id == self.user_id:
932 932 # set admin if owner, even for member of other user group
933 933 p = 'usergroup.admin'
934 934 o = PermOrigin.USERGROUP_OWNER
935 935 self.permissions_user_groups[ug_k] = p, o, obj_id
936 936
937 937 if self.user_is_admin:
938 938 p = 'usergroup.admin'
939 939 o = PermOrigin.SUPER_ADMIN
940 940 self.permissions_user_groups[ug_k] = p, o, obj_id
941 941
942 942 # user explicit permission for user groups
943 943 user_user_groups_perms = Permission.get_default_user_group_perms(
944 944 self.user_id, self.scope_user_group_id)
945 945 for perm in user_user_groups_perms:
946 946 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
947 947 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
948 948 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
949 949 .user.username
950 950 p = perm.Permission.permission_name
951 951
952 952 if not self.explicit:
953 953 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
954 954 p = self._choose_permission(p, cur_perm)
955 955
956 956 self.permissions_user_groups[ug_k] = p, o, obj_id
957 957
958 958 if perm.UserGroup.user_id == self.user_id:
959 959 # set admin if owner
960 960 p = 'usergroup.admin'
961 961 o = PermOrigin.USERGROUP_OWNER
962 962 self.permissions_user_groups[ug_k] = p, o, obj_id
963 963
964 964 if self.user_is_admin:
965 965 p = 'usergroup.admin'
966 966 o = PermOrigin.SUPER_ADMIN
967 967 self.permissions_user_groups[ug_k] = p, o, obj_id
968 968
969 969 def _choose_permission(self, new_perm, cur_perm):
970 970 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
971 971 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
972 972 if self.algo == 'higherwin':
973 973 if new_perm_val > cur_perm_val:
974 974 return new_perm
975 975 return cur_perm
976 976 elif self.algo == 'lowerwin':
977 977 if new_perm_val < cur_perm_val:
978 978 return new_perm
979 979 return cur_perm
980 980
981 981 def _permission_structure(self):
982 982 return {
983 983 'global': self.permissions_global,
984 984 'repositories': self.permissions_repositories,
985 985 'repository_branches': self.permissions_repository_branches,
986 986 'repositories_groups': self.permissions_repository_groups,
987 987 'user_groups': self.permissions_user_groups,
988 988 }
989 989
990 990
991 991 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
992 992 """
993 993 Check if given controller_name is in whitelist of auth token access
994 994 """
995 995 if not whitelist:
996 996 from rhodecode import CONFIG
997 997 whitelist = aslist(
998 998 CONFIG.get('api_access_controllers_whitelist'), sep=',')
999 999 # backward compat translation
1000 1000 compat = {
1001 1001 # old controller, new VIEW
1002 1002 'ChangesetController:*': 'RepoCommitsView:*',
1003 1003 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1004 1004 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1005 1005 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1006 1006 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1007 1007 'GistsController:*': 'GistView:*',
1008 1008 }
1009 1009
1010 1010 log.debug(
1011 1011 'Allowed views for AUTH TOKEN access: %s', whitelist)
1012 1012 auth_token_access_valid = False
1013 1013
1014 1014 for entry in whitelist:
1015 1015 token_match = True
1016 1016 if entry in compat:
1017 1017 # translate from old Controllers to Pyramid Views
1018 1018 entry = compat[entry]
1019 1019
1020 1020 if '@' in entry:
1021 1021 # specific AuthToken
1022 1022 entry, allowed_token = entry.split('@', 1)
1023 1023 token_match = auth_token == allowed_token
1024 1024
1025 1025 if fnmatch.fnmatch(view_name, entry) and token_match:
1026 1026 auth_token_access_valid = True
1027 1027 break
1028 1028
1029 1029 if auth_token_access_valid:
1030 1030 log.debug('view: `%s` matches entry in whitelist: %s',
1031 1031 view_name, whitelist)
1032 1032
1033 1033 else:
1034 1034 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1035 1035 % (view_name, whitelist))
1036 1036 if auth_token:
1037 1037 # if we use auth token key and don't have access it's a warning
1038 1038 log.warning(msg)
1039 1039 else:
1040 1040 log.debug(msg)
1041 1041
1042 1042 return auth_token_access_valid
1043 1043
1044 1044
1045 1045 class AuthUser(object):
1046 1046 """
1047 1047 A simple object that handles all attributes of user in RhodeCode
1048 1048
1049 1049 It does lookup based on API key,given user, or user present in session
1050 1050 Then it fills all required information for such user. It also checks if
1051 1051 anonymous access is enabled and if so, it returns default user as logged in
1052 1052 """
1053 1053 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1054 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1055 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1054 1056
1055 1057 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1056 1058
1057 1059 self.user_id = user_id
1058 1060 self._api_key = api_key
1059 1061
1060 1062 self.api_key = None
1061 1063 self.username = username
1062 1064 self.ip_addr = ip_addr
1063 1065 self.name = ''
1064 1066 self.lastname = ''
1065 1067 self.first_name = ''
1066 1068 self.last_name = ''
1067 1069 self.email = ''
1068 1070 self.is_authenticated = False
1069 1071 self.admin = False
1070 1072 self.inherit_default_permissions = False
1071 1073 self.password = ''
1072 1074
1073 1075 self.anonymous_user = None # propagated on propagate_data
1074 1076 self.propagate_data()
1075 1077 self._instance = None
1076 1078 self._permissions_scoped_cache = {} # used to bind scoped calculation
1077 1079
1078 1080 @LazyProperty
1079 1081 def permissions(self):
1080 1082 return self.get_perms(user=self, cache=None)
1081 1083
1082 1084 @LazyProperty
1083 1085 def permissions_safe(self):
1084 1086 """
1085 1087 Filtered permissions excluding not allowed repositories
1086 1088 """
1087 1089 perms = self.get_perms(user=self, cache=None)
1088 1090
1089 1091 perms['repositories'] = {
1090 1092 k: v for k, v in perms['repositories'].items()
1091 1093 if v != 'repository.none'}
1092 1094 perms['repositories_groups'] = {
1093 1095 k: v for k, v in perms['repositories_groups'].items()
1094 1096 if v != 'group.none'}
1095 1097 perms['user_groups'] = {
1096 1098 k: v for k, v in perms['user_groups'].items()
1097 1099 if v != 'usergroup.none'}
1098 1100 perms['repository_branches'] = {
1099 1101 k: v for k, v in perms['repository_branches'].iteritems()
1100 1102 if v != 'branch.none'}
1101 1103 return perms
1102 1104
1103 1105 @LazyProperty
1104 1106 def permissions_full_details(self):
1105 1107 return self.get_perms(
1106 1108 user=self, cache=None, calculate_super_admin=True)
1107 1109
1108 1110 def permissions_with_scope(self, scope):
1109 1111 """
1110 1112 Call the get_perms function with scoped data. The scope in that function
1111 1113 narrows the SQL calls to the given ID of objects resulting in fetching
1112 1114 Just particular permission we want to obtain. If scope is an empty dict
1113 1115 then it basically narrows the scope to GLOBAL permissions only.
1114 1116
1115 1117 :param scope: dict
1116 1118 """
1117 1119 if 'repo_name' in scope:
1118 1120 obj = Repository.get_by_repo_name(scope['repo_name'])
1119 1121 if obj:
1120 1122 scope['repo_id'] = obj.repo_id
1121 1123 _scope = collections.OrderedDict()
1122 1124 _scope['repo_id'] = -1
1123 1125 _scope['user_group_id'] = -1
1124 1126 _scope['repo_group_id'] = -1
1125 1127
1126 1128 for k in sorted(scope.keys()):
1127 1129 _scope[k] = scope[k]
1128 1130
1129 1131 # store in cache to mimic how the @LazyProperty works,
1130 1132 # the difference here is that we use the unique key calculated
1131 1133 # from params and values
1132 1134 return self.get_perms(user=self, cache=None, scope=_scope)
1133 1135
1134 1136 def get_instance(self):
1135 1137 return User.get(self.user_id)
1136 1138
1137 1139 def propagate_data(self):
1138 1140 """
1139 1141 Fills in user data and propagates values to this instance. Maps fetched
1140 1142 user attributes to this class instance attributes
1141 1143 """
1142 1144 log.debug('AuthUser: starting data propagation for new potential user')
1143 1145 user_model = UserModel()
1144 1146 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1145 1147 is_user_loaded = False
1146 1148
1147 1149 # lookup by userid
1148 1150 if self.user_id is not None and self.user_id != anon_user.user_id:
1149 1151 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1150 1152 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1151 1153
1152 1154 # try go get user by api key
1153 1155 elif self._api_key and self._api_key != anon_user.api_key:
1154 1156 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1155 1157 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1156 1158
1157 1159 # lookup by username
1158 1160 elif self.username:
1159 1161 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1160 1162 is_user_loaded = user_model.fill_data(self, username=self.username)
1161 1163 else:
1162 1164 log.debug('No data in %s that could been used to log in', self)
1163 1165
1164 1166 if not is_user_loaded:
1165 1167 log.debug(
1166 1168 'Failed to load user. Fallback to default user %s', anon_user)
1167 1169 # if we cannot authenticate user try anonymous
1168 1170 if anon_user.active:
1169 1171 log.debug('default user is active, using it as a session user')
1170 1172 user_model.fill_data(self, user_id=anon_user.user_id)
1171 1173 # then we set this user is logged in
1172 1174 self.is_authenticated = True
1173 1175 else:
1174 1176 log.debug('default user is NOT active')
1175 1177 # in case of disabled anonymous user we reset some of the
1176 1178 # parameters so such user is "corrupted", skipping the fill_data
1177 1179 for attr in ['user_id', 'username', 'admin', 'active']:
1178 1180 setattr(self, attr, None)
1179 1181 self.is_authenticated = False
1180 1182
1181 1183 if not self.username:
1182 1184 self.username = 'None'
1183 1185
1184 1186 log.debug('AuthUser: propagated user is now %s', self)
1185 1187
1186 1188 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1187 1189 calculate_super_admin=False, cache=None):
1188 1190 """
1189 1191 Fills user permission attribute with permissions taken from database
1190 1192 works for permissions given for repositories, and for permissions that
1191 1193 are granted to groups
1192 1194
1193 1195 :param user: instance of User object from database
1194 1196 :param explicit: In case there are permissions both for user and a group
1195 1197 that user is part of, explicit flag will defiine if user will
1196 1198 explicitly override permissions from group, if it's False it will
1197 1199 make decision based on the algo
1198 1200 :param algo: algorithm to decide what permission should be choose if
1199 1201 it's multiple defined, eg user in two different groups. It also
1200 1202 decides if explicit flag is turned off how to specify the permission
1201 1203 for case when user is in a group + have defined separate permission
1202 1204 :param calculate_super_admin: calculate permissions for super-admin in the
1203 1205 same way as for regular user without speedups
1204 1206 :param cache: Use caching for calculation, None = let the cache backend decide
1205 1207 """
1206 1208 user_id = user.user_id
1207 1209 user_is_admin = user.is_admin
1208 1210
1209 1211 # inheritance of global permissions like create repo/fork repo etc
1210 1212 user_inherit_default_permissions = user.inherit_default_permissions
1211 1213
1212 1214 cache_seconds = safe_int(
1213 1215 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1214 1216
1215 1217 if cache is None:
1216 1218 # let the backend cache decide
1217 1219 cache_on = cache_seconds > 0
1218 1220 else:
1219 1221 cache_on = cache
1220 1222
1221 1223 log.debug(
1222 1224 'Computing PERMISSION tree for user %s scope `%s` '
1223 1225 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1224 1226
1225 1227 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1226 1228 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1227 1229
1228 1230 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1229 1231 condition=cache_on)
1230 1232 def compute_perm_tree(cache_name,
1231 1233 user_id, scope, user_is_admin,user_inherit_default_permissions,
1232 1234 explicit, algo, calculate_super_admin):
1233 1235 return _cached_perms_data(
1234 1236 user_id, scope, user_is_admin, user_inherit_default_permissions,
1235 1237 explicit, algo, calculate_super_admin)
1236 1238
1237 1239 start = time.time()
1238 1240 result = compute_perm_tree(
1239 1241 'permissions', user_id, scope, user_is_admin,
1240 1242 user_inherit_default_permissions, explicit, algo,
1241 1243 calculate_super_admin)
1242 1244
1243 1245 result_repr = []
1244 1246 for k in result:
1245 1247 result_repr.append((k, len(result[k])))
1246 1248 total = time.time() - start
1247 1249 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1248 1250 user, total, result_repr)
1249 1251
1250 1252 return result
1251 1253
1252 1254 @property
1253 1255 def is_default(self):
1254 1256 return self.username == User.DEFAULT_USER
1255 1257
1256 1258 @property
1257 1259 def is_admin(self):
1258 1260 return self.admin
1259 1261
1260 1262 @property
1261 1263 def is_user_object(self):
1262 1264 return self.user_id is not None
1263 1265
1264 1266 @property
1265 1267 def repositories_admin(self):
1266 1268 """
1267 1269 Returns list of repositories you're an admin of
1268 1270 """
1269 1271 return [
1270 1272 x[0] for x in self.permissions['repositories'].items()
1271 1273 if x[1] == 'repository.admin']
1272 1274
1273 1275 @property
1274 1276 def repository_groups_admin(self):
1275 1277 """
1276 1278 Returns list of repository groups you're an admin of
1277 1279 """
1278 1280 return [
1279 1281 x[0] for x in self.permissions['repositories_groups'].items()
1280 1282 if x[1] == 'group.admin']
1281 1283
1282 1284 @property
1283 1285 def user_groups_admin(self):
1284 1286 """
1285 1287 Returns list of user groups you're an admin of
1286 1288 """
1287 1289 return [
1288 1290 x[0] for x in self.permissions['user_groups'].items()
1289 1291 if x[1] == 'usergroup.admin']
1290 1292
1291 1293 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1292 1294 if not perms:
1293 1295 perms = ['repository.read', 'repository.write', 'repository.admin']
1294 1296 allowed_ids = []
1295 1297 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1296 1298 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1297 1299 if prefix_filter and not k.startswith(prefix_filter):
1298 1300 continue
1299 1301 if perm in perms:
1300 1302 allowed_ids.append(obj_id)
1301 1303 return allowed_ids
1302 1304
1303 1305 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 1306 """
1305 1307 Returns list of repository ids that user have access to based on given
1306 1308 perms. The cache flag should be only used in cases that are used for
1307 1309 display purposes, NOT IN ANY CASE for permission checks.
1308 1310 """
1309 1311 from rhodecode.model.scm import RepoList
1310 1312 if not perms:
1311 1313 perms = ['repository.read', 'repository.write', 'repository.admin']
1312 1314
1313 1315 def _cached_repo_acl(user_id, perm_def, _name_filter):
1314 1316 qry = Repository.query()
1315 1317 if _name_filter:
1316 1318 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1317 1319 qry = qry.filter(
1318 1320 Repository.repo_name.ilike(ilike_expression))
1319 1321
1320 1322 return [x.repo_id for x in
1321 1323 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1322 1324
1323 1325 return _cached_repo_acl(self.user_id, perms, name_filter)
1324 1326
1325 1327 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1326 1328 if not perms:
1327 1329 perms = ['group.read', 'group.write', 'group.admin']
1328 1330 allowed_ids = []
1329 1331 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1330 1332 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1331 1333 if prefix_filter and not k.startswith(prefix_filter):
1332 1334 continue
1333 1335 if perm in perms:
1334 1336 allowed_ids.append(obj_id)
1335 1337 return allowed_ids
1336 1338
1337 1339 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1338 1340 """
1339 1341 Returns list of repository group ids that user have access to based on given
1340 1342 perms. The cache flag should be only used in cases that are used for
1341 1343 display purposes, NOT IN ANY CASE for permission checks.
1342 1344 """
1343 1345 from rhodecode.model.scm import RepoGroupList
1344 1346 if not perms:
1345 1347 perms = ['group.read', 'group.write', 'group.admin']
1346 1348
1347 1349 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1348 1350 qry = RepoGroup.query()
1349 1351 if _name_filter:
1350 1352 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1351 1353 qry = qry.filter(
1352 1354 RepoGroup.group_name.ilike(ilike_expression))
1353 1355
1354 1356 return [x.group_id for x in
1355 1357 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1356 1358
1357 1359 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1358 1360
1359 1361 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1360 1362 if not perms:
1361 1363 perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1362 1364 allowed_ids = []
1363 1365 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1364 1366 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1365 1367 if perm in perms:
1366 1368 allowed_ids.append(obj_id)
1367 1369 return allowed_ids
1368 1370
1369 1371 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1370 1372 """
1371 1373 Returns list of user group ids that user have access to based on given
1372 1374 perms. The cache flag should be only used in cases that are used for
1373 1375 display purposes, NOT IN ANY CASE for permission checks.
1374 1376 """
1375 1377 from rhodecode.model.scm import UserGroupList
1376 1378 if not perms:
1377 1379 perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1378 1380
1379 1381 def _cached_user_group_acl(user_id, perm_def, name_filter):
1380 1382 qry = UserGroup.query()
1381 1383 if name_filter:
1382 1384 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1383 1385 qry = qry.filter(
1384 1386 UserGroup.users_group_name.ilike(ilike_expression))
1385 1387
1386 1388 return [x.users_group_id for x in
1387 1389 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1388 1390
1389 1391 return _cached_user_group_acl(self.user_id, perms, name_filter)
1390 1392
1391 1393 @property
1392 1394 def ip_allowed(self):
1393 1395 """
1394 1396 Checks if ip_addr used in constructor is allowed from defined list of
1395 1397 allowed ip_addresses for user
1396 1398
1397 1399 :returns: boolean, True if ip is in allowed ip range
1398 1400 """
1399 1401 # check IP
1400 1402 inherit = self.inherit_default_permissions
1401 1403 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1402 1404 inherit_from_default=inherit)
1403 1405 @property
1404 1406 def personal_repo_group(self):
1405 1407 return RepoGroup.get_user_personal_repo_group(self.user_id)
1406 1408
1407 1409 @LazyProperty
1408 1410 def feed_token(self):
1409 1411 return self.get_instance().feed_token
1410 1412
1411 1413 @LazyProperty
1412 1414 def artifact_token(self):
1413 1415 return self.get_instance().artifact_token
1414 1416
1415 1417 @classmethod
1416 1418 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1417 1419 allowed_ips = AuthUser.get_allowed_ips(
1418 1420 user_id, cache=True, inherit_from_default=inherit_from_default)
1419 1421 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1420 1422 log.debug('IP:%s for user %s is in range of %s',
1421 1423 ip_addr, user_id, allowed_ips)
1422 1424 return True
1423 1425 else:
1424 1426 log.info('Access for IP:%s forbidden for user %s, '
1425 1427 'not in %s', ip_addr, user_id, allowed_ips)
1426 1428 return False
1427 1429
1428 1430 def get_branch_permissions(self, repo_name, perms=None):
1429 1431 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1430 1432 branch_perms = perms.get('repository_branches', {})
1431 1433 if not branch_perms:
1432 1434 return {}
1433 1435 repo_branch_perms = branch_perms.get(repo_name)
1434 1436 return repo_branch_perms or {}
1435 1437
1436 1438 def get_rule_and_branch_permission(self, repo_name, branch_name):
1437 1439 """
1438 1440 Check if this AuthUser has defined any permissions for branches. If any of
1439 1441 the rules match in order, we return the matching permissions
1440 1442 """
1441 1443
1442 1444 rule = default_perm = ''
1443 1445
1444 1446 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1445 1447 if not repo_branch_perms:
1446 1448 return rule, default_perm
1447 1449
1448 1450 # now calculate the permissions
1449 1451 for pattern, branch_perm in repo_branch_perms.items():
1450 1452 if fnmatch.fnmatch(branch_name, pattern):
1451 1453 rule = '`{}`=>{}'.format(pattern, branch_perm)
1452 1454 return rule, branch_perm
1453 1455
1454 1456 return rule, default_perm
1455 1457
1456 1458 def __repr__(self):
1457 1459 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1458 1460 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1459 1461
1460 1462 def set_authenticated(self, authenticated=True):
1461 1463 if self.user_id != self.anonymous_user.user_id:
1462 1464 self.is_authenticated = authenticated
1463 1465
1464 1466 def get_cookie_store(self):
1465 1467 return {
1466 1468 'username': self.username,
1467 1469 'password': md5(self.password or ''),
1468 1470 'user_id': self.user_id,
1469 1471 'is_authenticated': self.is_authenticated
1470 1472 }
1471 1473
1472 1474 @classmethod
1473 1475 def from_cookie_store(cls, cookie_store):
1474 1476 """
1475 1477 Creates AuthUser from a cookie store
1476 1478
1477 1479 :param cls:
1478 1480 :param cookie_store:
1479 1481 """
1480 1482 user_id = cookie_store.get('user_id')
1481 1483 username = cookie_store.get('username')
1482 1484 api_key = cookie_store.get('api_key')
1483 1485 return AuthUser(user_id, api_key, username)
1484 1486
1485 1487 @classmethod
1486 1488 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1487 1489 _set = set()
1488 1490
1489 1491 if inherit_from_default:
1490 1492 def_user_id = User.get_default_user(cache=True).user_id
1491 1493 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1492 1494 if cache:
1493 1495 default_ips = default_ips.options(
1494 1496 FromCache("sql_cache_short", "get_user_ips_default"))
1495 1497
1496 1498 # populate from default user
1497 1499 for ip in default_ips:
1498 1500 try:
1499 1501 _set.add(ip.ip_addr)
1500 1502 except ObjectDeletedError:
1501 1503 # since we use heavy caching sometimes it happens that
1502 1504 # we get deleted objects here, we just skip them
1503 1505 pass
1504 1506
1505 1507 # NOTE:(marcink) we don't want to load any rules for empty
1506 1508 # user_id which is the case of access of non logged users when anonymous
1507 1509 # access is disabled
1508 1510 user_ips = []
1509 1511 if user_id:
1510 1512 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1511 1513 if cache:
1512 1514 user_ips = user_ips.options(
1513 1515 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1514 1516
1515 1517 for ip in user_ips:
1516 1518 try:
1517 1519 _set.add(ip.ip_addr)
1518 1520 except ObjectDeletedError:
1519 1521 # since we use heavy caching sometimes it happens that we get
1520 1522 # deleted objects here, we just skip them
1521 1523 pass
1522 1524 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1523 1525
1524 1526
1525 1527 def set_available_permissions(settings):
1526 1528 """
1527 1529 This function will propagate pyramid settings with all available defined
1528 1530 permission given in db. We don't want to check each time from db for new
1529 1531 permissions since adding a new permission also requires application restart
1530 1532 ie. to decorate new views with the newly created permission
1531 1533
1532 1534 :param settings: current pyramid registry.settings
1533 1535
1534 1536 """
1535 1537 log.debug('auth: getting information about all available permissions')
1536 1538 try:
1537 1539 sa = meta.Session
1538 1540 all_perms = sa.query(Permission).all()
1539 1541 settings.setdefault('available_permissions',
1540 1542 [x.permission_name for x in all_perms])
1541 1543 log.debug('auth: set available permissions')
1542 1544 except Exception:
1543 1545 log.exception('Failed to fetch permissions from the database.')
1544 1546 raise
1545 1547
1546 1548
1547 1549 def get_csrf_token(session, force_new=False, save_if_missing=True):
1548 1550 """
1549 1551 Return the current authentication token, creating one if one doesn't
1550 1552 already exist and the save_if_missing flag is present.
1551 1553
1552 1554 :param session: pass in the pyramid session, else we use the global ones
1553 1555 :param force_new: force to re-generate the token and store it in session
1554 1556 :param save_if_missing: save the newly generated token if it's missing in
1555 1557 session
1556 1558 """
1557 1559 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1558 1560 # from pyramid.csrf import get_csrf_token
1559 1561
1560 1562 if (csrf_token_key not in session and save_if_missing) or force_new:
1561 1563 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1562 1564 session[csrf_token_key] = token
1563 1565 if hasattr(session, 'save'):
1564 1566 session.save()
1565 1567 return session.get(csrf_token_key)
1566 1568
1567 1569
1568 1570 def get_request(perm_class_instance):
1569 1571 from pyramid.threadlocal import get_current_request
1570 1572 pyramid_request = get_current_request()
1571 1573 return pyramid_request
1572 1574
1573 1575
1574 1576 # CHECK DECORATORS
1575 1577 class CSRFRequired(object):
1576 1578 """
1577 1579 Decorator for authenticating a form
1578 1580
1579 1581 This decorator uses an authorization token stored in the client's
1580 1582 session for prevention of certain Cross-site request forgery (CSRF)
1581 1583 attacks (See
1582 1584 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1583 1585 information).
1584 1586
1585 1587 For use with the ``secure_form`` helper functions.
1586 1588
1587 1589 """
1588 1590 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1589 1591 self.token = token
1590 1592 self.header = header
1591 1593 self.except_methods = except_methods or []
1592 1594
1593 1595 def __call__(self, func):
1594 1596 return get_cython_compat_decorator(self.__wrapper, func)
1595 1597
1596 1598 def _get_csrf(self, _request):
1597 1599 return _request.POST.get(self.token, _request.headers.get(self.header))
1598 1600
1599 1601 def check_csrf(self, _request, cur_token):
1600 1602 supplied_token = self._get_csrf(_request)
1601 1603 return supplied_token and supplied_token == cur_token
1602 1604
1603 1605 def _get_request(self):
1604 1606 return get_request(self)
1605 1607
1606 1608 def __wrapper(self, func, *fargs, **fkwargs):
1607 1609 request = self._get_request()
1608 1610
1609 1611 if request.method in self.except_methods:
1610 1612 return func(*fargs, **fkwargs)
1611 1613
1612 1614 cur_token = get_csrf_token(request.session, save_if_missing=False)
1613 1615 if self.check_csrf(request, cur_token):
1614 1616 if request.POST.get(self.token):
1615 1617 del request.POST[self.token]
1616 1618 return func(*fargs, **fkwargs)
1617 1619 else:
1618 1620 reason = 'token-missing'
1619 1621 supplied_token = self._get_csrf(request)
1620 1622 if supplied_token and cur_token != supplied_token:
1621 1623 reason = 'token-mismatch [%s:%s]' % (
1622 1624 cur_token or ''[:6], supplied_token or ''[:6])
1623 1625
1624 1626 csrf_message = \
1625 1627 ("Cross-site request forgery detected, request denied. See "
1626 1628 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1627 1629 "more information.")
1628 1630 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1629 1631 'REMOTE_ADDR:%s, HEADERS:%s' % (
1630 1632 request, reason, request.remote_addr, request.headers))
1631 1633
1632 1634 raise HTTPForbidden(explanation=csrf_message)
1633 1635
1634 1636
1635 1637 class LoginRequired(object):
1636 1638 """
1637 1639 Must be logged in to execute this function else
1638 1640 redirect to login page
1639 1641
1640 1642 :param api_access: if enabled this checks only for valid auth token
1641 1643 and grants access based on valid token
1642 1644 """
1643 1645 def __init__(self, auth_token_access=None):
1644 1646 self.auth_token_access = auth_token_access
1645 1647 if self.auth_token_access:
1646 1648 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1647 1649 if not valid_type:
1648 1650 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1649 1651 UserApiKeys.ROLES, auth_token_access))
1650 1652
1651 1653 def __call__(self, func):
1652 1654 return get_cython_compat_decorator(self.__wrapper, func)
1653 1655
1654 1656 def _get_request(self):
1655 1657 return get_request(self)
1656 1658
1657 1659 def __wrapper(self, func, *fargs, **fkwargs):
1658 1660 from rhodecode.lib import helpers as h
1659 1661 cls = fargs[0]
1660 1662 user = cls._rhodecode_user
1661 1663 request = self._get_request()
1662 1664 _ = request.translate
1663 1665
1664 1666 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1665 1667 log.debug('Starting login restriction checks for user: %s', user)
1666 1668 # check if our IP is allowed
1667 1669 ip_access_valid = True
1668 1670 if not user.ip_allowed:
1669 1671 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1670 1672 category='warning')
1671 1673 ip_access_valid = False
1672 1674
1673 1675 # we used stored token that is extract from GET or URL param (if any)
1674 1676 _auth_token = request.user_auth_token
1675 1677
1676 1678 # check if we used an AUTH_TOKEN and it's a valid one
1677 1679 # defined white-list of controllers which API access will be enabled
1678 1680 whitelist = None
1679 1681 if self.auth_token_access:
1680 1682 # since this location is allowed by @LoginRequired decorator it's our
1681 1683 # only whitelist
1682 1684 whitelist = [loc]
1683 1685 auth_token_access_valid = allowed_auth_token_access(
1684 1686 loc, whitelist=whitelist, auth_token=_auth_token)
1685 1687
1686 1688 # explicit controller is enabled or API is in our whitelist
1687 1689 if auth_token_access_valid:
1688 1690 log.debug('Checking AUTH TOKEN access for %s', cls)
1689 1691 db_user = user.get_instance()
1690 1692
1691 1693 if db_user:
1692 1694 if self.auth_token_access:
1693 1695 roles = self.auth_token_access
1694 1696 else:
1695 1697 roles = [UserApiKeys.ROLE_HTTP]
1696 1698 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1697 1699 db_user, roles)
1698 1700 token_match = db_user.authenticate_by_token(
1699 1701 _auth_token, roles=roles)
1700 1702 else:
1701 1703 log.debug('Unable to fetch db instance for auth user: %s', user)
1702 1704 token_match = False
1703 1705
1704 1706 if _auth_token and token_match:
1705 1707 auth_token_access_valid = True
1706 1708 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1707 1709 else:
1708 1710 auth_token_access_valid = False
1709 1711 if not _auth_token:
1710 1712 log.debug("AUTH TOKEN *NOT* present in request")
1711 1713 else:
1712 1714 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1713 1715
1714 1716 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1715 1717 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1716 1718 else 'AUTH_TOKEN_AUTH'
1717 1719
1718 1720 if ip_access_valid and (
1719 1721 user.is_authenticated or auth_token_access_valid):
1720 1722 log.info('user %s authenticating with:%s IS authenticated on func %s',
1721 1723 user, reason, loc)
1722 1724
1723 1725 return func(*fargs, **fkwargs)
1724 1726 else:
1725 1727 log.warning(
1726 1728 'user %s authenticating with:%s NOT authenticated on '
1727 1729 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1728 1730 user, reason, loc, ip_access_valid, auth_token_access_valid)
1729 1731 # we preserve the get PARAM
1730 1732 came_from = get_came_from(request)
1731 1733
1732 1734 log.debug('redirecting to login page with %s', came_from)
1733 1735 raise HTTPFound(
1734 1736 h.route_path('login', _query={'came_from': came_from}))
1735 1737
1736 1738
1737 1739 class NotAnonymous(object):
1738 1740 """
1739 1741 Must be logged in to execute this function else
1740 1742 redirect to login page
1741 1743 """
1742 1744
1743 1745 def __call__(self, func):
1744 1746 return get_cython_compat_decorator(self.__wrapper, func)
1745 1747
1746 1748 def _get_request(self):
1747 1749 return get_request(self)
1748 1750
1749 1751 def __wrapper(self, func, *fargs, **fkwargs):
1750 1752 import rhodecode.lib.helpers as h
1751 1753 cls = fargs[0]
1752 1754 self.user = cls._rhodecode_user
1753 1755 request = self._get_request()
1754 1756 _ = request.translate
1755 1757 log.debug('Checking if user is not anonymous @%s', cls)
1756 1758
1757 1759 anonymous = self.user.username == User.DEFAULT_USER
1758 1760
1759 1761 if anonymous:
1760 1762 came_from = get_came_from(request)
1761 1763 h.flash(_('You need to be a registered user to '
1762 1764 'perform this action'),
1763 1765 category='warning')
1764 1766 raise HTTPFound(
1765 1767 h.route_path('login', _query={'came_from': came_from}))
1766 1768 else:
1767 1769 return func(*fargs, **fkwargs)
1768 1770
1769 1771
1770 1772 class PermsDecorator(object):
1771 1773 """
1772 1774 Base class for controller decorators, we extract the current user from
1773 1775 the class itself, which has it stored in base controllers
1774 1776 """
1775 1777
1776 1778 def __init__(self, *required_perms):
1777 1779 self.required_perms = set(required_perms)
1778 1780
1779 1781 def __call__(self, func):
1780 1782 return get_cython_compat_decorator(self.__wrapper, func)
1781 1783
1782 1784 def _get_request(self):
1783 1785 return get_request(self)
1784 1786
1785 1787 def __wrapper(self, func, *fargs, **fkwargs):
1786 1788 import rhodecode.lib.helpers as h
1787 1789 cls = fargs[0]
1788 1790 _user = cls._rhodecode_user
1789 1791 request = self._get_request()
1790 1792 _ = request.translate
1791 1793
1792 1794 log.debug('checking %s permissions %s for %s %s',
1793 1795 self.__class__.__name__, self.required_perms, cls, _user)
1794 1796
1795 1797 if self.check_permissions(_user):
1796 1798 log.debug('Permission granted for %s %s', cls, _user)
1797 1799 return func(*fargs, **fkwargs)
1798 1800
1799 1801 else:
1800 1802 log.debug('Permission denied for %s %s', cls, _user)
1801 1803 anonymous = _user.username == User.DEFAULT_USER
1802 1804
1803 1805 if anonymous:
1804 1806 came_from = get_came_from(self._get_request())
1805 1807 h.flash(_('You need to be signed in to view this page'),
1806 1808 category='warning')
1807 1809 raise HTTPFound(
1808 1810 h.route_path('login', _query={'came_from': came_from}))
1809 1811
1810 1812 else:
1811 1813 # redirect with 404 to prevent resource discovery
1812 1814 raise HTTPNotFound()
1813 1815
1814 1816 def check_permissions(self, user):
1815 1817 """Dummy function for overriding"""
1816 1818 raise NotImplementedError(
1817 1819 'You have to write this function in child class')
1818 1820
1819 1821
1820 1822 class HasPermissionAllDecorator(PermsDecorator):
1821 1823 """
1822 1824 Checks for access permission for all given predicates. All of them
1823 1825 have to be meet in order to fulfill the request
1824 1826 """
1825 1827
1826 1828 def check_permissions(self, user):
1827 1829 perms = user.permissions_with_scope({})
1828 1830 if self.required_perms.issubset(perms['global']):
1829 1831 return True
1830 1832 return False
1831 1833
1832 1834
1833 1835 class HasPermissionAnyDecorator(PermsDecorator):
1834 1836 """
1835 1837 Checks for access permission for any of given predicates. In order to
1836 1838 fulfill the request any of predicates must be meet
1837 1839 """
1838 1840
1839 1841 def check_permissions(self, user):
1840 1842 perms = user.permissions_with_scope({})
1841 1843 if self.required_perms.intersection(perms['global']):
1842 1844 return True
1843 1845 return False
1844 1846
1845 1847
1846 1848 class HasRepoPermissionAllDecorator(PermsDecorator):
1847 1849 """
1848 1850 Checks for access permission for all given predicates for specific
1849 1851 repository. All of them have to be meet in order to fulfill the request
1850 1852 """
1851 1853 def _get_repo_name(self):
1852 1854 _request = self._get_request()
1853 1855 return get_repo_slug(_request)
1854 1856
1855 1857 def check_permissions(self, user):
1856 1858 perms = user.permissions
1857 1859 repo_name = self._get_repo_name()
1858 1860
1859 1861 try:
1860 1862 user_perms = {perms['repositories'][repo_name]}
1861 1863 except KeyError:
1862 1864 log.debug('cannot locate repo with name: `%s` in permissions defs',
1863 1865 repo_name)
1864 1866 return False
1865 1867
1866 1868 log.debug('checking `%s` permissions for repo `%s`',
1867 1869 user_perms, repo_name)
1868 1870 if self.required_perms.issubset(user_perms):
1869 1871 return True
1870 1872 return False
1871 1873
1872 1874
1873 1875 class HasRepoPermissionAnyDecorator(PermsDecorator):
1874 1876 """
1875 1877 Checks for access permission for any of given predicates for specific
1876 1878 repository. In order to fulfill the request any of predicates must be meet
1877 1879 """
1878 1880 def _get_repo_name(self):
1879 1881 _request = self._get_request()
1880 1882 return get_repo_slug(_request)
1881 1883
1882 1884 def check_permissions(self, user):
1883 1885 perms = user.permissions
1884 1886 repo_name = self._get_repo_name()
1885 1887
1886 1888 try:
1887 1889 user_perms = {perms['repositories'][repo_name]}
1888 1890 except KeyError:
1889 1891 log.debug(
1890 1892 'cannot locate repo with name: `%s` in permissions defs',
1891 1893 repo_name)
1892 1894 return False
1893 1895
1894 1896 log.debug('checking `%s` permissions for repo `%s`',
1895 1897 user_perms, repo_name)
1896 1898 if self.required_perms.intersection(user_perms):
1897 1899 return True
1898 1900 return False
1899 1901
1900 1902
1901 1903 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1902 1904 """
1903 1905 Checks for access permission for all given predicates for specific
1904 1906 repository group. All of them have to be meet in order to
1905 1907 fulfill the request
1906 1908 """
1907 1909 def _get_repo_group_name(self):
1908 1910 _request = self._get_request()
1909 1911 return get_repo_group_slug(_request)
1910 1912
1911 1913 def check_permissions(self, user):
1912 1914 perms = user.permissions
1913 1915 group_name = self._get_repo_group_name()
1914 1916 try:
1915 1917 user_perms = {perms['repositories_groups'][group_name]}
1916 1918 except KeyError:
1917 1919 log.debug(
1918 1920 'cannot locate repo group with name: `%s` in permissions defs',
1919 1921 group_name)
1920 1922 return False
1921 1923
1922 1924 log.debug('checking `%s` permissions for repo group `%s`',
1923 1925 user_perms, group_name)
1924 1926 if self.required_perms.issubset(user_perms):
1925 1927 return True
1926 1928 return False
1927 1929
1928 1930
1929 1931 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1930 1932 """
1931 1933 Checks for access permission for any of given predicates for specific
1932 1934 repository group. In order to fulfill the request any
1933 1935 of predicates must be met
1934 1936 """
1935 1937 def _get_repo_group_name(self):
1936 1938 _request = self._get_request()
1937 1939 return get_repo_group_slug(_request)
1938 1940
1939 1941 def check_permissions(self, user):
1940 1942 perms = user.permissions
1941 1943 group_name = self._get_repo_group_name()
1942 1944
1943 1945 try:
1944 1946 user_perms = {perms['repositories_groups'][group_name]}
1945 1947 except KeyError:
1946 1948 log.debug(
1947 1949 'cannot locate repo group with name: `%s` in permissions defs',
1948 1950 group_name)
1949 1951 return False
1950 1952
1951 1953 log.debug('checking `%s` permissions for repo group `%s`',
1952 1954 user_perms, group_name)
1953 1955 if self.required_perms.intersection(user_perms):
1954 1956 return True
1955 1957 return False
1956 1958
1957 1959
1958 1960 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1959 1961 """
1960 1962 Checks for access permission for all given predicates for specific
1961 1963 user group. All of them have to be meet in order to fulfill the request
1962 1964 """
1963 1965 def _get_user_group_name(self):
1964 1966 _request = self._get_request()
1965 1967 return get_user_group_slug(_request)
1966 1968
1967 1969 def check_permissions(self, user):
1968 1970 perms = user.permissions
1969 1971 group_name = self._get_user_group_name()
1970 1972 try:
1971 1973 user_perms = {perms['user_groups'][group_name]}
1972 1974 except KeyError:
1973 1975 return False
1974 1976
1975 1977 if self.required_perms.issubset(user_perms):
1976 1978 return True
1977 1979 return False
1978 1980
1979 1981
1980 1982 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1981 1983 """
1982 1984 Checks for access permission for any of given predicates for specific
1983 1985 user group. In order to fulfill the request any of predicates must be meet
1984 1986 """
1985 1987 def _get_user_group_name(self):
1986 1988 _request = self._get_request()
1987 1989 return get_user_group_slug(_request)
1988 1990
1989 1991 def check_permissions(self, user):
1990 1992 perms = user.permissions
1991 1993 group_name = self._get_user_group_name()
1992 1994 try:
1993 1995 user_perms = {perms['user_groups'][group_name]}
1994 1996 except KeyError:
1995 1997 return False
1996 1998
1997 1999 if self.required_perms.intersection(user_perms):
1998 2000 return True
1999 2001 return False
2000 2002
2001 2003
2002 2004 # CHECK FUNCTIONS
2003 2005 class PermsFunction(object):
2004 2006 """Base function for other check functions"""
2005 2007
2006 2008 def __init__(self, *perms):
2007 2009 self.required_perms = set(perms)
2008 2010 self.repo_name = None
2009 2011 self.repo_group_name = None
2010 2012 self.user_group_name = None
2011 2013
2012 2014 def __bool__(self):
2013 2015 frame = inspect.currentframe()
2014 2016 stack_trace = traceback.format_stack(frame)
2015 2017 log.error('Checking bool value on a class instance of perm '
2016 2018 'function is not allowed: %s', ''.join(stack_trace))
2017 2019 # rather than throwing errors, here we always return False so if by
2018 2020 # accident someone checks truth for just an instance it will always end
2019 2021 # up in returning False
2020 2022 return False
2021 2023 __nonzero__ = __bool__
2022 2024
2023 2025 def __call__(self, check_location='', user=None):
2024 2026 if not user:
2025 2027 log.debug('Using user attribute from global request')
2026 2028 request = self._get_request()
2027 2029 user = request.user
2028 2030
2029 2031 # init auth user if not already given
2030 2032 if not isinstance(user, AuthUser):
2031 2033 log.debug('Wrapping user %s into AuthUser', user)
2032 2034 user = AuthUser(user.user_id)
2033 2035
2034 2036 cls_name = self.__class__.__name__
2035 2037 check_scope = self._get_check_scope(cls_name)
2036 2038 check_location = check_location or 'unspecified location'
2037 2039
2038 2040 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2039 2041 self.required_perms, user, check_scope, check_location)
2040 2042 if not user:
2041 2043 log.warning('Empty user given for permission check')
2042 2044 return False
2043 2045
2044 2046 if self.check_permissions(user):
2045 2047 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2046 2048 check_scope, user, check_location)
2047 2049 return True
2048 2050
2049 2051 else:
2050 2052 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2051 2053 check_scope, user, check_location)
2052 2054 return False
2053 2055
2054 2056 def _get_request(self):
2055 2057 return get_request(self)
2056 2058
2057 2059 def _get_check_scope(self, cls_name):
2058 2060 return {
2059 2061 'HasPermissionAll': 'GLOBAL',
2060 2062 'HasPermissionAny': 'GLOBAL',
2061 2063 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2062 2064 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2063 2065 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2064 2066 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2065 2067 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2066 2068 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2067 2069 }.get(cls_name, '?:%s' % cls_name)
2068 2070
2069 2071 def check_permissions(self, user):
2070 2072 """Dummy function for overriding"""
2071 2073 raise Exception('You have to write this function in child class')
2072 2074
2073 2075
2074 2076 class HasPermissionAll(PermsFunction):
2075 2077 def check_permissions(self, user):
2076 2078 perms = user.permissions_with_scope({})
2077 2079 if self.required_perms.issubset(perms.get('global')):
2078 2080 return True
2079 2081 return False
2080 2082
2081 2083
2082 2084 class HasPermissionAny(PermsFunction):
2083 2085 def check_permissions(self, user):
2084 2086 perms = user.permissions_with_scope({})
2085 2087 if self.required_perms.intersection(perms.get('global')):
2086 2088 return True
2087 2089 return False
2088 2090
2089 2091
2090 2092 class HasRepoPermissionAll(PermsFunction):
2091 2093 def __call__(self, repo_name=None, check_location='', user=None):
2092 2094 self.repo_name = repo_name
2093 2095 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2094 2096
2095 2097 def _get_repo_name(self):
2096 2098 if not self.repo_name:
2097 2099 _request = self._get_request()
2098 2100 self.repo_name = get_repo_slug(_request)
2099 2101 return self.repo_name
2100 2102
2101 2103 def check_permissions(self, user):
2102 2104 self.repo_name = self._get_repo_name()
2103 2105 perms = user.permissions
2104 2106 try:
2105 2107 user_perms = {perms['repositories'][self.repo_name]}
2106 2108 except KeyError:
2107 2109 return False
2108 2110 if self.required_perms.issubset(user_perms):
2109 2111 return True
2110 2112 return False
2111 2113
2112 2114
2113 2115 class HasRepoPermissionAny(PermsFunction):
2114 2116 def __call__(self, repo_name=None, check_location='', user=None):
2115 2117 self.repo_name = repo_name
2116 2118 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2117 2119
2118 2120 def _get_repo_name(self):
2119 2121 if not self.repo_name:
2120 2122 _request = self._get_request()
2121 2123 self.repo_name = get_repo_slug(_request)
2122 2124 return self.repo_name
2123 2125
2124 2126 def check_permissions(self, user):
2125 2127 self.repo_name = self._get_repo_name()
2126 2128 perms = user.permissions
2127 2129 try:
2128 2130 user_perms = {perms['repositories'][self.repo_name]}
2129 2131 except KeyError:
2130 2132 return False
2131 2133 if self.required_perms.intersection(user_perms):
2132 2134 return True
2133 2135 return False
2134 2136
2135 2137
2136 2138 class HasRepoGroupPermissionAny(PermsFunction):
2137 2139 def __call__(self, group_name=None, check_location='', user=None):
2138 2140 self.repo_group_name = group_name
2139 2141 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2140 2142
2141 2143 def check_permissions(self, user):
2142 2144 perms = user.permissions
2143 2145 try:
2144 2146 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2145 2147 except KeyError:
2146 2148 return False
2147 2149 if self.required_perms.intersection(user_perms):
2148 2150 return True
2149 2151 return False
2150 2152
2151 2153
2152 2154 class HasRepoGroupPermissionAll(PermsFunction):
2153 2155 def __call__(self, group_name=None, check_location='', user=None):
2154 2156 self.repo_group_name = group_name
2155 2157 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2156 2158
2157 2159 def check_permissions(self, user):
2158 2160 perms = user.permissions
2159 2161 try:
2160 2162 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2161 2163 except KeyError:
2162 2164 return False
2163 2165 if self.required_perms.issubset(user_perms):
2164 2166 return True
2165 2167 return False
2166 2168
2167 2169
2168 2170 class HasUserGroupPermissionAny(PermsFunction):
2169 2171 def __call__(self, user_group_name=None, check_location='', user=None):
2170 2172 self.user_group_name = user_group_name
2171 2173 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2172 2174
2173 2175 def check_permissions(self, user):
2174 2176 perms = user.permissions
2175 2177 try:
2176 2178 user_perms = {perms['user_groups'][self.user_group_name]}
2177 2179 except KeyError:
2178 2180 return False
2179 2181 if self.required_perms.intersection(user_perms):
2180 2182 return True
2181 2183 return False
2182 2184
2183 2185
2184 2186 class HasUserGroupPermissionAll(PermsFunction):
2185 2187 def __call__(self, user_group_name=None, check_location='', user=None):
2186 2188 self.user_group_name = user_group_name
2187 2189 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2188 2190
2189 2191 def check_permissions(self, user):
2190 2192 perms = user.permissions
2191 2193 try:
2192 2194 user_perms = {perms['user_groups'][self.user_group_name]}
2193 2195 except KeyError:
2194 2196 return False
2195 2197 if self.required_perms.issubset(user_perms):
2196 2198 return True
2197 2199 return False
2198 2200
2199 2201
2200 2202 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2201 2203 class HasPermissionAnyMiddleware(object):
2202 2204 def __init__(self, *perms):
2203 2205 self.required_perms = set(perms)
2204 2206
2205 2207 def __call__(self, auth_user, repo_name):
2206 2208 # repo_name MUST be unicode, since we handle keys in permission
2207 2209 # dict by unicode
2208 2210 repo_name = safe_unicode(repo_name)
2209 2211 log.debug(
2210 2212 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2211 2213 self.required_perms, auth_user, repo_name)
2212 2214
2213 2215 if self.check_permissions(auth_user, repo_name):
2214 2216 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2215 2217 repo_name, auth_user, 'PermissionMiddleware')
2216 2218 return True
2217 2219
2218 2220 else:
2219 2221 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2220 2222 repo_name, auth_user, 'PermissionMiddleware')
2221 2223 return False
2222 2224
2223 2225 def check_permissions(self, user, repo_name):
2224 2226 perms = user.permissions_with_scope({'repo_name': repo_name})
2225 2227
2226 2228 try:
2227 2229 user_perms = {perms['repositories'][repo_name]}
2228 2230 except Exception:
2229 2231 log.exception('Error while accessing user permissions')
2230 2232 return False
2231 2233
2232 2234 if self.required_perms.intersection(user_perms):
2233 2235 return True
2234 2236 return False
2235 2237
2236 2238
2237 2239 # SPECIAL VERSION TO HANDLE API AUTH
2238 2240 class _BaseApiPerm(object):
2239 2241 def __init__(self, *perms):
2240 2242 self.required_perms = set(perms)
2241 2243
2242 2244 def __call__(self, check_location=None, user=None, repo_name=None,
2243 2245 group_name=None, user_group_name=None):
2244 2246 cls_name = self.__class__.__name__
2245 2247 check_scope = 'global:%s' % (self.required_perms,)
2246 2248 if repo_name:
2247 2249 check_scope += ', repo_name:%s' % (repo_name,)
2248 2250
2249 2251 if group_name:
2250 2252 check_scope += ', repo_group_name:%s' % (group_name,)
2251 2253
2252 2254 if user_group_name:
2253 2255 check_scope += ', user_group_name:%s' % (user_group_name,)
2254 2256
2255 2257 log.debug('checking cls:%s %s %s @ %s',
2256 2258 cls_name, self.required_perms, check_scope, check_location)
2257 2259 if not user:
2258 2260 log.debug('Empty User passed into arguments')
2259 2261 return False
2260 2262
2261 2263 # process user
2262 2264 if not isinstance(user, AuthUser):
2263 2265 user = AuthUser(user.user_id)
2264 2266 if not check_location:
2265 2267 check_location = 'unspecified'
2266 2268 if self.check_permissions(user.permissions, repo_name, group_name,
2267 2269 user_group_name):
2268 2270 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2269 2271 check_scope, user, check_location)
2270 2272 return True
2271 2273
2272 2274 else:
2273 2275 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2274 2276 check_scope, user, check_location)
2275 2277 return False
2276 2278
2277 2279 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2278 2280 user_group_name=None):
2279 2281 """
2280 2282 implement in child class should return True if permissions are ok,
2281 2283 False otherwise
2282 2284
2283 2285 :param perm_defs: dict with permission definitions
2284 2286 :param repo_name: repo name
2285 2287 """
2286 2288 raise NotImplementedError()
2287 2289
2288 2290
2289 2291 class HasPermissionAllApi(_BaseApiPerm):
2290 2292 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2291 2293 user_group_name=None):
2292 2294 if self.required_perms.issubset(perm_defs.get('global')):
2293 2295 return True
2294 2296 return False
2295 2297
2296 2298
2297 2299 class HasPermissionAnyApi(_BaseApiPerm):
2298 2300 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2299 2301 user_group_name=None):
2300 2302 if self.required_perms.intersection(perm_defs.get('global')):
2301 2303 return True
2302 2304 return False
2303 2305
2304 2306
2305 2307 class HasRepoPermissionAllApi(_BaseApiPerm):
2306 2308 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2307 2309 user_group_name=None):
2308 2310 try:
2309 2311 _user_perms = {perm_defs['repositories'][repo_name]}
2310 2312 except KeyError:
2311 2313 log.warning(traceback.format_exc())
2312 2314 return False
2313 2315 if self.required_perms.issubset(_user_perms):
2314 2316 return True
2315 2317 return False
2316 2318
2317 2319
2318 2320 class HasRepoPermissionAnyApi(_BaseApiPerm):
2319 2321 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2320 2322 user_group_name=None):
2321 2323 try:
2322 2324 _user_perms = {perm_defs['repositories'][repo_name]}
2323 2325 except KeyError:
2324 2326 log.warning(traceback.format_exc())
2325 2327 return False
2326 2328 if self.required_perms.intersection(_user_perms):
2327 2329 return True
2328 2330 return False
2329 2331
2330 2332
2331 2333 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2332 2334 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2333 2335 user_group_name=None):
2334 2336 try:
2335 2337 _user_perms = {perm_defs['repositories_groups'][group_name]}
2336 2338 except KeyError:
2337 2339 log.warning(traceback.format_exc())
2338 2340 return False
2339 2341 if self.required_perms.intersection(_user_perms):
2340 2342 return True
2341 2343 return False
2342 2344
2343 2345
2344 2346 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2345 2347 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2346 2348 user_group_name=None):
2347 2349 try:
2348 2350 _user_perms = {perm_defs['repositories_groups'][group_name]}
2349 2351 except KeyError:
2350 2352 log.warning(traceback.format_exc())
2351 2353 return False
2352 2354 if self.required_perms.issubset(_user_perms):
2353 2355 return True
2354 2356 return False
2355 2357
2356 2358
2357 2359 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2358 2360 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2359 2361 user_group_name=None):
2360 2362 try:
2361 2363 _user_perms = {perm_defs['user_groups'][user_group_name]}
2362 2364 except KeyError:
2363 2365 log.warning(traceback.format_exc())
2364 2366 return False
2365 2367 if self.required_perms.intersection(_user_perms):
2366 2368 return True
2367 2369 return False
2368 2370
2369 2371
2370 2372 def check_ip_access(source_ip, allowed_ips=None):
2371 2373 """
2372 2374 Checks if source_ip is a subnet of any of allowed_ips.
2373 2375
2374 2376 :param source_ip:
2375 2377 :param allowed_ips: list of allowed ips together with mask
2376 2378 """
2377 2379 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2378 2380 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2379 2381 if isinstance(allowed_ips, (tuple, list, set)):
2380 2382 for ip in allowed_ips:
2381 2383 ip = safe_unicode(ip)
2382 2384 try:
2383 2385 network_address = ipaddress.ip_network(ip, strict=False)
2384 2386 if source_ip_address in network_address:
2385 2387 log.debug('IP %s is network %s', source_ip_address, network_address)
2386 2388 return True
2387 2389 # for any case we cannot determine the IP, don't crash just
2388 2390 # skip it and log as error, we want to say forbidden still when
2389 2391 # sending bad IP
2390 2392 except Exception:
2391 2393 log.error(traceback.format_exc())
2392 2394 continue
2393 2395 return False
2394 2396
2395 2397
2396 2398 def get_cython_compat_decorator(wrapper, func):
2397 2399 """
2398 2400 Creates a cython compatible decorator. The previously used
2399 2401 decorator.decorator() function seems to be incompatible with cython.
2400 2402
2401 2403 :param wrapper: __wrapper method of the decorator class
2402 2404 :param func: decorated function
2403 2405 """
2404 2406 @wraps(func)
2405 2407 def local_wrapper(*args, **kwds):
2406 2408 return wrapper(func, *args, **kwds)
2407 2409 local_wrapper.__wrapped__ = func
2408 2410 return local_wrapper
2409 2411
2410 2412
@@ -1,57 +1,57 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('My Account')}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='my_account')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24
25 25 <div class="sidebar-col-wrapper scw-small">
26 26 ##main
27 27 <div class="sidebar">
28 28 <ul class="nav nav-pills nav-stacked">
29 29 <li class="${h.is_active(['profile', 'profile_edit'], c.active)}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
30 30 <li class="${h.is_active('emails', c.active)}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
31 31 <li class="${h.is_active('password', c.active)}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
32 32 <li class="${h.is_active('bookmarks', c.active)}"><a href="${h.route_path('my_account_bookmarks')}">${_('Bookmarks')}</a></li>
33 33 <li class="${h.is_active('auth_tokens', c.active)}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
34 34 <li class="${h.is_active(['ssh_keys', 'ssh_keys_generate'], c.active)}"><a href="${h.route_path('my_account_ssh_keys')}">${_('SSH Keys')}</a></li>
35 35 <li class="${h.is_active('user_group_membership', c.active)}"><a href="${h.route_path('my_account_user_group_membership')}">${_('User Group Membership')}</a></li>
36 36
37 37 ## TODO: Find a better integration of oauth/saml views into navigation.
38 38 <% my_account_external_url = h.route_path_or_none('my_account_external_identity') %>
39 39 % if my_account_external_url:
40 40 <li class="${h.is_active('external_identity', c.active)}"><a href="${my_account_external_url}">${_('External Identities')}</a></li>
41 41 % endif
42 42
43 <li class="${h.is_active('repos', c.active)}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
44 <li class="${h.is_active('watched', c.active)}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
43 <li class="${h.is_active('repos', c.active)}"><a href="${h.route_path('my_account_repos')}">${_('Owned Repositories')}</a></li>
44 <li class="${h.is_active('watched', c.active)}"><a href="${h.route_path('my_account_watched')}">${_('Watched Repositories')}</a></li>
45 45 <li class="${h.is_active('pullrequests', c.active)}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
46 46 <li class="${h.is_active('perms', c.active)}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
47 47 <li class="${h.is_active('my_notifications', c.active)}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
48 48 </ul>
49 49 </div>
50 50
51 51 <div class="main-content-full-width">
52 52 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
53 53 </div>
54 54 </div>
55 55 </div>
56 56
57 57 </%def>
@@ -1,68 +1,49 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Repositories You Own')}</h3>
4 4 </div>
5 5
6 6 <div class="panel-body">
7 7 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
8 8
9 9 <div id="repos_list_wrap">
10 10 <table id="repo_list_table" class="display"></table>
11 11 </div>
12 12 </div>
13 13 </div>
14 14
15 15 <script>
16 16 $(document).ready(function() {
17 17
18 var get_datatable_count = function(){
19 var api = $('#repo_list_table').dataTable().api();
20 $('#repo_count').text(api.page.info().recordsDisplay);
21 };
18 // repo list
19 $repoListTable = $('#repo_list_table');
22 20
23 // repo list
24 $('#repo_list_table').DataTable({
21 $repoListTable.DataTable({
25 22 data: ${c.data|n},
26 23 dom: 'rtp',
27 24 pageLength: ${c.visual.admin_grid_items},
28 25 order: [[ 0, "asc" ]],
29 26 columns: [
30 27 { data: {"_": "name",
31 28 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
32 { data: 'menu', className: "quick_repo_menu" },
33 { data: {"_": "last_changeset",
34 "sort": "last_changeset_raw",
35 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
36 { data: {"_": "action",
37 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
38 29 ],
39 30 language: {
40 31 paginate: DEFAULT_GRID_PAGINATION,
41 32 emptyTable: _gettext("No repositories available yet.")
42 33 },
43 "initComplete": function( settings, json ) {
44 get_datatable_count();
45 quick_repo_menu();
46 }
47 });
48 34
49 // update the counter when doing search
50 $('#repo_list_table').on( 'search.dt', function (e,settings) {
51 get_datatable_count();
52 35 });
53 36
54 // filter, filter both grids
55 $('#q_filter').on( 'keyup', function () {
56 var repo_api = $('#repo_list_table').dataTable().api();
57 repo_api
58 .columns(0)
59 .search(this.value)
60 .draw();
61 });
37 // filter
38 $('#q_filter').on('keyup',
39 $.debounce(250, function() {
40 $repoListTable.DataTable().search(
41 $('#q_filter').val()
42 ).draw();
43 })
44 );
62 45
63 // refilter table if page load via back button
64 $("#q_filter").trigger('keyup');
65 46
66 47 });
67 48
68 49 </script>
@@ -1,66 +1,49 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Your Watched Repositories')}</h3>
4 4 </div>
5 5
6 6 <div class="panel-body">
7 7 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
8 8
9 9 <div id="repos_list_wrap">
10 10 <table id="repo_list_table" class="display"></table>
11 11 </div>
12 12 </div>
13 13 </div>
14 14
15 15 <script>
16 16 $(document).ready(function() {
17 17
18 var get_datatable_count = function(){
19 var api = $('#repo_list_table').dataTable().api();
20 $('#repo_count').text(api.page.info().recordsDisplay);
21 };
18 // repo list
19 $repoListTable = $('#repo_list_table');
22 20
23 // repo list
24 $('#repo_list_table').DataTable({
21 $repoListTable.DataTable({
25 22 data: ${c.data|n},
26 23 dom: 'rtp',
27 24 pageLength: ${c.visual.admin_grid_items},
28 25 order: [[ 0, "asc" ]],
29 26 columns: [
30 27 { data: {"_": "name",
31 28 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
32 { data: 'menu', className: "quick_repo_menu" },
33 { data: {"_": "last_changeset",
34 "sort": "last_changeset_raw",
35 "type": Number}, title: "${_('Commit')}", className: "td-hash" }
36 29 ],
37 30 language: {
38 31 paginate: DEFAULT_GRID_PAGINATION,
39 32 emptyTable: _gettext("No repositories available yet.")
40 33 },
41 "initComplete": function( settings, json ) {
42 get_datatable_count();
43 quick_repo_menu();
44 }
45 });
46 34
47 // update the counter when doing search
48 $('#repo_list_table').on( 'search.dt', function (e,settings) {
49 get_datatable_count();
50 35 });
51 36
52 // filter, filter both grids
53 $('#q_filter').on( 'keyup', function () {
54 var repo_api = $('#repo_list_table').dataTable().api();
55 repo_api
56 .columns(0)
57 .search(this.value)
58 .draw();
59 });
37 // filter
38 $('#q_filter').on('keyup',
39 $.debounce(250, function() {
40 $repoListTable.DataTable().search(
41 $('#q_filter').val()
42 ).draw();
43 })
44 );
60 45
61 // refilter table if page load via back button
62 $("#q_filter").trigger('keyup');
63 46
64 47 });
65 48
66 49 </script>
General Comments 0
You need to be logged in to leave comments. Login now