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