##// END OF EJS Templates
security: fix self-xss on modifing gist filename.
dan -
r1948:151fcf6c default
parent child Browse files
Show More
@@ -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 _render = self.request.get_partial_renderer(
115 _render = self.request.get_partial_renderer(
116 '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 gist_show(self):
273 def gist_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 = h.escape(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}
General Comments 0
You need to be logged in to leave comments. Login now