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