##// END OF EJS Templates
repos: recover properly from bad extraction of repo_id from URL and DB calls.
super-admin -
r4725:65a9cd36 default
parent child Browse files
Show More
@@ -1,1183 +1,1185 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 os
22 22 import re
23 23 import shutil
24 24 import time
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from zope.cachedescriptors.property import Lazy as LazyProperty
31 31
32 32 from rhodecode import events
33 33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 34 from rhodecode.lib.caching_query import FromCache
35 35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 36 from rhodecode.lib import hooks_base
37 37 from rhodecode.lib.user_log_filter import user_log_filter
38 38 from rhodecode.lib.utils import make_db_config
39 39 from rhodecode.lib.utils2 import (
40 40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 41 get_current_rhodecode_user, safe_int, action_logger_generic)
42 42 from rhodecode.lib.vcs.backends import get_backend
43 43 from rhodecode.model import BaseModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, func, case, joinedload, or_, in_filter_generator,
46 46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 49 from rhodecode.model.permission import PermissionModel
50 50 from rhodecode.model.settings import VcsSettingsModel
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class RepoModel(BaseModel):
56 56
57 57 cls = Repository
58 58
59 59 def _get_user_group(self, users_group):
60 60 return self._get_instance(UserGroup, users_group,
61 61 callback=UserGroup.get_by_group_name)
62 62
63 63 def _get_repo_group(self, repo_group):
64 64 return self._get_instance(RepoGroup, repo_group,
65 65 callback=RepoGroup.get_by_group_name)
66 66
67 67 def _create_default_perms(self, repository, private):
68 68 # create default permission
69 69 default = 'repository.read'
70 70 def_user = User.get_default_user()
71 71 for p in def_user.user_perms:
72 72 if p.permission.permission_name.startswith('repository.'):
73 73 default = p.permission.permission_name
74 74 break
75 75
76 76 default_perm = 'repository.none' if private else default
77 77
78 78 repo_to_perm = UserRepoToPerm()
79 79 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 80
81 81 repo_to_perm.repository = repository
82 82 repo_to_perm.user_id = def_user.user_id
83 83
84 84 return repo_to_perm
85 85
86 86 @LazyProperty
87 87 def repos_path(self):
88 88 """
89 89 Gets the repositories root path from database
90 90 """
91 91 settings_model = VcsSettingsModel(sa=self.sa)
92 92 return settings_model.get_repos_location()
93 93
94 94 def get(self, repo_id):
95 95 repo = self.sa.query(Repository) \
96 96 .filter(Repository.repo_id == repo_id)
97 97
98 98 return repo.scalar()
99 99
100 100 def get_repo(self, repository):
101 101 return self._get_repo(repository)
102 102
103 103 def get_by_repo_name(self, repo_name, cache=False):
104 104 repo = self.sa.query(Repository) \
105 105 .filter(Repository.repo_name == repo_name)
106 106
107 107 if cache:
108 108 name_key = _hash_key(repo_name)
109 109 repo = repo.options(
110 110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
111 111 return repo.scalar()
112 112
113 113 def _extract_id_from_repo_name(self, repo_name):
114 114 if repo_name.startswith('/'):
115 115 repo_name = repo_name.lstrip('/')
116 116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 117 if by_id_match:
118 118 return by_id_match.groups()[0]
119 119
120 120 def get_repo_by_id(self, repo_name):
121 121 """
122 122 Extracts repo_name by id from special urls.
123 123 Example url is _11/repo_name
124 124
125 125 :param repo_name:
126 126 :return: repo object if matched else None
127 127 """
128
128 _repo_id = None
129 129 try:
130 130 _repo_id = self._extract_id_from_repo_name(repo_name)
131 131 if _repo_id:
132 132 return self.get(_repo_id)
133 133 except Exception:
134 134 log.exception('Failed to extract repo_name from URL')
135 if _repo_id:
136 Session().rollback()
135 137
136 138 return None
137 139
138 140 def get_repos_for_root(self, root, traverse=False):
139 141 if traverse:
140 142 like_expression = u'{}%'.format(safe_unicode(root))
141 143 repos = Repository.query().filter(
142 144 Repository.repo_name.like(like_expression)).all()
143 145 else:
144 146 if root and not isinstance(root, RepoGroup):
145 147 raise ValueError(
146 148 'Root must be an instance '
147 149 'of RepoGroup, got:{} instead'.format(type(root)))
148 150 repos = Repository.query().filter(Repository.group == root).all()
149 151 return repos
150 152
151 153 def get_url(self, repo, request=None, permalink=False):
152 154 if not request:
153 155 request = get_current_request()
154 156
155 157 if not request:
156 158 return
157 159
158 160 if permalink:
159 161 return request.route_url(
160 162 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
161 163 else:
162 164 return request.route_url(
163 165 'repo_summary', repo_name=safe_str(repo.repo_name))
164 166
165 167 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
166 168 if not request:
167 169 request = get_current_request()
168 170
169 171 if not request:
170 172 return
171 173
172 174 if permalink:
173 175 return request.route_url(
174 176 'repo_commit', repo_name=safe_str(repo.repo_id),
175 177 commit_id=commit_id)
176 178
177 179 else:
178 180 return request.route_url(
179 181 'repo_commit', repo_name=safe_str(repo.repo_name),
180 182 commit_id=commit_id)
181 183
182 184 def get_repo_log(self, repo, filter_term):
183 185 repo_log = UserLog.query()\
184 186 .filter(or_(UserLog.repository_id == repo.repo_id,
185 187 UserLog.repository_name == repo.repo_name))\
186 188 .options(joinedload(UserLog.user))\
187 189 .options(joinedload(UserLog.repository))\
188 190 .order_by(UserLog.action_date.desc())
189 191
190 192 repo_log = user_log_filter(repo_log, filter_term)
191 193 return repo_log
192 194
193 195 @classmethod
194 196 def update_commit_cache(cls, repositories=None):
195 197 if not repositories:
196 198 repositories = Repository.getAll()
197 199 for repo in repositories:
198 200 repo.update_commit_cache()
199 201
200 202 def get_repos_as_dict(self, repo_list=None, admin=False,
201 203 super_user_actions=False, short_name=None):
202 204
203 205 _render = get_current_request().get_partial_renderer(
204 206 'rhodecode:templates/data_table/_dt_elements.mako')
205 207 c = _render.get_call_context()
206 208 h = _render.get_helpers()
207 209
208 210 def quick_menu(repo_name):
209 211 return _render('quick_menu', repo_name)
210 212
211 213 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
212 214 if short_name is not None:
213 215 short_name_var = short_name
214 216 else:
215 217 short_name_var = not admin
216 218 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
217 219 short_name=short_name_var, admin=False)
218 220
219 221 def last_change(last_change):
220 222 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
221 223 ts = time.time()
222 224 utc_offset = (datetime.datetime.fromtimestamp(ts)
223 225 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
224 226 last_change = last_change + datetime.timedelta(seconds=utc_offset)
225 227
226 228 return _render("last_change", last_change)
227 229
228 230 def rss_lnk(repo_name):
229 231 return _render("rss", repo_name)
230 232
231 233 def atom_lnk(repo_name):
232 234 return _render("atom", repo_name)
233 235
234 236 def last_rev(repo_name, cs_cache):
235 237 return _render('revision', repo_name, cs_cache.get('revision'),
236 238 cs_cache.get('raw_id'), cs_cache.get('author'),
237 239 cs_cache.get('message'), cs_cache.get('date'))
238 240
239 241 def desc(desc):
240 242 return _render('repo_desc', desc, c.visual.stylify_metatags)
241 243
242 244 def state(repo_state):
243 245 return _render("repo_state", repo_state)
244 246
245 247 def repo_actions(repo_name):
246 248 return _render('repo_actions', repo_name, super_user_actions)
247 249
248 250 def user_profile(username):
249 251 return _render('user_profile', username)
250 252
251 253 repos_data = []
252 254 for repo in repo_list:
253 255 # NOTE(marcink): because we use only raw column we need to load it like that
254 256 changeset_cache = Repository._load_changeset_cache(
255 257 repo.repo_id, repo._changeset_cache)
256 258
257 259 row = {
258 260 "menu": quick_menu(repo.repo_name),
259 261
260 262 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
261 263 repo.private, repo.archived, repo.fork),
262 264
263 265 "desc": desc(h.escape(repo.description)),
264 266
265 267 "last_change": last_change(repo.updated_on),
266 268
267 269 "last_changeset": last_rev(repo.repo_name, changeset_cache),
268 270 "last_changeset_raw": changeset_cache.get('revision'),
269 271
270 272 "owner": user_profile(repo.User.username),
271 273
272 274 "state": state(repo.repo_state),
273 275 "rss": rss_lnk(repo.repo_name),
274 276 "atom": atom_lnk(repo.repo_name),
275 277 }
276 278 if admin:
277 279 row.update({
278 280 "action": repo_actions(repo.repo_name),
279 281 })
280 282 repos_data.append(row)
281 283
282 284 return repos_data
283 285
284 286 def get_repos_data_table(
285 287 self, draw, start, limit,
286 288 search_q, order_by, order_dir,
287 289 auth_user, repo_group_id):
288 290 from rhodecode.model.scm import RepoList
289 291
290 292 _perms = ['repository.read', 'repository.write', 'repository.admin']
291 293
292 294 repos = Repository.query() \
293 295 .filter(Repository.group_id == repo_group_id) \
294 296 .all()
295 297 auth_repo_list = RepoList(
296 298 repos, perm_set=_perms,
297 299 extra_kwargs=dict(user=auth_user))
298 300
299 301 allowed_ids = [-1]
300 302 for repo in auth_repo_list:
301 303 allowed_ids.append(repo.repo_id)
302 304
303 305 repos_data_total_count = Repository.query() \
304 306 .filter(Repository.group_id == repo_group_id) \
305 307 .filter(or_(
306 308 # generate multiple IN to fix limitation problems
307 309 *in_filter_generator(Repository.repo_id, allowed_ids))
308 310 ) \
309 311 .count()
310 312
311 313 base_q = Session.query(
312 314 Repository.repo_id,
313 315 Repository.repo_name,
314 316 Repository.description,
315 317 Repository.repo_type,
316 318 Repository.repo_state,
317 319 Repository.private,
318 320 Repository.archived,
319 321 Repository.fork,
320 322 Repository.updated_on,
321 323 Repository._changeset_cache,
322 324 User,
323 325 ) \
324 326 .filter(Repository.group_id == repo_group_id) \
325 327 .filter(or_(
326 328 # generate multiple IN to fix limitation problems
327 329 *in_filter_generator(Repository.repo_id, allowed_ids))
328 330 ) \
329 331 .join(User, User.user_id == Repository.user_id) \
330 332 .group_by(Repository, User)
331 333
332 334 repos_data_total_filtered_count = base_q.count()
333 335
334 336 sort_defined = False
335 337 if order_by == 'repo_name':
336 338 sort_col = func.lower(Repository.repo_name)
337 339 sort_defined = True
338 340 elif order_by == 'user_username':
339 341 sort_col = User.username
340 342 else:
341 343 sort_col = getattr(Repository, order_by, None)
342 344
343 345 if sort_defined or sort_col:
344 346 if order_dir == 'asc':
345 347 sort_col = sort_col.asc()
346 348 else:
347 349 sort_col = sort_col.desc()
348 350
349 351 base_q = base_q.order_by(sort_col)
350 352 base_q = base_q.offset(start).limit(limit)
351 353
352 354 repos_list = base_q.all()
353 355
354 356 repos_data = RepoModel().get_repos_as_dict(
355 357 repo_list=repos_list, admin=False)
356 358
357 359 data = ({
358 360 'draw': draw,
359 361 'data': repos_data,
360 362 'recordsTotal': repos_data_total_count,
361 363 'recordsFiltered': repos_data_total_filtered_count,
362 364 })
363 365 return data
364 366
365 367 def _get_defaults(self, repo_name):
366 368 """
367 369 Gets information about repository, and returns a dict for
368 370 usage in forms
369 371
370 372 :param repo_name:
371 373 """
372 374
373 375 repo_info = Repository.get_by_repo_name(repo_name)
374 376
375 377 if repo_info is None:
376 378 return None
377 379
378 380 defaults = repo_info.get_dict()
379 381 defaults['repo_name'] = repo_info.just_name
380 382
381 383 groups = repo_info.groups_with_parents
382 384 parent_group = groups[-1] if groups else None
383 385
384 386 # we use -1 as this is how in HTML, we mark an empty group
385 387 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
386 388
387 389 keys_to_process = (
388 390 {'k': 'repo_type', 'strip': False},
389 391 {'k': 'repo_enable_downloads', 'strip': True},
390 392 {'k': 'repo_description', 'strip': True},
391 393 {'k': 'repo_enable_locking', 'strip': True},
392 394 {'k': 'repo_landing_rev', 'strip': True},
393 395 {'k': 'clone_uri', 'strip': False},
394 396 {'k': 'push_uri', 'strip': False},
395 397 {'k': 'repo_private', 'strip': True},
396 398 {'k': 'repo_enable_statistics', 'strip': True}
397 399 )
398 400
399 401 for item in keys_to_process:
400 402 attr = item['k']
401 403 if item['strip']:
402 404 attr = remove_prefix(item['k'], 'repo_')
403 405
404 406 val = defaults[attr]
405 407 if item['k'] == 'repo_landing_rev':
406 408 val = ':'.join(defaults[attr])
407 409 defaults[item['k']] = val
408 410 if item['k'] == 'clone_uri':
409 411 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
410 412 if item['k'] == 'push_uri':
411 413 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
412 414
413 415 # fill owner
414 416 if repo_info.user:
415 417 defaults.update({'user': repo_info.user.username})
416 418 else:
417 419 replacement_user = User.get_first_super_admin().username
418 420 defaults.update({'user': replacement_user})
419 421
420 422 return defaults
421 423
422 424 def update(self, repo, **kwargs):
423 425 try:
424 426 cur_repo = self._get_repo(repo)
425 427 source_repo_name = cur_repo.repo_name
426 428
427 429 affected_user_ids = []
428 430 if 'user' in kwargs:
429 431 old_owner_id = cur_repo.user.user_id
430 432 new_owner = User.get_by_username(kwargs['user'])
431 433 cur_repo.user = new_owner
432 434
433 435 if old_owner_id != new_owner.user_id:
434 436 affected_user_ids = [new_owner.user_id, old_owner_id]
435 437
436 438 if 'repo_group' in kwargs:
437 439 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
438 440 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
439 441
440 442 update_keys = [
441 443 (1, 'repo_description'),
442 444 (1, 'repo_landing_rev'),
443 445 (1, 'repo_private'),
444 446 (1, 'repo_enable_downloads'),
445 447 (1, 'repo_enable_locking'),
446 448 (1, 'repo_enable_statistics'),
447 449 (0, 'clone_uri'),
448 450 (0, 'push_uri'),
449 451 (0, 'fork_id')
450 452 ]
451 453 for strip, k in update_keys:
452 454 if k in kwargs:
453 455 val = kwargs[k]
454 456 if strip:
455 457 k = remove_prefix(k, 'repo_')
456 458
457 459 setattr(cur_repo, k, val)
458 460
459 461 new_name = cur_repo.get_new_name(kwargs['repo_name'])
460 462 cur_repo.repo_name = new_name
461 463
462 464 # if private flag is set, reset default permission to NONE
463 465 if kwargs.get('repo_private'):
464 466 EMPTY_PERM = 'repository.none'
465 467 RepoModel().grant_user_permission(
466 468 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
467 469 )
468 470
469 471 # handle extra fields
470 472 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
471 473 k = RepositoryField.un_prefix_key(field)
472 474 ex_field = RepositoryField.get_by_key_name(
473 475 key=k, repo=cur_repo)
474 476 if ex_field:
475 477 ex_field.field_value = kwargs[field]
476 478 self.sa.add(ex_field)
477 479
478 480 self.sa.add(cur_repo)
479 481
480 482 if source_repo_name != new_name:
481 483 # rename repository
482 484 self._rename_filesystem_repo(
483 485 old=source_repo_name, new=new_name)
484 486
485 487 if affected_user_ids:
486 488 PermissionModel().trigger_permission_flush(affected_user_ids)
487 489
488 490 return cur_repo
489 491 except Exception:
490 492 log.error(traceback.format_exc())
491 493 raise
492 494
493 495 def _create_repo(self, repo_name, repo_type, description, owner,
494 496 private=False, clone_uri=None, repo_group=None,
495 497 landing_rev='rev:tip', fork_of=None,
496 498 copy_fork_permissions=False, enable_statistics=False,
497 499 enable_locking=False, enable_downloads=False,
498 500 copy_group_permissions=False,
499 501 state=Repository.STATE_PENDING):
500 502 """
501 503 Create repository inside database with PENDING state, this should be
502 504 only executed by create() repo. With exception of importing existing
503 505 repos
504 506 """
505 507 from rhodecode.model.scm import ScmModel
506 508
507 509 owner = self._get_user(owner)
508 510 fork_of = self._get_repo(fork_of)
509 511 repo_group = self._get_repo_group(safe_int(repo_group))
510 512
511 513 try:
512 514 repo_name = safe_unicode(repo_name)
513 515 description = safe_unicode(description)
514 516 # repo name is just a name of repository
515 517 # while repo_name_full is a full qualified name that is combined
516 518 # with name and path of group
517 519 repo_name_full = repo_name
518 520 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
519 521
520 522 new_repo = Repository()
521 523 new_repo.repo_state = state
522 524 new_repo.enable_statistics = False
523 525 new_repo.repo_name = repo_name_full
524 526 new_repo.repo_type = repo_type
525 527 new_repo.user = owner
526 528 new_repo.group = repo_group
527 529 new_repo.description = description or repo_name
528 530 new_repo.private = private
529 531 new_repo.archived = False
530 532 new_repo.clone_uri = clone_uri
531 533 new_repo.landing_rev = landing_rev
532 534
533 535 new_repo.enable_statistics = enable_statistics
534 536 new_repo.enable_locking = enable_locking
535 537 new_repo.enable_downloads = enable_downloads
536 538
537 539 if repo_group:
538 540 new_repo.enable_locking = repo_group.enable_locking
539 541
540 542 if fork_of:
541 543 parent_repo = fork_of
542 544 new_repo.fork = parent_repo
543 545
544 546 events.trigger(events.RepoPreCreateEvent(new_repo))
545 547
546 548 self.sa.add(new_repo)
547 549
548 550 EMPTY_PERM = 'repository.none'
549 551 if fork_of and copy_fork_permissions:
550 552 repo = fork_of
551 553 user_perms = UserRepoToPerm.query() \
552 554 .filter(UserRepoToPerm.repository == repo).all()
553 555 group_perms = UserGroupRepoToPerm.query() \
554 556 .filter(UserGroupRepoToPerm.repository == repo).all()
555 557
556 558 for perm in user_perms:
557 559 UserRepoToPerm.create(
558 560 perm.user, new_repo, perm.permission)
559 561
560 562 for perm in group_perms:
561 563 UserGroupRepoToPerm.create(
562 564 perm.users_group, new_repo, perm.permission)
563 565 # in case we copy permissions and also set this repo to private
564 566 # override the default user permission to make it a private repo
565 567 if private:
566 568 RepoModel(self.sa).grant_user_permission(
567 569 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
568 570
569 571 elif repo_group and copy_group_permissions:
570 572 user_perms = UserRepoGroupToPerm.query() \
571 573 .filter(UserRepoGroupToPerm.group == repo_group).all()
572 574
573 575 group_perms = UserGroupRepoGroupToPerm.query() \
574 576 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
575 577
576 578 for perm in user_perms:
577 579 perm_name = perm.permission.permission_name.replace(
578 580 'group.', 'repository.')
579 581 perm_obj = Permission.get_by_key(perm_name)
580 582 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
581 583
582 584 for perm in group_perms:
583 585 perm_name = perm.permission.permission_name.replace(
584 586 'group.', 'repository.')
585 587 perm_obj = Permission.get_by_key(perm_name)
586 588 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
587 589
588 590 if private:
589 591 RepoModel(self.sa).grant_user_permission(
590 592 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
591 593
592 594 else:
593 595 perm_obj = self._create_default_perms(new_repo, private)
594 596 self.sa.add(perm_obj)
595 597
596 598 # now automatically start following this repository as owner
597 599 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
598 600
599 601 # we need to flush here, in order to check if database won't
600 602 # throw any exceptions, create filesystem dirs at the very end
601 603 self.sa.flush()
602 604 events.trigger(events.RepoCreateEvent(new_repo))
603 605 return new_repo
604 606
605 607 except Exception:
606 608 log.error(traceback.format_exc())
607 609 raise
608 610
609 611 def create(self, form_data, cur_user):
610 612 """
611 613 Create repository using celery tasks
612 614
613 615 :param form_data:
614 616 :param cur_user:
615 617 """
616 618 from rhodecode.lib.celerylib import tasks, run_task
617 619 return run_task(tasks.create_repo, form_data, cur_user)
618 620
619 621 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
620 622 perm_deletions=None, check_perms=True,
621 623 cur_user=None):
622 624 if not perm_additions:
623 625 perm_additions = []
624 626 if not perm_updates:
625 627 perm_updates = []
626 628 if not perm_deletions:
627 629 perm_deletions = []
628 630
629 631 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
630 632
631 633 changes = {
632 634 'added': [],
633 635 'updated': [],
634 636 'deleted': [],
635 637 'default_user_changed': None
636 638 }
637 639
638 640 repo = self._get_repo(repo)
639 641
640 642 # update permissions
641 643 for member_id, perm, member_type in perm_updates:
642 644 member_id = int(member_id)
643 645 if member_type == 'user':
644 646 member_name = User.get(member_id).username
645 647 if member_name == User.DEFAULT_USER:
646 648 # NOTE(dan): detect if we changed permissions for default user
647 649 perm_obj = self.sa.query(UserRepoToPerm) \
648 650 .filter(UserRepoToPerm.user_id == member_id) \
649 651 .filter(UserRepoToPerm.repository == repo) \
650 652 .scalar()
651 653 if perm_obj and perm_obj.permission.permission_name != perm:
652 654 changes['default_user_changed'] = True
653 655
654 656 # this updates also current one if found
655 657 self.grant_user_permission(
656 658 repo=repo, user=member_id, perm=perm)
657 659 elif member_type == 'user_group':
658 660 # check if we have permissions to alter this usergroup
659 661 member_name = UserGroup.get(member_id).users_group_name
660 662 if not check_perms or HasUserGroupPermissionAny(
661 663 *req_perms)(member_name, user=cur_user):
662 664 self.grant_user_group_permission(
663 665 repo=repo, group_name=member_id, perm=perm)
664 666 else:
665 667 raise ValueError("member_type must be 'user' or 'user_group' "
666 668 "got {} instead".format(member_type))
667 669 changes['updated'].append({'type': member_type, 'id': member_id,
668 670 'name': member_name, 'new_perm': perm})
669 671
670 672 # set new permissions
671 673 for member_id, perm, member_type in perm_additions:
672 674 member_id = int(member_id)
673 675 if member_type == 'user':
674 676 member_name = User.get(member_id).username
675 677 self.grant_user_permission(
676 678 repo=repo, user=member_id, perm=perm)
677 679 elif member_type == 'user_group':
678 680 # check if we have permissions to alter this usergroup
679 681 member_name = UserGroup.get(member_id).users_group_name
680 682 if not check_perms or HasUserGroupPermissionAny(
681 683 *req_perms)(member_name, user=cur_user):
682 684 self.grant_user_group_permission(
683 685 repo=repo, group_name=member_id, perm=perm)
684 686 else:
685 687 raise ValueError("member_type must be 'user' or 'user_group' "
686 688 "got {} instead".format(member_type))
687 689
688 690 changes['added'].append({'type': member_type, 'id': member_id,
689 691 'name': member_name, 'new_perm': perm})
690 692 # delete permissions
691 693 for member_id, perm, member_type in perm_deletions:
692 694 member_id = int(member_id)
693 695 if member_type == 'user':
694 696 member_name = User.get(member_id).username
695 697 self.revoke_user_permission(repo=repo, user=member_id)
696 698 elif member_type == 'user_group':
697 699 # check if we have permissions to alter this usergroup
698 700 member_name = UserGroup.get(member_id).users_group_name
699 701 if not check_perms or HasUserGroupPermissionAny(
700 702 *req_perms)(member_name, user=cur_user):
701 703 self.revoke_user_group_permission(
702 704 repo=repo, group_name=member_id)
703 705 else:
704 706 raise ValueError("member_type must be 'user' or 'user_group' "
705 707 "got {} instead".format(member_type))
706 708
707 709 changes['deleted'].append({'type': member_type, 'id': member_id,
708 710 'name': member_name, 'new_perm': perm})
709 711 return changes
710 712
711 713 def create_fork(self, form_data, cur_user):
712 714 """
713 715 Simple wrapper into executing celery task for fork creation
714 716
715 717 :param form_data:
716 718 :param cur_user:
717 719 """
718 720 from rhodecode.lib.celerylib import tasks, run_task
719 721 return run_task(tasks.create_repo_fork, form_data, cur_user)
720 722
721 723 def archive(self, repo):
722 724 """
723 725 Archive given repository. Set archive flag.
724 726
725 727 :param repo:
726 728 """
727 729 repo = self._get_repo(repo)
728 730 if repo:
729 731
730 732 try:
731 733 repo.archived = True
732 734 self.sa.add(repo)
733 735 self.sa.commit()
734 736 except Exception:
735 737 log.error(traceback.format_exc())
736 738 raise
737 739
738 740 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
739 741 """
740 742 Delete given repository, forks parameter defines what do do with
741 743 attached forks. Throws AttachedForksError if deleted repo has attached
742 744 forks
743 745
744 746 :param repo:
745 747 :param forks: str 'delete' or 'detach'
746 748 :param pull_requests: str 'delete' or None
747 749 :param fs_remove: remove(archive) repo from filesystem
748 750 """
749 751 if not cur_user:
750 752 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
751 753 repo = self._get_repo(repo)
752 754 if repo:
753 755 if forks == 'detach':
754 756 for r in repo.forks:
755 757 r.fork = None
756 758 self.sa.add(r)
757 759 elif forks == 'delete':
758 760 for r in repo.forks:
759 761 self.delete(r, forks='delete')
760 762 elif [f for f in repo.forks]:
761 763 raise AttachedForksError()
762 764
763 765 # check for pull requests
764 766 pr_sources = repo.pull_requests_source
765 767 pr_targets = repo.pull_requests_target
766 768 if pull_requests != 'delete' and (pr_sources or pr_targets):
767 769 raise AttachedPullRequestsError()
768 770
769 771 old_repo_dict = repo.get_dict()
770 772 events.trigger(events.RepoPreDeleteEvent(repo))
771 773 try:
772 774 self.sa.delete(repo)
773 775 if fs_remove:
774 776 self._delete_filesystem_repo(repo)
775 777 else:
776 778 log.debug('skipping removal from filesystem')
777 779 old_repo_dict.update({
778 780 'deleted_by': cur_user,
779 781 'deleted_on': time.time(),
780 782 })
781 783 hooks_base.delete_repository(**old_repo_dict)
782 784 events.trigger(events.RepoDeleteEvent(repo))
783 785 except Exception:
784 786 log.error(traceback.format_exc())
785 787 raise
786 788
787 789 def grant_user_permission(self, repo, user, perm):
788 790 """
789 791 Grant permission for user on given repository, or update existing one
790 792 if found
791 793
792 794 :param repo: Instance of Repository, repository_id, or repository name
793 795 :param user: Instance of User, user_id or username
794 796 :param perm: Instance of Permission, or permission_name
795 797 """
796 798 user = self._get_user(user)
797 799 repo = self._get_repo(repo)
798 800 permission = self._get_perm(perm)
799 801
800 802 # check if we have that permission already
801 803 obj = self.sa.query(UserRepoToPerm) \
802 804 .filter(UserRepoToPerm.user == user) \
803 805 .filter(UserRepoToPerm.repository == repo) \
804 806 .scalar()
805 807 if obj is None:
806 808 # create new !
807 809 obj = UserRepoToPerm()
808 810 obj.repository = repo
809 811 obj.user = user
810 812 obj.permission = permission
811 813 self.sa.add(obj)
812 814 log.debug('Granted perm %s to %s on %s', perm, user, repo)
813 815 action_logger_generic(
814 816 'granted permission: {} to user: {} on repo: {}'.format(
815 817 perm, user, repo), namespace='security.repo')
816 818 return obj
817 819
818 820 def revoke_user_permission(self, repo, user):
819 821 """
820 822 Revoke permission for user on given repository
821 823
822 824 :param repo: Instance of Repository, repository_id, or repository name
823 825 :param user: Instance of User, user_id or username
824 826 """
825 827
826 828 user = self._get_user(user)
827 829 repo = self._get_repo(repo)
828 830
829 831 obj = self.sa.query(UserRepoToPerm) \
830 832 .filter(UserRepoToPerm.repository == repo) \
831 833 .filter(UserRepoToPerm.user == user) \
832 834 .scalar()
833 835 if obj:
834 836 self.sa.delete(obj)
835 837 log.debug('Revoked perm on %s on %s', repo, user)
836 838 action_logger_generic(
837 839 'revoked permission from user: {} on repo: {}'.format(
838 840 user, repo), namespace='security.repo')
839 841
840 842 def grant_user_group_permission(self, repo, group_name, perm):
841 843 """
842 844 Grant permission for user group on given repository, or update
843 845 existing one if found
844 846
845 847 :param repo: Instance of Repository, repository_id, or repository name
846 848 :param group_name: Instance of UserGroup, users_group_id,
847 849 or user group name
848 850 :param perm: Instance of Permission, or permission_name
849 851 """
850 852 repo = self._get_repo(repo)
851 853 group_name = self._get_user_group(group_name)
852 854 permission = self._get_perm(perm)
853 855
854 856 # check if we have that permission already
855 857 obj = self.sa.query(UserGroupRepoToPerm) \
856 858 .filter(UserGroupRepoToPerm.users_group == group_name) \
857 859 .filter(UserGroupRepoToPerm.repository == repo) \
858 860 .scalar()
859 861
860 862 if obj is None:
861 863 # create new
862 864 obj = UserGroupRepoToPerm()
863 865
864 866 obj.repository = repo
865 867 obj.users_group = group_name
866 868 obj.permission = permission
867 869 self.sa.add(obj)
868 870 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
869 871 action_logger_generic(
870 872 'granted permission: {} to usergroup: {} on repo: {}'.format(
871 873 perm, group_name, repo), namespace='security.repo')
872 874
873 875 return obj
874 876
875 877 def revoke_user_group_permission(self, repo, group_name):
876 878 """
877 879 Revoke permission for user group on given repository
878 880
879 881 :param repo: Instance of Repository, repository_id, or repository name
880 882 :param group_name: Instance of UserGroup, users_group_id,
881 883 or user group name
882 884 """
883 885 repo = self._get_repo(repo)
884 886 group_name = self._get_user_group(group_name)
885 887
886 888 obj = self.sa.query(UserGroupRepoToPerm) \
887 889 .filter(UserGroupRepoToPerm.repository == repo) \
888 890 .filter(UserGroupRepoToPerm.users_group == group_name) \
889 891 .scalar()
890 892 if obj:
891 893 self.sa.delete(obj)
892 894 log.debug('Revoked perm to %s on %s', repo, group_name)
893 895 action_logger_generic(
894 896 'revoked permission from usergroup: {} on repo: {}'.format(
895 897 group_name, repo), namespace='security.repo')
896 898
897 899 def delete_stats(self, repo_name):
898 900 """
899 901 removes stats for given repo
900 902
901 903 :param repo_name:
902 904 """
903 905 repo = self._get_repo(repo_name)
904 906 try:
905 907 obj = self.sa.query(Statistics) \
906 908 .filter(Statistics.repository == repo).scalar()
907 909 if obj:
908 910 self.sa.delete(obj)
909 911 except Exception:
910 912 log.error(traceback.format_exc())
911 913 raise
912 914
913 915 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
914 916 field_type='str', field_desc=''):
915 917
916 918 repo = self._get_repo(repo_name)
917 919
918 920 new_field = RepositoryField()
919 921 new_field.repository = repo
920 922 new_field.field_key = field_key
921 923 new_field.field_type = field_type # python type
922 924 new_field.field_value = field_value
923 925 new_field.field_desc = field_desc
924 926 new_field.field_label = field_label
925 927 self.sa.add(new_field)
926 928 return new_field
927 929
928 930 def delete_repo_field(self, repo_name, field_key):
929 931 repo = self._get_repo(repo_name)
930 932 field = RepositoryField.get_by_key_name(field_key, repo)
931 933 if field:
932 934 self.sa.delete(field)
933 935
934 936 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
935 937 clone_uri=None, repo_store_location=None,
936 938 use_global_config=False, install_hooks=True):
937 939 """
938 940 makes repository on filesystem. It's group aware means it'll create
939 941 a repository within a group, and alter the paths accordingly of
940 942 group location
941 943
942 944 :param repo_name:
943 945 :param alias:
944 946 :param parent:
945 947 :param clone_uri:
946 948 :param repo_store_location:
947 949 """
948 950 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
949 951 from rhodecode.model.scm import ScmModel
950 952
951 953 if Repository.NAME_SEP in repo_name:
952 954 raise ValueError(
953 955 'repo_name must not contain groups got `%s`' % repo_name)
954 956
955 957 if isinstance(repo_group, RepoGroup):
956 958 new_parent_path = os.sep.join(repo_group.full_path_splitted)
957 959 else:
958 960 new_parent_path = repo_group or ''
959 961
960 962 if repo_store_location:
961 963 _paths = [repo_store_location]
962 964 else:
963 965 _paths = [self.repos_path, new_parent_path, repo_name]
964 966 # we need to make it str for mercurial
965 967 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
966 968
967 969 # check if this path is not a repository
968 970 if is_valid_repo(repo_path, self.repos_path):
969 971 raise Exception('This path %s is a valid repository' % repo_path)
970 972
971 973 # check if this path is a group
972 974 if is_valid_repo_group(repo_path, self.repos_path):
973 975 raise Exception('This path %s is a valid group' % repo_path)
974 976
975 977 log.info('creating repo %s in %s from url: `%s`',
976 978 repo_name, safe_unicode(repo_path),
977 979 obfuscate_url_pw(clone_uri))
978 980
979 981 backend = get_backend(repo_type)
980 982
981 983 config_repo = None if use_global_config else repo_name
982 984 if config_repo and new_parent_path:
983 985 config_repo = Repository.NAME_SEP.join(
984 986 (new_parent_path, config_repo))
985 987 config = make_db_config(clear_session=False, repo=config_repo)
986 988 config.set('extensions', 'largefiles', '')
987 989
988 990 # patch and reset hooks section of UI config to not run any
989 991 # hooks on creating remote repo
990 992 config.clear_section('hooks')
991 993
992 994 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
993 995 if repo_type == 'git':
994 996 repo = backend(
995 997 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
996 998 with_wire={"cache": False})
997 999 else:
998 1000 repo = backend(
999 1001 repo_path, config=config, create=True, src_url=clone_uri,
1000 1002 with_wire={"cache": False})
1001 1003
1002 1004 if install_hooks:
1003 1005 repo.install_hooks()
1004 1006
1005 1007 log.debug('Created repo %s with %s backend',
1006 1008 safe_unicode(repo_name), safe_unicode(repo_type))
1007 1009 return repo
1008 1010
1009 1011 def _rename_filesystem_repo(self, old, new):
1010 1012 """
1011 1013 renames repository on filesystem
1012 1014
1013 1015 :param old: old name
1014 1016 :param new: new name
1015 1017 """
1016 1018 log.info('renaming repo from %s to %s', old, new)
1017 1019
1018 1020 old_path = os.path.join(self.repos_path, old)
1019 1021 new_path = os.path.join(self.repos_path, new)
1020 1022 if os.path.isdir(new_path):
1021 1023 raise Exception(
1022 1024 'Was trying to rename to already existing dir %s' % new_path
1023 1025 )
1024 1026 shutil.move(old_path, new_path)
1025 1027
1026 1028 def _delete_filesystem_repo(self, repo):
1027 1029 """
1028 1030 removes repo from filesystem, the removal is acctually made by
1029 1031 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1030 1032 repository is no longer valid for rhodecode, can be undeleted later on
1031 1033 by reverting the renames on this repository
1032 1034
1033 1035 :param repo: repo object
1034 1036 """
1035 1037 rm_path = os.path.join(self.repos_path, repo.repo_name)
1036 1038 repo_group = repo.group
1037 1039 log.info("Removing repository %s", rm_path)
1038 1040 # disable hg/git internal that it doesn't get detected as repo
1039 1041 alias = repo.repo_type
1040 1042
1041 1043 config = make_db_config(clear_session=False)
1042 1044 config.set('extensions', 'largefiles', '')
1043 1045 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1044 1046
1045 1047 # skip this for bare git repos
1046 1048 if not bare:
1047 1049 # disable VCS repo
1048 1050 vcs_path = os.path.join(rm_path, '.%s' % alias)
1049 1051 if os.path.exists(vcs_path):
1050 1052 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1051 1053
1052 1054 _now = datetime.datetime.now()
1053 1055 _ms = str(_now.microsecond).rjust(6, '0')
1054 1056 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1055 1057 repo.just_name)
1056 1058 if repo_group:
1057 1059 # if repository is in group, prefix the removal path with the group
1058 1060 args = repo_group.full_path_splitted + [_d]
1059 1061 _d = os.path.join(*args)
1060 1062
1061 1063 if os.path.isdir(rm_path):
1062 1064 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1063 1065
1064 1066 # finally cleanup diff-cache if it exists
1065 1067 cached_diffs_dir = repo.cached_diffs_dir
1066 1068 if os.path.isdir(cached_diffs_dir):
1067 1069 shutil.rmtree(cached_diffs_dir)
1068 1070
1069 1071
1070 1072 class ReadmeFinder:
1071 1073 """
1072 1074 Utility which knows how to find a readme for a specific commit.
1073 1075
1074 1076 The main idea is that this is a configurable algorithm. When creating an
1075 1077 instance you can define parameters, currently only the `default_renderer`.
1076 1078 Based on this configuration the method :meth:`search` behaves slightly
1077 1079 different.
1078 1080 """
1079 1081
1080 1082 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1081 1083 path_re = re.compile(r'^docs?', re.IGNORECASE)
1082 1084
1083 1085 default_priorities = {
1084 1086 None: 0,
1085 1087 '.text': 2,
1086 1088 '.txt': 3,
1087 1089 '.rst': 1,
1088 1090 '.rest': 2,
1089 1091 '.md': 1,
1090 1092 '.mkdn': 2,
1091 1093 '.mdown': 3,
1092 1094 '.markdown': 4,
1093 1095 }
1094 1096
1095 1097 path_priority = {
1096 1098 'doc': 0,
1097 1099 'docs': 1,
1098 1100 }
1099 1101
1100 1102 FALLBACK_PRIORITY = 99
1101 1103
1102 1104 RENDERER_TO_EXTENSION = {
1103 1105 'rst': ['.rst', '.rest'],
1104 1106 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1105 1107 }
1106 1108
1107 1109 def __init__(self, default_renderer=None):
1108 1110 self._default_renderer = default_renderer
1109 1111 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1110 1112 default_renderer, [])
1111 1113
1112 1114 def search(self, commit, path=u'/'):
1113 1115 """
1114 1116 Find a readme in the given `commit`.
1115 1117 """
1116 1118 nodes = commit.get_nodes(path)
1117 1119 matches = self._match_readmes(nodes)
1118 1120 matches = self._sort_according_to_priority(matches)
1119 1121 if matches:
1120 1122 return matches[0].node
1121 1123
1122 1124 paths = self._match_paths(nodes)
1123 1125 paths = self._sort_paths_according_to_priority(paths)
1124 1126 for path in paths:
1125 1127 match = self.search(commit, path=path)
1126 1128 if match:
1127 1129 return match
1128 1130
1129 1131 return None
1130 1132
1131 1133 def _match_readmes(self, nodes):
1132 1134 for node in nodes:
1133 1135 if not node.is_file():
1134 1136 continue
1135 1137 path = node.path.rsplit('/', 1)[-1]
1136 1138 match = self.readme_re.match(path)
1137 1139 if match:
1138 1140 extension = match.group(1)
1139 1141 yield ReadmeMatch(node, match, self._priority(extension))
1140 1142
1141 1143 def _match_paths(self, nodes):
1142 1144 for node in nodes:
1143 1145 if not node.is_dir():
1144 1146 continue
1145 1147 match = self.path_re.match(node.path)
1146 1148 if match:
1147 1149 yield node.path
1148 1150
1149 1151 def _priority(self, extension):
1150 1152 renderer_priority = (
1151 1153 0 if extension in self._renderer_extensions else 1)
1152 1154 extension_priority = self.default_priorities.get(
1153 1155 extension, self.FALLBACK_PRIORITY)
1154 1156 return (renderer_priority, extension_priority)
1155 1157
1156 1158 def _sort_according_to_priority(self, matches):
1157 1159
1158 1160 def priority_and_path(match):
1159 1161 return (match.priority, match.path)
1160 1162
1161 1163 return sorted(matches, key=priority_and_path)
1162 1164
1163 1165 def _sort_paths_according_to_priority(self, paths):
1164 1166
1165 1167 def priority_and_path(path):
1166 1168 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1167 1169
1168 1170 return sorted(paths, key=priority_and_path)
1169 1171
1170 1172
1171 1173 class ReadmeMatch:
1172 1174
1173 1175 def __init__(self, node, match, priority):
1174 1176 self.node = node
1175 1177 self._match = match
1176 1178 self.priority = priority
1177 1179
1178 1180 @property
1179 1181 def path(self):
1180 1182 return self.node.path
1181 1183
1182 1184 def __repr__(self):
1183 1185 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
General Comments 0
You need to be logged in to leave comments. Login now