##// END OF EJS Templates
forks: prevent XSS in datagrid of forks data.
marcink -
r2996:7441eff4 default
parent child Browse files
Show More
@@ -1,263 +1,263 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.celerylib.utils import get_task_id
37 from rhodecode.lib.celerylib.utils import get_task_id
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.forms import RepoForkForm
40 from rhodecode.model.forms import RepoForkForm
41 from rhodecode.model.scm import ScmModel, RepoGroupList
41 from rhodecode.model.scm import ScmModel, RepoGroupList
42 from rhodecode.lib.utils2 import safe_int, safe_unicode
42 from rhodecode.lib.utils2 import safe_int, safe_unicode
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoForksView(RepoAppView, DataGridAppView):
47 class RepoForksView(RepoAppView, DataGridAppView):
48
48
49 def load_default_context(self):
49 def load_default_context(self):
50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c.rhodecode_repo = self.rhodecode_vcs_repo
51 c.rhodecode_repo = self.rhodecode_vcs_repo
52
52
53 acl_groups = RepoGroupList(
53 acl_groups = RepoGroupList(
54 RepoGroup.query().all(),
54 RepoGroup.query().all(),
55 perm_set=['group.write', 'group.admin'])
55 perm_set=['group.write', 'group.admin'])
56 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
56 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
57 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
59 self.request.translate)
59 self.request.translate)
60 c.landing_revs_choices = choices
60 c.landing_revs_choices = choices
61 c.personal_repo_group = c.rhodecode_user.personal_repo_group
61 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62
62
63 return c
63 return c
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator(
66 @HasRepoPermissionAnyDecorator(
67 'repository.read', 'repository.write', 'repository.admin')
67 'repository.read', 'repository.write', 'repository.admin')
68 @view_config(
68 @view_config(
69 route_name='repo_forks_show_all', request_method='GET',
69 route_name='repo_forks_show_all', request_method='GET',
70 renderer='rhodecode:templates/forks/forks.mako')
70 renderer='rhodecode:templates/forks/forks.mako')
71 def repo_forks_show_all(self):
71 def repo_forks_show_all(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73 return self._get_template_context(c)
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator(
76 @HasRepoPermissionAnyDecorator(
77 'repository.read', 'repository.write', 'repository.admin')
77 'repository.read', 'repository.write', 'repository.admin')
78 @view_config(
78 @view_config(
79 route_name='repo_forks_data', request_method='GET',
79 route_name='repo_forks_data', request_method='GET',
80 renderer='json_ext', xhr=True)
80 renderer='json_ext', xhr=True)
81 def repo_forks_data(self):
81 def repo_forks_data(self):
82 _ = self.request.translate
82 _ = self.request.translate
83 self.load_default_context()
83 self.load_default_context()
84 column_map = {
84 column_map = {
85 'fork_name': 'repo_name',
85 'fork_name': 'repo_name',
86 'fork_date': 'created_on',
86 'fork_date': 'created_on',
87 'last_activity': 'updated_on'
87 'last_activity': 'updated_on'
88 }
88 }
89 draw, start, limit = self._extract_chunk(self.request)
89 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
90 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
91 self.request, column_map=column_map)
92
92
93 acl_check = HasRepoPermissionAny(
93 acl_check = HasRepoPermissionAny(
94 'repository.read', 'repository.write', 'repository.admin')
94 'repository.read', 'repository.write', 'repository.admin')
95 repo_id = self.db_repo.repo_id
95 repo_id = self.db_repo.repo_id
96 allowed_ids = [-1]
96 allowed_ids = [-1]
97 for f in Repository.query().filter(Repository.fork_id == repo_id):
97 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 if acl_check(f.repo_name, 'get forks check'):
98 if acl_check(f.repo_name, 'get forks check'):
99 allowed_ids.append(f.repo_id)
99 allowed_ids.append(f.repo_id)
100
100
101 forks_data_total_count = Repository.query()\
101 forks_data_total_count = Repository.query()\
102 .filter(Repository.fork_id == repo_id)\
102 .filter(Repository.fork_id == repo_id)\
103 .filter(Repository.repo_id.in_(allowed_ids))\
103 .filter(Repository.repo_id.in_(allowed_ids))\
104 .count()
104 .count()
105
105
106 # json generate
106 # json generate
107 base_q = Repository.query()\
107 base_q = Repository.query()\
108 .filter(Repository.fork_id == repo_id)\
108 .filter(Repository.fork_id == repo_id)\
109 .filter(Repository.repo_id.in_(allowed_ids))\
109 .filter(Repository.repo_id.in_(allowed_ids))\
110
110
111 if search_q:
111 if search_q:
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 base_q = base_q.filter(or_(
113 base_q = base_q.filter(or_(
114 Repository.repo_name.ilike(like_expression),
114 Repository.repo_name.ilike(like_expression),
115 Repository.description.ilike(like_expression),
115 Repository.description.ilike(like_expression),
116 ))
116 ))
117
117
118 forks_data_total_filtered_count = base_q.count()
118 forks_data_total_filtered_count = base_q.count()
119
119
120 sort_col = getattr(Repository, order_by, None)
120 sort_col = getattr(Repository, order_by, None)
121 if sort_col:
121 if sort_col:
122 if order_dir == 'asc':
122 if order_dir == 'asc':
123 # handle null values properly to order by NULL last
123 # handle null values properly to order by NULL last
124 if order_by in ['last_activity']:
124 if order_by in ['last_activity']:
125 sort_col = coalesce(sort_col, datetime.date.max)
125 sort_col = coalesce(sort_col, datetime.date.max)
126 sort_col = sort_col.asc()
126 sort_col = sort_col.asc()
127 else:
127 else:
128 # handle null values properly to order by NULL last
128 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
129 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.min)
130 sort_col = coalesce(sort_col, datetime.date.min)
131 sort_col = sort_col.desc()
131 sort_col = sort_col.desc()
132
132
133 base_q = base_q.order_by(sort_col)
133 base_q = base_q.order_by(sort_col)
134 base_q = base_q.offset(start).limit(limit)
134 base_q = base_q.offset(start).limit(limit)
135
135
136 fork_list = base_q.all()
136 fork_list = base_q.all()
137
137
138 def fork_actions(fork):
138 def fork_actions(fork):
139 url_link = h.route_path(
139 url_link = h.route_path(
140 'repo_compare',
140 'repo_compare',
141 repo_name=fork.repo_name,
141 repo_name=fork.repo_name,
142 source_ref_type=self.db_repo.landing_rev[0],
142 source_ref_type=self.db_repo.landing_rev[0],
143 source_ref=self.db_repo.landing_rev[1],
143 source_ref=self.db_repo.landing_rev[1],
144 target_ref_type=self.db_repo.landing_rev[0],
144 target_ref_type=self.db_repo.landing_rev[0],
145 target_ref=self.db_repo.landing_rev[1],
145 target_ref=self.db_repo.landing_rev[1],
146 _query=dict(merge=1, target_repo=f.repo_name))
146 _query=dict(merge=1, target_repo=f.repo_name))
147 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
147 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148
148
149 def fork_name(fork):
149 def fork_name(fork):
150 return h.link_to(fork.repo_name,
150 return h.link_to(fork.repo_name,
151 h.route_path('repo_summary', repo_name=fork.repo_name))
151 h.route_path('repo_summary', repo_name=fork.repo_name))
152
152
153 forks_data = []
153 forks_data = []
154 for fork in fork_list:
154 for fork in fork_list:
155 forks_data.append({
155 forks_data.append({
156 "username": h.gravatar_with_user(self.request, fork.user.username),
156 "username": h.gravatar_with_user(self.request, fork.user.username),
157 "fork_name": fork_name(fork),
157 "fork_name": fork_name(fork),
158 "description": fork.description,
158 "description": fork.description_safe,
159 "fork_date": h.age_component(fork.created_on, time_is_local=True),
159 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 "last_activity": h.format_date(fork.updated_on),
160 "last_activity": h.format_date(fork.updated_on),
161 "action": fork_actions(fork),
161 "action": fork_actions(fork),
162 })
162 })
163
163
164 data = ({
164 data = ({
165 'draw': draw,
165 'draw': draw,
166 'data': forks_data,
166 'data': forks_data,
167 'recordsTotal': forks_data_total_count,
167 'recordsTotal': forks_data_total_count,
168 'recordsFiltered': forks_data_total_filtered_count,
168 'recordsFiltered': forks_data_total_filtered_count,
169 })
169 })
170
170
171 return data
171 return data
172
172
173 @LoginRequired()
173 @LoginRequired()
174 @NotAnonymous()
174 @NotAnonymous()
175 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
175 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 @HasRepoPermissionAnyDecorator(
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
177 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
178 @view_config(
179 route_name='repo_fork_new', request_method='GET',
179 route_name='repo_fork_new', request_method='GET',
180 renderer='rhodecode:templates/forks/forks.mako')
180 renderer='rhodecode:templates/forks/forks.mako')
181 def repo_fork_new(self):
181 def repo_fork_new(self):
182 c = self.load_default_context()
182 c = self.load_default_context()
183
183
184 defaults = RepoModel()._get_defaults(self.db_repo_name)
184 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 # alter the description to indicate a fork
185 # alter the description to indicate a fork
186 defaults['description'] = (
186 defaults['description'] = (
187 'fork of repository: %s \n%s' % (
187 'fork of repository: %s \n%s' % (
188 defaults['repo_name'], defaults['description']))
188 defaults['repo_name'], defaults['description']))
189 # add suffix to fork
189 # add suffix to fork
190 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
190 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191
191
192 data = render('rhodecode:templates/forks/fork.mako',
192 data = render('rhodecode:templates/forks/fork.mako',
193 self._get_template_context(c), self.request)
193 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
194 html = formencode.htmlfill.render(
195 data,
195 data,
196 defaults=defaults,
196 defaults=defaults,
197 encoding="UTF-8",
197 encoding="UTF-8",
198 force_defaults=False
198 force_defaults=False
199 )
199 )
200 return Response(html)
200 return Response(html)
201
201
202 @LoginRequired()
202 @LoginRequired()
203 @NotAnonymous()
203 @NotAnonymous()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
204 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 @HasRepoPermissionAnyDecorator(
205 @HasRepoPermissionAnyDecorator(
206 'repository.read', 'repository.write', 'repository.admin')
206 'repository.read', 'repository.write', 'repository.admin')
207 @CSRFRequired()
207 @CSRFRequired()
208 @view_config(
208 @view_config(
209 route_name='repo_fork_create', request_method='POST',
209 route_name='repo_fork_create', request_method='POST',
210 renderer='rhodecode:templates/forks/fork.mako')
210 renderer='rhodecode:templates/forks/fork.mako')
211 def repo_fork_create(self):
211 def repo_fork_create(self):
212 _ = self.request.translate
212 _ = self.request.translate
213 c = self.load_default_context()
213 c = self.load_default_context()
214
214
215 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
215 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
216 repo_groups=c.repo_groups_choices,
216 repo_groups=c.repo_groups_choices,
217 landing_revs=c.landing_revs_choices)()
217 landing_revs=c.landing_revs_choices)()
218 post_data = dict(self.request.POST)
218 post_data = dict(self.request.POST)
219
219
220 # forbid injecting other repo by forging a request
220 # forbid injecting other repo by forging a request
221 post_data['fork_parent_id'] = self.db_repo.repo_id
221 post_data['fork_parent_id'] = self.db_repo.repo_id
222
222
223 form_result = {}
223 form_result = {}
224 task_id = None
224 task_id = None
225 try:
225 try:
226 form_result = _form.to_python(post_data)
226 form_result = _form.to_python(post_data)
227 # create fork is done sometimes async on celery, db transaction
227 # create fork is done sometimes async on celery, db transaction
228 # management is handled there.
228 # management is handled there.
229 task = RepoModel().create_fork(
229 task = RepoModel().create_fork(
230 form_result, c.rhodecode_user.user_id)
230 form_result, c.rhodecode_user.user_id)
231
231
232 task_id = get_task_id(task)
232 task_id = get_task_id(task)
233 except formencode.Invalid as errors:
233 except formencode.Invalid as errors:
234 c.rhodecode_db_repo = self.db_repo
234 c.rhodecode_db_repo = self.db_repo
235
235
236 data = render('rhodecode:templates/forks/fork.mako',
236 data = render('rhodecode:templates/forks/fork.mako',
237 self._get_template_context(c), self.request)
237 self._get_template_context(c), self.request)
238 html = formencode.htmlfill.render(
238 html = formencode.htmlfill.render(
239 data,
239 data,
240 defaults=errors.value,
240 defaults=errors.value,
241 errors=errors.error_dict or {},
241 errors=errors.error_dict or {},
242 prefix_error=False,
242 prefix_error=False,
243 encoding="UTF-8",
243 encoding="UTF-8",
244 force_defaults=False
244 force_defaults=False
245 )
245 )
246 return Response(html)
246 return Response(html)
247 except Exception:
247 except Exception:
248 log.exception(
248 log.exception(
249 u'Exception while trying to fork the repository %s',
249 u'Exception while trying to fork the repository %s',
250 self.db_repo_name)
250 self.db_repo_name)
251 msg = (
251 msg = (
252 _('An error occurred during repository forking %s') % (
252 _('An error occurred during repository forking %s') % (
253 self.db_repo_name, ))
253 self.db_repo_name, ))
254 h.flash(msg, category='error')
254 h.flash(msg, category='error')
255
255
256 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256 repo_name = form_result.get('repo_name_full', self.db_repo_name)
257
257
258 events.trigger(events.UserPermissionsChange([self._rhodecode_user.user_id]))
258 events.trigger(events.UserPermissionsChange([self._rhodecode_user.user_id]))
259
259
260 raise HTTPFound(
260 raise HTTPFound(
261 h.route_path('repo_creating',
261 h.route_path('repo_creating',
262 repo_name=repo_name,
262 repo_name=repo_name,
263 _query=dict(task_id=task_id)))
263 _query=dict(task_id=task_id)))
General Comments 0
You need to be logged in to leave comments. Login now