##// END OF EJS Templates
gists: handle 500 error on sending unsupported format
dan -
r3538:e456c2e1 default
parent child Browse files
Show More
@@ -1,412 +1,414 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import peppercorn
27 27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound, HTTPBadRequest
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import BaseAppView
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 36 from rhodecode.lib.utils2 import time_to_datetime
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
39 39 from rhodecode.model.gist import GistModel
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.db import Gist, User, or_
42 42 from rhodecode.model import validation_schema
43 43 from rhodecode.model.validation_schema.schemas import gist_schema
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class GistView(BaseAppView):
50 50
51 51 def load_default_context(self):
52 52 _ = self.request.translate
53 53 c = self._get_local_tmpl_context()
54 54 c.user = c.auth_user.get_instance()
55 55
56 56 c.lifetime_values = [
57 57 (-1, _('forever')),
58 58 (5, _('5 minutes')),
59 59 (60, _('1 hour')),
60 60 (60 * 24, _('1 day')),
61 61 (60 * 24 * 30, _('1 month')),
62 62 ]
63 63
64 64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
65 65 c.acl_options = [
66 66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
67 67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
68 68 ]
69 69
70 70 return c
71 71
72 72 @LoginRequired()
73 73 @view_config(
74 74 route_name='gists_show', request_method='GET',
75 75 renderer='rhodecode:templates/admin/gists/index.mako')
76 76 def gist_show_all(self):
77 77 c = self.load_default_context()
78 78
79 79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 80 c.show_private = self.request.GET.get('private') and not_default_user
81 81 c.show_public = self.request.GET.get('public') and not_default_user
82 82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83 83
84 84 gists = _gists = Gist().query()\
85 85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 86 .order_by(Gist.created_on.desc())
87 87
88 88 c.active = 'public'
89 89 # MY private
90 90 if c.show_private and not c.show_public:
91 91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 93 c.active = 'my_private'
94 94 # MY public
95 95 elif c.show_public and not c.show_private:
96 96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 98 c.active = 'my_public'
99 99 # MY public+private
100 100 elif c.show_private and c.show_public:
101 101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 102 Gist.gist_type == Gist.GIST_PRIVATE))\
103 103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 104 c.active = 'my_all'
105 105 # Show all by super-admin
106 106 elif c.show_all:
107 107 c.active = 'all'
108 108 gists = _gists
109 109
110 110 # default show ALL public gists
111 111 if not c.show_public and not c.show_private and not c.show_all:
112 112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 113 c.active = 'public'
114 114
115 115 _render = self.request.get_partial_renderer(
116 116 'rhodecode:templates/data_table/_dt_elements.mako')
117 117
118 118 data = []
119 119
120 120 for gist in gists:
121 121 data.append({
122 122 'created_on': _render('gist_created', gist.created_on),
123 123 'created_on_raw': gist.created_on,
124 124 'type': _render('gist_type', gist.gist_type),
125 125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 127 'author_raw': h.escape(gist.owner.full_contact),
128 128 'expires': _render('gist_expires', gist.gist_expires),
129 129 'description': _render('gist_description', gist.gist_description)
130 130 })
131 131 c.data = json.dumps(data)
132 132
133 133 return self._get_template_context(c)
134 134
135 135 @LoginRequired()
136 136 @NotAnonymous()
137 137 @view_config(
138 138 route_name='gists_new', request_method='GET',
139 139 renderer='rhodecode:templates/admin/gists/new.mako')
140 140 def gist_new(self):
141 141 c = self.load_default_context()
142 142 return self._get_template_context(c)
143 143
144 144 @LoginRequired()
145 145 @NotAnonymous()
146 146 @CSRFRequired()
147 147 @view_config(
148 148 route_name='gists_create', request_method='POST',
149 149 renderer='rhodecode:templates/admin/gists/new.mako')
150 150 def gist_create(self):
151 151 _ = self.request.translate
152 152 c = self.load_default_context()
153 153
154 154 data = dict(self.request.POST)
155 155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 156 data['nodes'] = [{
157 157 'filename': data['filename'],
158 158 'content': data.get('content'),
159 159 'mimetype': data.get('mimetype') # None is autodetect
160 160 }]
161 161
162 162 data['gist_type'] = (
163 163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 164 data['gist_acl_level'] = (
165 165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166 166
167 167 schema = gist_schema.GistSchema().bind(
168 168 lifetime_options=[x[0] for x in c.lifetime_values])
169 169
170 170 try:
171 171
172 172 schema_data = schema.deserialize(data)
173 173 # convert to safer format with just KEYs so we sure no duplicates
174 174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 175 schema_data['nodes'])
176 176
177 177 gist = GistModel().create(
178 178 gist_id=schema_data['gistid'], # custom access id not real ID
179 179 description=schema_data['description'],
180 180 owner=self._rhodecode_user.user_id,
181 181 gist_mapping=schema_data['nodes'],
182 182 gist_type=schema_data['gist_type'],
183 183 lifetime=schema_data['lifetime'],
184 184 gist_acl_level=schema_data['gist_acl_level']
185 185 )
186 186 Session().commit()
187 187 new_gist_id = gist.gist_access_id
188 188 except validation_schema.Invalid as errors:
189 189 defaults = data
190 190 errors = errors.asdict()
191 191
192 192 if 'nodes.0.content' in errors:
193 193 errors['content'] = errors['nodes.0.content']
194 194 del errors['nodes.0.content']
195 195 if 'nodes.0.filename' in errors:
196 196 errors['filename'] = errors['nodes.0.filename']
197 197 del errors['nodes.0.filename']
198 198
199 199 data = render('rhodecode:templates/admin/gists/new.mako',
200 200 self._get_template_context(c), self.request)
201 201 html = formencode.htmlfill.render(
202 202 data,
203 203 defaults=defaults,
204 204 errors=errors,
205 205 prefix_error=False,
206 206 encoding="UTF-8",
207 207 force_defaults=False
208 208 )
209 209 return Response(html)
210 210
211 211 except Exception:
212 212 log.exception("Exception while trying to create a gist")
213 213 h.flash(_('Error occurred during gist creation'), category='error')
214 214 raise HTTPFound(h.route_url('gists_new'))
215 215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216 216
217 217 @LoginRequired()
218 218 @NotAnonymous()
219 219 @CSRFRequired()
220 220 @view_config(
221 221 route_name='gist_delete', request_method='POST')
222 222 def gist_delete(self):
223 223 _ = self.request.translate
224 224 gist_id = self.request.matchdict['gist_id']
225 225
226 226 c = self.load_default_context()
227 227 c.gist = Gist.get_or_404(gist_id)
228 228
229 229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 230 if not (h.HasPermissionAny('hg.admin')() or owner):
231 231 log.warning('Deletion of Gist was forbidden '
232 232 'by unauthorized user: `%s`', self._rhodecode_user)
233 233 raise HTTPNotFound()
234 234
235 235 GistModel().delete(c.gist)
236 236 Session().commit()
237 237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238 238
239 239 raise HTTPFound(h.route_url('gists_show'))
240 240
241 241 def _get_gist(self, gist_id):
242 242
243 243 gist = Gist.get_or_404(gist_id)
244 244
245 245 # Check if this gist is expired
246 246 if gist.gist_expires != -1:
247 247 if time.time() > gist.gist_expires:
248 248 log.error(
249 249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 250 raise HTTPNotFound()
251 251
252 252 # check if this gist requires a login
253 253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
255 255 log.error("Anonymous user %s tried to access protected gist `%s`",
256 256 self._rhodecode_user, gist_id)
257 257 raise HTTPNotFound()
258 258 return gist
259 259
260 260 @LoginRequired()
261 261 @view_config(
262 262 route_name='gist_show', request_method='GET',
263 263 renderer='rhodecode:templates/admin/gists/show.mako')
264 264 @view_config(
265 265 route_name='gist_show_rev', request_method='GET',
266 266 renderer='rhodecode:templates/admin/gists/show.mako')
267 267 @view_config(
268 268 route_name='gist_show_formatted', request_method='GET',
269 269 renderer=None)
270 270 @view_config(
271 271 route_name='gist_show_formatted_path', request_method='GET',
272 272 renderer=None)
273 273 def gist_show(self):
274 274 gist_id = self.request.matchdict['gist_id']
275 275
276 276 # TODO(marcink): expose those via matching dict
277 277 revision = self.request.matchdict.get('revision', 'tip')
278 278 f_path = self.request.matchdict.get('f_path', None)
279 279 return_format = self.request.matchdict.get('format')
280 280
281 281 c = self.load_default_context()
282 282 c.gist = self._get_gist(gist_id)
283 283 c.render = not self.request.GET.get('no-render', False)
284 284
285 285 try:
286 286 c.file_last_commit, c.files = GistModel().get_gist_files(
287 287 gist_id, revision=revision)
288 288 except VCSError:
289 289 log.exception("Exception in gist show")
290 290 raise HTTPNotFound()
291 291
292 292 if return_format == 'raw':
293 293 content = '\n\n'.join([f.content for f in c.files
294 294 if (f_path is None or f.path == f_path)])
295 295 response = Response(content)
296 296 response.content_type = 'text/plain'
297 297 return response
298 elif return_format:
299 raise HTTPBadRequest()
298 300
299 301 return self._get_template_context(c)
300 302
301 303 @LoginRequired()
302 304 @NotAnonymous()
303 305 @view_config(
304 306 route_name='gist_edit', request_method='GET',
305 307 renderer='rhodecode:templates/admin/gists/edit.mako')
306 308 def gist_edit(self):
307 309 _ = self.request.translate
308 310 gist_id = self.request.matchdict['gist_id']
309 311 c = self.load_default_context()
310 312 c.gist = self._get_gist(gist_id)
311 313
312 314 owner = c.gist.gist_owner == self._rhodecode_user.user_id
313 315 if not (h.HasPermissionAny('hg.admin')() or owner):
314 316 raise HTTPNotFound()
315 317
316 318 try:
317 319 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
318 320 except VCSError:
319 321 log.exception("Exception in gist edit")
320 322 raise HTTPNotFound()
321 323
322 324 if c.gist.gist_expires == -1:
323 325 expiry = _('never')
324 326 else:
325 327 # this cannot use timeago, since it's used in select2 as a value
326 328 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
327 329
328 330 c.lifetime_values.append(
329 331 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
330 332 )
331 333
332 334 return self._get_template_context(c)
333 335
334 336 @LoginRequired()
335 337 @NotAnonymous()
336 338 @CSRFRequired()
337 339 @view_config(
338 340 route_name='gist_update', request_method='POST',
339 341 renderer='rhodecode:templates/admin/gists/edit.mako')
340 342 def gist_update(self):
341 343 _ = self.request.translate
342 344 gist_id = self.request.matchdict['gist_id']
343 345 c = self.load_default_context()
344 346 c.gist = self._get_gist(gist_id)
345 347
346 348 owner = c.gist.gist_owner == self._rhodecode_user.user_id
347 349 if not (h.HasPermissionAny('hg.admin')() or owner):
348 350 raise HTTPNotFound()
349 351
350 352 data = peppercorn.parse(self.request.POST.items())
351 353
352 354 schema = gist_schema.GistSchema()
353 355 schema = schema.bind(
354 356 # '0' is special value to leave lifetime untouched
355 357 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
356 358 )
357 359
358 360 try:
359 361 schema_data = schema.deserialize(data)
360 362 # convert to safer format with just KEYs so we sure no duplicates
361 363 schema_data['nodes'] = gist_schema.sequence_to_nodes(
362 364 schema_data['nodes'])
363 365
364 366 GistModel().update(
365 367 gist=c.gist,
366 368 description=schema_data['description'],
367 369 owner=c.gist.owner,
368 370 gist_mapping=schema_data['nodes'],
369 371 lifetime=schema_data['lifetime'],
370 372 gist_acl_level=schema_data['gist_acl_level']
371 373 )
372 374
373 375 Session().commit()
374 376 h.flash(_('Successfully updated gist content'), category='success')
375 377 except NodeNotChangedError:
376 378 # raised if nothing was changed in repo itself. We anyway then
377 379 # store only DB stuff for gist
378 380 Session().commit()
379 381 h.flash(_('Successfully updated gist data'), category='success')
380 382 except validation_schema.Invalid as errors:
381 383 errors = h.escape(errors.asdict())
382 384 h.flash(_('Error occurred during update of gist {}: {}').format(
383 385 gist_id, errors), category='error')
384 386 except Exception:
385 387 log.exception("Exception in gist edit")
386 388 h.flash(_('Error occurred during update of gist %s') % gist_id,
387 389 category='error')
388 390
389 391 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
390 392
391 393 @LoginRequired()
392 394 @NotAnonymous()
393 395 @view_config(
394 396 route_name='gist_edit_check_revision', request_method='GET',
395 397 renderer='json_ext')
396 398 def gist_edit_check_revision(self):
397 399 _ = self.request.translate
398 400 gist_id = self.request.matchdict['gist_id']
399 401 c = self.load_default_context()
400 402 c.gist = self._get_gist(gist_id)
401 403
402 404 last_rev = c.gist.scm_instance().get_commit()
403 405 success = True
404 406 revision = self.request.GET.get('revision')
405 407
406 408 if revision != last_rev.raw_id:
407 409 log.error('Last revision %s is different then submitted %s',
408 410 revision, last_rev)
409 411 # our gist has newer version than we
410 412 success = False
411 413
412 414 return {'success': success}
General Comments 0
You need to be logged in to leave comments. Login now