##// END OF EJS Templates
core: use new style pyramid partial renderer where possible.
marcink -
r1897:01df07bd default
parent child Browse files
Show More
@@ -1,358 +1,357 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.utils import PartialRenderer
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
31 from rhodecode.model import repo
30 from rhodecode.model import repo
32 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
33 from rhodecode.model.db import User
32 from rhodecode.model.db import User
34 from rhodecode.model.scm import ScmModel
33 from rhodecode.model.scm import ScmModel
35
34
36 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
37
36
38
37
39 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
40 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
41
40
42
41
43 def add_route_with_slash(config,name, pattern, **kw):
42 def add_route_with_slash(config,name, pattern, **kw):
44 config.add_route(name, pattern, **kw)
43 config.add_route(name, pattern, **kw)
45 if not pattern.endswith('/'):
44 if not pattern.endswith('/'):
46 config.add_route(name + '_slash', pattern + '/', **kw)
45 config.add_route(name + '_slash', pattern + '/', **kw)
47
46
48
47
49 def get_format_ref_id(repo):
48 def get_format_ref_id(repo):
50 """Returns a `repo` specific reference formatter function"""
49 """Returns a `repo` specific reference formatter function"""
51 if h.is_svn(repo):
50 if h.is_svn(repo):
52 return _format_ref_id_svn
51 return _format_ref_id_svn
53 else:
52 else:
54 return _format_ref_id
53 return _format_ref_id
55
54
56
55
57 def _format_ref_id(name, raw_id):
56 def _format_ref_id(name, raw_id):
58 """Default formatting of a given reference `name`"""
57 """Default formatting of a given reference `name`"""
59 return name
58 return name
60
59
61
60
62 def _format_ref_id_svn(name, raw_id):
61 def _format_ref_id_svn(name, raw_id):
63 """Special way of formatting a reference for Subversion including path"""
62 """Special way of formatting a reference for Subversion including path"""
64 return '%s@%s' % (name, raw_id)
63 return '%s@%s' % (name, raw_id)
65
64
66
65
67 class TemplateArgs(StrictAttributeDict):
66 class TemplateArgs(StrictAttributeDict):
68 pass
67 pass
69
68
70
69
71 class BaseAppView(object):
70 class BaseAppView(object):
72
71
73 def __init__(self, context, request):
72 def __init__(self, context, request):
74 self.request = request
73 self.request = request
75 self.context = context
74 self.context = context
76 self.session = request.session
75 self.session = request.session
77 self._rhodecode_user = request.user # auth user
76 self._rhodecode_user = request.user # auth user
78 self._rhodecode_db_user = self._rhodecode_user.get_instance()
77 self._rhodecode_db_user = self._rhodecode_user.get_instance()
79 self._maybe_needs_password_change(
78 self._maybe_needs_password_change(
80 request.matched_route.name, self._rhodecode_db_user)
79 request.matched_route.name, self._rhodecode_db_user)
81
80
82 def _maybe_needs_password_change(self, view_name, user_obj):
81 def _maybe_needs_password_change(self, view_name, user_obj):
83 log.debug('Checking if user %s needs password change on view %s',
82 log.debug('Checking if user %s needs password change on view %s',
84 user_obj, view_name)
83 user_obj, view_name)
85 skip_user_views = [
84 skip_user_views = [
86 'logout', 'login',
85 'logout', 'login',
87 'my_account_password', 'my_account_password_update'
86 'my_account_password', 'my_account_password_update'
88 ]
87 ]
89
88
90 if not user_obj:
89 if not user_obj:
91 return
90 return
92
91
93 if user_obj.username == User.DEFAULT_USER:
92 if user_obj.username == User.DEFAULT_USER:
94 return
93 return
95
94
96 now = time.time()
95 now = time.time()
97 should_change = user_obj.user_data.get('force_password_change')
96 should_change = user_obj.user_data.get('force_password_change')
98 change_after = safe_int(should_change) or 0
97 change_after = safe_int(should_change) or 0
99 if should_change and now > change_after:
98 if should_change and now > change_after:
100 log.debug('User %s requires password change', user_obj)
99 log.debug('User %s requires password change', user_obj)
101 h.flash('You are required to change your password', 'warning',
100 h.flash('You are required to change your password', 'warning',
102 ignore_duplicate=True)
101 ignore_duplicate=True)
103
102
104 if view_name not in skip_user_views:
103 if view_name not in skip_user_views:
105 raise HTTPFound(
104 raise HTTPFound(
106 self.request.route_path('my_account_password'))
105 self.request.route_path('my_account_password'))
107
106
108 def _get_local_tmpl_context(self, include_app_defaults=False):
107 def _get_local_tmpl_context(self, include_app_defaults=False):
109 c = TemplateArgs()
108 c = TemplateArgs()
110 c.auth_user = self.request.user
109 c.auth_user = self.request.user
111 if include_app_defaults:
110 if include_app_defaults:
112 # NOTE(marcink): after full pyramid migration include_app_defaults
111 # NOTE(marcink): after full pyramid migration include_app_defaults
113 # should be turned on by default
112 # should be turned on by default
114 from rhodecode.lib.base import attach_context_attributes
113 from rhodecode.lib.base import attach_context_attributes
115 attach_context_attributes(c, self.request, self.request.user.user_id)
114 attach_context_attributes(c, self.request, self.request.user.user_id)
116 return c
115 return c
117
116
118 def _register_global_c(self, tmpl_args):
117 def _register_global_c(self, tmpl_args):
119 """
118 """
120 Registers attributes to pylons global `c`
119 Registers attributes to pylons global `c`
121 """
120 """
122 # TODO(marcink): remove once pyramid migration is finished
121 # TODO(marcink): remove once pyramid migration is finished
123 from pylons import tmpl_context as c
122 from pylons import tmpl_context as c
124 for k, v in tmpl_args.items():
123 for k, v in tmpl_args.items():
125 setattr(c, k, v)
124 setattr(c, k, v)
126
125
127 def _get_template_context(self, tmpl_args):
126 def _get_template_context(self, tmpl_args):
128 self._register_global_c(tmpl_args)
127 self._register_global_c(tmpl_args)
129
128
130 local_tmpl_args = {
129 local_tmpl_args = {
131 'defaults': {},
130 'defaults': {},
132 'errors': {},
131 'errors': {},
133 }
132 }
134 local_tmpl_args.update(tmpl_args)
133 local_tmpl_args.update(tmpl_args)
135 return local_tmpl_args
134 return local_tmpl_args
136
135
137 def load_default_context(self):
136 def load_default_context(self):
138 """
137 """
139 example:
138 example:
140
139
141 def load_default_context(self):
140 def load_default_context(self):
142 c = self._get_local_tmpl_context()
141 c = self._get_local_tmpl_context()
143 c.custom_var = 'foobar'
142 c.custom_var = 'foobar'
144 self._register_global_c(c)
143 self._register_global_c(c)
145 return c
144 return c
146 """
145 """
147 raise NotImplementedError('Needs implementation in view class')
146 raise NotImplementedError('Needs implementation in view class')
148
147
149
148
150 class RepoAppView(BaseAppView):
149 class RepoAppView(BaseAppView):
151
150
152 def __init__(self, context, request):
151 def __init__(self, context, request):
153 super(RepoAppView, self).__init__(context, request)
152 super(RepoAppView, self).__init__(context, request)
154 self.db_repo = request.db_repo
153 self.db_repo = request.db_repo
155 self.db_repo_name = self.db_repo.repo_name
154 self.db_repo_name = self.db_repo.repo_name
156 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
155 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
157
156
158 def _handle_missing_requirements(self, error):
157 def _handle_missing_requirements(self, error):
159 log.error(
158 log.error(
160 'Requirements are missing for repository %s: %s',
159 'Requirements are missing for repository %s: %s',
161 self.db_repo_name, error.message)
160 self.db_repo_name, error.message)
162
161
163 def _get_local_tmpl_context(self, include_app_defaults=False):
162 def _get_local_tmpl_context(self, include_app_defaults=False):
164 c = super(RepoAppView, self)._get_local_tmpl_context(
163 c = super(RepoAppView, self)._get_local_tmpl_context(
165 include_app_defaults=include_app_defaults)
164 include_app_defaults=include_app_defaults)
166
165
167 # register common vars for this type of view
166 # register common vars for this type of view
168 c.rhodecode_db_repo = self.db_repo
167 c.rhodecode_db_repo = self.db_repo
169 c.repo_name = self.db_repo_name
168 c.repo_name = self.db_repo_name
170 c.repository_pull_requests = self.db_repo_pull_requests
169 c.repository_pull_requests = self.db_repo_pull_requests
171
170
172 c.repository_requirements_missing = False
171 c.repository_requirements_missing = False
173 try:
172 try:
174 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
173 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
175 except RepositoryRequirementError as e:
174 except RepositoryRequirementError as e:
176 c.repository_requirements_missing = True
175 c.repository_requirements_missing = True
177 self._handle_missing_requirements(e)
176 self._handle_missing_requirements(e)
178
177
179 return c
178 return c
180
179
181
180
182 class DataGridAppView(object):
181 class DataGridAppView(object):
183 """
182 """
184 Common class to have re-usable grid rendering components
183 Common class to have re-usable grid rendering components
185 """
184 """
186
185
187 def _extract_ordering(self, request, column_map=None):
186 def _extract_ordering(self, request, column_map=None):
188 column_map = column_map or {}
187 column_map = column_map or {}
189 column_index = safe_int(request.GET.get('order[0][column]'))
188 column_index = safe_int(request.GET.get('order[0][column]'))
190 order_dir = request.GET.get(
189 order_dir = request.GET.get(
191 'order[0][dir]', 'desc')
190 'order[0][dir]', 'desc')
192 order_by = request.GET.get(
191 order_by = request.GET.get(
193 'columns[%s][data][sort]' % column_index, 'name_raw')
192 'columns[%s][data][sort]' % column_index, 'name_raw')
194
193
195 # translate datatable to DB columns
194 # translate datatable to DB columns
196 order_by = column_map.get(order_by) or order_by
195 order_by = column_map.get(order_by) or order_by
197
196
198 search_q = request.GET.get('search[value]')
197 search_q = request.GET.get('search[value]')
199 return search_q, order_by, order_dir
198 return search_q, order_by, order_dir
200
199
201 def _extract_chunk(self, request):
200 def _extract_chunk(self, request):
202 start = safe_int(request.GET.get('start'), 0)
201 start = safe_int(request.GET.get('start'), 0)
203 length = safe_int(request.GET.get('length'), 25)
202 length = safe_int(request.GET.get('length'), 25)
204 draw = safe_int(request.GET.get('draw'))
203 draw = safe_int(request.GET.get('draw'))
205 return draw, start, length
204 return draw, start, length
206
205
207
206
208 class BaseReferencesView(RepoAppView):
207 class BaseReferencesView(RepoAppView):
209 """
208 """
210 Base for reference view for branches, tags and bookmarks.
209 Base for reference view for branches, tags and bookmarks.
211 """
210 """
212 def load_default_context(self):
211 def load_default_context(self):
213 c = self._get_local_tmpl_context()
212 c = self._get_local_tmpl_context()
214
213
215 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
214 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
216 c.repo_info = self.db_repo
215 c.repo_info = self.db_repo
217
216
218 self._register_global_c(c)
217 self._register_global_c(c)
219 return c
218 return c
220
219
221 def load_refs_context(self, ref_items, partials_template):
220 def load_refs_context(self, ref_items, partials_template):
222 _render = PartialRenderer(partials_template)
223 _data = []
221 _data = []
222 _render = self.request.get_partial_renderer(partials_template)
224 pre_load = ["author", "date", "message"]
223 pre_load = ["author", "date", "message"]
225
224
226 is_svn = h.is_svn(self.rhodecode_vcs_repo)
225 is_svn = h.is_svn(self.rhodecode_vcs_repo)
227 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
226 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
228
227
229 for ref_name, commit_id in ref_items:
228 for ref_name, commit_id in ref_items:
230 commit = self.rhodecode_vcs_repo.get_commit(
229 commit = self.rhodecode_vcs_repo.get_commit(
231 commit_id=commit_id, pre_load=pre_load)
230 commit_id=commit_id, pre_load=pre_load)
232
231
233 # TODO: johbo: Unify generation of reference links
232 # TODO: johbo: Unify generation of reference links
234 use_commit_id = '/' in ref_name or is_svn
233 use_commit_id = '/' in ref_name or is_svn
235 files_url = h.url(
234 files_url = h.url(
236 'files_home',
235 'files_home',
237 repo_name=c.repo_name,
236 repo_name=c.repo_name,
238 f_path=ref_name if is_svn else '',
237 f_path=ref_name if is_svn else '',
239 revision=commit_id if use_commit_id else ref_name,
238 revision=commit_id if use_commit_id else ref_name,
240 at=ref_name)
239 at=ref_name)
241
240
242 _data.append({
241 _data.append({
243 "name": _render('name', ref_name, files_url),
242 "name": _render('name', ref_name, files_url),
244 "name_raw": ref_name,
243 "name_raw": ref_name,
245 "date": _render('date', commit.date),
244 "date": _render('date', commit.date),
246 "date_raw": datetime_to_time(commit.date),
245 "date_raw": datetime_to_time(commit.date),
247 "author": _render('author', commit.author),
246 "author": _render('author', commit.author),
248 "commit": _render(
247 "commit": _render(
249 'commit', commit.message, commit.raw_id, commit.idx),
248 'commit', commit.message, commit.raw_id, commit.idx),
250 "commit_raw": commit.idx,
249 "commit_raw": commit.idx,
251 "compare": _render(
250 "compare": _render(
252 'compare', format_ref_id(ref_name, commit.raw_id)),
251 'compare', format_ref_id(ref_name, commit.raw_id)),
253 })
252 })
254 c.has_references = bool(_data)
253 c.has_references = bool(_data)
255 c.data = json.dumps(_data)
254 c.data = json.dumps(_data)
256
255
257
256
258 class RepoRoutePredicate(object):
257 class RepoRoutePredicate(object):
259 def __init__(self, val, config):
258 def __init__(self, val, config):
260 self.val = val
259 self.val = val
261
260
262 def text(self):
261 def text(self):
263 return 'repo_route = %s' % self.val
262 return 'repo_route = %s' % self.val
264
263
265 phash = text
264 phash = text
266
265
267 def __call__(self, info, request):
266 def __call__(self, info, request):
268
267
269 if hasattr(request, 'vcs_call'):
268 if hasattr(request, 'vcs_call'):
270 # skip vcs calls
269 # skip vcs calls
271 return
270 return
272
271
273 repo_name = info['match']['repo_name']
272 repo_name = info['match']['repo_name']
274 repo_model = repo.RepoModel()
273 repo_model = repo.RepoModel()
275 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
274 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
276
275
277 if by_name_match:
276 if by_name_match:
278 # register this as request object we can re-use later
277 # register this as request object we can re-use later
279 request.db_repo = by_name_match
278 request.db_repo = by_name_match
280 return True
279 return True
281
280
282 by_id_match = repo_model.get_repo_by_id(repo_name)
281 by_id_match = repo_model.get_repo_by_id(repo_name)
283 if by_id_match:
282 if by_id_match:
284 request.db_repo = by_id_match
283 request.db_repo = by_id_match
285 return True
284 return True
286
285
287 return False
286 return False
288
287
289
288
290 class RepoTypeRoutePredicate(object):
289 class RepoTypeRoutePredicate(object):
291 def __init__(self, val, config):
290 def __init__(self, val, config):
292 self.val = val or ['hg', 'git', 'svn']
291 self.val = val or ['hg', 'git', 'svn']
293
292
294 def text(self):
293 def text(self):
295 return 'repo_accepted_type = %s' % self.val
294 return 'repo_accepted_type = %s' % self.val
296
295
297 phash = text
296 phash = text
298
297
299 def __call__(self, info, request):
298 def __call__(self, info, request):
300 if hasattr(request, 'vcs_call'):
299 if hasattr(request, 'vcs_call'):
301 # skip vcs calls
300 # skip vcs calls
302 return
301 return
303
302
304 rhodecode_db_repo = request.db_repo
303 rhodecode_db_repo = request.db_repo
305
304
306 log.debug(
305 log.debug(
307 '%s checking repo type for %s in %s',
306 '%s checking repo type for %s in %s',
308 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
307 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
309
308
310 if rhodecode_db_repo.repo_type in self.val:
309 if rhodecode_db_repo.repo_type in self.val:
311 return True
310 return True
312 else:
311 else:
313 log.warning('Current view is not supported for repo type:%s',
312 log.warning('Current view is not supported for repo type:%s',
314 rhodecode_db_repo.repo_type)
313 rhodecode_db_repo.repo_type)
315 #
314 #
316 # h.flash(h.literal(
315 # h.flash(h.literal(
317 # _('Action not supported for %s.' % rhodecode_repo.alias)),
316 # _('Action not supported for %s.' % rhodecode_repo.alias)),
318 # category='warning')
317 # category='warning')
319 # return redirect(
318 # return redirect(
320 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
319 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
321
320
322 return False
321 return False
323
322
324
323
325 class RepoGroupRoutePredicate(object):
324 class RepoGroupRoutePredicate(object):
326 def __init__(self, val, config):
325 def __init__(self, val, config):
327 self.val = val
326 self.val = val
328
327
329 def text(self):
328 def text(self):
330 return 'repo_group_route = %s' % self.val
329 return 'repo_group_route = %s' % self.val
331
330
332 phash = text
331 phash = text
333
332
334 def __call__(self, info, request):
333 def __call__(self, info, request):
335 if hasattr(request, 'vcs_call'):
334 if hasattr(request, 'vcs_call'):
336 # skip vcs calls
335 # skip vcs calls
337 return
336 return
338
337
339 repo_group_name = info['match']['repo_group_name']
338 repo_group_name = info['match']['repo_group_name']
340 repo_group_model = repo_group.RepoGroupModel()
339 repo_group_model = repo_group.RepoGroupModel()
341 by_name_match = repo_group_model.get_by_group_name(
340 by_name_match = repo_group_model.get_by_group_name(
342 repo_group_name, cache=True)
341 repo_group_name, cache=True)
343
342
344 if by_name_match:
343 if by_name_match:
345 # register this as request object we can re-use later
344 # register this as request object we can re-use later
346 request.db_repo_group = by_name_match
345 request.db_repo_group = by_name_match
347 return True
346 return True
348
347
349 return False
348 return False
350
349
351
350
352 def includeme(config):
351 def includeme(config):
353 config.add_route_predicate(
352 config.add_route_predicate(
354 'repo_route', RepoRoutePredicate)
353 'repo_route', RepoRoutePredicate)
355 config.add_route_predicate(
354 config.add_route_predicate(
356 'repo_accepted_types', RepoTypeRoutePredicate)
355 'repo_accepted_types', RepoTypeRoutePredicate)
357 config.add_route_predicate(
356 config.add_route_predicate(
358 'repo_group_route', RepoGroupRoutePredicate)
357 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,505 +1,505 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from sqlalchemy.sql.functions import coalesce
27 from sqlalchemy.sql.functions import coalesce
28
28
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
30
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import PartialRenderer
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
40 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
40 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
42 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
43
42
44 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
45
44
46
45
47 class AdminUsersView(BaseAppView, DataGridAppView):
46 class AdminUsersView(BaseAppView, DataGridAppView):
48 ALLOW_SCOPED_TOKENS = False
47 ALLOW_SCOPED_TOKENS = False
49 """
48 """
50 This view has alternative version inside EE, if modified please take a look
49 This view has alternative version inside EE, if modified please take a look
51 in there as well.
50 in there as well.
52 """
51 """
53
52
54 def load_default_context(self):
53 def load_default_context(self):
55 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
56 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
57 self._register_global_c(c)
56 self._register_global_c(c)
58 return c
57 return c
59
58
60 def _redirect_for_default_user(self, username):
59 def _redirect_for_default_user(self, username):
61 _ = self.request.translate
60 _ = self.request.translate
62 if username == User.DEFAULT_USER:
61 if username == User.DEFAULT_USER:
63 h.flash(_("You can't edit this user"), category='warning')
62 h.flash(_("You can't edit this user"), category='warning')
64 # TODO(marcink): redirect to 'users' admin panel once this
63 # TODO(marcink): redirect to 'users' admin panel once this
65 # is a pyramid view
64 # is a pyramid view
66 raise HTTPFound('/')
65 raise HTTPFound('/')
67
66
68 @HasPermissionAllDecorator('hg.admin')
67 @HasPermissionAllDecorator('hg.admin')
69 @view_config(
68 @view_config(
70 route_name='users', request_method='GET',
69 route_name='users', request_method='GET',
71 renderer='rhodecode:templates/admin/users/users.mako')
70 renderer='rhodecode:templates/admin/users/users.mako')
72 def users_list(self):
71 def users_list(self):
73 c = self.load_default_context()
72 c = self.load_default_context()
74 return self._get_template_context(c)
73 return self._get_template_context(c)
75
74
76 @HasPermissionAllDecorator('hg.admin')
75 @HasPermissionAllDecorator('hg.admin')
77 @view_config(
76 @view_config(
78 # renderer defined below
77 # renderer defined below
79 route_name='users_data', request_method='GET',
78 route_name='users_data', request_method='GET',
80 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
81 def users_list_data(self):
80 def users_list_data(self):
82 draw, start, limit = self._extract_chunk(self.request)
81 draw, start, limit = self._extract_chunk(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(self.request)
82 search_q, order_by, order_dir = self._extract_ordering(self.request)
84
83
85 _render = PartialRenderer('data_table/_dt_elements.mako')
84 _render = self.request.get_partial_renderer(
85 'data_table/_dt_elements.mako')
86
86
87 def user_actions(user_id, username):
87 def user_actions(user_id, username):
88 return _render("user_actions", user_id, username)
88 return _render("user_actions", user_id, username)
89
89
90 users_data_total_count = User.query()\
90 users_data_total_count = User.query()\
91 .filter(User.username != User.DEFAULT_USER) \
91 .filter(User.username != User.DEFAULT_USER) \
92 .count()
92 .count()
93
93
94 # json generate
94 # json generate
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
96
96
97 if search_q:
97 if search_q:
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
99 base_q = base_q.filter(or_(
99 base_q = base_q.filter(or_(
100 User.username.ilike(like_expression),
100 User.username.ilike(like_expression),
101 User._email.ilike(like_expression),
101 User._email.ilike(like_expression),
102 User.name.ilike(like_expression),
102 User.name.ilike(like_expression),
103 User.lastname.ilike(like_expression),
103 User.lastname.ilike(like_expression),
104 ))
104 ))
105
105
106 users_data_total_filtered_count = base_q.count()
106 users_data_total_filtered_count = base_q.count()
107
107
108 sort_col = getattr(User, order_by, None)
108 sort_col = getattr(User, order_by, None)
109 if sort_col:
109 if sort_col:
110 if order_dir == 'asc':
110 if order_dir == 'asc':
111 # handle null values properly to order by NULL last
111 # handle null values properly to order by NULL last
112 if order_by in ['last_activity']:
112 if order_by in ['last_activity']:
113 sort_col = coalesce(sort_col, datetime.date.max)
113 sort_col = coalesce(sort_col, datetime.date.max)
114 sort_col = sort_col.asc()
114 sort_col = sort_col.asc()
115 else:
115 else:
116 # handle null values properly to order by NULL last
116 # handle null values properly to order by NULL last
117 if order_by in ['last_activity']:
117 if order_by in ['last_activity']:
118 sort_col = coalesce(sort_col, datetime.date.min)
118 sort_col = coalesce(sort_col, datetime.date.min)
119 sort_col = sort_col.desc()
119 sort_col = sort_col.desc()
120
120
121 base_q = base_q.order_by(sort_col)
121 base_q = base_q.order_by(sort_col)
122 base_q = base_q.offset(start).limit(limit)
122 base_q = base_q.offset(start).limit(limit)
123
123
124 users_list = base_q.all()
124 users_list = base_q.all()
125
125
126 users_data = []
126 users_data = []
127 for user in users_list:
127 for user in users_list:
128 users_data.append({
128 users_data.append({
129 "username": h.gravatar_with_user(user.username),
129 "username": h.gravatar_with_user(user.username),
130 "email": user.email,
130 "email": user.email,
131 "first_name": user.first_name,
131 "first_name": user.first_name,
132 "last_name": user.last_name,
132 "last_name": user.last_name,
133 "last_login": h.format_date(user.last_login),
133 "last_login": h.format_date(user.last_login),
134 "last_activity": h.format_date(user.last_activity),
134 "last_activity": h.format_date(user.last_activity),
135 "active": h.bool2icon(user.active),
135 "active": h.bool2icon(user.active),
136 "active_raw": user.active,
136 "active_raw": user.active,
137 "admin": h.bool2icon(user.admin),
137 "admin": h.bool2icon(user.admin),
138 "extern_type": user.extern_type,
138 "extern_type": user.extern_type,
139 "extern_name": user.extern_name,
139 "extern_name": user.extern_name,
140 "action": user_actions(user.user_id, user.username),
140 "action": user_actions(user.user_id, user.username),
141 })
141 })
142
142
143 data = ({
143 data = ({
144 'draw': draw,
144 'draw': draw,
145 'data': users_data,
145 'data': users_data,
146 'recordsTotal': users_data_total_count,
146 'recordsTotal': users_data_total_count,
147 'recordsFiltered': users_data_total_filtered_count,
147 'recordsFiltered': users_data_total_filtered_count,
148 })
148 })
149
149
150 return data
150 return data
151
151
152 @LoginRequired()
152 @LoginRequired()
153 @HasPermissionAllDecorator('hg.admin')
153 @HasPermissionAllDecorator('hg.admin')
154 @view_config(
154 @view_config(
155 route_name='edit_user_auth_tokens', request_method='GET',
155 route_name='edit_user_auth_tokens', request_method='GET',
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
157 def auth_tokens(self):
157 def auth_tokens(self):
158 _ = self.request.translate
158 _ = self.request.translate
159 c = self.load_default_context()
159 c = self.load_default_context()
160
160
161 user_id = self.request.matchdict.get('user_id')
161 user_id = self.request.matchdict.get('user_id')
162 c.user = User.get_or_404(user_id, pyramid_exc=True)
162 c.user = User.get_or_404(user_id, pyramid_exc=True)
163 self._redirect_for_default_user(c.user.username)
163 self._redirect_for_default_user(c.user.username)
164
164
165 c.active = 'auth_tokens'
165 c.active = 'auth_tokens'
166
166
167 c.lifetime_values = [
167 c.lifetime_values = [
168 (str(-1), _('forever')),
168 (str(-1), _('forever')),
169 (str(5), _('5 minutes')),
169 (str(5), _('5 minutes')),
170 (str(60), _('1 hour')),
170 (str(60), _('1 hour')),
171 (str(60 * 24), _('1 day')),
171 (str(60 * 24), _('1 day')),
172 (str(60 * 24 * 30), _('1 month')),
172 (str(60 * 24 * 30), _('1 month')),
173 ]
173 ]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
175 c.role_values = [
175 c.role_values = [
176 (x, AuthTokenModel.cls._get_role_name(x))
176 (x, AuthTokenModel.cls._get_role_name(x))
177 for x in AuthTokenModel.cls.ROLES]
177 for x in AuthTokenModel.cls.ROLES]
178 c.role_options = [(c.role_values, _("Role"))]
178 c.role_options = [(c.role_values, _("Role"))]
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
180 c.user.user_id, show_expired=True)
180 c.user.user_id, show_expired=True)
181 return self._get_template_context(c)
181 return self._get_template_context(c)
182
182
183 def maybe_attach_token_scope(self, token):
183 def maybe_attach_token_scope(self, token):
184 # implemented in EE edition
184 # implemented in EE edition
185 pass
185 pass
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasPermissionAllDecorator('hg.admin')
188 @HasPermissionAllDecorator('hg.admin')
189 @CSRFRequired()
189 @CSRFRequired()
190 @view_config(
190 @view_config(
191 route_name='edit_user_auth_tokens_add', request_method='POST')
191 route_name='edit_user_auth_tokens_add', request_method='POST')
192 def auth_tokens_add(self):
192 def auth_tokens_add(self):
193 _ = self.request.translate
193 _ = self.request.translate
194 c = self.load_default_context()
194 c = self.load_default_context()
195
195
196 user_id = self.request.matchdict.get('user_id')
196 user_id = self.request.matchdict.get('user_id')
197 c.user = User.get_or_404(user_id, pyramid_exc=True)
197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198
198
199 self._redirect_for_default_user(c.user.username)
199 self._redirect_for_default_user(c.user.username)
200
200
201 user_data = c.user.get_api_data()
201 user_data = c.user.get_api_data()
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
203 description = self.request.POST.get('description')
203 description = self.request.POST.get('description')
204 role = self.request.POST.get('role')
204 role = self.request.POST.get('role')
205
205
206 token = AuthTokenModel().create(
206 token = AuthTokenModel().create(
207 c.user.user_id, description, lifetime, role)
207 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
208 token_data = token.get_api_data()
209
209
210 self.maybe_attach_token_scope(token)
210 self.maybe_attach_token_scope(token)
211 audit_logger.store_web(
211 audit_logger.store_web(
212 'user.edit.token.add', action_data={
212 'user.edit.token.add', action_data={
213 'data': {'token': token_data, 'user': user_data}},
213 'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
214 user=self._rhodecode_user, )
215 Session().commit()
215 Session().commit()
216
216
217 h.flash(_("Auth token successfully created"), category='success')
217 h.flash(_("Auth token successfully created"), category='success')
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
219
219
220 @LoginRequired()
220 @LoginRequired()
221 @HasPermissionAllDecorator('hg.admin')
221 @HasPermissionAllDecorator('hg.admin')
222 @CSRFRequired()
222 @CSRFRequired()
223 @view_config(
223 @view_config(
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
225 def auth_tokens_delete(self):
225 def auth_tokens_delete(self):
226 _ = self.request.translate
226 _ = self.request.translate
227 c = self.load_default_context()
227 c = self.load_default_context()
228
228
229 user_id = self.request.matchdict.get('user_id')
229 user_id = self.request.matchdict.get('user_id')
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
231 self._redirect_for_default_user(c.user.username)
231 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
232 user_data = c.user.get_api_data()
233
233
234 del_auth_token = self.request.POST.get('del_auth_token')
234 del_auth_token = self.request.POST.get('del_auth_token')
235
235
236 if del_auth_token:
236 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
238 token_data = token.get_api_data()
238 token_data = token.get_api_data()
239
239
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store_web(
241 audit_logger.store_web(
242 'user.edit.token.delete', action_data={
242 'user.edit.token.delete', action_data={
243 'data': {'token': token_data, 'user': user_data}},
243 'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
244 user=self._rhodecode_user,)
245 Session().commit()
245 Session().commit()
246 h.flash(_("Auth token successfully deleted"), category='success')
246 h.flash(_("Auth token successfully deleted"), category='success')
247
247
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
249
249
250 @LoginRequired()
250 @LoginRequired()
251 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
252 @view_config(
253 route_name='edit_user_emails', request_method='GET',
253 route_name='edit_user_emails', request_method='GET',
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 def emails(self):
255 def emails(self):
256 _ = self.request.translate
256 _ = self.request.translate
257 c = self.load_default_context()
257 c = self.load_default_context()
258
258
259 user_id = self.request.matchdict.get('user_id')
259 user_id = self.request.matchdict.get('user_id')
260 c.user = User.get_or_404(user_id, pyramid_exc=True)
260 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 self._redirect_for_default_user(c.user.username)
261 self._redirect_for_default_user(c.user.username)
262
262
263 c.active = 'emails'
263 c.active = 'emails'
264 c.user_email_map = UserEmailMap.query() \
264 c.user_email_map = UserEmailMap.query() \
265 .filter(UserEmailMap.user == c.user).all()
265 .filter(UserEmailMap.user == c.user).all()
266
266
267 return self._get_template_context(c)
267 return self._get_template_context(c)
268
268
269 @LoginRequired()
269 @LoginRequired()
270 @HasPermissionAllDecorator('hg.admin')
270 @HasPermissionAllDecorator('hg.admin')
271 @CSRFRequired()
271 @CSRFRequired()
272 @view_config(
272 @view_config(
273 route_name='edit_user_emails_add', request_method='POST')
273 route_name='edit_user_emails_add', request_method='POST')
274 def emails_add(self):
274 def emails_add(self):
275 _ = self.request.translate
275 _ = self.request.translate
276 c = self.load_default_context()
276 c = self.load_default_context()
277
277
278 user_id = self.request.matchdict.get('user_id')
278 user_id = self.request.matchdict.get('user_id')
279 c.user = User.get_or_404(user_id, pyramid_exc=True)
279 c.user = User.get_or_404(user_id, pyramid_exc=True)
280 self._redirect_for_default_user(c.user.username)
280 self._redirect_for_default_user(c.user.username)
281
281
282 email = self.request.POST.get('new_email')
282 email = self.request.POST.get('new_email')
283 user_data = c.user.get_api_data()
283 user_data = c.user.get_api_data()
284 try:
284 try:
285 UserModel().add_extra_email(c.user.user_id, email)
285 UserModel().add_extra_email(c.user.user_id, email)
286 audit_logger.store_web(
286 audit_logger.store_web(
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
288 user=self._rhodecode_user)
288 user=self._rhodecode_user)
289 Session().commit()
289 Session().commit()
290 h.flash(_("Added new email address `%s` for user account") % email,
290 h.flash(_("Added new email address `%s` for user account") % email,
291 category='success')
291 category='success')
292 except formencode.Invalid as error:
292 except formencode.Invalid as error:
293 h.flash(h.escape(error.error_dict['email']), category='error')
293 h.flash(h.escape(error.error_dict['email']), category='error')
294 except Exception:
294 except Exception:
295 log.exception("Exception during email saving")
295 log.exception("Exception during email saving")
296 h.flash(_('An error occurred during email saving'),
296 h.flash(_('An error occurred during email saving'),
297 category='error')
297 category='error')
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
299
299
300 @LoginRequired()
300 @LoginRequired()
301 @HasPermissionAllDecorator('hg.admin')
301 @HasPermissionAllDecorator('hg.admin')
302 @CSRFRequired()
302 @CSRFRequired()
303 @view_config(
303 @view_config(
304 route_name='edit_user_emails_delete', request_method='POST')
304 route_name='edit_user_emails_delete', request_method='POST')
305 def emails_delete(self):
305 def emails_delete(self):
306 _ = self.request.translate
306 _ = self.request.translate
307 c = self.load_default_context()
307 c = self.load_default_context()
308
308
309 user_id = self.request.matchdict.get('user_id')
309 user_id = self.request.matchdict.get('user_id')
310 c.user = User.get_or_404(user_id, pyramid_exc=True)
310 c.user = User.get_or_404(user_id, pyramid_exc=True)
311 self._redirect_for_default_user(c.user.username)
311 self._redirect_for_default_user(c.user.username)
312
312
313 email_id = self.request.POST.get('del_email_id')
313 email_id = self.request.POST.get('del_email_id')
314 user_model = UserModel()
314 user_model = UserModel()
315
315
316 email = UserEmailMap.query().get(email_id).email
316 email = UserEmailMap.query().get(email_id).email
317 user_data = c.user.get_api_data()
317 user_data = c.user.get_api_data()
318 user_model.delete_extra_email(c.user.user_id, email_id)
318 user_model.delete_extra_email(c.user.user_id, email_id)
319 audit_logger.store_web(
319 audit_logger.store_web(
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
321 user=self._rhodecode_user)
321 user=self._rhodecode_user)
322 Session().commit()
322 Session().commit()
323 h.flash(_("Removed email address from user account"),
323 h.flash(_("Removed email address from user account"),
324 category='success')
324 category='success')
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
326
326
327 @LoginRequired()
327 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
329 @view_config(
330 route_name='edit_user_ips', request_method='GET',
330 route_name='edit_user_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 def ips(self):
332 def ips(self):
333 _ = self.request.translate
333 _ = self.request.translate
334 c = self.load_default_context()
334 c = self.load_default_context()
335
335
336 user_id = self.request.matchdict.get('user_id')
336 user_id = self.request.matchdict.get('user_id')
337 c.user = User.get_or_404(user_id, pyramid_exc=True)
337 c.user = User.get_or_404(user_id, pyramid_exc=True)
338 self._redirect_for_default_user(c.user.username)
338 self._redirect_for_default_user(c.user.username)
339
339
340 c.active = 'ips'
340 c.active = 'ips'
341 c.user_ip_map = UserIpMap.query() \
341 c.user_ip_map = UserIpMap.query() \
342 .filter(UserIpMap.user == c.user).all()
342 .filter(UserIpMap.user == c.user).all()
343
343
344 c.inherit_default_ips = c.user.inherit_default_permissions
344 c.inherit_default_ips = c.user.inherit_default_permissions
345 c.default_user_ip_map = UserIpMap.query() \
345 c.default_user_ip_map = UserIpMap.query() \
346 .filter(UserIpMap.user == User.get_default_user()).all()
346 .filter(UserIpMap.user == User.get_default_user()).all()
347
347
348 return self._get_template_context(c)
348 return self._get_template_context(c)
349
349
350 @LoginRequired()
350 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
351 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
352 @CSRFRequired()
353 @view_config(
353 @view_config(
354 route_name='edit_user_ips_add', request_method='POST')
354 route_name='edit_user_ips_add', request_method='POST')
355 def ips_add(self):
355 def ips_add(self):
356 _ = self.request.translate
356 _ = self.request.translate
357 c = self.load_default_context()
357 c = self.load_default_context()
358
358
359 user_id = self.request.matchdict.get('user_id')
359 user_id = self.request.matchdict.get('user_id')
360 c.user = User.get_or_404(user_id, pyramid_exc=True)
360 c.user = User.get_or_404(user_id, pyramid_exc=True)
361 # NOTE(marcink): this view is allowed for default users, as we can
361 # NOTE(marcink): this view is allowed for default users, as we can
362 # edit their IP white list
362 # edit their IP white list
363
363
364 user_model = UserModel()
364 user_model = UserModel()
365 desc = self.request.POST.get('description')
365 desc = self.request.POST.get('description')
366 try:
366 try:
367 ip_list = user_model.parse_ip_range(
367 ip_list = user_model.parse_ip_range(
368 self.request.POST.get('new_ip'))
368 self.request.POST.get('new_ip'))
369 except Exception as e:
369 except Exception as e:
370 ip_list = []
370 ip_list = []
371 log.exception("Exception during ip saving")
371 log.exception("Exception during ip saving")
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
373 category='error')
373 category='error')
374 added = []
374 added = []
375 user_data = c.user.get_api_data()
375 user_data = c.user.get_api_data()
376 for ip in ip_list:
376 for ip in ip_list:
377 try:
377 try:
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
379 audit_logger.store_web(
379 audit_logger.store_web(
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
381 user=self._rhodecode_user)
381 user=self._rhodecode_user)
382 Session().commit()
382 Session().commit()
383 added.append(ip)
383 added.append(ip)
384 except formencode.Invalid as error:
384 except formencode.Invalid as error:
385 msg = error.error_dict['ip']
385 msg = error.error_dict['ip']
386 h.flash(msg, category='error')
386 h.flash(msg, category='error')
387 except Exception:
387 except Exception:
388 log.exception("Exception during ip saving")
388 log.exception("Exception during ip saving")
389 h.flash(_('An error occurred during ip saving'),
389 h.flash(_('An error occurred during ip saving'),
390 category='error')
390 category='error')
391 if added:
391 if added:
392 h.flash(
392 h.flash(
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
394 category='success')
394 category='success')
395 if 'default_user' in self.request.POST:
395 if 'default_user' in self.request.POST:
396 # case for editing global IP list we do it for 'DEFAULT' user
396 # case for editing global IP list we do it for 'DEFAULT' user
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
399
399
400 @LoginRequired()
400 @LoginRequired()
401 @HasPermissionAllDecorator('hg.admin')
401 @HasPermissionAllDecorator('hg.admin')
402 @CSRFRequired()
402 @CSRFRequired()
403 @view_config(
403 @view_config(
404 route_name='edit_user_ips_delete', request_method='POST')
404 route_name='edit_user_ips_delete', request_method='POST')
405 def ips_delete(self):
405 def ips_delete(self):
406 _ = self.request.translate
406 _ = self.request.translate
407 c = self.load_default_context()
407 c = self.load_default_context()
408
408
409 user_id = self.request.matchdict.get('user_id')
409 user_id = self.request.matchdict.get('user_id')
410 c.user = User.get_or_404(user_id, pyramid_exc=True)
410 c.user = User.get_or_404(user_id, pyramid_exc=True)
411 # NOTE(marcink): this view is allowed for default users, as we can
411 # NOTE(marcink): this view is allowed for default users, as we can
412 # edit their IP white list
412 # edit their IP white list
413
413
414 ip_id = self.request.POST.get('del_ip_id')
414 ip_id = self.request.POST.get('del_ip_id')
415 user_model = UserModel()
415 user_model = UserModel()
416 user_data = c.user.get_api_data()
416 user_data = c.user.get_api_data()
417 ip = UserIpMap.query().get(ip_id).ip_addr
417 ip = UserIpMap.query().get(ip_id).ip_addr
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
419 audit_logger.store_web(
419 audit_logger.store_web(
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
421 user=self._rhodecode_user)
421 user=self._rhodecode_user)
422 Session().commit()
422 Session().commit()
423 h.flash(_("Removed ip address from user whitelist"), category='success')
423 h.flash(_("Removed ip address from user whitelist"), category='success')
424
424
425 if 'default_user' in self.request.POST:
425 if 'default_user' in self.request.POST:
426 # case for editing global IP list we do it for 'DEFAULT' user
426 # case for editing global IP list we do it for 'DEFAULT' user
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
429
429
430 @LoginRequired()
430 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
431 @HasPermissionAllDecorator('hg.admin')
432 @view_config(
432 @view_config(
433 route_name='edit_user_groups_management', request_method='GET',
433 route_name='edit_user_groups_management', request_method='GET',
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
435 def groups_management(self):
435 def groups_management(self):
436 c = self.load_default_context()
436 c = self.load_default_context()
437
437
438 user_id = self.request.matchdict.get('user_id')
438 user_id = self.request.matchdict.get('user_id')
439 c.user = User.get_or_404(user_id, pyramid_exc=True)
439 c.user = User.get_or_404(user_id, pyramid_exc=True)
440 c.data = c.user.group_member
440 c.data = c.user.group_member
441 self._redirect_for_default_user(c.user.username)
441 self._redirect_for_default_user(c.user.username)
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
443 for group in c.user.group_member]
443 for group in c.user.group_member]
444 c.groups = json.dumps(groups)
444 c.groups = json.dumps(groups)
445 c.active = 'groups'
445 c.active = 'groups'
446
446
447 return self._get_template_context(c)
447 return self._get_template_context(c)
448
448
449 @LoginRequired()
449 @LoginRequired()
450 @HasPermissionAllDecorator('hg.admin')
450 @HasPermissionAllDecorator('hg.admin')
451 @CSRFRequired()
451 @CSRFRequired()
452 @view_config(
452 @view_config(
453 route_name='edit_user_groups_management_updates', request_method='POST')
453 route_name='edit_user_groups_management_updates', request_method='POST')
454 def groups_management_updates(self):
454 def groups_management_updates(self):
455 _ = self.request.translate
455 _ = self.request.translate
456 c = self.load_default_context()
456 c = self.load_default_context()
457
457
458 user_id = self.request.matchdict.get('user_id')
458 user_id = self.request.matchdict.get('user_id')
459 c.user = User.get_or_404(user_id, pyramid_exc=True)
459 c.user = User.get_or_404(user_id, pyramid_exc=True)
460 self._redirect_for_default_user(c.user.username)
460 self._redirect_for_default_user(c.user.username)
461
461
462 users_groups = set(self.request.POST.getall('users_group_id'))
462 users_groups = set(self.request.POST.getall('users_group_id'))
463 users_groups_model = []
463 users_groups_model = []
464
464
465 for ugid in users_groups:
465 for ugid in users_groups:
466 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
466 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
467 user_group_model = UserGroupModel()
467 user_group_model = UserGroupModel()
468 user_group_model.change_groups(c.user, users_groups_model)
468 user_group_model.change_groups(c.user, users_groups_model)
469
469
470 Session().commit()
470 Session().commit()
471 c.active = 'user_groups_management'
471 c.active = 'user_groups_management'
472 h.flash(_("Groups successfully changed"), category='success')
472 h.flash(_("Groups successfully changed"), category='success')
473
473
474 return HTTPFound(h.route_path(
474 return HTTPFound(h.route_path(
475 'edit_user_groups_management', user_id=user_id))
475 'edit_user_groups_management', user_id=user_id))
476
476
477 @LoginRequired()
477 @LoginRequired()
478 @HasPermissionAllDecorator('hg.admin')
478 @HasPermissionAllDecorator('hg.admin')
479 @view_config(
479 @view_config(
480 route_name='edit_user_audit_logs', request_method='GET',
480 route_name='edit_user_audit_logs', request_method='GET',
481 renderer='rhodecode:templates/admin/users/user_edit.mako')
481 renderer='rhodecode:templates/admin/users/user_edit.mako')
482 def user_audit_logs(self):
482 def user_audit_logs(self):
483 _ = self.request.translate
483 _ = self.request.translate
484 c = self.load_default_context()
484 c = self.load_default_context()
485
485
486 user_id = self.request.matchdict.get('user_id')
486 user_id = self.request.matchdict.get('user_id')
487 c.user = User.get_or_404(user_id, pyramid_exc=True)
487 c.user = User.get_or_404(user_id, pyramid_exc=True)
488 self._redirect_for_default_user(c.user.username)
488 self._redirect_for_default_user(c.user.username)
489 c.active = 'audit'
489 c.active = 'audit'
490
490
491 p = safe_int(self.request.GET.get('page', 1), 1)
491 p = safe_int(self.request.GET.get('page', 1), 1)
492
492
493 filter_term = self.request.GET.get('filter')
493 filter_term = self.request.GET.get('filter')
494 user_log = UserModel().get_user_log(c.user, filter_term)
494 user_log = UserModel().get_user_log(c.user, filter_term)
495
495
496 def url_generator(**kw):
496 def url_generator(**kw):
497 if filter_term:
497 if filter_term:
498 kw['filter'] = filter_term
498 kw['filter'] = filter_term
499 return self.request.current_route_path(_query=kw)
499 return self.request.current_route_path(_query=kw)
500
500
501 c.audit_logs = h.Page(
501 c.audit_logs = h.Page(
502 user_log, page=p, items_per_page=10, url=url_generator)
502 user_log, page=p, items_per_page=10, url=url_generator)
503 c.filter_term = filter_term
503 c.filter_term = filter_term
504 return self._get_template_context(c)
504 return self._get_template_context(c)
505
505
@@ -1,412 +1,412 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 import peppercorn
25 import peppercorn
26
26
27 from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
27 from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.utils2 import time_to_datetime
35 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.model.gist import GistModel
38 from rhodecode.model.gist import GistModel
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.db import Gist, User, or_
40 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model import validation_schema
41 from rhodecode.model import validation_schema
42 from rhodecode.model.validation_schema.schemas import gist_schema
42 from rhodecode.model.validation_schema.schemas import gist_schema
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class GistView(BaseAppView):
48 class GistView(BaseAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 _ = self.request.translate
51 _ = self.request.translate
52 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
53 c.user = c.auth_user.get_instance()
53 c.user = c.auth_user.get_instance()
54
54
55 c.lifetime_values = [
55 c.lifetime_values = [
56 (-1, _('forever')),
56 (-1, _('forever')),
57 (5, _('5 minutes')),
57 (5, _('5 minutes')),
58 (60, _('1 hour')),
58 (60, _('1 hour')),
59 (60 * 24, _('1 day')),
59 (60 * 24, _('1 day')),
60 (60 * 24 * 30, _('1 month')),
60 (60 * 24 * 30, _('1 month')),
61 ]
61 ]
62
62
63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.acl_options = [
64 c.acl_options = [
65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 ]
67 ]
68
68
69 self._register_global_c(c)
69 self._register_global_c(c)
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @view_config(
73 @view_config(
74 route_name='gists_show', request_method='GET',
74 route_name='gists_show', request_method='GET',
75 renderer='rhodecode:templates/admin/gists/index.mako')
75 renderer='rhodecode:templates/admin/gists/index.mako')
76 def gist_show_all(self):
76 def gist_show_all(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78
78
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 c.show_private = self.request.GET.get('private') and not_default_user
80 c.show_private = self.request.GET.get('private') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83
83
84 gists = _gists = Gist().query()\
84 gists = _gists = Gist().query()\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .order_by(Gist.created_on.desc())
86 .order_by(Gist.created_on.desc())
87
87
88 c.active = 'public'
88 c.active = 'public'
89 # MY private
89 # MY private
90 if c.show_private and not c.show_public:
90 if c.show_private and not c.show_public:
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 c.active = 'my_private'
93 c.active = 'my_private'
94 # MY public
94 # MY public
95 elif c.show_public and not c.show_private:
95 elif c.show_public and not c.show_private:
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 c.active = 'my_public'
98 c.active = 'my_public'
99 # MY public+private
99 # MY public+private
100 elif c.show_private and c.show_public:
100 elif c.show_private and c.show_public:
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 Gist.gist_type == Gist.GIST_PRIVATE))\
102 Gist.gist_type == Gist.GIST_PRIVATE))\
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 c.active = 'my_all'
104 c.active = 'my_all'
105 # Show all by super-admin
105 # Show all by super-admin
106 elif c.show_all:
106 elif c.show_all:
107 c.active = 'all'
107 c.active = 'all'
108 gists = _gists
108 gists = _gists
109
109
110 # default show ALL public gists
110 # default show ALL public gists
111 if not c.show_public and not c.show_private and not c.show_all:
111 if not c.show_public and not c.show_private and not c.show_all:
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 c.active = 'public'
113 c.active = 'public'
114
114
115 from rhodecode.lib.utils import PartialRenderer
115 _render = self.request.get_partial_renderer(
116 _render = PartialRenderer('data_table/_dt_elements.mako')
116 'data_table/_dt_elements.mako')
117
117
118 data = []
118 data = []
119
119
120 for gist in gists:
120 for gist in gists:
121 data.append({
121 data.append({
122 'created_on': _render('gist_created', gist.created_on),
122 'created_on': _render('gist_created', gist.created_on),
123 'created_on_raw': gist.created_on,
123 'created_on_raw': gist.created_on,
124 'type': _render('gist_type', gist.gist_type),
124 'type': _render('gist_type', gist.gist_type),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author_raw': h.escape(gist.owner.full_contact),
127 'author_raw': h.escape(gist.owner.full_contact),
128 'expires': _render('gist_expires', gist.gist_expires),
128 'expires': _render('gist_expires', gist.gist_expires),
129 'description': _render('gist_description', gist.gist_description)
129 'description': _render('gist_description', gist.gist_description)
130 })
130 })
131 c.data = json.dumps(data)
131 c.data = json.dumps(data)
132
132
133 return self._get_template_context(c)
133 return self._get_template_context(c)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @NotAnonymous()
136 @NotAnonymous()
137 @view_config(
137 @view_config(
138 route_name='gists_new', request_method='GET',
138 route_name='gists_new', request_method='GET',
139 renderer='rhodecode:templates/admin/gists/new.mako')
139 renderer='rhodecode:templates/admin/gists/new.mako')
140 def gist_new(self):
140 def gist_new(self):
141 c = self.load_default_context()
141 c = self.load_default_context()
142 return self._get_template_context(c)
142 return self._get_template_context(c)
143
143
144 @LoginRequired()
144 @LoginRequired()
145 @NotAnonymous()
145 @NotAnonymous()
146 @CSRFRequired()
146 @CSRFRequired()
147 @view_config(
147 @view_config(
148 route_name='gists_create', request_method='POST',
148 route_name='gists_create', request_method='POST',
149 renderer='rhodecode:templates/admin/gists/new.mako')
149 renderer='rhodecode:templates/admin/gists/new.mako')
150 def gist_create(self):
150 def gist_create(self):
151 _ = self.request.translate
151 _ = self.request.translate
152 c = self.load_default_context()
152 c = self.load_default_context()
153
153
154 data = dict(self.request.POST)
154 data = dict(self.request.POST)
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 data['nodes'] = [{
156 data['nodes'] = [{
157 'filename': data['filename'],
157 'filename': data['filename'],
158 'content': data.get('content'),
158 'content': data.get('content'),
159 'mimetype': data.get('mimetype') # None is autodetect
159 'mimetype': data.get('mimetype') # None is autodetect
160 }]
160 }]
161
161
162 data['gist_type'] = (
162 data['gist_type'] = (
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 data['gist_acl_level'] = (
164 data['gist_acl_level'] = (
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166
166
167 schema = gist_schema.GistSchema().bind(
167 schema = gist_schema.GistSchema().bind(
168 lifetime_options=[x[0] for x in c.lifetime_values])
168 lifetime_options=[x[0] for x in c.lifetime_values])
169
169
170 try:
170 try:
171
171
172 schema_data = schema.deserialize(data)
172 schema_data = schema.deserialize(data)
173 # convert to safer format with just KEYs so we sure no duplicates
173 # convert to safer format with just KEYs so we sure no duplicates
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 schema_data['nodes'])
175 schema_data['nodes'])
176
176
177 gist = GistModel().create(
177 gist = GistModel().create(
178 gist_id=schema_data['gistid'], # custom access id not real ID
178 gist_id=schema_data['gistid'], # custom access id not real ID
179 description=schema_data['description'],
179 description=schema_data['description'],
180 owner=self._rhodecode_user.user_id,
180 owner=self._rhodecode_user.user_id,
181 gist_mapping=schema_data['nodes'],
181 gist_mapping=schema_data['nodes'],
182 gist_type=schema_data['gist_type'],
182 gist_type=schema_data['gist_type'],
183 lifetime=schema_data['lifetime'],
183 lifetime=schema_data['lifetime'],
184 gist_acl_level=schema_data['gist_acl_level']
184 gist_acl_level=schema_data['gist_acl_level']
185 )
185 )
186 Session().commit()
186 Session().commit()
187 new_gist_id = gist.gist_access_id
187 new_gist_id = gist.gist_access_id
188 except validation_schema.Invalid as errors:
188 except validation_schema.Invalid as errors:
189 defaults = data
189 defaults = data
190 errors = errors.asdict()
190 errors = errors.asdict()
191
191
192 if 'nodes.0.content' in errors:
192 if 'nodes.0.content' in errors:
193 errors['content'] = errors['nodes.0.content']
193 errors['content'] = errors['nodes.0.content']
194 del errors['nodes.0.content']
194 del errors['nodes.0.content']
195 if 'nodes.0.filename' in errors:
195 if 'nodes.0.filename' in errors:
196 errors['filename'] = errors['nodes.0.filename']
196 errors['filename'] = errors['nodes.0.filename']
197 del errors['nodes.0.filename']
197 del errors['nodes.0.filename']
198
198
199 data = render('rhodecode:templates/admin/gists/new.mako',
199 data = render('rhodecode:templates/admin/gists/new.mako',
200 self._get_template_context(c), self.request)
200 self._get_template_context(c), self.request)
201 html = formencode.htmlfill.render(
201 html = formencode.htmlfill.render(
202 data,
202 data,
203 defaults=defaults,
203 defaults=defaults,
204 errors=errors,
204 errors=errors,
205 prefix_error=False,
205 prefix_error=False,
206 encoding="UTF-8",
206 encoding="UTF-8",
207 force_defaults=False
207 force_defaults=False
208 )
208 )
209 return Response(html)
209 return Response(html)
210
210
211 except Exception:
211 except Exception:
212 log.exception("Exception while trying to create a gist")
212 log.exception("Exception while trying to create a gist")
213 h.flash(_('Error occurred during gist creation'), category='error')
213 h.flash(_('Error occurred during gist creation'), category='error')
214 raise HTTPFound(h.route_url('gists_new'))
214 raise HTTPFound(h.route_url('gists_new'))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216
216
217 @LoginRequired()
217 @LoginRequired()
218 @NotAnonymous()
218 @NotAnonymous()
219 @CSRFRequired()
219 @CSRFRequired()
220 @view_config(
220 @view_config(
221 route_name='gist_delete', request_method='POST')
221 route_name='gist_delete', request_method='POST')
222 def gist_delete(self):
222 def gist_delete(self):
223 _ = self.request.translate
223 _ = self.request.translate
224 gist_id = self.request.matchdict['gist_id']
224 gist_id = self.request.matchdict['gist_id']
225
225
226 c = self.load_default_context()
226 c = self.load_default_context()
227 c.gist = Gist.get_or_404(gist_id)
227 c.gist = Gist.get_or_404(gist_id)
228
228
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 if not (h.HasPermissionAny('hg.admin')() or owner):
230 if not (h.HasPermissionAny('hg.admin')() or owner):
231 log.warning('Deletion of Gist was forbidden '
231 log.warning('Deletion of Gist was forbidden '
232 'by unauthorized user: `%s`', self._rhodecode_user)
232 'by unauthorized user: `%s`', self._rhodecode_user)
233 raise HTTPNotFound()
233 raise HTTPNotFound()
234
234
235 GistModel().delete(c.gist)
235 GistModel().delete(c.gist)
236 Session().commit()
236 Session().commit()
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238
238
239 raise HTTPFound(h.route_url('gists_show'))
239 raise HTTPFound(h.route_url('gists_show'))
240
240
241 def _get_gist(self, gist_id):
241 def _get_gist(self, gist_id):
242
242
243 gist = Gist.get_or_404(gist_id)
243 gist = Gist.get_or_404(gist_id)
244
244
245 # Check if this gist is expired
245 # Check if this gist is expired
246 if gist.gist_expires != -1:
246 if gist.gist_expires != -1:
247 if time.time() > gist.gist_expires:
247 if time.time() > gist.gist_expires:
248 log.error(
248 log.error(
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 raise HTTPNotFound()
250 raise HTTPNotFound()
251
251
252 # check if this gist requires a login
252 # check if this gist requires a login
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
255 log.error("Anonymous user %s tried to access protected gist `%s`",
255 log.error("Anonymous user %s tried to access protected gist `%s`",
256 self._rhodecode_user, gist_id)
256 self._rhodecode_user, gist_id)
257 raise HTTPNotFound()
257 raise HTTPNotFound()
258 return gist
258 return gist
259
259
260 @LoginRequired()
260 @LoginRequired()
261 @view_config(
261 @view_config(
262 route_name='gist_show', request_method='GET',
262 route_name='gist_show', request_method='GET',
263 renderer='rhodecode:templates/admin/gists/show.mako')
263 renderer='rhodecode:templates/admin/gists/show.mako')
264 @view_config(
264 @view_config(
265 route_name='gist_show_rev', request_method='GET',
265 route_name='gist_show_rev', request_method='GET',
266 renderer='rhodecode:templates/admin/gists/show.mako')
266 renderer='rhodecode:templates/admin/gists/show.mako')
267 @view_config(
267 @view_config(
268 route_name='gist_show_formatted', request_method='GET',
268 route_name='gist_show_formatted', request_method='GET',
269 renderer=None)
269 renderer=None)
270 @view_config(
270 @view_config(
271 route_name='gist_show_formatted_path', request_method='GET',
271 route_name='gist_show_formatted_path', request_method='GET',
272 renderer=None)
272 renderer=None)
273 def show(self):
273 def show(self):
274 gist_id = self.request.matchdict['gist_id']
274 gist_id = self.request.matchdict['gist_id']
275
275
276 # TODO(marcink): expose those via matching dict
276 # TODO(marcink): expose those via matching dict
277 revision = self.request.matchdict.get('revision', 'tip')
277 revision = self.request.matchdict.get('revision', 'tip')
278 f_path = self.request.matchdict.get('f_path', None)
278 f_path = self.request.matchdict.get('f_path', None)
279 return_format = self.request.matchdict.get('format')
279 return_format = self.request.matchdict.get('format')
280
280
281 c = self.load_default_context()
281 c = self.load_default_context()
282 c.gist = self._get_gist(gist_id)
282 c.gist = self._get_gist(gist_id)
283 c.render = not self.request.GET.get('no-render', False)
283 c.render = not self.request.GET.get('no-render', False)
284
284
285 try:
285 try:
286 c.file_last_commit, c.files = GistModel().get_gist_files(
286 c.file_last_commit, c.files = GistModel().get_gist_files(
287 gist_id, revision=revision)
287 gist_id, revision=revision)
288 except VCSError:
288 except VCSError:
289 log.exception("Exception in gist show")
289 log.exception("Exception in gist show")
290 raise HTTPNotFound()
290 raise HTTPNotFound()
291
291
292 if return_format == 'raw':
292 if return_format == 'raw':
293 content = '\n\n'.join([f.content for f in c.files
293 content = '\n\n'.join([f.content for f in c.files
294 if (f_path is None or f.path == f_path)])
294 if (f_path is None or f.path == f_path)])
295 response = Response(content)
295 response = Response(content)
296 response.content_type = 'text/plain'
296 response.content_type = 'text/plain'
297 return response
297 return response
298
298
299 return self._get_template_context(c)
299 return self._get_template_context(c)
300
300
301 @LoginRequired()
301 @LoginRequired()
302 @NotAnonymous()
302 @NotAnonymous()
303 @view_config(
303 @view_config(
304 route_name='gist_edit', request_method='GET',
304 route_name='gist_edit', request_method='GET',
305 renderer='rhodecode:templates/admin/gists/edit.mako')
305 renderer='rhodecode:templates/admin/gists/edit.mako')
306 def gist_edit(self):
306 def gist_edit(self):
307 _ = self.request.translate
307 _ = self.request.translate
308 gist_id = self.request.matchdict['gist_id']
308 gist_id = self.request.matchdict['gist_id']
309 c = self.load_default_context()
309 c = self.load_default_context()
310 c.gist = self._get_gist(gist_id)
310 c.gist = self._get_gist(gist_id)
311
311
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
313 if not (h.HasPermissionAny('hg.admin')() or owner):
313 if not (h.HasPermissionAny('hg.admin')() or owner):
314 raise HTTPNotFound()
314 raise HTTPNotFound()
315
315
316 try:
316 try:
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
318 except VCSError:
318 except VCSError:
319 log.exception("Exception in gist edit")
319 log.exception("Exception in gist edit")
320 raise HTTPNotFound()
320 raise HTTPNotFound()
321
321
322 if c.gist.gist_expires == -1:
322 if c.gist.gist_expires == -1:
323 expiry = _('never')
323 expiry = _('never')
324 else:
324 else:
325 # this cannot use timeago, since it's used in select2 as a value
325 # this cannot use timeago, since it's used in select2 as a value
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
327
327
328 c.lifetime_values.append(
328 c.lifetime_values.append(
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
330 )
330 )
331
331
332 return self._get_template_context(c)
332 return self._get_template_context(c)
333
333
334 @LoginRequired()
334 @LoginRequired()
335 @NotAnonymous()
335 @NotAnonymous()
336 @CSRFRequired()
336 @CSRFRequired()
337 @view_config(
337 @view_config(
338 route_name='gist_update', request_method='POST',
338 route_name='gist_update', request_method='POST',
339 renderer='rhodecode:templates/admin/gists/edit.mako')
339 renderer='rhodecode:templates/admin/gists/edit.mako')
340 def gist_update(self):
340 def gist_update(self):
341 _ = self.request.translate
341 _ = self.request.translate
342 gist_id = self.request.matchdict['gist_id']
342 gist_id = self.request.matchdict['gist_id']
343 c = self.load_default_context()
343 c = self.load_default_context()
344 c.gist = self._get_gist(gist_id)
344 c.gist = self._get_gist(gist_id)
345
345
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
347 if not (h.HasPermissionAny('hg.admin')() or owner):
347 if not (h.HasPermissionAny('hg.admin')() or owner):
348 raise HTTPNotFound()
348 raise HTTPNotFound()
349
349
350 data = peppercorn.parse(self.request.POST.items())
350 data = peppercorn.parse(self.request.POST.items())
351
351
352 schema = gist_schema.GistSchema()
352 schema = gist_schema.GistSchema()
353 schema = schema.bind(
353 schema = schema.bind(
354 # '0' is special value to leave lifetime untouched
354 # '0' is special value to leave lifetime untouched
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
356 )
356 )
357
357
358 try:
358 try:
359 schema_data = schema.deserialize(data)
359 schema_data = schema.deserialize(data)
360 # convert to safer format with just KEYs so we sure no duplicates
360 # convert to safer format with just KEYs so we sure no duplicates
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
362 schema_data['nodes'])
362 schema_data['nodes'])
363
363
364 GistModel().update(
364 GistModel().update(
365 gist=c.gist,
365 gist=c.gist,
366 description=schema_data['description'],
366 description=schema_data['description'],
367 owner=c.gist.owner,
367 owner=c.gist.owner,
368 gist_mapping=schema_data['nodes'],
368 gist_mapping=schema_data['nodes'],
369 lifetime=schema_data['lifetime'],
369 lifetime=schema_data['lifetime'],
370 gist_acl_level=schema_data['gist_acl_level']
370 gist_acl_level=schema_data['gist_acl_level']
371 )
371 )
372
372
373 Session().commit()
373 Session().commit()
374 h.flash(_('Successfully updated gist content'), category='success')
374 h.flash(_('Successfully updated gist content'), category='success')
375 except NodeNotChangedError:
375 except NodeNotChangedError:
376 # raised if nothing was changed in repo itself. We anyway then
376 # raised if nothing was changed in repo itself. We anyway then
377 # store only DB stuff for gist
377 # store only DB stuff for gist
378 Session().commit()
378 Session().commit()
379 h.flash(_('Successfully updated gist data'), category='success')
379 h.flash(_('Successfully updated gist data'), category='success')
380 except validation_schema.Invalid as errors:
380 except validation_schema.Invalid as errors:
381 errors = errors.asdict()
381 errors = errors.asdict()
382 h.flash(_('Error occurred during update of gist {}: {}').format(
382 h.flash(_('Error occurred during update of gist {}: {}').format(
383 gist_id, errors), category='error')
383 gist_id, errors), category='error')
384 except Exception:
384 except Exception:
385 log.exception("Exception in gist edit")
385 log.exception("Exception in gist edit")
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
387 category='error')
387 category='error')
388
388
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
390
390
391 @LoginRequired()
391 @LoginRequired()
392 @NotAnonymous()
392 @NotAnonymous()
393 @view_config(
393 @view_config(
394 route_name='gist_edit_check_revision', request_method='GET',
394 route_name='gist_edit_check_revision', request_method='GET',
395 renderer='json_ext')
395 renderer='json_ext')
396 def gist_edit_check_revision(self):
396 def gist_edit_check_revision(self):
397 _ = self.request.translate
397 _ = self.request.translate
398 gist_id = self.request.matchdict['gist_id']
398 gist_id = self.request.matchdict['gist_id']
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.gist = self._get_gist(gist_id)
400 c.gist = self._get_gist(gist_id)
401
401
402 last_rev = c.gist.scm_instance().get_commit()
402 last_rev = c.gist.scm_instance().get_commit()
403 success = True
403 success = True
404 revision = self.request.GET.get('revision')
404 revision = self.request.GET.get('revision')
405
405
406 if revision != last_rev.raw_id:
406 if revision != last_rev.raw_id:
407 log.error('Last revision %s is different then submitted %s'
407 log.error('Last revision %s is different then submitted %s'
408 % (revision, last_rev))
408 % (revision, last_rev))
409 # our gist has newer version than we
409 # our gist has newer version than we
410 success = False
410 success = False
411
411
412 return {'success': success}
412 return {'success': success}
@@ -1,584 +1,584 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 import formencode
24 import formencode
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode import forms
31 from rhodecode import forms
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.channelstream import channelstream_request, \
36 from rhodecode.lib.channelstream import channelstream_request, \
37 ChannelstreamException
37 ChannelstreamException
38 from rhodecode.lib.utils import PartialRenderer
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
38 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
41 from rhodecode.model.db import (
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
42 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 PullRequest)
43 PullRequest)
45 from rhodecode.model.forms import UserForm
44 from rhodecode.model.forms import UserForm
46 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
46 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.scm import RepoList
47 from rhodecode.model.scm import RepoList
49 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
52
51
53 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
54
53
55
54
56 class MyAccountView(BaseAppView, DataGridAppView):
55 class MyAccountView(BaseAppView, DataGridAppView):
57 ALLOW_SCOPED_TOKENS = False
56 ALLOW_SCOPED_TOKENS = False
58 """
57 """
59 This view has alternative version inside EE, if modified please take a look
58 This view has alternative version inside EE, if modified please take a look
60 in there as well.
59 in there as well.
61 """
60 """
62
61
63 def load_default_context(self):
62 def load_default_context(self):
64 c = self._get_local_tmpl_context()
63 c = self._get_local_tmpl_context()
65 c.user = c.auth_user.get_instance()
64 c.user = c.auth_user.get_instance()
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 self._register_global_c(c)
66 self._register_global_c(c)
68 return c
67 return c
69
68
70 @LoginRequired()
69 @LoginRequired()
71 @NotAnonymous()
70 @NotAnonymous()
72 @view_config(
71 @view_config(
73 route_name='my_account_profile', request_method='GET',
72 route_name='my_account_profile', request_method='GET',
74 renderer='rhodecode:templates/admin/my_account/my_account.mako')
73 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 def my_account_profile(self):
74 def my_account_profile(self):
76 c = self.load_default_context()
75 c = self.load_default_context()
77 c.active = 'profile'
76 c.active = 'profile'
78 return self._get_template_context(c)
77 return self._get_template_context(c)
79
78
80 @LoginRequired()
79 @LoginRequired()
81 @NotAnonymous()
80 @NotAnonymous()
82 @view_config(
81 @view_config(
83 route_name='my_account_password', request_method='GET',
82 route_name='my_account_password', request_method='GET',
84 renderer='rhodecode:templates/admin/my_account/my_account.mako')
83 renderer='rhodecode:templates/admin/my_account/my_account.mako')
85 def my_account_password(self):
84 def my_account_password(self):
86 c = self.load_default_context()
85 c = self.load_default_context()
87 c.active = 'password'
86 c.active = 'password'
88 c.extern_type = c.user.extern_type
87 c.extern_type = c.user.extern_type
89
88
90 schema = user_schema.ChangePasswordSchema().bind(
89 schema = user_schema.ChangePasswordSchema().bind(
91 username=c.user.username)
90 username=c.user.username)
92
91
93 form = forms.Form(
92 form = forms.Form(
94 schema, buttons=(forms.buttons.save, forms.buttons.reset))
93 schema, buttons=(forms.buttons.save, forms.buttons.reset))
95
94
96 c.form = form
95 c.form = form
97 return self._get_template_context(c)
96 return self._get_template_context(c)
98
97
99 @LoginRequired()
98 @LoginRequired()
100 @NotAnonymous()
99 @NotAnonymous()
101 @CSRFRequired()
100 @CSRFRequired()
102 @view_config(
101 @view_config(
103 route_name='my_account_password', request_method='POST',
102 route_name='my_account_password', request_method='POST',
104 renderer='rhodecode:templates/admin/my_account/my_account.mako')
103 renderer='rhodecode:templates/admin/my_account/my_account.mako')
105 def my_account_password_update(self):
104 def my_account_password_update(self):
106 _ = self.request.translate
105 _ = self.request.translate
107 c = self.load_default_context()
106 c = self.load_default_context()
108 c.active = 'password'
107 c.active = 'password'
109 c.extern_type = c.user.extern_type
108 c.extern_type = c.user.extern_type
110
109
111 schema = user_schema.ChangePasswordSchema().bind(
110 schema = user_schema.ChangePasswordSchema().bind(
112 username=c.user.username)
111 username=c.user.username)
113
112
114 form = forms.Form(
113 form = forms.Form(
115 schema, buttons=(forms.buttons.save, forms.buttons.reset))
114 schema, buttons=(forms.buttons.save, forms.buttons.reset))
116
115
117 if c.extern_type != 'rhodecode':
116 if c.extern_type != 'rhodecode':
118 raise HTTPFound(self.request.route_path('my_account_password'))
117 raise HTTPFound(self.request.route_path('my_account_password'))
119
118
120 controls = self.request.POST.items()
119 controls = self.request.POST.items()
121 try:
120 try:
122 valid_data = form.validate(controls)
121 valid_data = form.validate(controls)
123 UserModel().update_user(c.user.user_id, **valid_data)
122 UserModel().update_user(c.user.user_id, **valid_data)
124 c.user.update_userdata(force_password_change=False)
123 c.user.update_userdata(force_password_change=False)
125 Session().commit()
124 Session().commit()
126 except forms.ValidationFailure as e:
125 except forms.ValidationFailure as e:
127 c.form = e
126 c.form = e
128 return self._get_template_context(c)
127 return self._get_template_context(c)
129
128
130 except Exception:
129 except Exception:
131 log.exception("Exception updating password")
130 log.exception("Exception updating password")
132 h.flash(_('Error occurred during update of user password'),
131 h.flash(_('Error occurred during update of user password'),
133 category='error')
132 category='error')
134 else:
133 else:
135 instance = c.auth_user.get_instance()
134 instance = c.auth_user.get_instance()
136 self.session.setdefault('rhodecode_user', {}).update(
135 self.session.setdefault('rhodecode_user', {}).update(
137 {'password': md5(instance.password)})
136 {'password': md5(instance.password)})
138 self.session.save()
137 self.session.save()
139 h.flash(_("Successfully updated password"), category='success')
138 h.flash(_("Successfully updated password"), category='success')
140
139
141 raise HTTPFound(self.request.route_path('my_account_password'))
140 raise HTTPFound(self.request.route_path('my_account_password'))
142
141
143 @LoginRequired()
142 @LoginRequired()
144 @NotAnonymous()
143 @NotAnonymous()
145 @view_config(
144 @view_config(
146 route_name='my_account_auth_tokens', request_method='GET',
145 route_name='my_account_auth_tokens', request_method='GET',
147 renderer='rhodecode:templates/admin/my_account/my_account.mako')
146 renderer='rhodecode:templates/admin/my_account/my_account.mako')
148 def my_account_auth_tokens(self):
147 def my_account_auth_tokens(self):
149 _ = self.request.translate
148 _ = self.request.translate
150
149
151 c = self.load_default_context()
150 c = self.load_default_context()
152 c.active = 'auth_tokens'
151 c.active = 'auth_tokens'
153
152
154 c.lifetime_values = [
153 c.lifetime_values = [
155 (str(-1), _('forever')),
154 (str(-1), _('forever')),
156 (str(5), _('5 minutes')),
155 (str(5), _('5 minutes')),
157 (str(60), _('1 hour')),
156 (str(60), _('1 hour')),
158 (str(60 * 24), _('1 day')),
157 (str(60 * 24), _('1 day')),
159 (str(60 * 24 * 30), _('1 month')),
158 (str(60 * 24 * 30), _('1 month')),
160 ]
159 ]
161 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
160 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
162 c.role_values = [
161 c.role_values = [
163 (x, AuthTokenModel.cls._get_role_name(x))
162 (x, AuthTokenModel.cls._get_role_name(x))
164 for x in AuthTokenModel.cls.ROLES]
163 for x in AuthTokenModel.cls.ROLES]
165 c.role_options = [(c.role_values, _("Role"))]
164 c.role_options = [(c.role_values, _("Role"))]
166 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
165 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
167 c.user.user_id, show_expired=True)
166 c.user.user_id, show_expired=True)
168 return self._get_template_context(c)
167 return self._get_template_context(c)
169
168
170 def maybe_attach_token_scope(self, token):
169 def maybe_attach_token_scope(self, token):
171 # implemented in EE edition
170 # implemented in EE edition
172 pass
171 pass
173
172
174 @LoginRequired()
173 @LoginRequired()
175 @NotAnonymous()
174 @NotAnonymous()
176 @CSRFRequired()
175 @CSRFRequired()
177 @view_config(
176 @view_config(
178 route_name='my_account_auth_tokens_add', request_method='POST',)
177 route_name='my_account_auth_tokens_add', request_method='POST',)
179 def my_account_auth_tokens_add(self):
178 def my_account_auth_tokens_add(self):
180 _ = self.request.translate
179 _ = self.request.translate
181 c = self.load_default_context()
180 c = self.load_default_context()
182
181
183 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
182 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
184 description = self.request.POST.get('description')
183 description = self.request.POST.get('description')
185 role = self.request.POST.get('role')
184 role = self.request.POST.get('role')
186
185
187 token = AuthTokenModel().create(
186 token = AuthTokenModel().create(
188 c.user.user_id, description, lifetime, role)
187 c.user.user_id, description, lifetime, role)
189 token_data = token.get_api_data()
188 token_data = token.get_api_data()
190
189
191 self.maybe_attach_token_scope(token)
190 self.maybe_attach_token_scope(token)
192 audit_logger.store_web(
191 audit_logger.store_web(
193 'user.edit.token.add', action_data={
192 'user.edit.token.add', action_data={
194 'data': {'token': token_data, 'user': 'self'}},
193 'data': {'token': token_data, 'user': 'self'}},
195 user=self._rhodecode_user, )
194 user=self._rhodecode_user, )
196 Session().commit()
195 Session().commit()
197
196
198 h.flash(_("Auth token successfully created"), category='success')
197 h.flash(_("Auth token successfully created"), category='success')
199 return HTTPFound(h.route_path('my_account_auth_tokens'))
198 return HTTPFound(h.route_path('my_account_auth_tokens'))
200
199
201 @LoginRequired()
200 @LoginRequired()
202 @NotAnonymous()
201 @NotAnonymous()
203 @CSRFRequired()
202 @CSRFRequired()
204 @view_config(
203 @view_config(
205 route_name='my_account_auth_tokens_delete', request_method='POST')
204 route_name='my_account_auth_tokens_delete', request_method='POST')
206 def my_account_auth_tokens_delete(self):
205 def my_account_auth_tokens_delete(self):
207 _ = self.request.translate
206 _ = self.request.translate
208 c = self.load_default_context()
207 c = self.load_default_context()
209
208
210 del_auth_token = self.request.POST.get('del_auth_token')
209 del_auth_token = self.request.POST.get('del_auth_token')
211
210
212 if del_auth_token:
211 if del_auth_token:
213 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
212 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
214 token_data = token.get_api_data()
213 token_data = token.get_api_data()
215
214
216 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 AuthTokenModel().delete(del_auth_token, c.user.user_id)
217 audit_logger.store_web(
216 audit_logger.store_web(
218 'user.edit.token.delete', action_data={
217 'user.edit.token.delete', action_data={
219 'data': {'token': token_data, 'user': 'self'}},
218 'data': {'token': token_data, 'user': 'self'}},
220 user=self._rhodecode_user,)
219 user=self._rhodecode_user,)
221 Session().commit()
220 Session().commit()
222 h.flash(_("Auth token successfully deleted"), category='success')
221 h.flash(_("Auth token successfully deleted"), category='success')
223
222
224 return HTTPFound(h.route_path('my_account_auth_tokens'))
223 return HTTPFound(h.route_path('my_account_auth_tokens'))
225
224
226 @LoginRequired()
225 @LoginRequired()
227 @NotAnonymous()
226 @NotAnonymous()
228 @view_config(
227 @view_config(
229 route_name='my_account_emails', request_method='GET',
228 route_name='my_account_emails', request_method='GET',
230 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 renderer='rhodecode:templates/admin/my_account/my_account.mako')
231 def my_account_emails(self):
230 def my_account_emails(self):
232 _ = self.request.translate
231 _ = self.request.translate
233
232
234 c = self.load_default_context()
233 c = self.load_default_context()
235 c.active = 'emails'
234 c.active = 'emails'
236
235
237 c.user_email_map = UserEmailMap.query()\
236 c.user_email_map = UserEmailMap.query()\
238 .filter(UserEmailMap.user == c.user).all()
237 .filter(UserEmailMap.user == c.user).all()
239 return self._get_template_context(c)
238 return self._get_template_context(c)
240
239
241 @LoginRequired()
240 @LoginRequired()
242 @NotAnonymous()
241 @NotAnonymous()
243 @CSRFRequired()
242 @CSRFRequired()
244 @view_config(
243 @view_config(
245 route_name='my_account_emails_add', request_method='POST')
244 route_name='my_account_emails_add', request_method='POST')
246 def my_account_emails_add(self):
245 def my_account_emails_add(self):
247 _ = self.request.translate
246 _ = self.request.translate
248 c = self.load_default_context()
247 c = self.load_default_context()
249
248
250 email = self.request.POST.get('new_email')
249 email = self.request.POST.get('new_email')
251
250
252 try:
251 try:
253 UserModel().add_extra_email(c.user.user_id, email)
252 UserModel().add_extra_email(c.user.user_id, email)
254 audit_logger.store_web(
253 audit_logger.store_web(
255 'user.edit.email.add', action_data={
254 'user.edit.email.add', action_data={
256 'data': {'email': email, 'user': 'self'}},
255 'data': {'email': email, 'user': 'self'}},
257 user=self._rhodecode_user,)
256 user=self._rhodecode_user,)
258
257
259 Session().commit()
258 Session().commit()
260 h.flash(_("Added new email address `%s` for user account") % email,
259 h.flash(_("Added new email address `%s` for user account") % email,
261 category='success')
260 category='success')
262 except formencode.Invalid as error:
261 except formencode.Invalid as error:
263 h.flash(h.escape(error.error_dict['email']), category='error')
262 h.flash(h.escape(error.error_dict['email']), category='error')
264 except Exception:
263 except Exception:
265 log.exception("Exception in my_account_emails")
264 log.exception("Exception in my_account_emails")
266 h.flash(_('An error occurred during email saving'),
265 h.flash(_('An error occurred during email saving'),
267 category='error')
266 category='error')
268 return HTTPFound(h.route_path('my_account_emails'))
267 return HTTPFound(h.route_path('my_account_emails'))
269
268
270 @LoginRequired()
269 @LoginRequired()
271 @NotAnonymous()
270 @NotAnonymous()
272 @CSRFRequired()
271 @CSRFRequired()
273 @view_config(
272 @view_config(
274 route_name='my_account_emails_delete', request_method='POST')
273 route_name='my_account_emails_delete', request_method='POST')
275 def my_account_emails_delete(self):
274 def my_account_emails_delete(self):
276 _ = self.request.translate
275 _ = self.request.translate
277 c = self.load_default_context()
276 c = self.load_default_context()
278
277
279 del_email_id = self.request.POST.get('del_email_id')
278 del_email_id = self.request.POST.get('del_email_id')
280 if del_email_id:
279 if del_email_id:
281 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
280 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
282 UserModel().delete_extra_email(c.user.user_id, del_email_id)
281 UserModel().delete_extra_email(c.user.user_id, del_email_id)
283 audit_logger.store_web(
282 audit_logger.store_web(
284 'user.edit.email.delete', action_data={
283 'user.edit.email.delete', action_data={
285 'data': {'email': email, 'user': 'self'}},
284 'data': {'email': email, 'user': 'self'}},
286 user=self._rhodecode_user,)
285 user=self._rhodecode_user,)
287 Session().commit()
286 Session().commit()
288 h.flash(_("Email successfully deleted"),
287 h.flash(_("Email successfully deleted"),
289 category='success')
288 category='success')
290 return HTTPFound(h.route_path('my_account_emails'))
289 return HTTPFound(h.route_path('my_account_emails'))
291
290
292 @LoginRequired()
291 @LoginRequired()
293 @NotAnonymous()
292 @NotAnonymous()
294 @CSRFRequired()
293 @CSRFRequired()
295 @view_config(
294 @view_config(
296 route_name='my_account_notifications_test_channelstream',
295 route_name='my_account_notifications_test_channelstream',
297 request_method='POST', renderer='json_ext')
296 request_method='POST', renderer='json_ext')
298 def my_account_notifications_test_channelstream(self):
297 def my_account_notifications_test_channelstream(self):
299 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
298 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
300 self._rhodecode_user.username, datetime.datetime.now())
299 self._rhodecode_user.username, datetime.datetime.now())
301 payload = {
300 payload = {
302 # 'channel': 'broadcast',
301 # 'channel': 'broadcast',
303 'type': 'message',
302 'type': 'message',
304 'timestamp': datetime.datetime.utcnow(),
303 'timestamp': datetime.datetime.utcnow(),
305 'user': 'system',
304 'user': 'system',
306 'pm_users': [self._rhodecode_user.username],
305 'pm_users': [self._rhodecode_user.username],
307 'message': {
306 'message': {
308 'message': message,
307 'message': message,
309 'level': 'info',
308 'level': 'info',
310 'topic': '/notifications'
309 'topic': '/notifications'
311 }
310 }
312 }
311 }
313
312
314 registry = self.request.registry
313 registry = self.request.registry
315 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
314 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
316 channelstream_config = rhodecode_plugins.get('channelstream', {})
315 channelstream_config = rhodecode_plugins.get('channelstream', {})
317
316
318 try:
317 try:
319 channelstream_request(channelstream_config, [payload], '/message')
318 channelstream_request(channelstream_config, [payload], '/message')
320 except ChannelstreamException as e:
319 except ChannelstreamException as e:
321 log.exception('Failed to send channelstream data')
320 log.exception('Failed to send channelstream data')
322 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
321 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
323 return {"response": 'Channelstream data sent. '
322 return {"response": 'Channelstream data sent. '
324 'You should see a new live message now.'}
323 'You should see a new live message now.'}
325
324
326 def _load_my_repos_data(self, watched=False):
325 def _load_my_repos_data(self, watched=False):
327 if watched:
326 if watched:
328 admin = False
327 admin = False
329 follows_repos = Session().query(UserFollowing)\
328 follows_repos = Session().query(UserFollowing)\
330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
329 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
331 .options(joinedload(UserFollowing.follows_repository))\
330 .options(joinedload(UserFollowing.follows_repository))\
332 .all()
331 .all()
333 repo_list = [x.follows_repository for x in follows_repos]
332 repo_list = [x.follows_repository for x in follows_repos]
334 else:
333 else:
335 admin = True
334 admin = True
336 repo_list = Repository.get_all_repos(
335 repo_list = Repository.get_all_repos(
337 user_id=self._rhodecode_user.user_id)
336 user_id=self._rhodecode_user.user_id)
338 repo_list = RepoList(repo_list, perm_set=[
337 repo_list = RepoList(repo_list, perm_set=[
339 'repository.read', 'repository.write', 'repository.admin'])
338 'repository.read', 'repository.write', 'repository.admin'])
340
339
341 repos_data = RepoModel().get_repos_as_dict(
340 repos_data = RepoModel().get_repos_as_dict(
342 repo_list=repo_list, admin=admin)
341 repo_list=repo_list, admin=admin)
343 # json used to render the grid
342 # json used to render the grid
344 return json.dumps(repos_data)
343 return json.dumps(repos_data)
345
344
346 @LoginRequired()
345 @LoginRequired()
347 @NotAnonymous()
346 @NotAnonymous()
348 @view_config(
347 @view_config(
349 route_name='my_account_repos', request_method='GET',
348 route_name='my_account_repos', request_method='GET',
350 renderer='rhodecode:templates/admin/my_account/my_account.mako')
349 renderer='rhodecode:templates/admin/my_account/my_account.mako')
351 def my_account_repos(self):
350 def my_account_repos(self):
352 c = self.load_default_context()
351 c = self.load_default_context()
353 c.active = 'repos'
352 c.active = 'repos'
354
353
355 # json used to render the grid
354 # json used to render the grid
356 c.data = self._load_my_repos_data()
355 c.data = self._load_my_repos_data()
357 return self._get_template_context(c)
356 return self._get_template_context(c)
358
357
359 @LoginRequired()
358 @LoginRequired()
360 @NotAnonymous()
359 @NotAnonymous()
361 @view_config(
360 @view_config(
362 route_name='my_account_watched', request_method='GET',
361 route_name='my_account_watched', request_method='GET',
363 renderer='rhodecode:templates/admin/my_account/my_account.mako')
362 renderer='rhodecode:templates/admin/my_account/my_account.mako')
364 def my_account_watched(self):
363 def my_account_watched(self):
365 c = self.load_default_context()
364 c = self.load_default_context()
366 c.active = 'watched'
365 c.active = 'watched'
367
366
368 # json used to render the grid
367 # json used to render the grid
369 c.data = self._load_my_repos_data(watched=True)
368 c.data = self._load_my_repos_data(watched=True)
370 return self._get_template_context(c)
369 return self._get_template_context(c)
371
370
372 @LoginRequired()
371 @LoginRequired()
373 @NotAnonymous()
372 @NotAnonymous()
374 @view_config(
373 @view_config(
375 route_name='my_account_perms', request_method='GET',
374 route_name='my_account_perms', request_method='GET',
376 renderer='rhodecode:templates/admin/my_account/my_account.mako')
375 renderer='rhodecode:templates/admin/my_account/my_account.mako')
377 def my_account_perms(self):
376 def my_account_perms(self):
378 c = self.load_default_context()
377 c = self.load_default_context()
379 c.active = 'perms'
378 c.active = 'perms'
380
379
381 c.perm_user = c.auth_user
380 c.perm_user = c.auth_user
382 return self._get_template_context(c)
381 return self._get_template_context(c)
383
382
384 @LoginRequired()
383 @LoginRequired()
385 @NotAnonymous()
384 @NotAnonymous()
386 @view_config(
385 @view_config(
387 route_name='my_account_notifications', request_method='GET',
386 route_name='my_account_notifications', request_method='GET',
388 renderer='rhodecode:templates/admin/my_account/my_account.mako')
387 renderer='rhodecode:templates/admin/my_account/my_account.mako')
389 def my_notifications(self):
388 def my_notifications(self):
390 c = self.load_default_context()
389 c = self.load_default_context()
391 c.active = 'notifications'
390 c.active = 'notifications'
392
391
393 return self._get_template_context(c)
392 return self._get_template_context(c)
394
393
395 @LoginRequired()
394 @LoginRequired()
396 @NotAnonymous()
395 @NotAnonymous()
397 @CSRFRequired()
396 @CSRFRequired()
398 @view_config(
397 @view_config(
399 route_name='my_account_notifications_toggle_visibility',
398 route_name='my_account_notifications_toggle_visibility',
400 request_method='POST', renderer='json_ext')
399 request_method='POST', renderer='json_ext')
401 def my_notifications_toggle_visibility(self):
400 def my_notifications_toggle_visibility(self):
402 user = self._rhodecode_db_user
401 user = self._rhodecode_db_user
403 new_status = not user.user_data.get('notification_status', True)
402 new_status = not user.user_data.get('notification_status', True)
404 user.update_userdata(notification_status=new_status)
403 user.update_userdata(notification_status=new_status)
405 Session().commit()
404 Session().commit()
406 return user.user_data['notification_status']
405 return user.user_data['notification_status']
407
406
408 @LoginRequired()
407 @LoginRequired()
409 @NotAnonymous()
408 @NotAnonymous()
410 @view_config(
409 @view_config(
411 route_name='my_account_edit',
410 route_name='my_account_edit',
412 request_method='GET',
411 request_method='GET',
413 renderer='rhodecode:templates/admin/my_account/my_account.mako')
412 renderer='rhodecode:templates/admin/my_account/my_account.mako')
414 def my_account_edit(self):
413 def my_account_edit(self):
415 c = self.load_default_context()
414 c = self.load_default_context()
416 c.active = 'profile_edit'
415 c.active = 'profile_edit'
417
416
418 c.perm_user = c.auth_user
417 c.perm_user = c.auth_user
419 c.extern_type = c.user.extern_type
418 c.extern_type = c.user.extern_type
420 c.extern_name = c.user.extern_name
419 c.extern_name = c.user.extern_name
421
420
422 defaults = c.user.get_dict()
421 defaults = c.user.get_dict()
423
422
424 data = render('rhodecode:templates/admin/my_account/my_account.mako',
423 data = render('rhodecode:templates/admin/my_account/my_account.mako',
425 self._get_template_context(c), self.request)
424 self._get_template_context(c), self.request)
426 html = formencode.htmlfill.render(
425 html = formencode.htmlfill.render(
427 data,
426 data,
428 defaults=defaults,
427 defaults=defaults,
429 encoding="UTF-8",
428 encoding="UTF-8",
430 force_defaults=False
429 force_defaults=False
431 )
430 )
432 return Response(html)
431 return Response(html)
433
432
434 @LoginRequired()
433 @LoginRequired()
435 @NotAnonymous()
434 @NotAnonymous()
436 @CSRFRequired()
435 @CSRFRequired()
437 @view_config(
436 @view_config(
438 route_name='my_account_update',
437 route_name='my_account_update',
439 request_method='POST',
438 request_method='POST',
440 renderer='rhodecode:templates/admin/my_account/my_account.mako')
439 renderer='rhodecode:templates/admin/my_account/my_account.mako')
441 def my_account_update(self):
440 def my_account_update(self):
442 _ = self.request.translate
441 _ = self.request.translate
443 c = self.load_default_context()
442 c = self.load_default_context()
444 c.active = 'profile_edit'
443 c.active = 'profile_edit'
445
444
446 c.perm_user = c.auth_user
445 c.perm_user = c.auth_user
447 c.extern_type = c.user.extern_type
446 c.extern_type = c.user.extern_type
448 c.extern_name = c.user.extern_name
447 c.extern_name = c.user.extern_name
449
448
450 _form = UserForm(edit=True,
449 _form = UserForm(edit=True,
451 old_data={'user_id': self._rhodecode_user.user_id,
450 old_data={'user_id': self._rhodecode_user.user_id,
452 'email': self._rhodecode_user.email})()
451 'email': self._rhodecode_user.email})()
453 form_result = {}
452 form_result = {}
454 try:
453 try:
455 post_data = dict(self.request.POST)
454 post_data = dict(self.request.POST)
456 post_data['new_password'] = ''
455 post_data['new_password'] = ''
457 post_data['password_confirmation'] = ''
456 post_data['password_confirmation'] = ''
458 form_result = _form.to_python(post_data)
457 form_result = _form.to_python(post_data)
459 # skip updating those attrs for my account
458 # skip updating those attrs for my account
460 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
459 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
461 'new_password', 'password_confirmation']
460 'new_password', 'password_confirmation']
462 # TODO: plugin should define if username can be updated
461 # TODO: plugin should define if username can be updated
463 if c.extern_type != "rhodecode":
462 if c.extern_type != "rhodecode":
464 # forbid updating username for external accounts
463 # forbid updating username for external accounts
465 skip_attrs.append('username')
464 skip_attrs.append('username')
466
465
467 UserModel().update_user(
466 UserModel().update_user(
468 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
467 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
469 **form_result)
468 **form_result)
470 h.flash(_('Your account was updated successfully'),
469 h.flash(_('Your account was updated successfully'),
471 category='success')
470 category='success')
472 Session().commit()
471 Session().commit()
473
472
474 except formencode.Invalid as errors:
473 except formencode.Invalid as errors:
475 data = render(
474 data = render(
476 'rhodecode:templates/admin/my_account/my_account.mako',
475 'rhodecode:templates/admin/my_account/my_account.mako',
477 self._get_template_context(c), self.request)
476 self._get_template_context(c), self.request)
478
477
479 html = formencode.htmlfill.render(
478 html = formencode.htmlfill.render(
480 data,
479 data,
481 defaults=errors.value,
480 defaults=errors.value,
482 errors=errors.error_dict or {},
481 errors=errors.error_dict or {},
483 prefix_error=False,
482 prefix_error=False,
484 encoding="UTF-8",
483 encoding="UTF-8",
485 force_defaults=False)
484 force_defaults=False)
486 return Response(html)
485 return Response(html)
487
486
488 except Exception:
487 except Exception:
489 log.exception("Exception updating user")
488 log.exception("Exception updating user")
490 h.flash(_('Error occurred during update of user %s')
489 h.flash(_('Error occurred during update of user %s')
491 % form_result.get('username'), category='error')
490 % form_result.get('username'), category='error')
492 raise HTTPFound(h.route_path('my_account_profile'))
491 raise HTTPFound(h.route_path('my_account_profile'))
493
492
494 raise HTTPFound(h.route_path('my_account_profile'))
493 raise HTTPFound(h.route_path('my_account_profile'))
495
494
496 def _get_pull_requests_list(self, statuses):
495 def _get_pull_requests_list(self, statuses):
497 draw, start, limit = self._extract_chunk(self.request)
496 draw, start, limit = self._extract_chunk(self.request)
498 search_q, order_by, order_dir = self._extract_ordering(self.request)
497 search_q, order_by, order_dir = self._extract_ordering(self.request)
499 _render = PartialRenderer('data_table/_dt_elements.mako')
498 _render = self.request.get_partial_renderer(
499 'data_table/_dt_elements.mako')
500
500
501 pull_requests = PullRequestModel().get_im_participating_in(
501 pull_requests = PullRequestModel().get_im_participating_in(
502 user_id=self._rhodecode_user.user_id,
502 user_id=self._rhodecode_user.user_id,
503 statuses=statuses,
503 statuses=statuses,
504 offset=start, length=limit, order_by=order_by,
504 offset=start, length=limit, order_by=order_by,
505 order_dir=order_dir)
505 order_dir=order_dir)
506
506
507 pull_requests_total_count = PullRequestModel().count_im_participating_in(
507 pull_requests_total_count = PullRequestModel().count_im_participating_in(
508 user_id=self._rhodecode_user.user_id, statuses=statuses)
508 user_id=self._rhodecode_user.user_id, statuses=statuses)
509
509
510 data = []
510 data = []
511 comments_model = CommentsModel()
511 comments_model = CommentsModel()
512 for pr in pull_requests:
512 for pr in pull_requests:
513 repo_id = pr.target_repo_id
513 repo_id = pr.target_repo_id
514 comments = comments_model.get_all_comments(
514 comments = comments_model.get_all_comments(
515 repo_id, pull_request=pr)
515 repo_id, pull_request=pr)
516 owned = pr.user_id == self._rhodecode_user.user_id
516 owned = pr.user_id == self._rhodecode_user.user_id
517
517
518 data.append({
518 data.append({
519 'target_repo': _render('pullrequest_target_repo',
519 'target_repo': _render('pullrequest_target_repo',
520 pr.target_repo.repo_name),
520 pr.target_repo.repo_name),
521 'name': _render('pullrequest_name',
521 'name': _render('pullrequest_name',
522 pr.pull_request_id, pr.target_repo.repo_name,
522 pr.pull_request_id, pr.target_repo.repo_name,
523 short=True),
523 short=True),
524 'name_raw': pr.pull_request_id,
524 'name_raw': pr.pull_request_id,
525 'status': _render('pullrequest_status',
525 'status': _render('pullrequest_status',
526 pr.calculated_review_status()),
526 pr.calculated_review_status()),
527 'title': _render(
527 'title': _render(
528 'pullrequest_title', pr.title, pr.description),
528 'pullrequest_title', pr.title, pr.description),
529 'description': h.escape(pr.description),
529 'description': h.escape(pr.description),
530 'updated_on': _render('pullrequest_updated_on',
530 'updated_on': _render('pullrequest_updated_on',
531 h.datetime_to_time(pr.updated_on)),
531 h.datetime_to_time(pr.updated_on)),
532 'updated_on_raw': h.datetime_to_time(pr.updated_on),
532 'updated_on_raw': h.datetime_to_time(pr.updated_on),
533 'created_on': _render('pullrequest_updated_on',
533 'created_on': _render('pullrequest_updated_on',
534 h.datetime_to_time(pr.created_on)),
534 h.datetime_to_time(pr.created_on)),
535 'created_on_raw': h.datetime_to_time(pr.created_on),
535 'created_on_raw': h.datetime_to_time(pr.created_on),
536 'author': _render('pullrequest_author',
536 'author': _render('pullrequest_author',
537 pr.author.full_contact, ),
537 pr.author.full_contact, ),
538 'author_raw': pr.author.full_name,
538 'author_raw': pr.author.full_name,
539 'comments': _render('pullrequest_comments', len(comments)),
539 'comments': _render('pullrequest_comments', len(comments)),
540 'comments_raw': len(comments),
540 'comments_raw': len(comments),
541 'closed': pr.is_closed(),
541 'closed': pr.is_closed(),
542 'owned': owned
542 'owned': owned
543 })
543 })
544
544
545 # json used to render the grid
545 # json used to render the grid
546 data = ({
546 data = ({
547 'draw': draw,
547 'draw': draw,
548 'data': data,
548 'data': data,
549 'recordsTotal': pull_requests_total_count,
549 'recordsTotal': pull_requests_total_count,
550 'recordsFiltered': pull_requests_total_count,
550 'recordsFiltered': pull_requests_total_count,
551 })
551 })
552 return data
552 return data
553
553
554 @LoginRequired()
554 @LoginRequired()
555 @NotAnonymous()
555 @NotAnonymous()
556 @view_config(
556 @view_config(
557 route_name='my_account_pullrequests',
557 route_name='my_account_pullrequests',
558 request_method='GET',
558 request_method='GET',
559 renderer='rhodecode:templates/admin/my_account/my_account.mako')
559 renderer='rhodecode:templates/admin/my_account/my_account.mako')
560 def my_account_pullrequests(self):
560 def my_account_pullrequests(self):
561 c = self.load_default_context()
561 c = self.load_default_context()
562 c.active = 'pullrequests'
562 c.active = 'pullrequests'
563 req_get = self.request.GET
563 req_get = self.request.GET
564
564
565 c.closed = str2bool(req_get.get('pr_show_closed'))
565 c.closed = str2bool(req_get.get('pr_show_closed'))
566
566
567 return self._get_template_context(c)
567 return self._get_template_context(c)
568
568
569 @LoginRequired()
569 @LoginRequired()
570 @NotAnonymous()
570 @NotAnonymous()
571 @view_config(
571 @view_config(
572 route_name='my_account_pullrequests_data',
572 route_name='my_account_pullrequests_data',
573 request_method='GET', renderer='json_ext')
573 request_method='GET', renderer='json_ext')
574 def my_account_pullrequests_data(self):
574 def my_account_pullrequests_data(self):
575 req_get = self.request.GET
575 req_get = self.request.GET
576 closed = str2bool(req_get.get('closed'))
576 closed = str2bool(req_get.get('closed'))
577
577
578 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
578 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
579 if closed:
579 if closed:
580 statuses += [PullRequest.STATUS_CLOSED]
580 statuses += [PullRequest.STATUS_CLOSED]
581
581
582 data = self._get_pull_requests_list(statuses=statuses)
582 data = self._get_pull_requests_list(statuses=statuses)
583 return data
583 return data
584
584
@@ -1,584 +1,584 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import collections
23 import collections
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView, DataGridAppView
27 from rhodecode.apps._base import RepoAppView, DataGridAppView
28 from rhodecode.lib import helpers as h, diffs, codeblocks
28 from rhodecode.lib import helpers as h, diffs, codeblocks
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator)
30 LoginRequired, HasRepoPermissionAnyDecorator)
31 from rhodecode.lib.utils import PartialRenderer
32 from rhodecode.lib.utils2 import str2bool, safe_int, safe_str
31 from rhodecode.lib.utils2 import str2bool, safe_int, safe_str
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, \
33 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, \
35 RepositoryRequirementError, NodeDoesNotExistError
34 RepositoryRequirementError, NodeDoesNotExistError
36 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.comment import CommentsModel
37 from rhodecode.model.db import PullRequest, PullRequestVersion, \
36 from rhodecode.model.db import PullRequest, PullRequestVersion, \
38 ChangesetComment, ChangesetStatus
37 ChangesetComment, ChangesetStatus
39 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
38 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
40
39
41 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
42
41
43
42
44 class RepoPullRequestsView(RepoAppView, DataGridAppView):
43 class RepoPullRequestsView(RepoAppView, DataGridAppView):
45
44
46 def load_default_context(self):
45 def load_default_context(self):
47 c = self._get_local_tmpl_context(include_app_defaults=True)
46 c = self._get_local_tmpl_context(include_app_defaults=True)
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
48 c.repo_info = self.db_repo
50 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
49 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
51 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
50 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
52 self._register_global_c(c)
51 self._register_global_c(c)
53 return c
52 return c
54
53
55 def _get_pull_requests_list(
54 def _get_pull_requests_list(
56 self, repo_name, source, filter_type, opened_by, statuses):
55 self, repo_name, source, filter_type, opened_by, statuses):
57
56
58 draw, start, limit = self._extract_chunk(self.request)
57 draw, start, limit = self._extract_chunk(self.request)
59 search_q, order_by, order_dir = self._extract_ordering(self.request)
58 search_q, order_by, order_dir = self._extract_ordering(self.request)
60 _render = PartialRenderer('data_table/_dt_elements.mako')
59 _render = self.request.get_partial_renderer(
60 'data_table/_dt_elements.mako')
61
61
62 # pagination
62 # pagination
63
63
64 if filter_type == 'awaiting_review':
64 if filter_type == 'awaiting_review':
65 pull_requests = PullRequestModel().get_awaiting_review(
65 pull_requests = PullRequestModel().get_awaiting_review(
66 repo_name, source=source, opened_by=opened_by,
66 repo_name, source=source, opened_by=opened_by,
67 statuses=statuses, offset=start, length=limit,
67 statuses=statuses, offset=start, length=limit,
68 order_by=order_by, order_dir=order_dir)
68 order_by=order_by, order_dir=order_dir)
69 pull_requests_total_count = PullRequestModel().count_awaiting_review(
69 pull_requests_total_count = PullRequestModel().count_awaiting_review(
70 repo_name, source=source, statuses=statuses,
70 repo_name, source=source, statuses=statuses,
71 opened_by=opened_by)
71 opened_by=opened_by)
72 elif filter_type == 'awaiting_my_review':
72 elif filter_type == 'awaiting_my_review':
73 pull_requests = PullRequestModel().get_awaiting_my_review(
73 pull_requests = PullRequestModel().get_awaiting_my_review(
74 repo_name, source=source, opened_by=opened_by,
74 repo_name, source=source, opened_by=opened_by,
75 user_id=self._rhodecode_user.user_id, statuses=statuses,
75 user_id=self._rhodecode_user.user_id, statuses=statuses,
76 offset=start, length=limit, order_by=order_by,
76 offset=start, length=limit, order_by=order_by,
77 order_dir=order_dir)
77 order_dir=order_dir)
78 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
78 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
79 repo_name, source=source, user_id=self._rhodecode_user.user_id,
79 repo_name, source=source, user_id=self._rhodecode_user.user_id,
80 statuses=statuses, opened_by=opened_by)
80 statuses=statuses, opened_by=opened_by)
81 else:
81 else:
82 pull_requests = PullRequestModel().get_all(
82 pull_requests = PullRequestModel().get_all(
83 repo_name, source=source, opened_by=opened_by,
83 repo_name, source=source, opened_by=opened_by,
84 statuses=statuses, offset=start, length=limit,
84 statuses=statuses, offset=start, length=limit,
85 order_by=order_by, order_dir=order_dir)
85 order_by=order_by, order_dir=order_dir)
86 pull_requests_total_count = PullRequestModel().count_all(
86 pull_requests_total_count = PullRequestModel().count_all(
87 repo_name, source=source, statuses=statuses,
87 repo_name, source=source, statuses=statuses,
88 opened_by=opened_by)
88 opened_by=opened_by)
89
89
90 data = []
90 data = []
91 comments_model = CommentsModel()
91 comments_model = CommentsModel()
92 for pr in pull_requests:
92 for pr in pull_requests:
93 comments = comments_model.get_all_comments(
93 comments = comments_model.get_all_comments(
94 self.db_repo.repo_id, pull_request=pr)
94 self.db_repo.repo_id, pull_request=pr)
95
95
96 data.append({
96 data.append({
97 'name': _render('pullrequest_name',
97 'name': _render('pullrequest_name',
98 pr.pull_request_id, pr.target_repo.repo_name),
98 pr.pull_request_id, pr.target_repo.repo_name),
99 'name_raw': pr.pull_request_id,
99 'name_raw': pr.pull_request_id,
100 'status': _render('pullrequest_status',
100 'status': _render('pullrequest_status',
101 pr.calculated_review_status()),
101 pr.calculated_review_status()),
102 'title': _render(
102 'title': _render(
103 'pullrequest_title', pr.title, pr.description),
103 'pullrequest_title', pr.title, pr.description),
104 'description': h.escape(pr.description),
104 'description': h.escape(pr.description),
105 'updated_on': _render('pullrequest_updated_on',
105 'updated_on': _render('pullrequest_updated_on',
106 h.datetime_to_time(pr.updated_on)),
106 h.datetime_to_time(pr.updated_on)),
107 'updated_on_raw': h.datetime_to_time(pr.updated_on),
107 'updated_on_raw': h.datetime_to_time(pr.updated_on),
108 'created_on': _render('pullrequest_updated_on',
108 'created_on': _render('pullrequest_updated_on',
109 h.datetime_to_time(pr.created_on)),
109 h.datetime_to_time(pr.created_on)),
110 'created_on_raw': h.datetime_to_time(pr.created_on),
110 'created_on_raw': h.datetime_to_time(pr.created_on),
111 'author': _render('pullrequest_author',
111 'author': _render('pullrequest_author',
112 pr.author.full_contact, ),
112 pr.author.full_contact, ),
113 'author_raw': pr.author.full_name,
113 'author_raw': pr.author.full_name,
114 'comments': _render('pullrequest_comments', len(comments)),
114 'comments': _render('pullrequest_comments', len(comments)),
115 'comments_raw': len(comments),
115 'comments_raw': len(comments),
116 'closed': pr.is_closed(),
116 'closed': pr.is_closed(),
117 })
117 })
118
118
119 data = ({
119 data = ({
120 'draw': draw,
120 'draw': draw,
121 'data': data,
121 'data': data,
122 'recordsTotal': pull_requests_total_count,
122 'recordsTotal': pull_requests_total_count,
123 'recordsFiltered': pull_requests_total_count,
123 'recordsFiltered': pull_requests_total_count,
124 })
124 })
125 return data
125 return data
126
126
127 @LoginRequired()
127 @LoginRequired()
128 @HasRepoPermissionAnyDecorator(
128 @HasRepoPermissionAnyDecorator(
129 'repository.read', 'repository.write', 'repository.admin')
129 'repository.read', 'repository.write', 'repository.admin')
130 @view_config(
130 @view_config(
131 route_name='pullrequest_show_all', request_method='GET',
131 route_name='pullrequest_show_all', request_method='GET',
132 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
132 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
133 def pull_request_list(self):
133 def pull_request_list(self):
134 c = self.load_default_context()
134 c = self.load_default_context()
135
135
136 req_get = self.request.GET
136 req_get = self.request.GET
137 c.source = str2bool(req_get.get('source'))
137 c.source = str2bool(req_get.get('source'))
138 c.closed = str2bool(req_get.get('closed'))
138 c.closed = str2bool(req_get.get('closed'))
139 c.my = str2bool(req_get.get('my'))
139 c.my = str2bool(req_get.get('my'))
140 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
140 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
141 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
141 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
142
142
143 c.active = 'open'
143 c.active = 'open'
144 if c.my:
144 if c.my:
145 c.active = 'my'
145 c.active = 'my'
146 if c.closed:
146 if c.closed:
147 c.active = 'closed'
147 c.active = 'closed'
148 if c.awaiting_review and not c.source:
148 if c.awaiting_review and not c.source:
149 c.active = 'awaiting'
149 c.active = 'awaiting'
150 if c.source and not c.awaiting_review:
150 if c.source and not c.awaiting_review:
151 c.active = 'source'
151 c.active = 'source'
152 if c.awaiting_my_review:
152 if c.awaiting_my_review:
153 c.active = 'awaiting_my'
153 c.active = 'awaiting_my'
154
154
155 return self._get_template_context(c)
155 return self._get_template_context(c)
156
156
157 @LoginRequired()
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator(
158 @HasRepoPermissionAnyDecorator(
159 'repository.read', 'repository.write', 'repository.admin')
159 'repository.read', 'repository.write', 'repository.admin')
160 @view_config(
160 @view_config(
161 route_name='pullrequest_show_all_data', request_method='GET',
161 route_name='pullrequest_show_all_data', request_method='GET',
162 renderer='json_ext', xhr=True)
162 renderer='json_ext', xhr=True)
163 def pull_request_list_data(self):
163 def pull_request_list_data(self):
164
164
165 # additional filters
165 # additional filters
166 req_get = self.request.GET
166 req_get = self.request.GET
167 source = str2bool(req_get.get('source'))
167 source = str2bool(req_get.get('source'))
168 closed = str2bool(req_get.get('closed'))
168 closed = str2bool(req_get.get('closed'))
169 my = str2bool(req_get.get('my'))
169 my = str2bool(req_get.get('my'))
170 awaiting_review = str2bool(req_get.get('awaiting_review'))
170 awaiting_review = str2bool(req_get.get('awaiting_review'))
171 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
171 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
172
172
173 filter_type = 'awaiting_review' if awaiting_review \
173 filter_type = 'awaiting_review' if awaiting_review \
174 else 'awaiting_my_review' if awaiting_my_review \
174 else 'awaiting_my_review' if awaiting_my_review \
175 else None
175 else None
176
176
177 opened_by = None
177 opened_by = None
178 if my:
178 if my:
179 opened_by = [self._rhodecode_user.user_id]
179 opened_by = [self._rhodecode_user.user_id]
180
180
181 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
181 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
182 if closed:
182 if closed:
183 statuses = [PullRequest.STATUS_CLOSED]
183 statuses = [PullRequest.STATUS_CLOSED]
184
184
185 data = self._get_pull_requests_list(
185 data = self._get_pull_requests_list(
186 repo_name=self.db_repo_name, source=source,
186 repo_name=self.db_repo_name, source=source,
187 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
187 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
188
188
189 return data
189 return data
190
190
191 def _get_pr_version(self, pull_request_id, version=None):
191 def _get_pr_version(self, pull_request_id, version=None):
192 pull_request_id = safe_int(pull_request_id)
192 pull_request_id = safe_int(pull_request_id)
193 at_version = None
193 at_version = None
194
194
195 if version and version == 'latest':
195 if version and version == 'latest':
196 pull_request_ver = PullRequest.get(pull_request_id)
196 pull_request_ver = PullRequest.get(pull_request_id)
197 pull_request_obj = pull_request_ver
197 pull_request_obj = pull_request_ver
198 _org_pull_request_obj = pull_request_obj
198 _org_pull_request_obj = pull_request_obj
199 at_version = 'latest'
199 at_version = 'latest'
200 elif version:
200 elif version:
201 pull_request_ver = PullRequestVersion.get_or_404(version)
201 pull_request_ver = PullRequestVersion.get_or_404(version)
202 pull_request_obj = pull_request_ver
202 pull_request_obj = pull_request_ver
203 _org_pull_request_obj = pull_request_ver.pull_request
203 _org_pull_request_obj = pull_request_ver.pull_request
204 at_version = pull_request_ver.pull_request_version_id
204 at_version = pull_request_ver.pull_request_version_id
205 else:
205 else:
206 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
206 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
207 pull_request_id)
207 pull_request_id)
208
208
209 pull_request_display_obj = PullRequest.get_pr_display_object(
209 pull_request_display_obj = PullRequest.get_pr_display_object(
210 pull_request_obj, _org_pull_request_obj)
210 pull_request_obj, _org_pull_request_obj)
211
211
212 return _org_pull_request_obj, pull_request_obj, \
212 return _org_pull_request_obj, pull_request_obj, \
213 pull_request_display_obj, at_version
213 pull_request_display_obj, at_version
214
214
215 def _get_diffset(self, source_repo_name, source_repo,
215 def _get_diffset(self, source_repo_name, source_repo,
216 source_ref_id, target_ref_id,
216 source_ref_id, target_ref_id,
217 target_commit, source_commit, diff_limit, fulldiff,
217 target_commit, source_commit, diff_limit, fulldiff,
218 file_limit, display_inline_comments):
218 file_limit, display_inline_comments):
219
219
220 vcs_diff = PullRequestModel().get_diff(
220 vcs_diff = PullRequestModel().get_diff(
221 source_repo, source_ref_id, target_ref_id)
221 source_repo, source_ref_id, target_ref_id)
222
222
223 diff_processor = diffs.DiffProcessor(
223 diff_processor = diffs.DiffProcessor(
224 vcs_diff, format='newdiff', diff_limit=diff_limit,
224 vcs_diff, format='newdiff', diff_limit=diff_limit,
225 file_limit=file_limit, show_full_diff=fulldiff)
225 file_limit=file_limit, show_full_diff=fulldiff)
226
226
227 _parsed = diff_processor.prepare()
227 _parsed = diff_processor.prepare()
228
228
229 def _node_getter(commit):
229 def _node_getter(commit):
230 def get_node(fname):
230 def get_node(fname):
231 try:
231 try:
232 return commit.get_node(fname)
232 return commit.get_node(fname)
233 except NodeDoesNotExistError:
233 except NodeDoesNotExistError:
234 return None
234 return None
235
235
236 return get_node
236 return get_node
237
237
238 diffset = codeblocks.DiffSet(
238 diffset = codeblocks.DiffSet(
239 repo_name=self.db_repo_name,
239 repo_name=self.db_repo_name,
240 source_repo_name=source_repo_name,
240 source_repo_name=source_repo_name,
241 source_node_getter=_node_getter(target_commit),
241 source_node_getter=_node_getter(target_commit),
242 target_node_getter=_node_getter(source_commit),
242 target_node_getter=_node_getter(source_commit),
243 comments=display_inline_comments
243 comments=display_inline_comments
244 )
244 )
245 diffset = diffset.render_patchset(
245 diffset = diffset.render_patchset(
246 _parsed, target_commit.raw_id, source_commit.raw_id)
246 _parsed, target_commit.raw_id, source_commit.raw_id)
247
247
248 return diffset
248 return diffset
249
249
250 @LoginRequired()
250 @LoginRequired()
251 @HasRepoPermissionAnyDecorator(
251 @HasRepoPermissionAnyDecorator(
252 'repository.read', 'repository.write', 'repository.admin')
252 'repository.read', 'repository.write', 'repository.admin')
253 # @view_config(
253 # @view_config(
254 # route_name='pullrequest_show', request_method='GET',
254 # route_name='pullrequest_show', request_method='GET',
255 # renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
255 # renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
256 def pull_request_show(self):
256 def pull_request_show(self):
257 pull_request_id = safe_int(
257 pull_request_id = safe_int(
258 self.request.matchdict.get('pull_request_id'))
258 self.request.matchdict.get('pull_request_id'))
259 c = self.load_default_context()
259 c = self.load_default_context()
260
260
261 version = self.request.GET.get('version')
261 version = self.request.GET.get('version')
262 from_version = self.request.GET.get('from_version') or version
262 from_version = self.request.GET.get('from_version') or version
263 merge_checks = self.request.GET.get('merge_checks')
263 merge_checks = self.request.GET.get('merge_checks')
264 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
264 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
265
265
266 (pull_request_latest,
266 (pull_request_latest,
267 pull_request_at_ver,
267 pull_request_at_ver,
268 pull_request_display_obj,
268 pull_request_display_obj,
269 at_version) = self._get_pr_version(
269 at_version) = self._get_pr_version(
270 pull_request_id, version=version)
270 pull_request_id, version=version)
271 pr_closed = pull_request_latest.is_closed()
271 pr_closed = pull_request_latest.is_closed()
272
272
273 if pr_closed and (version or from_version):
273 if pr_closed and (version or from_version):
274 # not allow to browse versions
274 # not allow to browse versions
275 raise HTTPFound(h.route_path(
275 raise HTTPFound(h.route_path(
276 'pullrequest_show', repo_name=self.db_repo_name,
276 'pullrequest_show', repo_name=self.db_repo_name,
277 pull_request_id=pull_request_id))
277 pull_request_id=pull_request_id))
278
278
279 versions = pull_request_display_obj.versions()
279 versions = pull_request_display_obj.versions()
280
280
281 c.at_version = at_version
281 c.at_version = at_version
282 c.at_version_num = (at_version
282 c.at_version_num = (at_version
283 if at_version and at_version != 'latest'
283 if at_version and at_version != 'latest'
284 else None)
284 else None)
285 c.at_version_pos = ChangesetComment.get_index_from_version(
285 c.at_version_pos = ChangesetComment.get_index_from_version(
286 c.at_version_num, versions)
286 c.at_version_num, versions)
287
287
288 (prev_pull_request_latest,
288 (prev_pull_request_latest,
289 prev_pull_request_at_ver,
289 prev_pull_request_at_ver,
290 prev_pull_request_display_obj,
290 prev_pull_request_display_obj,
291 prev_at_version) = self._get_pr_version(
291 prev_at_version) = self._get_pr_version(
292 pull_request_id, version=from_version)
292 pull_request_id, version=from_version)
293
293
294 c.from_version = prev_at_version
294 c.from_version = prev_at_version
295 c.from_version_num = (prev_at_version
295 c.from_version_num = (prev_at_version
296 if prev_at_version and prev_at_version != 'latest'
296 if prev_at_version and prev_at_version != 'latest'
297 else None)
297 else None)
298 c.from_version_pos = ChangesetComment.get_index_from_version(
298 c.from_version_pos = ChangesetComment.get_index_from_version(
299 c.from_version_num, versions)
299 c.from_version_num, versions)
300
300
301 # define if we're in COMPARE mode or VIEW at version mode
301 # define if we're in COMPARE mode or VIEW at version mode
302 compare = at_version != prev_at_version
302 compare = at_version != prev_at_version
303
303
304 # pull_requests repo_name we opened it against
304 # pull_requests repo_name we opened it against
305 # ie. target_repo must match
305 # ie. target_repo must match
306 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
306 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
307 raise HTTPNotFound()
307 raise HTTPNotFound()
308
308
309 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
309 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
310 pull_request_at_ver)
310 pull_request_at_ver)
311
311
312 c.pull_request = pull_request_display_obj
312 c.pull_request = pull_request_display_obj
313 c.pull_request_latest = pull_request_latest
313 c.pull_request_latest = pull_request_latest
314
314
315 if compare or (at_version and not at_version == 'latest'):
315 if compare or (at_version and not at_version == 'latest'):
316 c.allowed_to_change_status = False
316 c.allowed_to_change_status = False
317 c.allowed_to_update = False
317 c.allowed_to_update = False
318 c.allowed_to_merge = False
318 c.allowed_to_merge = False
319 c.allowed_to_delete = False
319 c.allowed_to_delete = False
320 c.allowed_to_comment = False
320 c.allowed_to_comment = False
321 c.allowed_to_close = False
321 c.allowed_to_close = False
322 else:
322 else:
323 can_change_status = PullRequestModel().check_user_change_status(
323 can_change_status = PullRequestModel().check_user_change_status(
324 pull_request_at_ver, self._rhodecode_user)
324 pull_request_at_ver, self._rhodecode_user)
325 c.allowed_to_change_status = can_change_status and not pr_closed
325 c.allowed_to_change_status = can_change_status and not pr_closed
326
326
327 c.allowed_to_update = PullRequestModel().check_user_update(
327 c.allowed_to_update = PullRequestModel().check_user_update(
328 pull_request_latest, self._rhodecode_user) and not pr_closed
328 pull_request_latest, self._rhodecode_user) and not pr_closed
329 c.allowed_to_merge = PullRequestModel().check_user_merge(
329 c.allowed_to_merge = PullRequestModel().check_user_merge(
330 pull_request_latest, self._rhodecode_user) and not pr_closed
330 pull_request_latest, self._rhodecode_user) and not pr_closed
331 c.allowed_to_delete = PullRequestModel().check_user_delete(
331 c.allowed_to_delete = PullRequestModel().check_user_delete(
332 pull_request_latest, self._rhodecode_user) and not pr_closed
332 pull_request_latest, self._rhodecode_user) and not pr_closed
333 c.allowed_to_comment = not pr_closed
333 c.allowed_to_comment = not pr_closed
334 c.allowed_to_close = c.allowed_to_merge and not pr_closed
334 c.allowed_to_close = c.allowed_to_merge and not pr_closed
335
335
336 c.forbid_adding_reviewers = False
336 c.forbid_adding_reviewers = False
337 c.forbid_author_to_review = False
337 c.forbid_author_to_review = False
338 c.forbid_commit_author_to_review = False
338 c.forbid_commit_author_to_review = False
339
339
340 if pull_request_latest.reviewer_data and \
340 if pull_request_latest.reviewer_data and \
341 'rules' in pull_request_latest.reviewer_data:
341 'rules' in pull_request_latest.reviewer_data:
342 rules = pull_request_latest.reviewer_data['rules'] or {}
342 rules = pull_request_latest.reviewer_data['rules'] or {}
343 try:
343 try:
344 c.forbid_adding_reviewers = rules.get(
344 c.forbid_adding_reviewers = rules.get(
345 'forbid_adding_reviewers')
345 'forbid_adding_reviewers')
346 c.forbid_author_to_review = rules.get(
346 c.forbid_author_to_review = rules.get(
347 'forbid_author_to_review')
347 'forbid_author_to_review')
348 c.forbid_commit_author_to_review = rules.get(
348 c.forbid_commit_author_to_review = rules.get(
349 'forbid_commit_author_to_review')
349 'forbid_commit_author_to_review')
350 except Exception:
350 except Exception:
351 pass
351 pass
352
352
353 # check merge capabilities
353 # check merge capabilities
354 _merge_check = MergeCheck.validate(
354 _merge_check = MergeCheck.validate(
355 pull_request_latest, user=self._rhodecode_user)
355 pull_request_latest, user=self._rhodecode_user)
356 c.pr_merge_errors = _merge_check.error_details
356 c.pr_merge_errors = _merge_check.error_details
357 c.pr_merge_possible = not _merge_check.failed
357 c.pr_merge_possible = not _merge_check.failed
358 c.pr_merge_message = _merge_check.merge_msg
358 c.pr_merge_message = _merge_check.merge_msg
359
359
360 c.pull_request_review_status = _merge_check.review_status
360 c.pull_request_review_status = _merge_check.review_status
361 if merge_checks:
361 if merge_checks:
362 self.request.override_renderer = \
362 self.request.override_renderer = \
363 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
363 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
364 return self._get_template_context(c)
364 return self._get_template_context(c)
365
365
366 comments_model = CommentsModel()
366 comments_model = CommentsModel()
367
367
368 # reviewers and statuses
368 # reviewers and statuses
369 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
369 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
370 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
370 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
371
371
372 # GENERAL COMMENTS with versions #
372 # GENERAL COMMENTS with versions #
373 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
373 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
374 q = q.order_by(ChangesetComment.comment_id.asc())
374 q = q.order_by(ChangesetComment.comment_id.asc())
375 general_comments = q
375 general_comments = q
376
376
377 # pick comments we want to render at current version
377 # pick comments we want to render at current version
378 c.comment_versions = comments_model.aggregate_comments(
378 c.comment_versions = comments_model.aggregate_comments(
379 general_comments, versions, c.at_version_num)
379 general_comments, versions, c.at_version_num)
380 c.comments = c.comment_versions[c.at_version_num]['until']
380 c.comments = c.comment_versions[c.at_version_num]['until']
381
381
382 # INLINE COMMENTS with versions #
382 # INLINE COMMENTS with versions #
383 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
383 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
384 q = q.order_by(ChangesetComment.comment_id.asc())
384 q = q.order_by(ChangesetComment.comment_id.asc())
385 inline_comments = q
385 inline_comments = q
386
386
387 c.inline_versions = comments_model.aggregate_comments(
387 c.inline_versions = comments_model.aggregate_comments(
388 inline_comments, versions, c.at_version_num, inline=True)
388 inline_comments, versions, c.at_version_num, inline=True)
389
389
390 # inject latest version
390 # inject latest version
391 latest_ver = PullRequest.get_pr_display_object(
391 latest_ver = PullRequest.get_pr_display_object(
392 pull_request_latest, pull_request_latest)
392 pull_request_latest, pull_request_latest)
393
393
394 c.versions = versions + [latest_ver]
394 c.versions = versions + [latest_ver]
395
395
396 # if we use version, then do not show later comments
396 # if we use version, then do not show later comments
397 # than current version
397 # than current version
398 display_inline_comments = collections.defaultdict(
398 display_inline_comments = collections.defaultdict(
399 lambda: collections.defaultdict(list))
399 lambda: collections.defaultdict(list))
400 for co in inline_comments:
400 for co in inline_comments:
401 if c.at_version_num:
401 if c.at_version_num:
402 # pick comments that are at least UPTO given version, so we
402 # pick comments that are at least UPTO given version, so we
403 # don't render comments for higher version
403 # don't render comments for higher version
404 should_render = co.pull_request_version_id and \
404 should_render = co.pull_request_version_id and \
405 co.pull_request_version_id <= c.at_version_num
405 co.pull_request_version_id <= c.at_version_num
406 else:
406 else:
407 # showing all, for 'latest'
407 # showing all, for 'latest'
408 should_render = True
408 should_render = True
409
409
410 if should_render:
410 if should_render:
411 display_inline_comments[co.f_path][co.line_no].append(co)
411 display_inline_comments[co.f_path][co.line_no].append(co)
412
412
413 # load diff data into template context, if we use compare mode then
413 # load diff data into template context, if we use compare mode then
414 # diff is calculated based on changes between versions of PR
414 # diff is calculated based on changes between versions of PR
415
415
416 source_repo = pull_request_at_ver.source_repo
416 source_repo = pull_request_at_ver.source_repo
417 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
417 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
418
418
419 target_repo = pull_request_at_ver.target_repo
419 target_repo = pull_request_at_ver.target_repo
420 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
420 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
421
421
422 if compare:
422 if compare:
423 # in compare switch the diff base to latest commit from prev version
423 # in compare switch the diff base to latest commit from prev version
424 target_ref_id = prev_pull_request_display_obj.revisions[0]
424 target_ref_id = prev_pull_request_display_obj.revisions[0]
425
425
426 # despite opening commits for bookmarks/branches/tags, we always
426 # despite opening commits for bookmarks/branches/tags, we always
427 # convert this to rev to prevent changes after bookmark or branch change
427 # convert this to rev to prevent changes after bookmark or branch change
428 c.source_ref_type = 'rev'
428 c.source_ref_type = 'rev'
429 c.source_ref = source_ref_id
429 c.source_ref = source_ref_id
430
430
431 c.target_ref_type = 'rev'
431 c.target_ref_type = 'rev'
432 c.target_ref = target_ref_id
432 c.target_ref = target_ref_id
433
433
434 c.source_repo = source_repo
434 c.source_repo = source_repo
435 c.target_repo = target_repo
435 c.target_repo = target_repo
436
436
437 c.commit_ranges = []
437 c.commit_ranges = []
438 source_commit = EmptyCommit()
438 source_commit = EmptyCommit()
439 target_commit = EmptyCommit()
439 target_commit = EmptyCommit()
440 c.missing_requirements = False
440 c.missing_requirements = False
441
441
442 source_scm = source_repo.scm_instance()
442 source_scm = source_repo.scm_instance()
443 target_scm = target_repo.scm_instance()
443 target_scm = target_repo.scm_instance()
444
444
445 # try first shadow repo, fallback to regular repo
445 # try first shadow repo, fallback to regular repo
446 try:
446 try:
447 commits_source_repo = pull_request_latest.get_shadow_repo()
447 commits_source_repo = pull_request_latest.get_shadow_repo()
448 except Exception:
448 except Exception:
449 log.debug('Failed to get shadow repo', exc_info=True)
449 log.debug('Failed to get shadow repo', exc_info=True)
450 commits_source_repo = source_scm
450 commits_source_repo = source_scm
451
451
452 c.commits_source_repo = commits_source_repo
452 c.commits_source_repo = commits_source_repo
453 commit_cache = {}
453 commit_cache = {}
454 try:
454 try:
455 pre_load = ["author", "branch", "date", "message"]
455 pre_load = ["author", "branch", "date", "message"]
456 show_revs = pull_request_at_ver.revisions
456 show_revs = pull_request_at_ver.revisions
457 for rev in show_revs:
457 for rev in show_revs:
458 comm = commits_source_repo.get_commit(
458 comm = commits_source_repo.get_commit(
459 commit_id=rev, pre_load=pre_load)
459 commit_id=rev, pre_load=pre_load)
460 c.commit_ranges.append(comm)
460 c.commit_ranges.append(comm)
461 commit_cache[comm.raw_id] = comm
461 commit_cache[comm.raw_id] = comm
462
462
463 # Order here matters, we first need to get target, and then
463 # Order here matters, we first need to get target, and then
464 # the source
464 # the source
465 target_commit = commits_source_repo.get_commit(
465 target_commit = commits_source_repo.get_commit(
466 commit_id=safe_str(target_ref_id))
466 commit_id=safe_str(target_ref_id))
467
467
468 source_commit = commits_source_repo.get_commit(
468 source_commit = commits_source_repo.get_commit(
469 commit_id=safe_str(source_ref_id))
469 commit_id=safe_str(source_ref_id))
470
470
471 except CommitDoesNotExistError:
471 except CommitDoesNotExistError:
472 log.warning(
472 log.warning(
473 'Failed to get commit from `{}` repo'.format(
473 'Failed to get commit from `{}` repo'.format(
474 commits_source_repo), exc_info=True)
474 commits_source_repo), exc_info=True)
475 except RepositoryRequirementError:
475 except RepositoryRequirementError:
476 log.warning(
476 log.warning(
477 'Failed to get all required data from repo', exc_info=True)
477 'Failed to get all required data from repo', exc_info=True)
478 c.missing_requirements = True
478 c.missing_requirements = True
479
479
480 c.ancestor = None # set it to None, to hide it from PR view
480 c.ancestor = None # set it to None, to hide it from PR view
481
481
482 try:
482 try:
483 ancestor_id = source_scm.get_common_ancestor(
483 ancestor_id = source_scm.get_common_ancestor(
484 source_commit.raw_id, target_commit.raw_id, target_scm)
484 source_commit.raw_id, target_commit.raw_id, target_scm)
485 c.ancestor_commit = source_scm.get_commit(ancestor_id)
485 c.ancestor_commit = source_scm.get_commit(ancestor_id)
486 except Exception:
486 except Exception:
487 c.ancestor_commit = None
487 c.ancestor_commit = None
488
488
489 c.statuses = source_repo.statuses(
489 c.statuses = source_repo.statuses(
490 [x.raw_id for x in c.commit_ranges])
490 [x.raw_id for x in c.commit_ranges])
491
491
492 # auto collapse if we have more than limit
492 # auto collapse if we have more than limit
493 collapse_limit = diffs.DiffProcessor._collapse_commits_over
493 collapse_limit = diffs.DiffProcessor._collapse_commits_over
494 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
494 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
495 c.compare_mode = compare
495 c.compare_mode = compare
496
496
497 # diff_limit is the old behavior, will cut off the whole diff
497 # diff_limit is the old behavior, will cut off the whole diff
498 # if the limit is applied otherwise will just hide the
498 # if the limit is applied otherwise will just hide the
499 # big files from the front-end
499 # big files from the front-end
500 diff_limit = c.visual.cut_off_limit_diff
500 diff_limit = c.visual.cut_off_limit_diff
501 file_limit = c.visual.cut_off_limit_file
501 file_limit = c.visual.cut_off_limit_file
502
502
503 c.missing_commits = False
503 c.missing_commits = False
504 if (c.missing_requirements
504 if (c.missing_requirements
505 or isinstance(source_commit, EmptyCommit)
505 or isinstance(source_commit, EmptyCommit)
506 or source_commit == target_commit):
506 or source_commit == target_commit):
507
507
508 c.missing_commits = True
508 c.missing_commits = True
509 else:
509 else:
510
510
511 c.diffset = self._get_diffset(
511 c.diffset = self._get_diffset(
512 c.source_repo.repo_name, commits_source_repo,
512 c.source_repo.repo_name, commits_source_repo,
513 source_ref_id, target_ref_id,
513 source_ref_id, target_ref_id,
514 target_commit, source_commit,
514 target_commit, source_commit,
515 diff_limit, c.fulldiff, file_limit, display_inline_comments)
515 diff_limit, c.fulldiff, file_limit, display_inline_comments)
516
516
517 c.limited_diff = c.diffset.limited_diff
517 c.limited_diff = c.diffset.limited_diff
518
518
519 # calculate removed files that are bound to comments
519 # calculate removed files that are bound to comments
520 comment_deleted_files = [
520 comment_deleted_files = [
521 fname for fname in display_inline_comments
521 fname for fname in display_inline_comments
522 if fname not in c.diffset.file_stats]
522 if fname not in c.diffset.file_stats]
523
523
524 c.deleted_files_comments = collections.defaultdict(dict)
524 c.deleted_files_comments = collections.defaultdict(dict)
525 for fname, per_line_comments in display_inline_comments.items():
525 for fname, per_line_comments in display_inline_comments.items():
526 if fname in comment_deleted_files:
526 if fname in comment_deleted_files:
527 c.deleted_files_comments[fname]['stats'] = 0
527 c.deleted_files_comments[fname]['stats'] = 0
528 c.deleted_files_comments[fname]['comments'] = list()
528 c.deleted_files_comments[fname]['comments'] = list()
529 for lno, comments in per_line_comments.items():
529 for lno, comments in per_line_comments.items():
530 c.deleted_files_comments[fname]['comments'].extend(
530 c.deleted_files_comments[fname]['comments'].extend(
531 comments)
531 comments)
532
532
533 # this is a hack to properly display links, when creating PR, the
533 # this is a hack to properly display links, when creating PR, the
534 # compare view and others uses different notation, and
534 # compare view and others uses different notation, and
535 # compare_commits.mako renders links based on the target_repo.
535 # compare_commits.mako renders links based on the target_repo.
536 # We need to swap that here to generate it properly on the html side
536 # We need to swap that here to generate it properly on the html side
537 c.target_repo = c.source_repo
537 c.target_repo = c.source_repo
538
538
539 c.commit_statuses = ChangesetStatus.STATUSES
539 c.commit_statuses = ChangesetStatus.STATUSES
540
540
541 c.show_version_changes = not pr_closed
541 c.show_version_changes = not pr_closed
542 if c.show_version_changes:
542 if c.show_version_changes:
543 cur_obj = pull_request_at_ver
543 cur_obj = pull_request_at_ver
544 prev_obj = prev_pull_request_at_ver
544 prev_obj = prev_pull_request_at_ver
545
545
546 old_commit_ids = prev_obj.revisions
546 old_commit_ids = prev_obj.revisions
547 new_commit_ids = cur_obj.revisions
547 new_commit_ids = cur_obj.revisions
548 commit_changes = PullRequestModel()._calculate_commit_id_changes(
548 commit_changes = PullRequestModel()._calculate_commit_id_changes(
549 old_commit_ids, new_commit_ids)
549 old_commit_ids, new_commit_ids)
550 c.commit_changes_summary = commit_changes
550 c.commit_changes_summary = commit_changes
551
551
552 # calculate the diff for commits between versions
552 # calculate the diff for commits between versions
553 c.commit_changes = []
553 c.commit_changes = []
554 mark = lambda cs, fw: list(
554 mark = lambda cs, fw: list(
555 h.itertools.izip_longest([], cs, fillvalue=fw))
555 h.itertools.izip_longest([], cs, fillvalue=fw))
556 for c_type, raw_id in mark(commit_changes.added, 'a') \
556 for c_type, raw_id in mark(commit_changes.added, 'a') \
557 + mark(commit_changes.removed, 'r') \
557 + mark(commit_changes.removed, 'r') \
558 + mark(commit_changes.common, 'c'):
558 + mark(commit_changes.common, 'c'):
559
559
560 if raw_id in commit_cache:
560 if raw_id in commit_cache:
561 commit = commit_cache[raw_id]
561 commit = commit_cache[raw_id]
562 else:
562 else:
563 try:
563 try:
564 commit = commits_source_repo.get_commit(raw_id)
564 commit = commits_source_repo.get_commit(raw_id)
565 except CommitDoesNotExistError:
565 except CommitDoesNotExistError:
566 # in case we fail extracting still use "dummy" commit
566 # in case we fail extracting still use "dummy" commit
567 # for display in commit diff
567 # for display in commit diff
568 commit = h.AttributeDict(
568 commit = h.AttributeDict(
569 {'raw_id': raw_id,
569 {'raw_id': raw_id,
570 'message': 'EMPTY or MISSING COMMIT'})
570 'message': 'EMPTY or MISSING COMMIT'})
571 c.commit_changes.append([c_type, commit])
571 c.commit_changes.append([c_type, commit])
572
572
573 # current user review statuses for each version
573 # current user review statuses for each version
574 c.review_versions = {}
574 c.review_versions = {}
575 if self._rhodecode_user.user_id in allowed_reviewers:
575 if self._rhodecode_user.user_id in allowed_reviewers:
576 for co in general_comments:
576 for co in general_comments:
577 if co.author.user_id == self._rhodecode_user.user_id:
577 if co.author.user_id == self._rhodecode_user.user_id:
578 # each comment has a status change
578 # each comment has a status change
579 status = co.status_change
579 status = co.status_change
580 if status:
580 if status:
581 _ver_pr = status[0].comment.pull_request_version_id
581 _ver_pr = status[0].comment.pull_request_version_id
582 c.review_versions[_ver_pr] = status[0]
582 c.review_versions[_ver_pr] = status[0]
583
583
584 return self._get_template_context(c)
584 return self._get_template_context(c)
@@ -1,513 +1,514 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 import peppercorn
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
41 RepoGroupAssignmentError
41 RepoGroupAssignmentError
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
46 HasPermissionAnyDecorator, XHRRequired)
46 HasPermissionAnyDecorator, XHRRequired)
47 from rhodecode.lib.base import BaseController, render
47 from rhodecode.lib.base import BaseController, render
48 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.scm import UserGroupList
49 from rhodecode.model.scm import UserGroupList
50 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
53 from rhodecode.model.forms import (
53 from rhodecode.model.forms import (
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
55 UserPermissionsForm)
55 UserPermissionsForm)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class UserGroupsController(BaseController):
62 class UserGroupsController(BaseController):
63 """REST Controller styled on the Atom Publishing Protocol"""
63 """REST Controller styled on the Atom Publishing Protocol"""
64
64
65 @LoginRequired()
65 @LoginRequired()
66 def __before__(self):
66 def __before__(self):
67 super(UserGroupsController, self).__before__()
67 super(UserGroupsController, self).__before__()
68 c.available_permissions = config['available_permissions']
68 c.available_permissions = config['available_permissions']
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
70
70
71 def __load_data(self, user_group_id):
71 def __load_data(self, user_group_id):
72 c.group_members_obj = [x.user for x in c.user_group.members]
72 c.group_members_obj = [x.user for x in c.user_group.members]
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
75
75
76 def __load_defaults(self, user_group_id):
76 def __load_defaults(self, user_group_id):
77 """
77 """
78 Load defaults settings for edit, and update
78 Load defaults settings for edit, and update
79
79
80 :param user_group_id:
80 :param user_group_id:
81 """
81 """
82 user_group = UserGroup.get_or_404(user_group_id)
82 user_group = UserGroup.get_or_404(user_group_id)
83 data = user_group.get_dict()
83 data = user_group.get_dict()
84 # fill owner
84 # fill owner
85 if user_group.user:
85 if user_group.user:
86 data.update({'user': user_group.user.username})
86 data.update({'user': user_group.user.username})
87 else:
87 else:
88 replacement_user = User.get_first_super_admin().username
88 replacement_user = User.get_first_super_admin().username
89 data.update({'user': replacement_user})
89 data.update({'user': replacement_user})
90 return data
90 return data
91
91
92 def _revoke_perms_on_yourself(self, form_result):
92 def _revoke_perms_on_yourself(self, form_result):
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 form_result['perm_updates'])
94 form_result['perm_updates'])
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_additions'])
96 form_result['perm_additions'])
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_deletions'])
98 form_result['perm_deletions'])
99 admin_perm = 'usergroup.admin'
99 admin_perm = 'usergroup.admin'
100 if _updates and _updates[0][1] != admin_perm or \
100 if _updates and _updates[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
102 _deletions and _deletions[0][1] != admin_perm:
102 _deletions and _deletions[0][1] != admin_perm:
103 return True
103 return True
104 return False
104 return False
105
105
106 # permission check inside
106 # permission check inside
107 @NotAnonymous()
107 @NotAnonymous()
108 def index(self):
108 def index(self):
109
109 # TODO(marcink): remove bind to self.request after pyramid migration
110 from rhodecode.lib.utils import PartialRenderer
110 self.request = c.pyramid_request
111 _render = PartialRenderer('data_table/_dt_elements.mako')
111 _render = self.request.get_partial_renderer(
112 'data_table/_dt_elements.mako')
112
113
113 def user_group_name(user_group_id, user_group_name):
114 def user_group_name(user_group_id, user_group_name):
114 return _render("user_group_name", user_group_id, user_group_name)
115 return _render("user_group_name", user_group_id, user_group_name)
115
116
116 def user_group_actions(user_group_id, user_group_name):
117 def user_group_actions(user_group_id, user_group_name):
117 return _render("user_group_actions", user_group_id, user_group_name)
118 return _render("user_group_actions", user_group_id, user_group_name)
118
119
119 # json generate
120 # json generate
120 group_iter = UserGroupList(UserGroup.query().all(),
121 group_iter = UserGroupList(UserGroup.query().all(),
121 perm_set=['usergroup.admin'])
122 perm_set=['usergroup.admin'])
122
123
123 user_groups_data = []
124 user_groups_data = []
124 for user_gr in group_iter:
125 for user_gr in group_iter:
125 user_groups_data.append({
126 user_groups_data.append({
126 "group_name": user_group_name(
127 "group_name": user_group_name(
127 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
128 "group_name_raw": user_gr.users_group_name,
129 "group_name_raw": user_gr.users_group_name,
129 "desc": h.escape(user_gr.user_group_description),
130 "desc": h.escape(user_gr.user_group_description),
130 "members": len(user_gr.members),
131 "members": len(user_gr.members),
131 "sync": user_gr.group_data.get('extern_type'),
132 "sync": user_gr.group_data.get('extern_type'),
132 "active": h.bool2icon(user_gr.users_group_active),
133 "active": h.bool2icon(user_gr.users_group_active),
133 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 "action": user_group_actions(
135 "action": user_group_actions(
135 user_gr.users_group_id, user_gr.users_group_name)
136 user_gr.users_group_id, user_gr.users_group_name)
136 })
137 })
137
138
138 c.data = json.dumps(user_groups_data)
139 c.data = json.dumps(user_groups_data)
139 return render('admin/user_groups/user_groups.mako')
140 return render('admin/user_groups/user_groups.mako')
140
141
141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
142 @auth.CSRFRequired()
143 @auth.CSRFRequired()
143 def create(self):
144 def create(self):
144
145
145 users_group_form = UserGroupForm()()
146 users_group_form = UserGroupForm()()
146 try:
147 try:
147 form_result = users_group_form.to_python(dict(request.POST))
148 form_result = users_group_form.to_python(dict(request.POST))
148 user_group = UserGroupModel().create(
149 user_group = UserGroupModel().create(
149 name=form_result['users_group_name'],
150 name=form_result['users_group_name'],
150 description=form_result['user_group_description'],
151 description=form_result['user_group_description'],
151 owner=c.rhodecode_user.user_id,
152 owner=c.rhodecode_user.user_id,
152 active=form_result['users_group_active'])
153 active=form_result['users_group_active'])
153 Session().flush()
154 Session().flush()
154 creation_data = user_group.get_api_data()
155 creation_data = user_group.get_api_data()
155 user_group_name = form_result['users_group_name']
156 user_group_name = form_result['users_group_name']
156
157
157 audit_logger.store_web(
158 audit_logger.store_web(
158 'user_group.create', action_data={'data': creation_data},
159 'user_group.create', action_data={'data': creation_data},
159 user=c.rhodecode_user)
160 user=c.rhodecode_user)
160
161
161 user_group_link = h.link_to(
162 user_group_link = h.link_to(
162 h.escape(user_group_name),
163 h.escape(user_group_name),
163 url('edit_users_group', user_group_id=user_group.users_group_id))
164 url('edit_users_group', user_group_id=user_group.users_group_id))
164 h.flash(h.literal(_('Created user group %(user_group_link)s')
165 h.flash(h.literal(_('Created user group %(user_group_link)s')
165 % {'user_group_link': user_group_link}),
166 % {'user_group_link': user_group_link}),
166 category='success')
167 category='success')
167 Session().commit()
168 Session().commit()
168 except formencode.Invalid as errors:
169 except formencode.Invalid as errors:
169 return htmlfill.render(
170 return htmlfill.render(
170 render('admin/user_groups/user_group_add.mako'),
171 render('admin/user_groups/user_group_add.mako'),
171 defaults=errors.value,
172 defaults=errors.value,
172 errors=errors.error_dict or {},
173 errors=errors.error_dict or {},
173 prefix_error=False,
174 prefix_error=False,
174 encoding="UTF-8",
175 encoding="UTF-8",
175 force_defaults=False)
176 force_defaults=False)
176 except Exception:
177 except Exception:
177 log.exception("Exception creating user group")
178 log.exception("Exception creating user group")
178 h.flash(_('Error occurred during creation of user group %s') \
179 h.flash(_('Error occurred during creation of user group %s') \
179 % request.POST.get('users_group_name'), category='error')
180 % request.POST.get('users_group_name'), category='error')
180
181
181 return redirect(
182 return redirect(
182 url('edit_users_group', user_group_id=user_group.users_group_id))
183 url('edit_users_group', user_group_id=user_group.users_group_id))
183
184
184 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
185 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
185 def new(self):
186 def new(self):
186 """GET /user_groups/new: Form to create a new item"""
187 """GET /user_groups/new: Form to create a new item"""
187 # url('new_users_group')
188 # url('new_users_group')
188 return render('admin/user_groups/user_group_add.mako')
189 return render('admin/user_groups/user_group_add.mako')
189
190
190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
191 @auth.CSRFRequired()
192 @auth.CSRFRequired()
192 def update(self, user_group_id):
193 def update(self, user_group_id):
193
194
194 user_group_id = safe_int(user_group_id)
195 user_group_id = safe_int(user_group_id)
195 c.user_group = UserGroup.get_or_404(user_group_id)
196 c.user_group = UserGroup.get_or_404(user_group_id)
196 c.active = 'settings'
197 c.active = 'settings'
197 self.__load_data(user_group_id)
198 self.__load_data(user_group_id)
198
199
199 users_group_form = UserGroupForm(
200 users_group_form = UserGroupForm(
200 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
201 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
201
202
202 old_values = c.user_group.get_api_data()
203 old_values = c.user_group.get_api_data()
203 try:
204 try:
204 form_result = users_group_form.to_python(request.POST)
205 form_result = users_group_form.to_python(request.POST)
205 pstruct = peppercorn.parse(request.POST.items())
206 pstruct = peppercorn.parse(request.POST.items())
206 form_result['users_group_members'] = pstruct['user_group_members']
207 form_result['users_group_members'] = pstruct['user_group_members']
207
208
208 user_group, added_members, removed_members = \
209 user_group, added_members, removed_members = \
209 UserGroupModel().update(c.user_group, form_result)
210 UserGroupModel().update(c.user_group, form_result)
210 updated_user_group = form_result['users_group_name']
211 updated_user_group = form_result['users_group_name']
211
212
212 audit_logger.store_web(
213 audit_logger.store_web(
213 'user_group.edit', action_data={'old_data': old_values},
214 'user_group.edit', action_data={'old_data': old_values},
214 user=c.rhodecode_user)
215 user=c.rhodecode_user)
215
216
216 # TODO(marcink): use added/removed to set user_group.edit.member.add
217 # TODO(marcink): use added/removed to set user_group.edit.member.add
217
218
218 h.flash(_('Updated user group %s') % updated_user_group,
219 h.flash(_('Updated user group %s') % updated_user_group,
219 category='success')
220 category='success')
220 Session().commit()
221 Session().commit()
221 except formencode.Invalid as errors:
222 except formencode.Invalid as errors:
222 defaults = errors.value
223 defaults = errors.value
223 e = errors.error_dict or {}
224 e = errors.error_dict or {}
224
225
225 return htmlfill.render(
226 return htmlfill.render(
226 render('admin/user_groups/user_group_edit.mako'),
227 render('admin/user_groups/user_group_edit.mako'),
227 defaults=defaults,
228 defaults=defaults,
228 errors=e,
229 errors=e,
229 prefix_error=False,
230 prefix_error=False,
230 encoding="UTF-8",
231 encoding="UTF-8",
231 force_defaults=False)
232 force_defaults=False)
232 except Exception:
233 except Exception:
233 log.exception("Exception during update of user group")
234 log.exception("Exception during update of user group")
234 h.flash(_('Error occurred during update of user group %s')
235 h.flash(_('Error occurred during update of user group %s')
235 % request.POST.get('users_group_name'), category='error')
236 % request.POST.get('users_group_name'), category='error')
236
237
237 return redirect(url('edit_users_group', user_group_id=user_group_id))
238 return redirect(url('edit_users_group', user_group_id=user_group_id))
238
239
239 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
240 @auth.CSRFRequired()
241 @auth.CSRFRequired()
241 def delete(self, user_group_id):
242 def delete(self, user_group_id):
242 user_group_id = safe_int(user_group_id)
243 user_group_id = safe_int(user_group_id)
243 c.user_group = UserGroup.get_or_404(user_group_id)
244 c.user_group = UserGroup.get_or_404(user_group_id)
244 force = str2bool(request.POST.get('force'))
245 force = str2bool(request.POST.get('force'))
245
246
246 old_values = c.user_group.get_api_data()
247 old_values = c.user_group.get_api_data()
247 try:
248 try:
248 UserGroupModel().delete(c.user_group, force=force)
249 UserGroupModel().delete(c.user_group, force=force)
249 audit_logger.store_web(
250 audit_logger.store_web(
250 'user.delete', action_data={'old_data': old_values},
251 'user.delete', action_data={'old_data': old_values},
251 user=c.rhodecode_user)
252 user=c.rhodecode_user)
252 Session().commit()
253 Session().commit()
253 h.flash(_('Successfully deleted user group'), category='success')
254 h.flash(_('Successfully deleted user group'), category='success')
254 except UserGroupAssignedException as e:
255 except UserGroupAssignedException as e:
255 h.flash(str(e), category='error')
256 h.flash(str(e), category='error')
256 except Exception:
257 except Exception:
257 log.exception("Exception during deletion of user group")
258 log.exception("Exception during deletion of user group")
258 h.flash(_('An error occurred during deletion of user group'),
259 h.flash(_('An error occurred during deletion of user group'),
259 category='error')
260 category='error')
260 return redirect(url('users_groups'))
261 return redirect(url('users_groups'))
261
262
262 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
263 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
263 def edit(self, user_group_id):
264 def edit(self, user_group_id):
264 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
265 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
265 # url('edit_users_group', user_group_id=ID)
266 # url('edit_users_group', user_group_id=ID)
266
267
267 user_group_id = safe_int(user_group_id)
268 user_group_id = safe_int(user_group_id)
268 c.user_group = UserGroup.get_or_404(user_group_id)
269 c.user_group = UserGroup.get_or_404(user_group_id)
269 c.active = 'settings'
270 c.active = 'settings'
270 self.__load_data(user_group_id)
271 self.__load_data(user_group_id)
271
272
272 defaults = self.__load_defaults(user_group_id)
273 defaults = self.__load_defaults(user_group_id)
273
274
274 return htmlfill.render(
275 return htmlfill.render(
275 render('admin/user_groups/user_group_edit.mako'),
276 render('admin/user_groups/user_group_edit.mako'),
276 defaults=defaults,
277 defaults=defaults,
277 encoding="UTF-8",
278 encoding="UTF-8",
278 force_defaults=False
279 force_defaults=False
279 )
280 )
280
281
281 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
282 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
282 def edit_perms(self, user_group_id):
283 def edit_perms(self, user_group_id):
283 user_group_id = safe_int(user_group_id)
284 user_group_id = safe_int(user_group_id)
284 c.user_group = UserGroup.get_or_404(user_group_id)
285 c.user_group = UserGroup.get_or_404(user_group_id)
285 c.active = 'perms'
286 c.active = 'perms'
286
287
287 defaults = {}
288 defaults = {}
288 # fill user group users
289 # fill user group users
289 for p in c.user_group.user_user_group_to_perm:
290 for p in c.user_group.user_user_group_to_perm:
290 defaults.update({'u_perm_%s' % p.user.user_id:
291 defaults.update({'u_perm_%s' % p.user.user_id:
291 p.permission.permission_name})
292 p.permission.permission_name})
292
293
293 for p in c.user_group.user_group_user_group_to_perm:
294 for p in c.user_group.user_group_user_group_to_perm:
294 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
295 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
295 p.permission.permission_name})
296 p.permission.permission_name})
296
297
297 return htmlfill.render(
298 return htmlfill.render(
298 render('admin/user_groups/user_group_edit.mako'),
299 render('admin/user_groups/user_group_edit.mako'),
299 defaults=defaults,
300 defaults=defaults,
300 encoding="UTF-8",
301 encoding="UTF-8",
301 force_defaults=False
302 force_defaults=False
302 )
303 )
303
304
304 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
305 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
305 @auth.CSRFRequired()
306 @auth.CSRFRequired()
306 def update_perms(self, user_group_id):
307 def update_perms(self, user_group_id):
307 """
308 """
308 grant permission for given usergroup
309 grant permission for given usergroup
309
310
310 :param user_group_id:
311 :param user_group_id:
311 """
312 """
312 user_group_id = safe_int(user_group_id)
313 user_group_id = safe_int(user_group_id)
313 c.user_group = UserGroup.get_or_404(user_group_id)
314 c.user_group = UserGroup.get_or_404(user_group_id)
314 form = UserGroupPermsForm()().to_python(request.POST)
315 form = UserGroupPermsForm()().to_python(request.POST)
315
316
316 if not c.rhodecode_user.is_admin:
317 if not c.rhodecode_user.is_admin:
317 if self._revoke_perms_on_yourself(form):
318 if self._revoke_perms_on_yourself(form):
318 msg = _('Cannot change permission for yourself as admin')
319 msg = _('Cannot change permission for yourself as admin')
319 h.flash(msg, category='warning')
320 h.flash(msg, category='warning')
320 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
321 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
321
322
322 try:
323 try:
323 UserGroupModel().update_permissions(user_group_id,
324 UserGroupModel().update_permissions(user_group_id,
324 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
325 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
325 except RepoGroupAssignmentError:
326 except RepoGroupAssignmentError:
326 h.flash(_('Target group cannot be the same'), category='error')
327 h.flash(_('Target group cannot be the same'), category='error')
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328
329
329 # TODO(marcink): implement global permissions
330 # TODO(marcink): implement global permissions
330 # audit_log.store_web('user_group.edit.permissions')
331 # audit_log.store_web('user_group.edit.permissions')
331 Session().commit()
332 Session().commit()
332 h.flash(_('User Group permissions updated'), category='success')
333 h.flash(_('User Group permissions updated'), category='success')
333 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
334
335
335 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
336 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
336 def edit_perms_summary(self, user_group_id):
337 def edit_perms_summary(self, user_group_id):
337 user_group_id = safe_int(user_group_id)
338 user_group_id = safe_int(user_group_id)
338 c.user_group = UserGroup.get_or_404(user_group_id)
339 c.user_group = UserGroup.get_or_404(user_group_id)
339 c.active = 'perms_summary'
340 c.active = 'perms_summary'
340 permissions = {
341 permissions = {
341 'repositories': {},
342 'repositories': {},
342 'repositories_groups': {},
343 'repositories_groups': {},
343 }
344 }
344 ugroup_repo_perms = UserGroupRepoToPerm.query()\
345 ugroup_repo_perms = UserGroupRepoToPerm.query()\
345 .options(joinedload(UserGroupRepoToPerm.permission))\
346 .options(joinedload(UserGroupRepoToPerm.permission))\
346 .options(joinedload(UserGroupRepoToPerm.repository))\
347 .options(joinedload(UserGroupRepoToPerm.repository))\
347 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
348 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
348 .all()
349 .all()
349
350
350 for gr in ugroup_repo_perms:
351 for gr in ugroup_repo_perms:
351 permissions['repositories'][gr.repository.repo_name] \
352 permissions['repositories'][gr.repository.repo_name] \
352 = gr.permission.permission_name
353 = gr.permission.permission_name
353
354
354 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
355 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
355 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
356 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
356 .options(joinedload(UserGroupRepoGroupToPerm.group))\
357 .options(joinedload(UserGroupRepoGroupToPerm.group))\
357 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
358 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
358 .all()
359 .all()
359
360
360 for gr in ugroup_group_perms:
361 for gr in ugroup_group_perms:
361 permissions['repositories_groups'][gr.group.group_name] \
362 permissions['repositories_groups'][gr.group.group_name] \
362 = gr.permission.permission_name
363 = gr.permission.permission_name
363 c.permissions = permissions
364 c.permissions = permissions
364 return render('admin/user_groups/user_group_edit.mako')
365 return render('admin/user_groups/user_group_edit.mako')
365
366
366 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
367 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
367 def edit_global_perms(self, user_group_id):
368 def edit_global_perms(self, user_group_id):
368 user_group_id = safe_int(user_group_id)
369 user_group_id = safe_int(user_group_id)
369 c.user_group = UserGroup.get_or_404(user_group_id)
370 c.user_group = UserGroup.get_or_404(user_group_id)
370 c.active = 'global_perms'
371 c.active = 'global_perms'
371
372
372 c.default_user = User.get_default_user()
373 c.default_user = User.get_default_user()
373 defaults = c.user_group.get_dict()
374 defaults = c.user_group.get_dict()
374 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
375 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
375 defaults.update(c.user_group.get_default_perms())
376 defaults.update(c.user_group.get_default_perms())
376
377
377 return htmlfill.render(
378 return htmlfill.render(
378 render('admin/user_groups/user_group_edit.mako'),
379 render('admin/user_groups/user_group_edit.mako'),
379 defaults=defaults,
380 defaults=defaults,
380 encoding="UTF-8",
381 encoding="UTF-8",
381 force_defaults=False
382 force_defaults=False
382 )
383 )
383
384
384 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
385 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
385 @auth.CSRFRequired()
386 @auth.CSRFRequired()
386 def update_global_perms(self, user_group_id):
387 def update_global_perms(self, user_group_id):
387 user_group_id = safe_int(user_group_id)
388 user_group_id = safe_int(user_group_id)
388 user_group = UserGroup.get_or_404(user_group_id)
389 user_group = UserGroup.get_or_404(user_group_id)
389 c.active = 'global_perms'
390 c.active = 'global_perms'
390
391
391 try:
392 try:
392 # first stage that verifies the checkbox
393 # first stage that verifies the checkbox
393 _form = UserIndividualPermissionsForm()
394 _form = UserIndividualPermissionsForm()
394 form_result = _form.to_python(dict(request.POST))
395 form_result = _form.to_python(dict(request.POST))
395 inherit_perms = form_result['inherit_default_permissions']
396 inherit_perms = form_result['inherit_default_permissions']
396 user_group.inherit_default_permissions = inherit_perms
397 user_group.inherit_default_permissions = inherit_perms
397 Session().add(user_group)
398 Session().add(user_group)
398
399
399 if not inherit_perms:
400 if not inherit_perms:
400 # only update the individual ones if we un check the flag
401 # only update the individual ones if we un check the flag
401 _form = UserPermissionsForm(
402 _form = UserPermissionsForm(
402 [x[0] for x in c.repo_create_choices],
403 [x[0] for x in c.repo_create_choices],
403 [x[0] for x in c.repo_create_on_write_choices],
404 [x[0] for x in c.repo_create_on_write_choices],
404 [x[0] for x in c.repo_group_create_choices],
405 [x[0] for x in c.repo_group_create_choices],
405 [x[0] for x in c.user_group_create_choices],
406 [x[0] for x in c.user_group_create_choices],
406 [x[0] for x in c.fork_choices],
407 [x[0] for x in c.fork_choices],
407 [x[0] for x in c.inherit_default_permission_choices])()
408 [x[0] for x in c.inherit_default_permission_choices])()
408
409
409 form_result = _form.to_python(dict(request.POST))
410 form_result = _form.to_python(dict(request.POST))
410 form_result.update({'perm_user_group_id': user_group.users_group_id})
411 form_result.update({'perm_user_group_id': user_group.users_group_id})
411
412
412 PermissionModel().update_user_group_permissions(form_result)
413 PermissionModel().update_user_group_permissions(form_result)
413
414
414 Session().commit()
415 Session().commit()
415 h.flash(_('User Group global permissions updated successfully'),
416 h.flash(_('User Group global permissions updated successfully'),
416 category='success')
417 category='success')
417
418
418 except formencode.Invalid as errors:
419 except formencode.Invalid as errors:
419 defaults = errors.value
420 defaults = errors.value
420 c.user_group = user_group
421 c.user_group = user_group
421 return htmlfill.render(
422 return htmlfill.render(
422 render('admin/user_groups/user_group_edit.mako'),
423 render('admin/user_groups/user_group_edit.mako'),
423 defaults=defaults,
424 defaults=defaults,
424 errors=errors.error_dict or {},
425 errors=errors.error_dict or {},
425 prefix_error=False,
426 prefix_error=False,
426 encoding="UTF-8",
427 encoding="UTF-8",
427 force_defaults=False)
428 force_defaults=False)
428 except Exception:
429 except Exception:
429 log.exception("Exception during permissions saving")
430 log.exception("Exception during permissions saving")
430 h.flash(_('An error occurred during permissions saving'),
431 h.flash(_('An error occurred during permissions saving'),
431 category='error')
432 category='error')
432
433
433 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
434 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
434
435
435 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
436 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
436 def edit_advanced(self, user_group_id):
437 def edit_advanced(self, user_group_id):
437 user_group_id = safe_int(user_group_id)
438 user_group_id = safe_int(user_group_id)
438 c.user_group = UserGroup.get_or_404(user_group_id)
439 c.user_group = UserGroup.get_or_404(user_group_id)
439 c.active = 'advanced'
440 c.active = 'advanced'
440 c.group_members_obj = sorted(
441 c.group_members_obj = sorted(
441 (x.user for x in c.user_group.members),
442 (x.user for x in c.user_group.members),
442 key=lambda u: u.username.lower())
443 key=lambda u: u.username.lower())
443
444
444 c.group_to_repos = sorted(
445 c.group_to_repos = sorted(
445 (x.repository for x in c.user_group.users_group_repo_to_perm),
446 (x.repository for x in c.user_group.users_group_repo_to_perm),
446 key=lambda u: u.repo_name.lower())
447 key=lambda u: u.repo_name.lower())
447
448
448 c.group_to_repo_groups = sorted(
449 c.group_to_repo_groups = sorted(
449 (x.group for x in c.user_group.users_group_repo_group_to_perm),
450 (x.group for x in c.user_group.users_group_repo_group_to_perm),
450 key=lambda u: u.group_name.lower())
451 key=lambda u: u.group_name.lower())
451
452
452 return render('admin/user_groups/user_group_edit.mako')
453 return render('admin/user_groups/user_group_edit.mako')
453
454
454 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
455 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
455 def edit_advanced_set_synchronization(self, user_group_id):
456 def edit_advanced_set_synchronization(self, user_group_id):
456 user_group_id = safe_int(user_group_id)
457 user_group_id = safe_int(user_group_id)
457 user_group = UserGroup.get_or_404(user_group_id)
458 user_group = UserGroup.get_or_404(user_group_id)
458
459
459 existing = user_group.group_data.get('extern_type')
460 existing = user_group.group_data.get('extern_type')
460
461
461 if existing:
462 if existing:
462 new_state = user_group.group_data
463 new_state = user_group.group_data
463 new_state['extern_type'] = None
464 new_state['extern_type'] = None
464 else:
465 else:
465 new_state = user_group.group_data
466 new_state = user_group.group_data
466 new_state['extern_type'] = 'manual'
467 new_state['extern_type'] = 'manual'
467 new_state['extern_type_set_by'] = c.rhodecode_user.username
468 new_state['extern_type_set_by'] = c.rhodecode_user.username
468
469
469 try:
470 try:
470 user_group.group_data = new_state
471 user_group.group_data = new_state
471 Session().add(user_group)
472 Session().add(user_group)
472 Session().commit()
473 Session().commit()
473
474
474 h.flash(_('User Group synchronization updated successfully'),
475 h.flash(_('User Group synchronization updated successfully'),
475 category='success')
476 category='success')
476 except Exception:
477 except Exception:
477 log.exception("Exception during sync settings saving")
478 log.exception("Exception during sync settings saving")
478 h.flash(_('An error occurred during synchronization update'),
479 h.flash(_('An error occurred during synchronization update'),
479 category='error')
480 category='error')
480
481
481 return redirect(
482 return redirect(
482 url('edit_user_group_advanced', user_group_id=user_group_id))
483 url('edit_user_group_advanced', user_group_id=user_group_id))
483
484
484 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
485 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
485 @XHRRequired()
486 @XHRRequired()
486 @jsonify
487 @jsonify
487 def user_group_members(self, user_group_id):
488 def user_group_members(self, user_group_id):
488 """
489 """
489 Return members of given user group
490 Return members of given user group
490 """
491 """
491 user_group_id = safe_int(user_group_id)
492 user_group_id = safe_int(user_group_id)
492 user_group = UserGroup.get_or_404(user_group_id)
493 user_group = UserGroup.get_or_404(user_group_id)
493 group_members_obj = sorted((x.user for x in user_group.members),
494 group_members_obj = sorted((x.user for x in user_group.members),
494 key=lambda u: u.username.lower())
495 key=lambda u: u.username.lower())
495
496
496 group_members = [
497 group_members = [
497 {
498 {
498 'id': user.user_id,
499 'id': user.user_id,
499 'first_name': user.first_name,
500 'first_name': user.first_name,
500 'last_name': user.last_name,
501 'last_name': user.last_name,
501 'username': user.username,
502 'username': user.username,
502 'icon_link': h.gravatar_url(user.email, 30),
503 'icon_link': h.gravatar_url(user.email, 30),
503 'value_display': h.person(user.email),
504 'value_display': h.person(user.email),
504 'value': user.username,
505 'value': user.username,
505 'value_type': 'user',
506 'value_type': 'user',
506 'active': user.active,
507 'active': user.active,
507 }
508 }
508 for user in group_members_obj
509 for user in group_members_obj
509 ]
510 ]
510
511
511 return {
512 return {
512 'members': group_members
513 'members': group_members
513 }
514 }
@@ -1,1029 +1,1028 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime, timedelta
31 from datetime import datetime, timedelta
32
32
33 from pyramid.threadlocal import get_current_request
33 from pyramid.threadlocal import get_current_request
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.exceptions import AttachedForksError
40 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.hooks_base import log_delete_repository
41 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.utils import make_db_config
42 from rhodecode.lib.utils import make_db_config
43 from rhodecode.lib.utils2 import (
43 from rhodecode.lib.utils2 import (
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 from rhodecode.lib.vcs.backends import get_backend
46 from rhodecode.lib.vcs.backends import get_backend
47 from rhodecode.model import BaseModel
47 from rhodecode.model import BaseModel
48 from rhodecode.model.db import (_hash_key,
48 from rhodecode.model.db import (_hash_key,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 RepoGroup, RepositoryField)
51 RepoGroup, RepositoryField)
52
52
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class RepoModel(BaseModel):
59 class RepoModel(BaseModel):
60
60
61 cls = Repository
61 cls = Repository
62
62
63 def _get_user_group(self, users_group):
63 def _get_user_group(self, users_group):
64 return self._get_instance(UserGroup, users_group,
64 return self._get_instance(UserGroup, users_group,
65 callback=UserGroup.get_by_group_name)
65 callback=UserGroup.get_by_group_name)
66
66
67 def _get_repo_group(self, repo_group):
67 def _get_repo_group(self, repo_group):
68 return self._get_instance(RepoGroup, repo_group,
68 return self._get_instance(RepoGroup, repo_group,
69 callback=RepoGroup.get_by_group_name)
69 callback=RepoGroup.get_by_group_name)
70
70
71 def _create_default_perms(self, repository, private):
71 def _create_default_perms(self, repository, private):
72 # create default permission
72 # create default permission
73 default = 'repository.read'
73 default = 'repository.read'
74 def_user = User.get_default_user()
74 def_user = User.get_default_user()
75 for p in def_user.user_perms:
75 for p in def_user.user_perms:
76 if p.permission.permission_name.startswith('repository.'):
76 if p.permission.permission_name.startswith('repository.'):
77 default = p.permission.permission_name
77 default = p.permission.permission_name
78 break
78 break
79
79
80 default_perm = 'repository.none' if private else default
80 default_perm = 'repository.none' if private else default
81
81
82 repo_to_perm = UserRepoToPerm()
82 repo_to_perm = UserRepoToPerm()
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
84
84
85 repo_to_perm.repository = repository
85 repo_to_perm.repository = repository
86 repo_to_perm.user_id = def_user.user_id
86 repo_to_perm.user_id = def_user.user_id
87
87
88 return repo_to_perm
88 return repo_to_perm
89
89
90 @LazyProperty
90 @LazyProperty
91 def repos_path(self):
91 def repos_path(self):
92 """
92 """
93 Gets the repositories root path from database
93 Gets the repositories root path from database
94 """
94 """
95 settings_model = VcsSettingsModel(sa=self.sa)
95 settings_model = VcsSettingsModel(sa=self.sa)
96 return settings_model.get_repos_location()
96 return settings_model.get_repos_location()
97
97
98 def get(self, repo_id, cache=False):
98 def get(self, repo_id, cache=False):
99 repo = self.sa.query(Repository) \
99 repo = self.sa.query(Repository) \
100 .filter(Repository.repo_id == repo_id)
100 .filter(Repository.repo_id == repo_id)
101
101
102 if cache:
102 if cache:
103 repo = repo.options(
103 repo = repo.options(
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
105 return repo.scalar()
105 return repo.scalar()
106
106
107 def get_repo(self, repository):
107 def get_repo(self, repository):
108 return self._get_repo(repository)
108 return self._get_repo(repository)
109
109
110 def get_by_repo_name(self, repo_name, cache=False):
110 def get_by_repo_name(self, repo_name, cache=False):
111 repo = self.sa.query(Repository) \
111 repo = self.sa.query(Repository) \
112 .filter(Repository.repo_name == repo_name)
112 .filter(Repository.repo_name == repo_name)
113
113
114 if cache:
114 if cache:
115 name_key = _hash_key(repo_name)
115 name_key = _hash_key(repo_name)
116 repo = repo.options(
116 repo = repo.options(
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
118 return repo.scalar()
118 return repo.scalar()
119
119
120 def _extract_id_from_repo_name(self, repo_name):
120 def _extract_id_from_repo_name(self, repo_name):
121 if repo_name.startswith('/'):
121 if repo_name.startswith('/'):
122 repo_name = repo_name.lstrip('/')
122 repo_name = repo_name.lstrip('/')
123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
124 if by_id_match:
124 if by_id_match:
125 return by_id_match.groups()[0]
125 return by_id_match.groups()[0]
126
126
127 def get_repo_by_id(self, repo_name):
127 def get_repo_by_id(self, repo_name):
128 """
128 """
129 Extracts repo_name by id from special urls.
129 Extracts repo_name by id from special urls.
130 Example url is _11/repo_name
130 Example url is _11/repo_name
131
131
132 :param repo_name:
132 :param repo_name:
133 :return: repo object if matched else None
133 :return: repo object if matched else None
134 """
134 """
135
135
136 try:
136 try:
137 _repo_id = self._extract_id_from_repo_name(repo_name)
137 _repo_id = self._extract_id_from_repo_name(repo_name)
138 if _repo_id:
138 if _repo_id:
139 return self.get(_repo_id)
139 return self.get(_repo_id)
140 except Exception:
140 except Exception:
141 log.exception('Failed to extract repo_name from URL')
141 log.exception('Failed to extract repo_name from URL')
142
142
143 return None
143 return None
144
144
145 def get_repos_for_root(self, root, traverse=False):
145 def get_repos_for_root(self, root, traverse=False):
146 if traverse:
146 if traverse:
147 like_expression = u'{}%'.format(safe_unicode(root))
147 like_expression = u'{}%'.format(safe_unicode(root))
148 repos = Repository.query().filter(
148 repos = Repository.query().filter(
149 Repository.repo_name.like(like_expression)).all()
149 Repository.repo_name.like(like_expression)).all()
150 else:
150 else:
151 if root and not isinstance(root, RepoGroup):
151 if root and not isinstance(root, RepoGroup):
152 raise ValueError(
152 raise ValueError(
153 'Root must be an instance '
153 'Root must be an instance '
154 'of RepoGroup, got:{} instead'.format(type(root)))
154 'of RepoGroup, got:{} instead'.format(type(root)))
155 repos = Repository.query().filter(Repository.group == root).all()
155 repos = Repository.query().filter(Repository.group == root).all()
156 return repos
156 return repos
157
157
158 def get_url(self, repo, request=None, permalink=False):
158 def get_url(self, repo, request=None, permalink=False):
159 if not request:
159 if not request:
160 request = get_current_request()
160 request = get_current_request()
161
161
162 if not request:
162 if not request:
163 return
163 return
164
164
165 if permalink:
165 if permalink:
166 return request.route_url(
166 return request.route_url(
167 'repo_summary', repo_name=safe_str(repo.repo_id))
167 'repo_summary', repo_name=safe_str(repo.repo_id))
168 else:
168 else:
169 return request.route_url(
169 return request.route_url(
170 'repo_summary', repo_name=safe_str(repo.repo_name))
170 'repo_summary', repo_name=safe_str(repo.repo_name))
171
171
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
173 if not request:
173 if not request:
174 request = get_current_request()
174 request = get_current_request()
175
175
176 if not request:
176 if not request:
177 return
177 return
178
178
179 if permalink:
179 if permalink:
180 return request.route_url(
180 return request.route_url(
181 'repo_commit', repo_name=safe_str(repo.repo_id),
181 'repo_commit', repo_name=safe_str(repo.repo_id),
182 commit_id=commit_id)
182 commit_id=commit_id)
183
183
184 else:
184 else:
185 return request.route_url(
185 return request.route_url(
186 'repo_commit', repo_name=safe_str(repo.repo_name),
186 'repo_commit', repo_name=safe_str(repo.repo_name),
187 commit_id=commit_id)
187 commit_id=commit_id)
188
188
189 @classmethod
189 @classmethod
190 def update_repoinfo(cls, repositories=None):
190 def update_repoinfo(cls, repositories=None):
191 if not repositories:
191 if not repositories:
192 repositories = Repository.getAll()
192 repositories = Repository.getAll()
193 for repo in repositories:
193 for repo in repositories:
194 repo.update_commit_cache()
194 repo.update_commit_cache()
195
195
196 def get_repos_as_dict(self, repo_list=None, admin=False,
196 def get_repos_as_dict(self, repo_list=None, admin=False,
197 super_user_actions=False):
197 super_user_actions=False):
198
198 _render = get_current_request().get_partial_renderer(
199 from rhodecode.lib.utils import PartialRenderer
199 'data_table/_dt_elements.mako')
200 _render = PartialRenderer('data_table/_dt_elements.mako')
200 c = _render.get_call_context()
201 c = _render.c
202
201
203 def quick_menu(repo_name):
202 def quick_menu(repo_name):
204 return _render('quick_menu', repo_name)
203 return _render('quick_menu', repo_name)
205
204
206 def repo_lnk(name, rtype, rstate, private, fork_of):
205 def repo_lnk(name, rtype, rstate, private, fork_of):
207 return _render('repo_name', name, rtype, rstate, private, fork_of,
206 return _render('repo_name', name, rtype, rstate, private, fork_of,
208 short_name=not admin, admin=False)
207 short_name=not admin, admin=False)
209
208
210 def last_change(last_change):
209 def last_change(last_change):
211 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
210 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
212 last_change = last_change + timedelta(seconds=
211 last_change = last_change + timedelta(seconds=
213 (datetime.now() - datetime.utcnow()).seconds)
212 (datetime.now() - datetime.utcnow()).seconds)
214 return _render("last_change", last_change)
213 return _render("last_change", last_change)
215
214
216 def rss_lnk(repo_name):
215 def rss_lnk(repo_name):
217 return _render("rss", repo_name)
216 return _render("rss", repo_name)
218
217
219 def atom_lnk(repo_name):
218 def atom_lnk(repo_name):
220 return _render("atom", repo_name)
219 return _render("atom", repo_name)
221
220
222 def last_rev(repo_name, cs_cache):
221 def last_rev(repo_name, cs_cache):
223 return _render('revision', repo_name, cs_cache.get('revision'),
222 return _render('revision', repo_name, cs_cache.get('revision'),
224 cs_cache.get('raw_id'), cs_cache.get('author'),
223 cs_cache.get('raw_id'), cs_cache.get('author'),
225 cs_cache.get('message'))
224 cs_cache.get('message'))
226
225
227 def desc(desc):
226 def desc(desc):
228 if c.visual.stylify_metatags:
227 if c.visual.stylify_metatags:
229 desc = h.urlify_text(h.escaped_stylize(desc))
228 desc = h.urlify_text(h.escaped_stylize(desc))
230 else:
229 else:
231 desc = h.urlify_text(h.html_escape(desc))
230 desc = h.urlify_text(h.html_escape(desc))
232
231
233 return _render('repo_desc', desc)
232 return _render('repo_desc', desc)
234
233
235 def state(repo_state):
234 def state(repo_state):
236 return _render("repo_state", repo_state)
235 return _render("repo_state", repo_state)
237
236
238 def repo_actions(repo_name):
237 def repo_actions(repo_name):
239 return _render('repo_actions', repo_name, super_user_actions)
238 return _render('repo_actions', repo_name, super_user_actions)
240
239
241 def user_profile(username):
240 def user_profile(username):
242 return _render('user_profile', username)
241 return _render('user_profile', username)
243
242
244 repos_data = []
243 repos_data = []
245 for repo in repo_list:
244 for repo in repo_list:
246 cs_cache = repo.changeset_cache
245 cs_cache = repo.changeset_cache
247 row = {
246 row = {
248 "menu": quick_menu(repo.repo_name),
247 "menu": quick_menu(repo.repo_name),
249
248
250 "name": repo_lnk(repo.repo_name, repo.repo_type,
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
251 repo.repo_state, repo.private, repo.fork),
250 repo.repo_state, repo.private, repo.fork),
252 "name_raw": repo.repo_name.lower(),
251 "name_raw": repo.repo_name.lower(),
253
252
254 "last_change": last_change(repo.last_db_change),
253 "last_change": last_change(repo.last_db_change),
255 "last_change_raw": datetime_to_time(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
256
255
257 "last_changeset": last_rev(repo.repo_name, cs_cache),
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
258 "last_changeset_raw": cs_cache.get('revision'),
257 "last_changeset_raw": cs_cache.get('revision'),
259
258
260 "desc": desc(repo.description_safe),
259 "desc": desc(repo.description_safe),
261 "owner": user_profile(repo.user.username),
260 "owner": user_profile(repo.user.username),
262
261
263 "state": state(repo.repo_state),
262 "state": state(repo.repo_state),
264 "rss": rss_lnk(repo.repo_name),
263 "rss": rss_lnk(repo.repo_name),
265
264
266 "atom": atom_lnk(repo.repo_name),
265 "atom": atom_lnk(repo.repo_name),
267 }
266 }
268 if admin:
267 if admin:
269 row.update({
268 row.update({
270 "action": repo_actions(repo.repo_name),
269 "action": repo_actions(repo.repo_name),
271 })
270 })
272 repos_data.append(row)
271 repos_data.append(row)
273
272
274 return repos_data
273 return repos_data
275
274
276 def _get_defaults(self, repo_name):
275 def _get_defaults(self, repo_name):
277 """
276 """
278 Gets information about repository, and returns a dict for
277 Gets information about repository, and returns a dict for
279 usage in forms
278 usage in forms
280
279
281 :param repo_name:
280 :param repo_name:
282 """
281 """
283
282
284 repo_info = Repository.get_by_repo_name(repo_name)
283 repo_info = Repository.get_by_repo_name(repo_name)
285
284
286 if repo_info is None:
285 if repo_info is None:
287 return None
286 return None
288
287
289 defaults = repo_info.get_dict()
288 defaults = repo_info.get_dict()
290 defaults['repo_name'] = repo_info.just_name
289 defaults['repo_name'] = repo_info.just_name
291
290
292 groups = repo_info.groups_with_parents
291 groups = repo_info.groups_with_parents
293 parent_group = groups[-1] if groups else None
292 parent_group = groups[-1] if groups else None
294
293
295 # we use -1 as this is how in HTML, we mark an empty group
294 # we use -1 as this is how in HTML, we mark an empty group
296 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
297
296
298 keys_to_process = (
297 keys_to_process = (
299 {'k': 'repo_type', 'strip': False},
298 {'k': 'repo_type', 'strip': False},
300 {'k': 'repo_enable_downloads', 'strip': True},
299 {'k': 'repo_enable_downloads', 'strip': True},
301 {'k': 'repo_description', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
302 {'k': 'repo_enable_locking', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
303 {'k': 'repo_landing_rev', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
304 {'k': 'clone_uri', 'strip': False},
303 {'k': 'clone_uri', 'strip': False},
305 {'k': 'repo_private', 'strip': True},
304 {'k': 'repo_private', 'strip': True},
306 {'k': 'repo_enable_statistics', 'strip': True}
305 {'k': 'repo_enable_statistics', 'strip': True}
307 )
306 )
308
307
309 for item in keys_to_process:
308 for item in keys_to_process:
310 attr = item['k']
309 attr = item['k']
311 if item['strip']:
310 if item['strip']:
312 attr = remove_prefix(item['k'], 'repo_')
311 attr = remove_prefix(item['k'], 'repo_')
313
312
314 val = defaults[attr]
313 val = defaults[attr]
315 if item['k'] == 'repo_landing_rev':
314 if item['k'] == 'repo_landing_rev':
316 val = ':'.join(defaults[attr])
315 val = ':'.join(defaults[attr])
317 defaults[item['k']] = val
316 defaults[item['k']] = val
318 if item['k'] == 'clone_uri':
317 if item['k'] == 'clone_uri':
319 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
318 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
320
319
321 # fill owner
320 # fill owner
322 if repo_info.user:
321 if repo_info.user:
323 defaults.update({'user': repo_info.user.username})
322 defaults.update({'user': repo_info.user.username})
324 else:
323 else:
325 replacement_user = User.get_first_super_admin().username
324 replacement_user = User.get_first_super_admin().username
326 defaults.update({'user': replacement_user})
325 defaults.update({'user': replacement_user})
327
326
328 return defaults
327 return defaults
329
328
330 def update(self, repo, **kwargs):
329 def update(self, repo, **kwargs):
331 try:
330 try:
332 cur_repo = self._get_repo(repo)
331 cur_repo = self._get_repo(repo)
333 source_repo_name = cur_repo.repo_name
332 source_repo_name = cur_repo.repo_name
334 if 'user' in kwargs:
333 if 'user' in kwargs:
335 cur_repo.user = User.get_by_username(kwargs['user'])
334 cur_repo.user = User.get_by_username(kwargs['user'])
336
335
337 if 'repo_group' in kwargs:
336 if 'repo_group' in kwargs:
338 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
337 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
339 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
338 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
340
339
341 update_keys = [
340 update_keys = [
342 (1, 'repo_description'),
341 (1, 'repo_description'),
343 (1, 'repo_landing_rev'),
342 (1, 'repo_landing_rev'),
344 (1, 'repo_private'),
343 (1, 'repo_private'),
345 (1, 'repo_enable_downloads'),
344 (1, 'repo_enable_downloads'),
346 (1, 'repo_enable_locking'),
345 (1, 'repo_enable_locking'),
347 (1, 'repo_enable_statistics'),
346 (1, 'repo_enable_statistics'),
348 (0, 'clone_uri'),
347 (0, 'clone_uri'),
349 (0, 'fork_id')
348 (0, 'fork_id')
350 ]
349 ]
351 for strip, k in update_keys:
350 for strip, k in update_keys:
352 if k in kwargs:
351 if k in kwargs:
353 val = kwargs[k]
352 val = kwargs[k]
354 if strip:
353 if strip:
355 k = remove_prefix(k, 'repo_')
354 k = remove_prefix(k, 'repo_')
356
355
357 setattr(cur_repo, k, val)
356 setattr(cur_repo, k, val)
358
357
359 new_name = cur_repo.get_new_name(kwargs['repo_name'])
358 new_name = cur_repo.get_new_name(kwargs['repo_name'])
360 cur_repo.repo_name = new_name
359 cur_repo.repo_name = new_name
361
360
362 # if private flag is set, reset default permission to NONE
361 # if private flag is set, reset default permission to NONE
363 if kwargs.get('repo_private'):
362 if kwargs.get('repo_private'):
364 EMPTY_PERM = 'repository.none'
363 EMPTY_PERM = 'repository.none'
365 RepoModel().grant_user_permission(
364 RepoModel().grant_user_permission(
366 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
365 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
367 )
366 )
368
367
369 # handle extra fields
368 # handle extra fields
370 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
369 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
371 kwargs):
370 kwargs):
372 k = RepositoryField.un_prefix_key(field)
371 k = RepositoryField.un_prefix_key(field)
373 ex_field = RepositoryField.get_by_key_name(
372 ex_field = RepositoryField.get_by_key_name(
374 key=k, repo=cur_repo)
373 key=k, repo=cur_repo)
375 if ex_field:
374 if ex_field:
376 ex_field.field_value = kwargs[field]
375 ex_field.field_value = kwargs[field]
377 self.sa.add(ex_field)
376 self.sa.add(ex_field)
378 self.sa.add(cur_repo)
377 self.sa.add(cur_repo)
379
378
380 if source_repo_name != new_name:
379 if source_repo_name != new_name:
381 # rename repository
380 # rename repository
382 self._rename_filesystem_repo(
381 self._rename_filesystem_repo(
383 old=source_repo_name, new=new_name)
382 old=source_repo_name, new=new_name)
384
383
385 return cur_repo
384 return cur_repo
386 except Exception:
385 except Exception:
387 log.error(traceback.format_exc())
386 log.error(traceback.format_exc())
388 raise
387 raise
389
388
390 def _create_repo(self, repo_name, repo_type, description, owner,
389 def _create_repo(self, repo_name, repo_type, description, owner,
391 private=False, clone_uri=None, repo_group=None,
390 private=False, clone_uri=None, repo_group=None,
392 landing_rev='rev:tip', fork_of=None,
391 landing_rev='rev:tip', fork_of=None,
393 copy_fork_permissions=False, enable_statistics=False,
392 copy_fork_permissions=False, enable_statistics=False,
394 enable_locking=False, enable_downloads=False,
393 enable_locking=False, enable_downloads=False,
395 copy_group_permissions=False,
394 copy_group_permissions=False,
396 state=Repository.STATE_PENDING):
395 state=Repository.STATE_PENDING):
397 """
396 """
398 Create repository inside database with PENDING state, this should be
397 Create repository inside database with PENDING state, this should be
399 only executed by create() repo. With exception of importing existing
398 only executed by create() repo. With exception of importing existing
400 repos
399 repos
401 """
400 """
402 from rhodecode.model.scm import ScmModel
401 from rhodecode.model.scm import ScmModel
403
402
404 owner = self._get_user(owner)
403 owner = self._get_user(owner)
405 fork_of = self._get_repo(fork_of)
404 fork_of = self._get_repo(fork_of)
406 repo_group = self._get_repo_group(safe_int(repo_group))
405 repo_group = self._get_repo_group(safe_int(repo_group))
407
406
408 try:
407 try:
409 repo_name = safe_unicode(repo_name)
408 repo_name = safe_unicode(repo_name)
410 description = safe_unicode(description)
409 description = safe_unicode(description)
411 # repo name is just a name of repository
410 # repo name is just a name of repository
412 # while repo_name_full is a full qualified name that is combined
411 # while repo_name_full is a full qualified name that is combined
413 # with name and path of group
412 # with name and path of group
414 repo_name_full = repo_name
413 repo_name_full = repo_name
415 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
414 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
416
415
417 new_repo = Repository()
416 new_repo = Repository()
418 new_repo.repo_state = state
417 new_repo.repo_state = state
419 new_repo.enable_statistics = False
418 new_repo.enable_statistics = False
420 new_repo.repo_name = repo_name_full
419 new_repo.repo_name = repo_name_full
421 new_repo.repo_type = repo_type
420 new_repo.repo_type = repo_type
422 new_repo.user = owner
421 new_repo.user = owner
423 new_repo.group = repo_group
422 new_repo.group = repo_group
424 new_repo.description = description or repo_name
423 new_repo.description = description or repo_name
425 new_repo.private = private
424 new_repo.private = private
426 new_repo.clone_uri = clone_uri
425 new_repo.clone_uri = clone_uri
427 new_repo.landing_rev = landing_rev
426 new_repo.landing_rev = landing_rev
428
427
429 new_repo.enable_statistics = enable_statistics
428 new_repo.enable_statistics = enable_statistics
430 new_repo.enable_locking = enable_locking
429 new_repo.enable_locking = enable_locking
431 new_repo.enable_downloads = enable_downloads
430 new_repo.enable_downloads = enable_downloads
432
431
433 if repo_group:
432 if repo_group:
434 new_repo.enable_locking = repo_group.enable_locking
433 new_repo.enable_locking = repo_group.enable_locking
435
434
436 if fork_of:
435 if fork_of:
437 parent_repo = fork_of
436 parent_repo = fork_of
438 new_repo.fork = parent_repo
437 new_repo.fork = parent_repo
439
438
440 events.trigger(events.RepoPreCreateEvent(new_repo))
439 events.trigger(events.RepoPreCreateEvent(new_repo))
441
440
442 self.sa.add(new_repo)
441 self.sa.add(new_repo)
443
442
444 EMPTY_PERM = 'repository.none'
443 EMPTY_PERM = 'repository.none'
445 if fork_of and copy_fork_permissions:
444 if fork_of and copy_fork_permissions:
446 repo = fork_of
445 repo = fork_of
447 user_perms = UserRepoToPerm.query() \
446 user_perms = UserRepoToPerm.query() \
448 .filter(UserRepoToPerm.repository == repo).all()
447 .filter(UserRepoToPerm.repository == repo).all()
449 group_perms = UserGroupRepoToPerm.query() \
448 group_perms = UserGroupRepoToPerm.query() \
450 .filter(UserGroupRepoToPerm.repository == repo).all()
449 .filter(UserGroupRepoToPerm.repository == repo).all()
451
450
452 for perm in user_perms:
451 for perm in user_perms:
453 UserRepoToPerm.create(
452 UserRepoToPerm.create(
454 perm.user, new_repo, perm.permission)
453 perm.user, new_repo, perm.permission)
455
454
456 for perm in group_perms:
455 for perm in group_perms:
457 UserGroupRepoToPerm.create(
456 UserGroupRepoToPerm.create(
458 perm.users_group, new_repo, perm.permission)
457 perm.users_group, new_repo, perm.permission)
459 # in case we copy permissions and also set this repo to private
458 # in case we copy permissions and also set this repo to private
460 # override the default user permission to make it a private
459 # override the default user permission to make it a private
461 # repo
460 # repo
462 if private:
461 if private:
463 RepoModel(self.sa).grant_user_permission(
462 RepoModel(self.sa).grant_user_permission(
464 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
463 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
465
464
466 elif repo_group and copy_group_permissions:
465 elif repo_group and copy_group_permissions:
467 user_perms = UserRepoGroupToPerm.query() \
466 user_perms = UserRepoGroupToPerm.query() \
468 .filter(UserRepoGroupToPerm.group == repo_group).all()
467 .filter(UserRepoGroupToPerm.group == repo_group).all()
469
468
470 group_perms = UserGroupRepoGroupToPerm.query() \
469 group_perms = UserGroupRepoGroupToPerm.query() \
471 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
470 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
472
471
473 for perm in user_perms:
472 for perm in user_perms:
474 perm_name = perm.permission.permission_name.replace(
473 perm_name = perm.permission.permission_name.replace(
475 'group.', 'repository.')
474 'group.', 'repository.')
476 perm_obj = Permission.get_by_key(perm_name)
475 perm_obj = Permission.get_by_key(perm_name)
477 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
476 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
478
477
479 for perm in group_perms:
478 for perm in group_perms:
480 perm_name = perm.permission.permission_name.replace(
479 perm_name = perm.permission.permission_name.replace(
481 'group.', 'repository.')
480 'group.', 'repository.')
482 perm_obj = Permission.get_by_key(perm_name)
481 perm_obj = Permission.get_by_key(perm_name)
483 UserGroupRepoToPerm.create(
482 UserGroupRepoToPerm.create(
484 perm.users_group, new_repo, perm_obj)
483 perm.users_group, new_repo, perm_obj)
485
484
486 if private:
485 if private:
487 RepoModel(self.sa).grant_user_permission(
486 RepoModel(self.sa).grant_user_permission(
488 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
487 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
489
488
490 else:
489 else:
491 perm_obj = self._create_default_perms(new_repo, private)
490 perm_obj = self._create_default_perms(new_repo, private)
492 self.sa.add(perm_obj)
491 self.sa.add(perm_obj)
493
492
494 # now automatically start following this repository as owner
493 # now automatically start following this repository as owner
495 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
494 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
496 owner.user_id)
495 owner.user_id)
497
496
498 # we need to flush here, in order to check if database won't
497 # we need to flush here, in order to check if database won't
499 # throw any exceptions, create filesystem dirs at the very end
498 # throw any exceptions, create filesystem dirs at the very end
500 self.sa.flush()
499 self.sa.flush()
501 events.trigger(events.RepoCreateEvent(new_repo))
500 events.trigger(events.RepoCreateEvent(new_repo))
502 return new_repo
501 return new_repo
503
502
504 except Exception:
503 except Exception:
505 log.error(traceback.format_exc())
504 log.error(traceback.format_exc())
506 raise
505 raise
507
506
508 def create(self, form_data, cur_user):
507 def create(self, form_data, cur_user):
509 """
508 """
510 Create repository using celery tasks
509 Create repository using celery tasks
511
510
512 :param form_data:
511 :param form_data:
513 :param cur_user:
512 :param cur_user:
514 """
513 """
515 from rhodecode.lib.celerylib import tasks, run_task
514 from rhodecode.lib.celerylib import tasks, run_task
516 return run_task(tasks.create_repo, form_data, cur_user)
515 return run_task(tasks.create_repo, form_data, cur_user)
517
516
518 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
517 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
519 perm_deletions=None, check_perms=True,
518 perm_deletions=None, check_perms=True,
520 cur_user=None):
519 cur_user=None):
521 if not perm_additions:
520 if not perm_additions:
522 perm_additions = []
521 perm_additions = []
523 if not perm_updates:
522 if not perm_updates:
524 perm_updates = []
523 perm_updates = []
525 if not perm_deletions:
524 if not perm_deletions:
526 perm_deletions = []
525 perm_deletions = []
527
526
528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
527 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
529
528
530 changes = {
529 changes = {
531 'added': [],
530 'added': [],
532 'updated': [],
531 'updated': [],
533 'deleted': []
532 'deleted': []
534 }
533 }
535 # update permissions
534 # update permissions
536 for member_id, perm, member_type in perm_updates:
535 for member_id, perm, member_type in perm_updates:
537 member_id = int(member_id)
536 member_id = int(member_id)
538 if member_type == 'user':
537 if member_type == 'user':
539 member_name = User.get(member_id).username
538 member_name = User.get(member_id).username
540 # this updates also current one if found
539 # this updates also current one if found
541 self.grant_user_permission(
540 self.grant_user_permission(
542 repo=repo, user=member_id, perm=perm)
541 repo=repo, user=member_id, perm=perm)
543 else: # set for user group
542 else: # set for user group
544 # check if we have permissions to alter this usergroup
543 # check if we have permissions to alter this usergroup
545 member_name = UserGroup.get(member_id).users_group_name
544 member_name = UserGroup.get(member_id).users_group_name
546 if not check_perms or HasUserGroupPermissionAny(
545 if not check_perms or HasUserGroupPermissionAny(
547 *req_perms)(member_name, user=cur_user):
546 *req_perms)(member_name, user=cur_user):
548 self.grant_user_group_permission(
547 self.grant_user_group_permission(
549 repo=repo, group_name=member_id, perm=perm)
548 repo=repo, group_name=member_id, perm=perm)
550
549
551 changes['updated'].append({'type': member_type, 'id': member_id,
550 changes['updated'].append({'type': member_type, 'id': member_id,
552 'name': member_name, 'new_perm': perm})
551 'name': member_name, 'new_perm': perm})
553
552
554 # set new permissions
553 # set new permissions
555 for member_id, perm, member_type in perm_additions:
554 for member_id, perm, member_type in perm_additions:
556 member_id = int(member_id)
555 member_id = int(member_id)
557 if member_type == 'user':
556 if member_type == 'user':
558 member_name = User.get(member_id).username
557 member_name = User.get(member_id).username
559 self.grant_user_permission(
558 self.grant_user_permission(
560 repo=repo, user=member_id, perm=perm)
559 repo=repo, user=member_id, perm=perm)
561 else: # set for user group
560 else: # set for user group
562 # check if we have permissions to alter this usergroup
561 # check if we have permissions to alter this usergroup
563 member_name = UserGroup.get(member_id).users_group_name
562 member_name = UserGroup.get(member_id).users_group_name
564 if not check_perms or HasUserGroupPermissionAny(
563 if not check_perms or HasUserGroupPermissionAny(
565 *req_perms)(member_name, user=cur_user):
564 *req_perms)(member_name, user=cur_user):
566 self.grant_user_group_permission(
565 self.grant_user_group_permission(
567 repo=repo, group_name=member_id, perm=perm)
566 repo=repo, group_name=member_id, perm=perm)
568 changes['added'].append({'type': member_type, 'id': member_id,
567 changes['added'].append({'type': member_type, 'id': member_id,
569 'name': member_name, 'new_perm': perm})
568 'name': member_name, 'new_perm': perm})
570 # delete permissions
569 # delete permissions
571 for member_id, perm, member_type in perm_deletions:
570 for member_id, perm, member_type in perm_deletions:
572 member_id = int(member_id)
571 member_id = int(member_id)
573 if member_type == 'user':
572 if member_type == 'user':
574 member_name = User.get(member_id).username
573 member_name = User.get(member_id).username
575 self.revoke_user_permission(repo=repo, user=member_id)
574 self.revoke_user_permission(repo=repo, user=member_id)
576 else: # set for user group
575 else: # set for user group
577 # check if we have permissions to alter this usergroup
576 # check if we have permissions to alter this usergroup
578 member_name = UserGroup.get(member_id).users_group_name
577 member_name = UserGroup.get(member_id).users_group_name
579 if not check_perms or HasUserGroupPermissionAny(
578 if not check_perms or HasUserGroupPermissionAny(
580 *req_perms)(member_name, user=cur_user):
579 *req_perms)(member_name, user=cur_user):
581 self.revoke_user_group_permission(
580 self.revoke_user_group_permission(
582 repo=repo, group_name=member_id)
581 repo=repo, group_name=member_id)
583
582
584 changes['deleted'].append({'type': member_type, 'id': member_id,
583 changes['deleted'].append({'type': member_type, 'id': member_id,
585 'name': member_name, 'new_perm': perm})
584 'name': member_name, 'new_perm': perm})
586 return changes
585 return changes
587
586
588 def create_fork(self, form_data, cur_user):
587 def create_fork(self, form_data, cur_user):
589 """
588 """
590 Simple wrapper into executing celery task for fork creation
589 Simple wrapper into executing celery task for fork creation
591
590
592 :param form_data:
591 :param form_data:
593 :param cur_user:
592 :param cur_user:
594 """
593 """
595 from rhodecode.lib.celerylib import tasks, run_task
594 from rhodecode.lib.celerylib import tasks, run_task
596 return run_task(tasks.create_repo_fork, form_data, cur_user)
595 return run_task(tasks.create_repo_fork, form_data, cur_user)
597
596
598 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
597 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
599 """
598 """
600 Delete given repository, forks parameter defines what do do with
599 Delete given repository, forks parameter defines what do do with
601 attached forks. Throws AttachedForksError if deleted repo has attached
600 attached forks. Throws AttachedForksError if deleted repo has attached
602 forks
601 forks
603
602
604 :param repo:
603 :param repo:
605 :param forks: str 'delete' or 'detach'
604 :param forks: str 'delete' or 'detach'
606 :param fs_remove: remove(archive) repo from filesystem
605 :param fs_remove: remove(archive) repo from filesystem
607 """
606 """
608 if not cur_user:
607 if not cur_user:
609 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
608 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
610 repo = self._get_repo(repo)
609 repo = self._get_repo(repo)
611 if repo:
610 if repo:
612 if forks == 'detach':
611 if forks == 'detach':
613 for r in repo.forks:
612 for r in repo.forks:
614 r.fork = None
613 r.fork = None
615 self.sa.add(r)
614 self.sa.add(r)
616 elif forks == 'delete':
615 elif forks == 'delete':
617 for r in repo.forks:
616 for r in repo.forks:
618 self.delete(r, forks='delete')
617 self.delete(r, forks='delete')
619 elif [f for f in repo.forks]:
618 elif [f for f in repo.forks]:
620 raise AttachedForksError()
619 raise AttachedForksError()
621
620
622 old_repo_dict = repo.get_dict()
621 old_repo_dict = repo.get_dict()
623 events.trigger(events.RepoPreDeleteEvent(repo))
622 events.trigger(events.RepoPreDeleteEvent(repo))
624 try:
623 try:
625 self.sa.delete(repo)
624 self.sa.delete(repo)
626 if fs_remove:
625 if fs_remove:
627 self._delete_filesystem_repo(repo)
626 self._delete_filesystem_repo(repo)
628 else:
627 else:
629 log.debug('skipping removal from filesystem')
628 log.debug('skipping removal from filesystem')
630 old_repo_dict.update({
629 old_repo_dict.update({
631 'deleted_by': cur_user,
630 'deleted_by': cur_user,
632 'deleted_on': time.time(),
631 'deleted_on': time.time(),
633 })
632 })
634 log_delete_repository(**old_repo_dict)
633 log_delete_repository(**old_repo_dict)
635 events.trigger(events.RepoDeleteEvent(repo))
634 events.trigger(events.RepoDeleteEvent(repo))
636 except Exception:
635 except Exception:
637 log.error(traceback.format_exc())
636 log.error(traceback.format_exc())
638 raise
637 raise
639
638
640 def grant_user_permission(self, repo, user, perm):
639 def grant_user_permission(self, repo, user, perm):
641 """
640 """
642 Grant permission for user on given repository, or update existing one
641 Grant permission for user on given repository, or update existing one
643 if found
642 if found
644
643
645 :param repo: Instance of Repository, repository_id, or repository name
644 :param repo: Instance of Repository, repository_id, or repository name
646 :param user: Instance of User, user_id or username
645 :param user: Instance of User, user_id or username
647 :param perm: Instance of Permission, or permission_name
646 :param perm: Instance of Permission, or permission_name
648 """
647 """
649 user = self._get_user(user)
648 user = self._get_user(user)
650 repo = self._get_repo(repo)
649 repo = self._get_repo(repo)
651 permission = self._get_perm(perm)
650 permission = self._get_perm(perm)
652
651
653 # check if we have that permission already
652 # check if we have that permission already
654 obj = self.sa.query(UserRepoToPerm) \
653 obj = self.sa.query(UserRepoToPerm) \
655 .filter(UserRepoToPerm.user == user) \
654 .filter(UserRepoToPerm.user == user) \
656 .filter(UserRepoToPerm.repository == repo) \
655 .filter(UserRepoToPerm.repository == repo) \
657 .scalar()
656 .scalar()
658 if obj is None:
657 if obj is None:
659 # create new !
658 # create new !
660 obj = UserRepoToPerm()
659 obj = UserRepoToPerm()
661 obj.repository = repo
660 obj.repository = repo
662 obj.user = user
661 obj.user = user
663 obj.permission = permission
662 obj.permission = permission
664 self.sa.add(obj)
663 self.sa.add(obj)
665 log.debug('Granted perm %s to %s on %s', perm, user, repo)
664 log.debug('Granted perm %s to %s on %s', perm, user, repo)
666 action_logger_generic(
665 action_logger_generic(
667 'granted permission: {} to user: {} on repo: {}'.format(
666 'granted permission: {} to user: {} on repo: {}'.format(
668 perm, user, repo), namespace='security.repo')
667 perm, user, repo), namespace='security.repo')
669 return obj
668 return obj
670
669
671 def revoke_user_permission(self, repo, user):
670 def revoke_user_permission(self, repo, user):
672 """
671 """
673 Revoke permission for user on given repository
672 Revoke permission for user on given repository
674
673
675 :param repo: Instance of Repository, repository_id, or repository name
674 :param repo: Instance of Repository, repository_id, or repository name
676 :param user: Instance of User, user_id or username
675 :param user: Instance of User, user_id or username
677 """
676 """
678
677
679 user = self._get_user(user)
678 user = self._get_user(user)
680 repo = self._get_repo(repo)
679 repo = self._get_repo(repo)
681
680
682 obj = self.sa.query(UserRepoToPerm) \
681 obj = self.sa.query(UserRepoToPerm) \
683 .filter(UserRepoToPerm.repository == repo) \
682 .filter(UserRepoToPerm.repository == repo) \
684 .filter(UserRepoToPerm.user == user) \
683 .filter(UserRepoToPerm.user == user) \
685 .scalar()
684 .scalar()
686 if obj:
685 if obj:
687 self.sa.delete(obj)
686 self.sa.delete(obj)
688 log.debug('Revoked perm on %s on %s', repo, user)
687 log.debug('Revoked perm on %s on %s', repo, user)
689 action_logger_generic(
688 action_logger_generic(
690 'revoked permission from user: {} on repo: {}'.format(
689 'revoked permission from user: {} on repo: {}'.format(
691 user, repo), namespace='security.repo')
690 user, repo), namespace='security.repo')
692
691
693 def grant_user_group_permission(self, repo, group_name, perm):
692 def grant_user_group_permission(self, repo, group_name, perm):
694 """
693 """
695 Grant permission for user group on given repository, or update
694 Grant permission for user group on given repository, or update
696 existing one if found
695 existing one if found
697
696
698 :param repo: Instance of Repository, repository_id, or repository name
697 :param repo: Instance of Repository, repository_id, or repository name
699 :param group_name: Instance of UserGroup, users_group_id,
698 :param group_name: Instance of UserGroup, users_group_id,
700 or user group name
699 or user group name
701 :param perm: Instance of Permission, or permission_name
700 :param perm: Instance of Permission, or permission_name
702 """
701 """
703 repo = self._get_repo(repo)
702 repo = self._get_repo(repo)
704 group_name = self._get_user_group(group_name)
703 group_name = self._get_user_group(group_name)
705 permission = self._get_perm(perm)
704 permission = self._get_perm(perm)
706
705
707 # check if we have that permission already
706 # check if we have that permission already
708 obj = self.sa.query(UserGroupRepoToPerm) \
707 obj = self.sa.query(UserGroupRepoToPerm) \
709 .filter(UserGroupRepoToPerm.users_group == group_name) \
708 .filter(UserGroupRepoToPerm.users_group == group_name) \
710 .filter(UserGroupRepoToPerm.repository == repo) \
709 .filter(UserGroupRepoToPerm.repository == repo) \
711 .scalar()
710 .scalar()
712
711
713 if obj is None:
712 if obj is None:
714 # create new
713 # create new
715 obj = UserGroupRepoToPerm()
714 obj = UserGroupRepoToPerm()
716
715
717 obj.repository = repo
716 obj.repository = repo
718 obj.users_group = group_name
717 obj.users_group = group_name
719 obj.permission = permission
718 obj.permission = permission
720 self.sa.add(obj)
719 self.sa.add(obj)
721 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
720 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
722 action_logger_generic(
721 action_logger_generic(
723 'granted permission: {} to usergroup: {} on repo: {}'.format(
722 'granted permission: {} to usergroup: {} on repo: {}'.format(
724 perm, group_name, repo), namespace='security.repo')
723 perm, group_name, repo), namespace='security.repo')
725
724
726 return obj
725 return obj
727
726
728 def revoke_user_group_permission(self, repo, group_name):
727 def revoke_user_group_permission(self, repo, group_name):
729 """
728 """
730 Revoke permission for user group on given repository
729 Revoke permission for user group on given repository
731
730
732 :param repo: Instance of Repository, repository_id, or repository name
731 :param repo: Instance of Repository, repository_id, or repository name
733 :param group_name: Instance of UserGroup, users_group_id,
732 :param group_name: Instance of UserGroup, users_group_id,
734 or user group name
733 or user group name
735 """
734 """
736 repo = self._get_repo(repo)
735 repo = self._get_repo(repo)
737 group_name = self._get_user_group(group_name)
736 group_name = self._get_user_group(group_name)
738
737
739 obj = self.sa.query(UserGroupRepoToPerm) \
738 obj = self.sa.query(UserGroupRepoToPerm) \
740 .filter(UserGroupRepoToPerm.repository == repo) \
739 .filter(UserGroupRepoToPerm.repository == repo) \
741 .filter(UserGroupRepoToPerm.users_group == group_name) \
740 .filter(UserGroupRepoToPerm.users_group == group_name) \
742 .scalar()
741 .scalar()
743 if obj:
742 if obj:
744 self.sa.delete(obj)
743 self.sa.delete(obj)
745 log.debug('Revoked perm to %s on %s', repo, group_name)
744 log.debug('Revoked perm to %s on %s', repo, group_name)
746 action_logger_generic(
745 action_logger_generic(
747 'revoked permission from usergroup: {} on repo: {}'.format(
746 'revoked permission from usergroup: {} on repo: {}'.format(
748 group_name, repo), namespace='security.repo')
747 group_name, repo), namespace='security.repo')
749
748
750 def delete_stats(self, repo_name):
749 def delete_stats(self, repo_name):
751 """
750 """
752 removes stats for given repo
751 removes stats for given repo
753
752
754 :param repo_name:
753 :param repo_name:
755 """
754 """
756 repo = self._get_repo(repo_name)
755 repo = self._get_repo(repo_name)
757 try:
756 try:
758 obj = self.sa.query(Statistics) \
757 obj = self.sa.query(Statistics) \
759 .filter(Statistics.repository == repo).scalar()
758 .filter(Statistics.repository == repo).scalar()
760 if obj:
759 if obj:
761 self.sa.delete(obj)
760 self.sa.delete(obj)
762 except Exception:
761 except Exception:
763 log.error(traceback.format_exc())
762 log.error(traceback.format_exc())
764 raise
763 raise
765
764
766 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
765 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
767 field_type='str', field_desc=''):
766 field_type='str', field_desc=''):
768
767
769 repo = self._get_repo(repo_name)
768 repo = self._get_repo(repo_name)
770
769
771 new_field = RepositoryField()
770 new_field = RepositoryField()
772 new_field.repository = repo
771 new_field.repository = repo
773 new_field.field_key = field_key
772 new_field.field_key = field_key
774 new_field.field_type = field_type # python type
773 new_field.field_type = field_type # python type
775 new_field.field_value = field_value
774 new_field.field_value = field_value
776 new_field.field_desc = field_desc
775 new_field.field_desc = field_desc
777 new_field.field_label = field_label
776 new_field.field_label = field_label
778 self.sa.add(new_field)
777 self.sa.add(new_field)
779 return new_field
778 return new_field
780
779
781 def delete_repo_field(self, repo_name, field_key):
780 def delete_repo_field(self, repo_name, field_key):
782 repo = self._get_repo(repo_name)
781 repo = self._get_repo(repo_name)
783 field = RepositoryField.get_by_key_name(field_key, repo)
782 field = RepositoryField.get_by_key_name(field_key, repo)
784 if field:
783 if field:
785 self.sa.delete(field)
784 self.sa.delete(field)
786
785
787 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
786 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
788 clone_uri=None, repo_store_location=None,
787 clone_uri=None, repo_store_location=None,
789 use_global_config=False):
788 use_global_config=False):
790 """
789 """
791 makes repository on filesystem. It's group aware means it'll create
790 makes repository on filesystem. It's group aware means it'll create
792 a repository within a group, and alter the paths accordingly of
791 a repository within a group, and alter the paths accordingly of
793 group location
792 group location
794
793
795 :param repo_name:
794 :param repo_name:
796 :param alias:
795 :param alias:
797 :param parent:
796 :param parent:
798 :param clone_uri:
797 :param clone_uri:
799 :param repo_store_location:
798 :param repo_store_location:
800 """
799 """
801 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
800 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
802 from rhodecode.model.scm import ScmModel
801 from rhodecode.model.scm import ScmModel
803
802
804 if Repository.NAME_SEP in repo_name:
803 if Repository.NAME_SEP in repo_name:
805 raise ValueError(
804 raise ValueError(
806 'repo_name must not contain groups got `%s`' % repo_name)
805 'repo_name must not contain groups got `%s`' % repo_name)
807
806
808 if isinstance(repo_group, RepoGroup):
807 if isinstance(repo_group, RepoGroup):
809 new_parent_path = os.sep.join(repo_group.full_path_splitted)
808 new_parent_path = os.sep.join(repo_group.full_path_splitted)
810 else:
809 else:
811 new_parent_path = repo_group or ''
810 new_parent_path = repo_group or ''
812
811
813 if repo_store_location:
812 if repo_store_location:
814 _paths = [repo_store_location]
813 _paths = [repo_store_location]
815 else:
814 else:
816 _paths = [self.repos_path, new_parent_path, repo_name]
815 _paths = [self.repos_path, new_parent_path, repo_name]
817 # we need to make it str for mercurial
816 # we need to make it str for mercurial
818 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
817 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
819
818
820 # check if this path is not a repository
819 # check if this path is not a repository
821 if is_valid_repo(repo_path, self.repos_path):
820 if is_valid_repo(repo_path, self.repos_path):
822 raise Exception('This path %s is a valid repository' % repo_path)
821 raise Exception('This path %s is a valid repository' % repo_path)
823
822
824 # check if this path is a group
823 # check if this path is a group
825 if is_valid_repo_group(repo_path, self.repos_path):
824 if is_valid_repo_group(repo_path, self.repos_path):
826 raise Exception('This path %s is a valid group' % repo_path)
825 raise Exception('This path %s is a valid group' % repo_path)
827
826
828 log.info('creating repo %s in %s from url: `%s`',
827 log.info('creating repo %s in %s from url: `%s`',
829 repo_name, safe_unicode(repo_path),
828 repo_name, safe_unicode(repo_path),
830 obfuscate_url_pw(clone_uri))
829 obfuscate_url_pw(clone_uri))
831
830
832 backend = get_backend(repo_type)
831 backend = get_backend(repo_type)
833
832
834 config_repo = None if use_global_config else repo_name
833 config_repo = None if use_global_config else repo_name
835 if config_repo and new_parent_path:
834 if config_repo and new_parent_path:
836 config_repo = Repository.NAME_SEP.join(
835 config_repo = Repository.NAME_SEP.join(
837 (new_parent_path, config_repo))
836 (new_parent_path, config_repo))
838 config = make_db_config(clear_session=False, repo=config_repo)
837 config = make_db_config(clear_session=False, repo=config_repo)
839 config.set('extensions', 'largefiles', '')
838 config.set('extensions', 'largefiles', '')
840
839
841 # patch and reset hooks section of UI config to not run any
840 # patch and reset hooks section of UI config to not run any
842 # hooks on creating remote repo
841 # hooks on creating remote repo
843 config.clear_section('hooks')
842 config.clear_section('hooks')
844
843
845 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
844 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
846 if repo_type == 'git':
845 if repo_type == 'git':
847 repo = backend(
846 repo = backend(
848 repo_path, config=config, create=True, src_url=clone_uri,
847 repo_path, config=config, create=True, src_url=clone_uri,
849 bare=True)
848 bare=True)
850 else:
849 else:
851 repo = backend(
850 repo = backend(
852 repo_path, config=config, create=True, src_url=clone_uri)
851 repo_path, config=config, create=True, src_url=clone_uri)
853
852
854 ScmModel().install_hooks(repo, repo_type=repo_type)
853 ScmModel().install_hooks(repo, repo_type=repo_type)
855
854
856 log.debug('Created repo %s with %s backend',
855 log.debug('Created repo %s with %s backend',
857 safe_unicode(repo_name), safe_unicode(repo_type))
856 safe_unicode(repo_name), safe_unicode(repo_type))
858 return repo
857 return repo
859
858
860 def _rename_filesystem_repo(self, old, new):
859 def _rename_filesystem_repo(self, old, new):
861 """
860 """
862 renames repository on filesystem
861 renames repository on filesystem
863
862
864 :param old: old name
863 :param old: old name
865 :param new: new name
864 :param new: new name
866 """
865 """
867 log.info('renaming repo from %s to %s', old, new)
866 log.info('renaming repo from %s to %s', old, new)
868
867
869 old_path = os.path.join(self.repos_path, old)
868 old_path = os.path.join(self.repos_path, old)
870 new_path = os.path.join(self.repos_path, new)
869 new_path = os.path.join(self.repos_path, new)
871 if os.path.isdir(new_path):
870 if os.path.isdir(new_path):
872 raise Exception(
871 raise Exception(
873 'Was trying to rename to already existing dir %s' % new_path
872 'Was trying to rename to already existing dir %s' % new_path
874 )
873 )
875 shutil.move(old_path, new_path)
874 shutil.move(old_path, new_path)
876
875
877 def _delete_filesystem_repo(self, repo):
876 def _delete_filesystem_repo(self, repo):
878 """
877 """
879 removes repo from filesystem, the removal is acctually made by
878 removes repo from filesystem, the removal is acctually made by
880 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
879 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
881 repository is no longer valid for rhodecode, can be undeleted later on
880 repository is no longer valid for rhodecode, can be undeleted later on
882 by reverting the renames on this repository
881 by reverting the renames on this repository
883
882
884 :param repo: repo object
883 :param repo: repo object
885 """
884 """
886 rm_path = os.path.join(self.repos_path, repo.repo_name)
885 rm_path = os.path.join(self.repos_path, repo.repo_name)
887 repo_group = repo.group
886 repo_group = repo.group
888 log.info("Removing repository %s", rm_path)
887 log.info("Removing repository %s", rm_path)
889 # disable hg/git internal that it doesn't get detected as repo
888 # disable hg/git internal that it doesn't get detected as repo
890 alias = repo.repo_type
889 alias = repo.repo_type
891
890
892 config = make_db_config(clear_session=False)
891 config = make_db_config(clear_session=False)
893 config.set('extensions', 'largefiles', '')
892 config.set('extensions', 'largefiles', '')
894 bare = getattr(repo.scm_instance(config=config), 'bare', False)
893 bare = getattr(repo.scm_instance(config=config), 'bare', False)
895
894
896 # skip this for bare git repos
895 # skip this for bare git repos
897 if not bare:
896 if not bare:
898 # disable VCS repo
897 # disable VCS repo
899 vcs_path = os.path.join(rm_path, '.%s' % alias)
898 vcs_path = os.path.join(rm_path, '.%s' % alias)
900 if os.path.exists(vcs_path):
899 if os.path.exists(vcs_path):
901 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
900 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
902
901
903 _now = datetime.now()
902 _now = datetime.now()
904 _ms = str(_now.microsecond).rjust(6, '0')
903 _ms = str(_now.microsecond).rjust(6, '0')
905 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
904 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
906 repo.just_name)
905 repo.just_name)
907 if repo_group:
906 if repo_group:
908 # if repository is in group, prefix the removal path with the group
907 # if repository is in group, prefix the removal path with the group
909 args = repo_group.full_path_splitted + [_d]
908 args = repo_group.full_path_splitted + [_d]
910 _d = os.path.join(*args)
909 _d = os.path.join(*args)
911
910
912 if os.path.isdir(rm_path):
911 if os.path.isdir(rm_path):
913 shutil.move(rm_path, os.path.join(self.repos_path, _d))
912 shutil.move(rm_path, os.path.join(self.repos_path, _d))
914
913
915
914
916 class ReadmeFinder:
915 class ReadmeFinder:
917 """
916 """
918 Utility which knows how to find a readme for a specific commit.
917 Utility which knows how to find a readme for a specific commit.
919
918
920 The main idea is that this is a configurable algorithm. When creating an
919 The main idea is that this is a configurable algorithm. When creating an
921 instance you can define parameters, currently only the `default_renderer`.
920 instance you can define parameters, currently only the `default_renderer`.
922 Based on this configuration the method :meth:`search` behaves slightly
921 Based on this configuration the method :meth:`search` behaves slightly
923 different.
922 different.
924 """
923 """
925
924
926 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
925 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
927 path_re = re.compile(r'^docs?', re.IGNORECASE)
926 path_re = re.compile(r'^docs?', re.IGNORECASE)
928
927
929 default_priorities = {
928 default_priorities = {
930 None: 0,
929 None: 0,
931 '.text': 2,
930 '.text': 2,
932 '.txt': 3,
931 '.txt': 3,
933 '.rst': 1,
932 '.rst': 1,
934 '.rest': 2,
933 '.rest': 2,
935 '.md': 1,
934 '.md': 1,
936 '.mkdn': 2,
935 '.mkdn': 2,
937 '.mdown': 3,
936 '.mdown': 3,
938 '.markdown': 4,
937 '.markdown': 4,
939 }
938 }
940
939
941 path_priority = {
940 path_priority = {
942 'doc': 0,
941 'doc': 0,
943 'docs': 1,
942 'docs': 1,
944 }
943 }
945
944
946 FALLBACK_PRIORITY = 99
945 FALLBACK_PRIORITY = 99
947
946
948 RENDERER_TO_EXTENSION = {
947 RENDERER_TO_EXTENSION = {
949 'rst': ['.rst', '.rest'],
948 'rst': ['.rst', '.rest'],
950 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
949 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
951 }
950 }
952
951
953 def __init__(self, default_renderer=None):
952 def __init__(self, default_renderer=None):
954 self._default_renderer = default_renderer
953 self._default_renderer = default_renderer
955 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
954 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
956 default_renderer, [])
955 default_renderer, [])
957
956
958 def search(self, commit, path='/'):
957 def search(self, commit, path='/'):
959 """
958 """
960 Find a readme in the given `commit`.
959 Find a readme in the given `commit`.
961 """
960 """
962 nodes = commit.get_nodes(path)
961 nodes = commit.get_nodes(path)
963 matches = self._match_readmes(nodes)
962 matches = self._match_readmes(nodes)
964 matches = self._sort_according_to_priority(matches)
963 matches = self._sort_according_to_priority(matches)
965 if matches:
964 if matches:
966 return matches[0].node
965 return matches[0].node
967
966
968 paths = self._match_paths(nodes)
967 paths = self._match_paths(nodes)
969 paths = self._sort_paths_according_to_priority(paths)
968 paths = self._sort_paths_according_to_priority(paths)
970 for path in paths:
969 for path in paths:
971 match = self.search(commit, path=path)
970 match = self.search(commit, path=path)
972 if match:
971 if match:
973 return match
972 return match
974
973
975 return None
974 return None
976
975
977 def _match_readmes(self, nodes):
976 def _match_readmes(self, nodes):
978 for node in nodes:
977 for node in nodes:
979 if not node.is_file():
978 if not node.is_file():
980 continue
979 continue
981 path = node.path.rsplit('/', 1)[-1]
980 path = node.path.rsplit('/', 1)[-1]
982 match = self.readme_re.match(path)
981 match = self.readme_re.match(path)
983 if match:
982 if match:
984 extension = match.group(1)
983 extension = match.group(1)
985 yield ReadmeMatch(node, match, self._priority(extension))
984 yield ReadmeMatch(node, match, self._priority(extension))
986
985
987 def _match_paths(self, nodes):
986 def _match_paths(self, nodes):
988 for node in nodes:
987 for node in nodes:
989 if not node.is_dir():
988 if not node.is_dir():
990 continue
989 continue
991 match = self.path_re.match(node.path)
990 match = self.path_re.match(node.path)
992 if match:
991 if match:
993 yield node.path
992 yield node.path
994
993
995 def _priority(self, extension):
994 def _priority(self, extension):
996 renderer_priority = (
995 renderer_priority = (
997 0 if extension in self._renderer_extensions else 1)
996 0 if extension in self._renderer_extensions else 1)
998 extension_priority = self.default_priorities.get(
997 extension_priority = self.default_priorities.get(
999 extension, self.FALLBACK_PRIORITY)
998 extension, self.FALLBACK_PRIORITY)
1000 return (renderer_priority, extension_priority)
999 return (renderer_priority, extension_priority)
1001
1000
1002 def _sort_according_to_priority(self, matches):
1001 def _sort_according_to_priority(self, matches):
1003
1002
1004 def priority_and_path(match):
1003 def priority_and_path(match):
1005 return (match.priority, match.path)
1004 return (match.priority, match.path)
1006
1005
1007 return sorted(matches, key=priority_and_path)
1006 return sorted(matches, key=priority_and_path)
1008
1007
1009 def _sort_paths_according_to_priority(self, paths):
1008 def _sort_paths_according_to_priority(self, paths):
1010
1009
1011 def priority_and_path(path):
1010 def priority_and_path(path):
1012 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1011 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1013
1012
1014 return sorted(paths, key=priority_and_path)
1013 return sorted(paths, key=priority_and_path)
1015
1014
1016
1015
1017 class ReadmeMatch:
1016 class ReadmeMatch:
1018
1017
1019 def __init__(self, node, match, priority):
1018 def __init__(self, node, match, priority):
1020 self.node = node
1019 self.node = node
1021 self._match = match
1020 self._match = match
1022 self.priority = priority
1021 self.priority = priority
1023
1022
1024 @property
1023 @property
1025 def path(self):
1024 def path(self):
1026 return self.node.path
1025 return self.node.path
1027
1026
1028 def __repr__(self):
1027 def __repr__(self):
1029 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1028 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,733 +1,734 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (_hash_key,
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic
43 from rhodecode.lib.utils2 import action_logger_generic
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 name_key = _hash_key(repo_group_name)
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 return repo.scalar()
79 return repo.scalar()
80
80
81 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
82 value = SettingsModel().get_setting_by_name(
82 value = SettingsModel().get_setting_by_name(
83 'create_personal_repo_group')
83 'create_personal_repo_group')
84 return value.app_settings_value if value else None or False
84 return value.app_settings_value if value else None or False
85
85
86 def get_personal_group_name_pattern(self):
86 def get_personal_group_name_pattern(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'personal_repo_group_pattern')
88 'personal_repo_group_pattern')
89 val = value.app_settings_value if value else None
89 val = value.app_settings_value if value else None
90 group_template = val or self.PERSONAL_GROUP_PATTERN
90 group_template = val or self.PERSONAL_GROUP_PATTERN
91
91
92 group_template = group_template.lstrip('/')
92 group_template = group_template.lstrip('/')
93 return group_template
93 return group_template
94
94
95 def get_personal_group_name(self, user):
95 def get_personal_group_name(self, user):
96 template = self.get_personal_group_name_pattern()
96 template = self.get_personal_group_name_pattern()
97 return string.Template(template).safe_substitute(
97 return string.Template(template).safe_substitute(
98 username=user.username,
98 username=user.username,
99 user_id=user.user_id,
99 user_id=user.user_id,
100 )
100 )
101
101
102 def create_personal_repo_group(self, user, commit_early=True):
102 def create_personal_repo_group(self, user, commit_early=True):
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 personal_repo_group_name = self.get_personal_group_name(user)
104 personal_repo_group_name = self.get_personal_group_name(user)
105
105
106 # create a new one
106 # create a new one
107 RepoGroupModel().create(
107 RepoGroupModel().create(
108 group_name=personal_repo_group_name,
108 group_name=personal_repo_group_name,
109 group_description=desc,
109 group_description=desc,
110 owner=user.username,
110 owner=user.username,
111 personal=True,
111 personal=True,
112 commit_early=commit_early)
112 commit_early=commit_early)
113
113
114 def _create_default_perms(self, new_group):
114 def _create_default_perms(self, new_group):
115 # create default permission
115 # create default permission
116 default_perm = 'group.read'
116 default_perm = 'group.read'
117 def_user = User.get_default_user()
117 def_user = User.get_default_user()
118 for p in def_user.user_perms:
118 for p in def_user.user_perms:
119 if p.permission.permission_name.startswith('group.'):
119 if p.permission.permission_name.startswith('group.'):
120 default_perm = p.permission.permission_name
120 default_perm = p.permission.permission_name
121 break
121 break
122
122
123 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm = UserRepoGroupToPerm()
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125
125
126 repo_group_to_perm.group = new_group
126 repo_group_to_perm.group = new_group
127 repo_group_to_perm.user_id = def_user.user_id
127 repo_group_to_perm.user_id = def_user.user_id
128 return repo_group_to_perm
128 return repo_group_to_perm
129
129
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 get_object=False):
131 get_object=False):
132 """
132 """
133 Get's the group name and a parent group name from given group name.
133 Get's the group name and a parent group name from given group name.
134 If repo_in_path is set to truth, we asume the full path also includes
134 If repo_in_path is set to truth, we asume the full path also includes
135 repo name, in such case we clean the last element.
135 repo name, in such case we clean the last element.
136
136
137 :param group_name_full:
137 :param group_name_full:
138 """
138 """
139 split_paths = 1
139 split_paths = 1
140 if repo_in_path:
140 if repo_in_path:
141 split_paths = 2
141 split_paths = 2
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143
143
144 if repo_in_path and len(_parts) > 1:
144 if repo_in_path and len(_parts) > 1:
145 # such case last element is the repo_name
145 # such case last element is the repo_name
146 _parts.pop(-1)
146 _parts.pop(-1)
147 group_name_cleaned = _parts[-1] # just the group name
147 group_name_cleaned = _parts[-1] # just the group name
148 parent_repo_group_name = None
148 parent_repo_group_name = None
149
149
150 if len(_parts) > 1:
150 if len(_parts) > 1:
151 parent_repo_group_name = _parts[0]
151 parent_repo_group_name = _parts[0]
152
152
153 parent_group = None
153 parent_group = None
154 if parent_repo_group_name:
154 if parent_repo_group_name:
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156
156
157 if get_object:
157 if get_object:
158 return group_name_cleaned, parent_repo_group_name, parent_group
158 return group_name_cleaned, parent_repo_group_name, parent_group
159
159
160 return group_name_cleaned, parent_repo_group_name
160 return group_name_cleaned, parent_repo_group_name
161
161
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 create_path = os.path.join(self.repos_path, group_name)
163 create_path = os.path.join(self.repos_path, group_name)
164 log.debug('creating new group in %s', create_path)
164 log.debug('creating new group in %s', create_path)
165
165
166 if os.path.isdir(create_path):
166 if os.path.isdir(create_path):
167 if exc_on_failure:
167 if exc_on_failure:
168 abs_create_path = os.path.abspath(create_path)
168 abs_create_path = os.path.abspath(create_path)
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 return False
170 return False
171 return True
171 return True
172
172
173 def _create_group(self, group_name):
173 def _create_group(self, group_name):
174 """
174 """
175 makes repository group on filesystem
175 makes repository group on filesystem
176
176
177 :param repo_name:
177 :param repo_name:
178 :param parent_id:
178 :param parent_id:
179 """
179 """
180
180
181 self.check_exist_filesystem(group_name)
181 self.check_exist_filesystem(group_name)
182 create_path = os.path.join(self.repos_path, group_name)
182 create_path = os.path.join(self.repos_path, group_name)
183 log.debug('creating new group in %s', create_path)
183 log.debug('creating new group in %s', create_path)
184 os.makedirs(create_path, mode=0755)
184 os.makedirs(create_path, mode=0755)
185 log.debug('created group in %s', create_path)
185 log.debug('created group in %s', create_path)
186
186
187 def _rename_group(self, old, new):
187 def _rename_group(self, old, new):
188 """
188 """
189 Renames a group on filesystem
189 Renames a group on filesystem
190
190
191 :param group_name:
191 :param group_name:
192 """
192 """
193
193
194 if old == new:
194 if old == new:
195 log.debug('skipping group rename')
195 log.debug('skipping group rename')
196 return
196 return
197
197
198 log.debug('renaming repository group from %s to %s', old, new)
198 log.debug('renaming repository group from %s to %s', old, new)
199
199
200 old_path = os.path.join(self.repos_path, old)
200 old_path = os.path.join(self.repos_path, old)
201 new_path = os.path.join(self.repos_path, new)
201 new_path = os.path.join(self.repos_path, new)
202
202
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204
204
205 if os.path.isdir(new_path):
205 if os.path.isdir(new_path):
206 raise Exception('Was trying to rename to already '
206 raise Exception('Was trying to rename to already '
207 'existing dir %s' % new_path)
207 'existing dir %s' % new_path)
208 shutil.move(old_path, new_path)
208 shutil.move(old_path, new_path)
209
209
210 def _delete_filesystem_group(self, group, force_delete=False):
210 def _delete_filesystem_group(self, group, force_delete=False):
211 """
211 """
212 Deletes a group from a filesystem
212 Deletes a group from a filesystem
213
213
214 :param group: instance of group from database
214 :param group: instance of group from database
215 :param force_delete: use shutil rmtree to remove all objects
215 :param force_delete: use shutil rmtree to remove all objects
216 """
216 """
217 paths = group.full_path.split(RepoGroup.url_sep())
217 paths = group.full_path.split(RepoGroup.url_sep())
218 paths = os.sep.join(paths)
218 paths = os.sep.join(paths)
219
219
220 rm_path = os.path.join(self.repos_path, paths)
220 rm_path = os.path.join(self.repos_path, paths)
221 log.info("Removing group %s", rm_path)
221 log.info("Removing group %s", rm_path)
222 # delete only if that path really exists
222 # delete only if that path really exists
223 if os.path.isdir(rm_path):
223 if os.path.isdir(rm_path):
224 if force_delete:
224 if force_delete:
225 shutil.rmtree(rm_path)
225 shutil.rmtree(rm_path)
226 else:
226 else:
227 # archive that group`
227 # archive that group`
228 _now = datetime.datetime.now()
228 _now = datetime.datetime.now()
229 _ms = str(_now.microsecond).rjust(6, '0')
229 _ms = str(_now.microsecond).rjust(6, '0')
230 _d = 'rm__%s_GROUP_%s' % (
230 _d = 'rm__%s_GROUP_%s' % (
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233
233
234 def create(self, group_name, group_description, owner, just_db=False,
234 def create(self, group_name, group_description, owner, just_db=False,
235 copy_permissions=False, personal=None, commit_early=True):
235 copy_permissions=False, personal=None, commit_early=True):
236
236
237 (group_name_cleaned,
237 (group_name_cleaned,
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239
239
240 parent_group = None
240 parent_group = None
241 if parent_group_name:
241 if parent_group_name:
242 parent_group = self._get_repo_group(parent_group_name)
242 parent_group = self._get_repo_group(parent_group_name)
243 if not parent_group:
243 if not parent_group:
244 # we tried to create a nested group, but the parent is not
244 # we tried to create a nested group, but the parent is not
245 # existing
245 # existing
246 raise ValueError(
246 raise ValueError(
247 'Parent group `%s` given in `%s` group name '
247 'Parent group `%s` given in `%s` group name '
248 'is not yet existing.' % (parent_group_name, group_name))
248 'is not yet existing.' % (parent_group_name, group_name))
249
249
250 # because we are doing a cleanup, we need to check if such directory
250 # because we are doing a cleanup, we need to check if such directory
251 # already exists. If we don't do that we can accidentally delete
251 # already exists. If we don't do that we can accidentally delete
252 # existing directory via cleanup that can cause data issues, since
252 # existing directory via cleanup that can cause data issues, since
253 # delete does a folder rename to special syntax later cleanup
253 # delete does a folder rename to special syntax later cleanup
254 # functions can delete this
254 # functions can delete this
255 cleanup_group = self.check_exist_filesystem(group_name,
255 cleanup_group = self.check_exist_filesystem(group_name,
256 exc_on_failure=False)
256 exc_on_failure=False)
257 try:
257 try:
258 user = self._get_user(owner)
258 user = self._get_user(owner)
259 new_repo_group = RepoGroup()
259 new_repo_group = RepoGroup()
260 new_repo_group.user = user
260 new_repo_group.user = user
261 new_repo_group.group_description = group_description or group_name
261 new_repo_group.group_description = group_description or group_name
262 new_repo_group.parent_group = parent_group
262 new_repo_group.parent_group = parent_group
263 new_repo_group.group_name = group_name
263 new_repo_group.group_name = group_name
264 new_repo_group.personal = personal
264 new_repo_group.personal = personal
265
265
266 self.sa.add(new_repo_group)
266 self.sa.add(new_repo_group)
267
267
268 # create an ADMIN permission for owner except if we're super admin,
268 # create an ADMIN permission for owner except if we're super admin,
269 # later owner should go into the owner field of groups
269 # later owner should go into the owner field of groups
270 if not user.is_admin:
270 if not user.is_admin:
271 self.grant_user_permission(repo_group=new_repo_group,
271 self.grant_user_permission(repo_group=new_repo_group,
272 user=owner, perm='group.admin')
272 user=owner, perm='group.admin')
273
273
274 if parent_group and copy_permissions:
274 if parent_group and copy_permissions:
275 # copy permissions from parent
275 # copy permissions from parent
276 user_perms = UserRepoGroupToPerm.query() \
276 user_perms = UserRepoGroupToPerm.query() \
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
278
278
279 group_perms = UserGroupRepoGroupToPerm.query() \
279 group_perms = UserGroupRepoGroupToPerm.query() \
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281
281
282 for perm in user_perms:
282 for perm in user_perms:
283 # don't copy over the permission for user who is creating
283 # don't copy over the permission for user who is creating
284 # this group, if he is not super admin he get's admin
284 # this group, if he is not super admin he get's admin
285 # permission set above
285 # permission set above
286 if perm.user != user or user.is_admin:
286 if perm.user != user or user.is_admin:
287 UserRepoGroupToPerm.create(
287 UserRepoGroupToPerm.create(
288 perm.user, new_repo_group, perm.permission)
288 perm.user, new_repo_group, perm.permission)
289
289
290 for perm in group_perms:
290 for perm in group_perms:
291 UserGroupRepoGroupToPerm.create(
291 UserGroupRepoGroupToPerm.create(
292 perm.users_group, new_repo_group, perm.permission)
292 perm.users_group, new_repo_group, perm.permission)
293 else:
293 else:
294 perm_obj = self._create_default_perms(new_repo_group)
294 perm_obj = self._create_default_perms(new_repo_group)
295 self.sa.add(perm_obj)
295 self.sa.add(perm_obj)
296
296
297 # now commit the changes, earlier so we are sure everything is in
297 # now commit the changes, earlier so we are sure everything is in
298 # the database.
298 # the database.
299 if commit_early:
299 if commit_early:
300 self.sa.commit()
300 self.sa.commit()
301 if not just_db:
301 if not just_db:
302 self._create_group(new_repo_group.group_name)
302 self._create_group(new_repo_group.group_name)
303
303
304 # trigger the post hook
304 # trigger the post hook
305 from rhodecode.lib.hooks_base import log_create_repository_group
305 from rhodecode.lib.hooks_base import log_create_repository_group
306 repo_group = RepoGroup.get_by_group_name(group_name)
306 repo_group = RepoGroup.get_by_group_name(group_name)
307 log_create_repository_group(
307 log_create_repository_group(
308 created_by=user.username, **repo_group.get_dict())
308 created_by=user.username, **repo_group.get_dict())
309
309
310 # Trigger create event.
310 # Trigger create event.
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
312
312
313 return new_repo_group
313 return new_repo_group
314 except Exception:
314 except Exception:
315 self.sa.rollback()
315 self.sa.rollback()
316 log.exception('Exception occurred when creating repository group, '
316 log.exception('Exception occurred when creating repository group, '
317 'doing cleanup...')
317 'doing cleanup...')
318 # rollback things manually !
318 # rollback things manually !
319 repo_group = RepoGroup.get_by_group_name(group_name)
319 repo_group = RepoGroup.get_by_group_name(group_name)
320 if repo_group:
320 if repo_group:
321 RepoGroup.delete(repo_group.group_id)
321 RepoGroup.delete(repo_group.group_id)
322 self.sa.commit()
322 self.sa.commit()
323 if cleanup_group:
323 if cleanup_group:
324 RepoGroupModel()._delete_filesystem_group(repo_group)
324 RepoGroupModel()._delete_filesystem_group(repo_group)
325 raise
325 raise
326
326
327 def update_permissions(
327 def update_permissions(
328 self, repo_group, perm_additions=None, perm_updates=None,
328 self, repo_group, perm_additions=None, perm_updates=None,
329 perm_deletions=None, recursive=None, check_perms=True,
329 perm_deletions=None, recursive=None, check_perms=True,
330 cur_user=None):
330 cur_user=None):
331 from rhodecode.model.repo import RepoModel
331 from rhodecode.model.repo import RepoModel
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
333
333
334 if not perm_additions:
334 if not perm_additions:
335 perm_additions = []
335 perm_additions = []
336 if not perm_updates:
336 if not perm_updates:
337 perm_updates = []
337 perm_updates = []
338 if not perm_deletions:
338 if not perm_deletions:
339 perm_deletions = []
339 perm_deletions = []
340
340
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342
342
343 changes = {
343 changes = {
344 'added': [],
344 'added': [],
345 'updated': [],
345 'updated': [],
346 'deleted': []
346 'deleted': []
347 }
347 }
348
348
349 def _set_perm_user(obj, user, perm):
349 def _set_perm_user(obj, user, perm):
350 if isinstance(obj, RepoGroup):
350 if isinstance(obj, RepoGroup):
351 self.grant_user_permission(
351 self.grant_user_permission(
352 repo_group=obj, user=user, perm=perm)
352 repo_group=obj, user=user, perm=perm)
353 elif isinstance(obj, Repository):
353 elif isinstance(obj, Repository):
354 # private repos will not allow to change the default
354 # private repos will not allow to change the default
355 # permissions using recursive mode
355 # permissions using recursive mode
356 if obj.private and user == User.DEFAULT_USER:
356 if obj.private and user == User.DEFAULT_USER:
357 return
357 return
358
358
359 # we set group permission but we have to switch to repo
359 # we set group permission but we have to switch to repo
360 # permission
360 # permission
361 perm = perm.replace('group.', 'repository.')
361 perm = perm.replace('group.', 'repository.')
362 RepoModel().grant_user_permission(
362 RepoModel().grant_user_permission(
363 repo=obj, user=user, perm=perm)
363 repo=obj, user=user, perm=perm)
364
364
365 def _set_perm_group(obj, users_group, perm):
365 def _set_perm_group(obj, users_group, perm):
366 if isinstance(obj, RepoGroup):
366 if isinstance(obj, RepoGroup):
367 self.grant_user_group_permission(
367 self.grant_user_group_permission(
368 repo_group=obj, group_name=users_group, perm=perm)
368 repo_group=obj, group_name=users_group, perm=perm)
369 elif isinstance(obj, Repository):
369 elif isinstance(obj, Repository):
370 # we set group permission but we have to switch to repo
370 # we set group permission but we have to switch to repo
371 # permission
371 # permission
372 perm = perm.replace('group.', 'repository.')
372 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_group_permission(
373 RepoModel().grant_user_group_permission(
374 repo=obj, group_name=users_group, perm=perm)
374 repo=obj, group_name=users_group, perm=perm)
375
375
376 def _revoke_perm_user(obj, user):
376 def _revoke_perm_user(obj, user):
377 if isinstance(obj, RepoGroup):
377 if isinstance(obj, RepoGroup):
378 self.revoke_user_permission(repo_group=obj, user=user)
378 self.revoke_user_permission(repo_group=obj, user=user)
379 elif isinstance(obj, Repository):
379 elif isinstance(obj, Repository):
380 RepoModel().revoke_user_permission(repo=obj, user=user)
380 RepoModel().revoke_user_permission(repo=obj, user=user)
381
381
382 def _revoke_perm_group(obj, user_group):
382 def _revoke_perm_group(obj, user_group):
383 if isinstance(obj, RepoGroup):
383 if isinstance(obj, RepoGroup):
384 self.revoke_user_group_permission(
384 self.revoke_user_group_permission(
385 repo_group=obj, group_name=user_group)
385 repo_group=obj, group_name=user_group)
386 elif isinstance(obj, Repository):
386 elif isinstance(obj, Repository):
387 RepoModel().revoke_user_group_permission(
387 RepoModel().revoke_user_group_permission(
388 repo=obj, group_name=user_group)
388 repo=obj, group_name=user_group)
389
389
390 # start updates
390 # start updates
391 log.debug('Now updating permissions for %s in recursive mode:%s',
391 log.debug('Now updating permissions for %s in recursive mode:%s',
392 repo_group, recursive)
392 repo_group, recursive)
393
393
394 # initialize check function, we'll call that multiple times
394 # initialize check function, we'll call that multiple times
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396
396
397 for obj in repo_group.recursive_groups_and_repos():
397 for obj in repo_group.recursive_groups_and_repos():
398 # iterated obj is an instance of a repos group or repository in
398 # iterated obj is an instance of a repos group or repository in
399 # that group, recursive option can be: none, repos, groups, all
399 # that group, recursive option can be: none, repos, groups, all
400 if recursive == 'all':
400 if recursive == 'all':
401 obj = obj
401 obj = obj
402 elif recursive == 'repos':
402 elif recursive == 'repos':
403 # skip groups, other than this one
403 # skip groups, other than this one
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 continue
405 continue
406 elif recursive == 'groups':
406 elif recursive == 'groups':
407 # skip repos
407 # skip repos
408 if isinstance(obj, Repository):
408 if isinstance(obj, Repository):
409 continue
409 continue
410 else: # recursive == 'none':
410 else: # recursive == 'none':
411 # DEFAULT option - don't apply to iterated objects
411 # DEFAULT option - don't apply to iterated objects
412 # also we do a break at the end of this loop. if we are not
412 # also we do a break at the end of this loop. if we are not
413 # in recursive mode
413 # in recursive mode
414 obj = repo_group
414 obj = repo_group
415
415
416 change_obj = obj.get_api_data()
416 change_obj = obj.get_api_data()
417
417
418 # update permissions
418 # update permissions
419 for member_id, perm, member_type in perm_updates:
419 for member_id, perm, member_type in perm_updates:
420 member_id = int(member_id)
420 member_id = int(member_id)
421 if member_type == 'user':
421 if member_type == 'user':
422 member_name = User.get(member_id).username
422 member_name = User.get(member_id).username
423 # this updates also current one if found
423 # this updates also current one if found
424 _set_perm_user(obj, user=member_id, perm=perm)
424 _set_perm_user(obj, user=member_id, perm=perm)
425 else: # set for user group
425 else: # set for user group
426 member_name = UserGroup.get(member_id).users_group_name
426 member_name = UserGroup.get(member_id).users_group_name
427 if not check_perms or has_group_perm(member_name,
427 if not check_perms or has_group_perm(member_name,
428 user=cur_user):
428 user=cur_user):
429 _set_perm_group(obj, users_group=member_id, perm=perm)
429 _set_perm_group(obj, users_group=member_id, perm=perm)
430
430
431 changes['updated'].append(
431 changes['updated'].append(
432 {'change_obj': change_obj, 'type': member_type,
432 {'change_obj': change_obj, 'type': member_type,
433 'id': member_id, 'name': member_name, 'new_perm': perm})
433 'id': member_id, 'name': member_name, 'new_perm': perm})
434
434
435 # set new permissions
435 # set new permissions
436 for member_id, perm, member_type in perm_additions:
436 for member_id, perm, member_type in perm_additions:
437 member_id = int(member_id)
437 member_id = int(member_id)
438 if member_type == 'user':
438 if member_type == 'user':
439 member_name = User.get(member_id).username
439 member_name = User.get(member_id).username
440 _set_perm_user(obj, user=member_id, perm=perm)
440 _set_perm_user(obj, user=member_id, perm=perm)
441 else: # set for user group
441 else: # set for user group
442 # check if we have permissions to alter this usergroup
442 # check if we have permissions to alter this usergroup
443 member_name = UserGroup.get(member_id).users_group_name
443 member_name = UserGroup.get(member_id).users_group_name
444 if not check_perms or has_group_perm(member_name,
444 if not check_perms or has_group_perm(member_name,
445 user=cur_user):
445 user=cur_user):
446 _set_perm_group(obj, users_group=member_id, perm=perm)
446 _set_perm_group(obj, users_group=member_id, perm=perm)
447
447
448 changes['added'].append(
448 changes['added'].append(
449 {'change_obj': change_obj, 'type': member_type,
449 {'change_obj': change_obj, 'type': member_type,
450 'id': member_id, 'name': member_name, 'new_perm': perm})
450 'id': member_id, 'name': member_name, 'new_perm': perm})
451
451
452 # delete permissions
452 # delete permissions
453 for member_id, perm, member_type in perm_deletions:
453 for member_id, perm, member_type in perm_deletions:
454 member_id = int(member_id)
454 member_id = int(member_id)
455 if member_type == 'user':
455 if member_type == 'user':
456 member_name = User.get(member_id).username
456 member_name = User.get(member_id).username
457 _revoke_perm_user(obj, user=member_id)
457 _revoke_perm_user(obj, user=member_id)
458 else: # set for user group
458 else: # set for user group
459 # check if we have permissions to alter this usergroup
459 # check if we have permissions to alter this usergroup
460 member_name = UserGroup.get(member_id).users_group_name
460 member_name = UserGroup.get(member_id).users_group_name
461 if not check_perms or has_group_perm(member_name,
461 if not check_perms or has_group_perm(member_name,
462 user=cur_user):
462 user=cur_user):
463 _revoke_perm_group(obj, user_group=member_id)
463 _revoke_perm_group(obj, user_group=member_id)
464
464
465 changes['deleted'].append(
465 changes['deleted'].append(
466 {'change_obj': change_obj, 'type': member_type,
466 {'change_obj': change_obj, 'type': member_type,
467 'id': member_id, 'name': member_name, 'new_perm': perm})
467 'id': member_id, 'name': member_name, 'new_perm': perm})
468
468
469 # if it's not recursive call for all,repos,groups
469 # if it's not recursive call for all,repos,groups
470 # break the loop and don't proceed with other changes
470 # break the loop and don't proceed with other changes
471 if recursive not in ['all', 'repos', 'groups']:
471 if recursive not in ['all', 'repos', 'groups']:
472 break
472 break
473
473
474 return changes
474 return changes
475
475
476 def update(self, repo_group, form_data):
476 def update(self, repo_group, form_data):
477 try:
477 try:
478 repo_group = self._get_repo_group(repo_group)
478 repo_group = self._get_repo_group(repo_group)
479 old_path = repo_group.full_path
479 old_path = repo_group.full_path
480
480
481 # change properties
481 # change properties
482 if 'group_description' in form_data:
482 if 'group_description' in form_data:
483 repo_group.group_description = form_data['group_description']
483 repo_group.group_description = form_data['group_description']
484
484
485 if 'enable_locking' in form_data:
485 if 'enable_locking' in form_data:
486 repo_group.enable_locking = form_data['enable_locking']
486 repo_group.enable_locking = form_data['enable_locking']
487
487
488 if 'group_parent_id' in form_data:
488 if 'group_parent_id' in form_data:
489 parent_group = (
489 parent_group = (
490 self._get_repo_group(form_data['group_parent_id']))
490 self._get_repo_group(form_data['group_parent_id']))
491 repo_group.group_parent_id = (
491 repo_group.group_parent_id = (
492 parent_group.group_id if parent_group else None)
492 parent_group.group_id if parent_group else None)
493 repo_group.parent_group = parent_group
493 repo_group.parent_group = parent_group
494
494
495 # mikhail: to update the full_path, we have to explicitly
495 # mikhail: to update the full_path, we have to explicitly
496 # update group_name
496 # update group_name
497 group_name = form_data.get('group_name', repo_group.name)
497 group_name = form_data.get('group_name', repo_group.name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
499
499
500 new_path = repo_group.full_path
500 new_path = repo_group.full_path
501
501
502 if 'user' in form_data:
502 if 'user' in form_data:
503 repo_group.user = User.get_by_username(form_data['user'])
503 repo_group.user = User.get_by_username(form_data['user'])
504
504
505 self.sa.add(repo_group)
505 self.sa.add(repo_group)
506
506
507 # iterate over all members of this groups and do fixes
507 # iterate over all members of this groups and do fixes
508 # set locking if given
508 # set locking if given
509 # if obj is a repoGroup also fix the name of the group according
509 # if obj is a repoGroup also fix the name of the group according
510 # to the parent
510 # to the parent
511 # if obj is a Repo fix it's name
511 # if obj is a Repo fix it's name
512 # this can be potentially heavy operation
512 # this can be potentially heavy operation
513 for obj in repo_group.recursive_groups_and_repos():
513 for obj in repo_group.recursive_groups_and_repos():
514 # set the value from it's parent
514 # set the value from it's parent
515 obj.enable_locking = repo_group.enable_locking
515 obj.enable_locking = repo_group.enable_locking
516 if isinstance(obj, RepoGroup):
516 if isinstance(obj, RepoGroup):
517 new_name = obj.get_new_name(obj.name)
517 new_name = obj.get_new_name(obj.name)
518 log.debug('Fixing group %s to new name %s',
518 log.debug('Fixing group %s to new name %s',
519 obj.group_name, new_name)
519 obj.group_name, new_name)
520 obj.group_name = new_name
520 obj.group_name = new_name
521 elif isinstance(obj, Repository):
521 elif isinstance(obj, Repository):
522 # we need to get all repositories from this new group and
522 # we need to get all repositories from this new group and
523 # rename them accordingly to new group path
523 # rename them accordingly to new group path
524 new_name = obj.get_new_name(obj.just_name)
524 new_name = obj.get_new_name(obj.just_name)
525 log.debug('Fixing repo %s to new name %s',
525 log.debug('Fixing repo %s to new name %s',
526 obj.repo_name, new_name)
526 obj.repo_name, new_name)
527 obj.repo_name = new_name
527 obj.repo_name = new_name
528 self.sa.add(obj)
528 self.sa.add(obj)
529
529
530 self._rename_group(old_path, new_path)
530 self._rename_group(old_path, new_path)
531
531
532 # Trigger update event.
532 # Trigger update event.
533 events.trigger(events.RepoGroupUpdateEvent(repo_group))
533 events.trigger(events.RepoGroupUpdateEvent(repo_group))
534
534
535 return repo_group
535 return repo_group
536 except Exception:
536 except Exception:
537 log.error(traceback.format_exc())
537 log.error(traceback.format_exc())
538 raise
538 raise
539
539
540 def delete(self, repo_group, force_delete=False, fs_remove=True):
540 def delete(self, repo_group, force_delete=False, fs_remove=True):
541 repo_group = self._get_repo_group(repo_group)
541 repo_group = self._get_repo_group(repo_group)
542 if not repo_group:
542 if not repo_group:
543 return False
543 return False
544 try:
544 try:
545 self.sa.delete(repo_group)
545 self.sa.delete(repo_group)
546 if fs_remove:
546 if fs_remove:
547 self._delete_filesystem_group(repo_group, force_delete)
547 self._delete_filesystem_group(repo_group, force_delete)
548 else:
548 else:
549 log.debug('skipping removal from filesystem')
549 log.debug('skipping removal from filesystem')
550
550
551 # Trigger delete event.
551 # Trigger delete event.
552 events.trigger(events.RepoGroupDeleteEvent(repo_group))
552 events.trigger(events.RepoGroupDeleteEvent(repo_group))
553 return True
553 return True
554
554
555 except Exception:
555 except Exception:
556 log.error('Error removing repo_group %s', repo_group)
556 log.error('Error removing repo_group %s', repo_group)
557 raise
557 raise
558
558
559 def grant_user_permission(self, repo_group, user, perm):
559 def grant_user_permission(self, repo_group, user, perm):
560 """
560 """
561 Grant permission for user on given repository group, or update
561 Grant permission for user on given repository group, or update
562 existing one if found
562 existing one if found
563
563
564 :param repo_group: Instance of RepoGroup, repositories_group_id,
564 :param repo_group: Instance of RepoGroup, repositories_group_id,
565 or repositories_group name
565 or repositories_group name
566 :param user: Instance of User, user_id or username
566 :param user: Instance of User, user_id or username
567 :param perm: Instance of Permission, or permission_name
567 :param perm: Instance of Permission, or permission_name
568 """
568 """
569
569
570 repo_group = self._get_repo_group(repo_group)
570 repo_group = self._get_repo_group(repo_group)
571 user = self._get_user(user)
571 user = self._get_user(user)
572 permission = self._get_perm(perm)
572 permission = self._get_perm(perm)
573
573
574 # check if we have that permission already
574 # check if we have that permission already
575 obj = self.sa.query(UserRepoGroupToPerm)\
575 obj = self.sa.query(UserRepoGroupToPerm)\
576 .filter(UserRepoGroupToPerm.user == user)\
576 .filter(UserRepoGroupToPerm.user == user)\
577 .filter(UserRepoGroupToPerm.group == repo_group)\
577 .filter(UserRepoGroupToPerm.group == repo_group)\
578 .scalar()
578 .scalar()
579 if obj is None:
579 if obj is None:
580 # create new !
580 # create new !
581 obj = UserRepoGroupToPerm()
581 obj = UserRepoGroupToPerm()
582 obj.group = repo_group
582 obj.group = repo_group
583 obj.user = user
583 obj.user = user
584 obj.permission = permission
584 obj.permission = permission
585 self.sa.add(obj)
585 self.sa.add(obj)
586 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
586 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
587 action_logger_generic(
587 action_logger_generic(
588 'granted permission: {} to user: {} on repogroup: {}'.format(
588 'granted permission: {} to user: {} on repogroup: {}'.format(
589 perm, user, repo_group), namespace='security.repogroup')
589 perm, user, repo_group), namespace='security.repogroup')
590 return obj
590 return obj
591
591
592 def revoke_user_permission(self, repo_group, user):
592 def revoke_user_permission(self, repo_group, user):
593 """
593 """
594 Revoke permission for user on given repository group
594 Revoke permission for user on given repository group
595
595
596 :param repo_group: Instance of RepoGroup, repositories_group_id,
596 :param repo_group: Instance of RepoGroup, repositories_group_id,
597 or repositories_group name
597 or repositories_group name
598 :param user: Instance of User, user_id or username
598 :param user: Instance of User, user_id or username
599 """
599 """
600
600
601 repo_group = self._get_repo_group(repo_group)
601 repo_group = self._get_repo_group(repo_group)
602 user = self._get_user(user)
602 user = self._get_user(user)
603
603
604 obj = self.sa.query(UserRepoGroupToPerm)\
604 obj = self.sa.query(UserRepoGroupToPerm)\
605 .filter(UserRepoGroupToPerm.user == user)\
605 .filter(UserRepoGroupToPerm.user == user)\
606 .filter(UserRepoGroupToPerm.group == repo_group)\
606 .filter(UserRepoGroupToPerm.group == repo_group)\
607 .scalar()
607 .scalar()
608 if obj:
608 if obj:
609 self.sa.delete(obj)
609 self.sa.delete(obj)
610 log.debug('Revoked perm on %s on %s', repo_group, user)
610 log.debug('Revoked perm on %s on %s', repo_group, user)
611 action_logger_generic(
611 action_logger_generic(
612 'revoked permission from user: {} on repogroup: {}'.format(
612 'revoked permission from user: {} on repogroup: {}'.format(
613 user, repo_group), namespace='security.repogroup')
613 user, repo_group), namespace='security.repogroup')
614
614
615 def grant_user_group_permission(self, repo_group, group_name, perm):
615 def grant_user_group_permission(self, repo_group, group_name, perm):
616 """
616 """
617 Grant permission for user group on given repository group, or update
617 Grant permission for user group on given repository group, or update
618 existing one if found
618 existing one if found
619
619
620 :param repo_group: Instance of RepoGroup, repositories_group_id,
620 :param repo_group: Instance of RepoGroup, repositories_group_id,
621 or repositories_group name
621 or repositories_group name
622 :param group_name: Instance of UserGroup, users_group_id,
622 :param group_name: Instance of UserGroup, users_group_id,
623 or user group name
623 or user group name
624 :param perm: Instance of Permission, or permission_name
624 :param perm: Instance of Permission, or permission_name
625 """
625 """
626 repo_group = self._get_repo_group(repo_group)
626 repo_group = self._get_repo_group(repo_group)
627 group_name = self._get_user_group(group_name)
627 group_name = self._get_user_group(group_name)
628 permission = self._get_perm(perm)
628 permission = self._get_perm(perm)
629
629
630 # check if we have that permission already
630 # check if we have that permission already
631 obj = self.sa.query(UserGroupRepoGroupToPerm)\
631 obj = self.sa.query(UserGroupRepoGroupToPerm)\
632 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
632 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
633 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
633 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
634 .scalar()
634 .scalar()
635
635
636 if obj is None:
636 if obj is None:
637 # create new
637 # create new
638 obj = UserGroupRepoGroupToPerm()
638 obj = UserGroupRepoGroupToPerm()
639
639
640 obj.group = repo_group
640 obj.group = repo_group
641 obj.users_group = group_name
641 obj.users_group = group_name
642 obj.permission = permission
642 obj.permission = permission
643 self.sa.add(obj)
643 self.sa.add(obj)
644 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
644 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
645 action_logger_generic(
645 action_logger_generic(
646 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
646 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
647 perm, group_name, repo_group), namespace='security.repogroup')
647 perm, group_name, repo_group), namespace='security.repogroup')
648 return obj
648 return obj
649
649
650 def revoke_user_group_permission(self, repo_group, group_name):
650 def revoke_user_group_permission(self, repo_group, group_name):
651 """
651 """
652 Revoke permission for user group on given repository group
652 Revoke permission for user group on given repository group
653
653
654 :param repo_group: Instance of RepoGroup, repositories_group_id,
654 :param repo_group: Instance of RepoGroup, repositories_group_id,
655 or repositories_group name
655 or repositories_group name
656 :param group_name: Instance of UserGroup, users_group_id,
656 :param group_name: Instance of UserGroup, users_group_id,
657 or user group name
657 or user group name
658 """
658 """
659 repo_group = self._get_repo_group(repo_group)
659 repo_group = self._get_repo_group(repo_group)
660 group_name = self._get_user_group(group_name)
660 group_name = self._get_user_group(group_name)
661
661
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
665 .scalar()
665 .scalar()
666 if obj:
666 if obj:
667 self.sa.delete(obj)
667 self.sa.delete(obj)
668 log.debug('Revoked perm to %s on %s', repo_group, group_name)
668 log.debug('Revoked perm to %s on %s', repo_group, group_name)
669 action_logger_generic(
669 action_logger_generic(
670 'revoked permission from usergroup: {} on repogroup: {}'.format(
670 'revoked permission from usergroup: {} on repogroup: {}'.format(
671 group_name, repo_group), namespace='security.repogroup')
671 group_name, repo_group), namespace='security.repogroup')
672
672
673 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
673 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
674 super_user_actions=False):
674 super_user_actions=False):
675
675
676 from rhodecode.lib.utils import PartialRenderer
676 from pyramid.threadlocal import get_current_request
677 _render = PartialRenderer('data_table/_dt_elements.mako')
677 _render = get_current_request().get_partial_renderer(
678 c = _render.c
678 'data_table/_dt_elements.mako')
679 h = _render.h
679 c = _render.get_call_context()
680 h = _render.get_helpers()
680
681
681 def quick_menu(repo_group_name):
682 def quick_menu(repo_group_name):
682 return _render('quick_repo_group_menu', repo_group_name)
683 return _render('quick_repo_group_menu', repo_group_name)
683
684
684 def repo_group_lnk(repo_group_name):
685 def repo_group_lnk(repo_group_name):
685 return _render('repo_group_name', repo_group_name)
686 return _render('repo_group_name', repo_group_name)
686
687
687 def desc(desc, personal):
688 def desc(desc, personal):
688 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
689 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
689
690
690 if c.visual.stylify_metatags:
691 if c.visual.stylify_metatags:
691 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
692 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
692 else:
693 else:
693 desc = h.urlify_text(prefix + h.html_escape(desc))
694 desc = h.urlify_text(prefix + h.html_escape(desc))
694
695
695 return _render('repo_group_desc', desc)
696 return _render('repo_group_desc', desc)
696
697
697 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
698 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
698 return _render(
699 return _render(
699 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
700 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
700
701
701 def repo_group_name(repo_group_name, children_groups):
702 def repo_group_name(repo_group_name, children_groups):
702 return _render("repo_group_name", repo_group_name, children_groups)
703 return _render("repo_group_name", repo_group_name, children_groups)
703
704
704 def user_profile(username):
705 def user_profile(username):
705 return _render('user_profile', username)
706 return _render('user_profile', username)
706
707
707 repo_group_data = []
708 repo_group_data = []
708 for group in repo_group_list:
709 for group in repo_group_list:
709
710
710 row = {
711 row = {
711 "menu": quick_menu(group.group_name),
712 "menu": quick_menu(group.group_name),
712 "name": repo_group_lnk(group.group_name),
713 "name": repo_group_lnk(group.group_name),
713 "name_raw": group.group_name,
714 "name_raw": group.group_name,
714 "desc": desc(group.description_safe, group.personal),
715 "desc": desc(group.description_safe, group.personal),
715 "top_level_repos": 0,
716 "top_level_repos": 0,
716 "owner": user_profile(group.user.username)
717 "owner": user_profile(group.user.username)
717 }
718 }
718 if admin:
719 if admin:
719 repo_count = group.repositories.count()
720 repo_count = group.repositories.count()
720 children_groups = map(
721 children_groups = map(
721 h.safe_unicode,
722 h.safe_unicode,
722 itertools.chain((g.name for g in group.parents),
723 itertools.chain((g.name for g in group.parents),
723 (x.name for x in [group])))
724 (x.name for x in [group])))
724 row.update({
725 row.update({
725 "action": repo_group_actions(
726 "action": repo_group_actions(
726 group.group_id, group.group_name, repo_count),
727 group.group_id, group.group_name, repo_count),
727 "top_level_repos": repo_count,
728 "top_level_repos": repo_count,
728 "name": repo_group_name(group.group_name, children_groups),
729 "name": repo_group_name(group.group_name, children_groups),
729
730
730 })
731 })
731 repo_group_data.append(row)
732 repo_group_data.append(row)
732
733
733 return repo_group_data
734 return repo_group_data
General Comments 0
You need to be logged in to leave comments. Login now