##// END OF EJS Templates
watch: add permission check for repo action.
ergo -
r3681:4898a572 new-ui
parent child Browse files
Show More
@@ -1,384 +1,387 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import itertools
24 24
25 25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26 26
27 27 from pyramid.view import view_config
28 28 from pyramid.httpexceptions import HTTPBadRequest
29 29 from pyramid.response import Response
30 30 from pyramid.renderers import render
31 31
32 32 from rhodecode.apps._base import BaseAppView
33 33 from rhodecode.model.db import (
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
35 35 from rhodecode.model.meta import Session
36 36 import rhodecode.lib.helpers as h
37 37 from rhodecode.lib.helpers import Page
38 38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
40 40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class JournalView(BaseAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50
51 51 self._load_defaults(c.rhodecode_name)
52 52
53 53 # TODO(marcink): what is this, why we need a global register ?
54 54 c.search_term = self.request.GET.get('filter') or ''
55 55 return c
56 56
57 57 def _get_config(self, rhodecode_name):
58 58 import rhodecode
59 59 config = rhodecode.CONFIG
60 60
61 61 return {
62 62 'language': 'en-us',
63 63 'feed_ttl': '5', # TTL of feed,
64 64 'feed_items_per_page':
65 65 safe_int(config.get('rss_items_per_page', 20)),
66 66 'rhodecode_name': rhodecode_name
67 67 }
68 68
69 69 def _load_defaults(self, rhodecode_name):
70 70 config = self._get_config(rhodecode_name)
71 71 # common values for feeds
72 72 self.language = config["language"]
73 73 self.ttl = config["feed_ttl"]
74 74 self.feed_items_per_page = config['feed_items_per_page']
75 75 self.rhodecode_name = config['rhodecode_name']
76 76
77 77 def _get_daily_aggregate(self, journal):
78 78 groups = []
79 79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 80 user_group = []
81 81 # groupby username if it's a present value, else
82 82 # fallback to journal username
83 83 for _, g2 in itertools.groupby(
84 84 list(g), lambda x: x.user.username if x.user else x.username):
85 85 l = list(g2)
86 86 user_group.append((l[0].user, l))
87 87
88 88 groups.append((k, user_group,))
89 89
90 90 return groups
91 91
92 92 def _get_journal_data(self, following_repos, search_term):
93 93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 94 if x.follows_repository is not None]
95 95 user_ids = [x.follows_user.user_id for x in following_repos
96 96 if x.follows_user is not None]
97 97
98 98 filtering_criterion = None
99 99
100 100 if repo_ids and user_ids:
101 101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 102 UserLog.user_id.in_(user_ids))
103 103 if repo_ids and not user_ids:
104 104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 105 if not repo_ids and user_ids:
106 106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 107 if filtering_criterion is not None:
108 108 journal = Session().query(UserLog)\
109 109 .options(joinedload(UserLog.user))\
110 110 .options(joinedload(UserLog.repository))
111 111 # filter
112 112 try:
113 113 journal = user_log_filter(journal, search_term)
114 114 except Exception:
115 115 # we want this to crash for now
116 116 raise
117 117 journal = journal.filter(filtering_criterion)\
118 118 .order_by(UserLog.action_date.desc())
119 119 else:
120 120 journal = []
121 121
122 122 return journal
123 123
124 124 def feed_uid(self, entry_id):
125 125 return '{}:{}'.format('journal', md5_safe(entry_id))
126 126
127 127 def _atom_feed(self, repos, search_term, public=True):
128 128 _ = self.request.translate
129 129 journal = self._get_journal_data(repos, search_term)
130 130 if public:
131 131 _link = h.route_url('journal_public_atom')
132 132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 133 'atom feed')
134 134 else:
135 135 _link = h.route_url('journal_atom')
136 136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137 137
138 138 feed = Atom1Feed(
139 139 title=_desc, link=_link, description=_desc,
140 140 language=self.language, ttl=self.ttl)
141 141
142 142 for entry in journal[:self.feed_items_per_page]:
143 143 user = entry.user
144 144 if user is None:
145 145 # fix deleted users
146 146 user = AttributeDict({'short_contact': entry.username,
147 147 'email': '',
148 148 'full_contact': ''})
149 149 action, action_extra, ico = h.action_parser(
150 150 self.request, entry, feed=True)
151 151 title = "%s - %s %s" % (user.short_contact, action(),
152 152 entry.repository.repo_name)
153 153 desc = action_extra()
154 154 _url = h.route_url('home')
155 155 if entry.repository is not None:
156 156 _url = h.route_url('repo_changelog',
157 157 repo_name=entry.repository.repo_name)
158 158
159 159 feed.add_item(
160 160 unique_id=self.feed_uid(entry.user_log_id),
161 161 title=title,
162 162 pubdate=entry.action_date,
163 163 link=_url,
164 164 author_email=user.email,
165 165 author_name=user.full_contact,
166 166 description=desc)
167 167
168 168 response = Response(feed.writeString('utf-8'))
169 169 response.content_type = feed.mime_type
170 170 return response
171 171
172 172 def _rss_feed(self, repos, search_term, public=True):
173 173 _ = self.request.translate
174 174 journal = self._get_journal_data(repos, search_term)
175 175 if public:
176 176 _link = h.route_url('journal_public_atom')
177 177 _desc = '%s %s %s' % (
178 178 self.rhodecode_name, _('public journal'), 'rss feed')
179 179 else:
180 180 _link = h.route_url('journal_atom')
181 181 _desc = '%s %s %s' % (
182 182 self.rhodecode_name, _('journal'), 'rss feed')
183 183
184 184 feed = Rss201rev2Feed(
185 185 title=_desc, link=_link, description=_desc,
186 186 language=self.language, ttl=self.ttl)
187 187
188 188 for entry in journal[:self.feed_items_per_page]:
189 189 user = entry.user
190 190 if user is None:
191 191 # fix deleted users
192 192 user = AttributeDict({'short_contact': entry.username,
193 193 'email': '',
194 194 'full_contact': ''})
195 195 action, action_extra, ico = h.action_parser(
196 196 self.request, entry, feed=True)
197 197 title = "%s - %s %s" % (user.short_contact, action(),
198 198 entry.repository.repo_name)
199 199 desc = action_extra()
200 200 _url = h.route_url('home')
201 201 if entry.repository is not None:
202 202 _url = h.route_url('repo_changelog',
203 203 repo_name=entry.repository.repo_name)
204 204
205 205 feed.add_item(
206 206 unique_id=self.feed_uid(entry.user_log_id),
207 207 title=title,
208 208 pubdate=entry.action_date,
209 209 link=_url,
210 210 author_email=user.email,
211 211 author_name=user.full_contact,
212 212 description=desc)
213 213
214 214 response = Response(feed.writeString('utf-8'))
215 215 response.content_type = feed.mime_type
216 216 return response
217 217
218 218 @LoginRequired()
219 219 @NotAnonymous()
220 220 @view_config(
221 221 route_name='journal', request_method='GET',
222 222 renderer=None)
223 223 def journal(self):
224 224 c = self.load_default_context()
225 225
226 226 p = safe_int(self.request.GET.get('page', 1), 1)
227 227 c.user = User.get(self._rhodecode_user.user_id)
228 228 following = Session().query(UserFollowing)\
229 229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
230 230 .options(joinedload(UserFollowing.follows_repository))\
231 231 .all()
232 232
233 233 journal = self._get_journal_data(following, c.search_term)
234 234
235 235 def url_generator(**kw):
236 236 query_params = {
237 237 'filter': c.search_term
238 238 }
239 239 query_params.update(kw)
240 240 return self.request.current_route_path(_query=query_params)
241 241
242 242 c.journal_pager = Page(
243 243 journal, page=p, items_per_page=20, url=url_generator)
244 244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245 245
246 246 c.journal_data = render(
247 247 'rhodecode:templates/journal/journal_data.mako',
248 248 self._get_template_context(c), self.request)
249 249
250 250 if self.request.is_xhr:
251 251 return Response(c.journal_data)
252 252
253 253 html = render(
254 254 'rhodecode:templates/journal/journal.mako',
255 255 self._get_template_context(c), self.request)
256 256 return Response(html)
257 257
258 258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
259 259 @NotAnonymous()
260 260 @view_config(
261 261 route_name='journal_atom', request_method='GET',
262 262 renderer=None)
263 263 def journal_atom(self):
264 264 """
265 265 Produce an atom-1.0 feed via feedgenerator module
266 266 """
267 267 c = self.load_default_context()
268 268 following_repos = Session().query(UserFollowing)\
269 269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
270 270 .options(joinedload(UserFollowing.follows_repository))\
271 271 .all()
272 272 return self._atom_feed(following_repos, c.search_term, public=False)
273 273
274 274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
275 275 @NotAnonymous()
276 276 @view_config(
277 277 route_name='journal_rss', request_method='GET',
278 278 renderer=None)
279 279 def journal_rss(self):
280 280 """
281 281 Produce an rss feed via feedgenerator module
282 282 """
283 283 c = self.load_default_context()
284 284 following_repos = Session().query(UserFollowing)\
285 285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
286 286 .options(joinedload(UserFollowing.follows_repository))\
287 287 .all()
288 288 return self._rss_feed(following_repos, c.search_term, public=False)
289 289
290 290 @LoginRequired()
291 291 @NotAnonymous()
292 292 @CSRFRequired()
293 293 @view_config(
294 294 route_name='toggle_following', request_method='POST',
295 295 renderer='json_ext')
296 296 def toggle_following(self):
297 297 user_id = self.request.POST.get('follows_user_id')
298 298 if user_id:
299 299 try:
300 300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
301 301 Session().commit()
302 302 return 'ok'
303 303 except Exception:
304 304 raise HTTPBadRequest()
305 305
306 306 repo_id = self.request.POST.get('follows_repo_id')
307 if repo_id:
307 repo = Repository.get_or_404(repo_id)
308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
310 if repo and has_perm:
308 311 try:
309 312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
310 313 Session().commit()
311 314 return 'ok'
312 315 except Exception:
313 316 raise HTTPBadRequest()
314 317
315 318 raise HTTPBadRequest()
316 319
317 320 @LoginRequired()
318 321 @view_config(
319 322 route_name='journal_public', request_method='GET',
320 323 renderer=None)
321 324 def journal_public(self):
322 325 c = self.load_default_context()
323 326 # Return a rendered template
324 327 p = safe_int(self.request.GET.get('page', 1), 1)
325 328
326 329 c.following = Session().query(UserFollowing)\
327 330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
328 331 .options(joinedload(UserFollowing.follows_repository))\
329 332 .all()
330 333
331 334 journal = self._get_journal_data(c.following, c.search_term)
332 335
333 336 def url_generator(**kw):
334 337 query_params = {}
335 338 query_params.update(kw)
336 339 return self.request.current_route_path(_query=query_params)
337 340
338 341 c.journal_pager = Page(
339 342 journal, page=p, items_per_page=20, url=url_generator)
340 343 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
341 344
342 345 c.journal_data = render(
343 346 'rhodecode:templates/journal/journal_data.mako',
344 347 self._get_template_context(c), self.request)
345 348
346 349 if self.request.is_xhr:
347 350 return Response(c.journal_data)
348 351
349 352 html = render(
350 353 'rhodecode:templates/journal/public_journal.mako',
351 354 self._get_template_context(c), self.request)
352 355 return Response(html)
353 356
354 357 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
355 358 @view_config(
356 359 route_name='journal_public_atom', request_method='GET',
357 360 renderer=None)
358 361 def journal_public_atom(self):
359 362 """
360 363 Produce an atom-1.0 feed via feedgenerator module
361 364 """
362 365 c = self.load_default_context()
363 366 following_repos = Session().query(UserFollowing)\
364 367 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
365 368 .options(joinedload(UserFollowing.follows_repository))\
366 369 .all()
367 370
368 371 return self._atom_feed(following_repos, c.search_term)
369 372
370 373 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
371 374 @view_config(
372 375 route_name='journal_public_rss', request_method='GET',
373 376 renderer=None)
374 377 def journal_public_rss(self):
375 378 """
376 379 Produce an rss2 feed via feedgenerator module
377 380 """
378 381 c = self.load_default_context()
379 382 following_repos = Session().query(UserFollowing)\
380 383 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
381 384 .options(joinedload(UserFollowing.follows_repository))\
382 385 .all()
383 386
384 387 return self._rss_feed(following_repos, c.search_term)
General Comments 0
You need to be logged in to leave comments. Login now