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