##// END OF EJS Templates
api-utils: added helper to validate repository-group access permissions.
marcink -
r1148:6c251832 default
parent child Browse files
Show More
@@ -1,376 +1,407 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 """
21 """
22 JSON RPC utils
22 JSON RPC utils
23 """
23 """
24
24
25 import collections
25 import collections
26 import logging
26 import logging
27
27
28 from rhodecode.api.exc import JSONRPCError
28 from rhodecode.api.exc import JSONRPCError
29 from rhodecode.lib.auth import HasPermissionAnyApi, HasRepoPermissionAnyApi
29 from rhodecode.lib.auth import HasPermissionAnyApi, HasRepoPermissionAnyApi, \
30 HasRepoGroupPermissionAnyApi
30 from rhodecode.lib.utils import safe_unicode
31 from rhodecode.lib.utils import safe_unicode
31 from rhodecode.controllers.utils import get_commit_from_ref_name
32 from rhodecode.controllers.utils import get_commit_from_ref_name
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.vcs.exceptions import RepositoryError
33
34
34 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
35
36
36
37
37 class OAttr(object):
38 class OAttr(object):
38 """
39 """
39 Special Option that defines other attribute, and can default to them
40 Special Option that defines other attribute, and can default to them
40
41
41 Example::
42 Example::
42
43
43 def test(apiuser, userid=Optional(OAttr('apiuser')):
44 def test(apiuser, userid=Optional(OAttr('apiuser')):
44 user = Optional.extract(userid, evaluate_locals=local())
45 user = Optional.extract(userid, evaluate_locals=local())
45 #if we pass in userid, we get it, else it will default to apiuser
46 #if we pass in userid, we get it, else it will default to apiuser
46 #attribute
47 #attribute
47 """
48 """
48
49
49 def __init__(self, attr_name):
50 def __init__(self, attr_name):
50 self.attr_name = attr_name
51 self.attr_name = attr_name
51
52
52 def __repr__(self):
53 def __repr__(self):
53 return '<OptionalAttr:%s>' % self.attr_name
54 return '<OptionalAttr:%s>' % self.attr_name
54
55
55 def __call__(self):
56 def __call__(self):
56 return self
57 return self
57
58
58
59
59 class Optional(object):
60 class Optional(object):
60 """
61 """
61 Defines an optional parameter::
62 Defines an optional parameter::
62
63
63 param = param.getval() if isinstance(param, Optional) else param
64 param = param.getval() if isinstance(param, Optional) else param
64 param = param() if isinstance(param, Optional) else param
65 param = param() if isinstance(param, Optional) else param
65
66
66 is equivalent of::
67 is equivalent of::
67
68
68 param = Optional.extract(param)
69 param = Optional.extract(param)
69
70
70 """
71 """
71
72
72 def __init__(self, type_):
73 def __init__(self, type_):
73 self.type_ = type_
74 self.type_ = type_
74
75
75 def __repr__(self):
76 def __repr__(self):
76 return '<Optional:%s>' % self.type_.__repr__()
77 return '<Optional:%s>' % self.type_.__repr__()
77
78
78 def __call__(self):
79 def __call__(self):
79 return self.getval()
80 return self.getval()
80
81
81 def getval(self, evaluate_locals=None):
82 def getval(self, evaluate_locals=None):
82 """
83 """
83 returns value from this Optional instance
84 returns value from this Optional instance
84 """
85 """
85 if isinstance(self.type_, OAttr):
86 if isinstance(self.type_, OAttr):
86 param_name = self.type_.attr_name
87 param_name = self.type_.attr_name
87 if evaluate_locals:
88 if evaluate_locals:
88 return evaluate_locals[param_name]
89 return evaluate_locals[param_name]
89 # use params name
90 # use params name
90 return param_name
91 return param_name
91 return self.type_
92 return self.type_
92
93
93 @classmethod
94 @classmethod
94 def extract(cls, val, evaluate_locals=None):
95 def extract(cls, val, evaluate_locals=None):
95 """
96 """
96 Extracts value from Optional() instance
97 Extracts value from Optional() instance
97
98
98 :param val:
99 :param val:
99 :return: original value if it's not Optional instance else
100 :return: original value if it's not Optional instance else
100 value of instance
101 value of instance
101 """
102 """
102 if isinstance(val, cls):
103 if isinstance(val, cls):
103 return val.getval(evaluate_locals)
104 return val.getval(evaluate_locals)
104 return val
105 return val
105
106
106
107
107 def parse_args(cli_args, key_prefix=''):
108 def parse_args(cli_args, key_prefix=''):
108 from rhodecode.lib.utils2 import (escape_split)
109 from rhodecode.lib.utils2 import (escape_split)
109 kwargs = collections.defaultdict(dict)
110 kwargs = collections.defaultdict(dict)
110 for el in escape_split(cli_args, ','):
111 for el in escape_split(cli_args, ','):
111 kv = escape_split(el, '=', 1)
112 kv = escape_split(el, '=', 1)
112 if len(kv) == 2:
113 if len(kv) == 2:
113 k, v = kv
114 k, v = kv
114 kwargs[key_prefix + k] = v
115 kwargs[key_prefix + k] = v
115 return kwargs
116 return kwargs
116
117
117
118
118 def get_origin(obj):
119 def get_origin(obj):
119 """
120 """
120 Get origin of permission from object.
121 Get origin of permission from object.
121
122
122 :param obj:
123 :param obj:
123 """
124 """
124 origin = 'permission'
125 origin = 'permission'
125
126
126 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
127 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
127 # admin and owner case, maybe we should use dual string ?
128 # admin and owner case, maybe we should use dual string ?
128 origin = 'owner'
129 origin = 'owner'
129 elif getattr(obj, 'owner_row', ''):
130 elif getattr(obj, 'owner_row', ''):
130 origin = 'owner'
131 origin = 'owner'
131 elif getattr(obj, 'admin_row', ''):
132 elif getattr(obj, 'admin_row', ''):
132 origin = 'super-admin'
133 origin = 'super-admin'
133 return origin
134 return origin
134
135
135
136
136 def store_update(updates, attr, name):
137 def store_update(updates, attr, name):
137 """
138 """
138 Stores param in updates dict if it's not instance of Optional
139 Stores param in updates dict if it's not instance of Optional
139 allows easy updates of passed in params
140 allows easy updates of passed in params
140 """
141 """
141 if not isinstance(attr, Optional):
142 if not isinstance(attr, Optional):
142 updates[name] = attr
143 updates[name] = attr
143
144
144
145
145 def has_superadmin_permission(apiuser):
146 def has_superadmin_permission(apiuser):
146 """
147 """
147 Return True if apiuser is admin or return False
148 Return True if apiuser is admin or return False
148
149
149 :param apiuser:
150 :param apiuser:
150 """
151 """
151 if HasPermissionAnyApi('hg.admin')(user=apiuser):
152 if HasPermissionAnyApi('hg.admin')(user=apiuser):
152 return True
153 return True
153 return False
154 return False
154
155
155
156
156 def has_repo_permissions(apiuser, repoid, repo, perms):
157 def has_repo_permissions(apiuser, repoid, repo, perms):
157 """
158 """
158 Raise JsonRPCError if apiuser is not authorized or return True
159 Raise JsonRPCError if apiuser is not authorized or return True
159
160
160 :param apiuser:
161 :param apiuser:
161 :param repoid:
162 :param repoid:
162 :param repo:
163 :param repo:
163 :param perms:
164 :param perms:
164 """
165 """
165 if not HasRepoPermissionAnyApi(*perms)(
166 if not HasRepoPermissionAnyApi(*perms)(
166 user=apiuser, repo_name=repo.repo_name):
167 user=apiuser, repo_name=repo.repo_name):
167 raise JSONRPCError(
168 raise JSONRPCError(
168 'repository `%s` does not exist' % repoid)
169 'repository `%s` does not exist' % repoid)
169
170
170 return True
171 return True
171
172
172
173
174 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
175 """
176 Raise JsonRPCError if apiuser is not authorized or return True
177
178 :param apiuser:
179 :param repogroupid: just the id of repository group
180 :param repo_group: instance of repo_group
181 :param perms:
182 """
183 if not HasRepoGroupPermissionAnyApi(*perms)(
184 user=apiuser, group_name=repo_group.group_name):
185 raise JSONRPCError(
186 'repository group `%s` does not exist' % repogroupid)
187
188 return True
189
190
191 def has_set_owner_permissions(apiuser, owner):
192 if isinstance(owner, Optional):
193 owner = get_user_or_error(apiuser.user_id)
194 else:
195 if has_superadmin_permission(apiuser):
196 owner = get_user_or_error(owner)
197 else:
198 # forbid setting owner for non-admins
199 raise JSONRPCError(
200 'Only RhodeCode super-admin can specify `owner` param')
201 return owner
202
203
173 def get_user_or_error(userid):
204 def get_user_or_error(userid):
174 """
205 """
175 Get user by id or name or return JsonRPCError if not found
206 Get user by id or name or return JsonRPCError if not found
176
207
177 :param userid:
208 :param userid:
178 """
209 """
179 from rhodecode.model.user import UserModel
210 from rhodecode.model.user import UserModel
180
211
181 user_model = UserModel()
212 user_model = UserModel()
182 try:
213 try:
183 user = user_model.get_user(int(userid))
214 user = user_model.get_user(int(userid))
184 except ValueError:
215 except ValueError:
185 user = user_model.get_by_username(userid)
216 user = user_model.get_by_username(userid)
186
217
187 if user is None:
218 if user is None:
188 raise JSONRPCError("user `%s` does not exist" % (userid,))
219 raise JSONRPCError("user `%s` does not exist" % (userid,))
189 return user
220 return user
190
221
191
222
192 def get_repo_or_error(repoid):
223 def get_repo_or_error(repoid):
193 """
224 """
194 Get repo by id or name or return JsonRPCError if not found
225 Get repo by id or name or return JsonRPCError if not found
195
226
196 :param repoid:
227 :param repoid:
197 """
228 """
198 from rhodecode.model.repo import RepoModel
229 from rhodecode.model.repo import RepoModel
199
230
200 repo = RepoModel().get_repo(repoid)
231 repo = RepoModel().get_repo(repoid)
201 if repo is None:
232 if repo is None:
202 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
233 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
203 return repo
234 return repo
204
235
205
236
206 def get_repo_group_or_error(repogroupid):
237 def get_repo_group_or_error(repogroupid):
207 """
238 """
208 Get repo group by id or name or return JsonRPCError if not found
239 Get repo group by id or name or return JsonRPCError if not found
209
240
210 :param repogroupid:
241 :param repogroupid:
211 """
242 """
212 from rhodecode.model.repo_group import RepoGroupModel
243 from rhodecode.model.repo_group import RepoGroupModel
213
244
214 repo_group = RepoGroupModel()._get_repo_group(repogroupid)
245 repo_group = RepoGroupModel()._get_repo_group(repogroupid)
215 if repo_group is None:
246 if repo_group is None:
216 raise JSONRPCError(
247 raise JSONRPCError(
217 'repository group `%s` does not exist' % (repogroupid,))
248 'repository group `%s` does not exist' % (repogroupid,))
218 return repo_group
249 return repo_group
219
250
220
251
221 def get_user_group_or_error(usergroupid):
252 def get_user_group_or_error(usergroupid):
222 """
253 """
223 Get user group by id or name or return JsonRPCError if not found
254 Get user group by id or name or return JsonRPCError if not found
224
255
225 :param usergroupid:
256 :param usergroupid:
226 """
257 """
227 from rhodecode.model.user_group import UserGroupModel
258 from rhodecode.model.user_group import UserGroupModel
228
259
229 user_group = UserGroupModel().get_group(usergroupid)
260 user_group = UserGroupModel().get_group(usergroupid)
230 if user_group is None:
261 if user_group is None:
231 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
262 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
232 return user_group
263 return user_group
233
264
234
265
235 def get_perm_or_error(permid, prefix=None):
266 def get_perm_or_error(permid, prefix=None):
236 """
267 """
237 Get permission by id or name or return JsonRPCError if not found
268 Get permission by id or name or return JsonRPCError if not found
238
269
239 :param permid:
270 :param permid:
240 """
271 """
241 from rhodecode.model.permission import PermissionModel
272 from rhodecode.model.permission import PermissionModel
242
273
243 perm = PermissionModel.cls.get_by_key(permid)
274 perm = PermissionModel.cls.get_by_key(permid)
244 if perm is None:
275 if perm is None:
245 raise JSONRPCError('permission `%s` does not exist' % (permid,))
276 raise JSONRPCError('permission `%s` does not exist' % (permid,))
246 if prefix:
277 if prefix:
247 if not perm.permission_name.startswith(prefix):
278 if not perm.permission_name.startswith(prefix):
248 raise JSONRPCError('permission `%s` is invalid, '
279 raise JSONRPCError('permission `%s` is invalid, '
249 'should start with %s' % (permid, prefix))
280 'should start with %s' % (permid, prefix))
250 return perm
281 return perm
251
282
252
283
253 def get_gist_or_error(gistid):
284 def get_gist_or_error(gistid):
254 """
285 """
255 Get gist by id or gist_access_id or return JsonRPCError if not found
286 Get gist by id or gist_access_id or return JsonRPCError if not found
256
287
257 :param gistid:
288 :param gistid:
258 """
289 """
259 from rhodecode.model.gist import GistModel
290 from rhodecode.model.gist import GistModel
260
291
261 gist = GistModel.cls.get_by_access_id(gistid)
292 gist = GistModel.cls.get_by_access_id(gistid)
262 if gist is None:
293 if gist is None:
263 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
294 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
264 return gist
295 return gist
265
296
266
297
267 def get_pull_request_or_error(pullrequestid):
298 def get_pull_request_or_error(pullrequestid):
268 """
299 """
269 Get pull request by id or return JsonRPCError if not found
300 Get pull request by id or return JsonRPCError if not found
270
301
271 :param pullrequestid:
302 :param pullrequestid:
272 """
303 """
273 from rhodecode.model.pull_request import PullRequestModel
304 from rhodecode.model.pull_request import PullRequestModel
274
305
275 try:
306 try:
276 pull_request = PullRequestModel().get(int(pullrequestid))
307 pull_request = PullRequestModel().get(int(pullrequestid))
277 except ValueError:
308 except ValueError:
278 raise JSONRPCError('pullrequestid must be an integer')
309 raise JSONRPCError('pullrequestid must be an integer')
279 if not pull_request:
310 if not pull_request:
280 raise JSONRPCError('pull request `%s` does not exist' % (
311 raise JSONRPCError('pull request `%s` does not exist' % (
281 pullrequestid,))
312 pullrequestid,))
282 return pull_request
313 return pull_request
283
314
284
315
285 def build_commit_data(commit, detail_level):
316 def build_commit_data(commit, detail_level):
286 parsed_diff = []
317 parsed_diff = []
287 if detail_level == 'extended':
318 if detail_level == 'extended':
288 for f in commit.added:
319 for f in commit.added:
289 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
320 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
290 for f in commit.changed:
321 for f in commit.changed:
291 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
322 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
292 for f in commit.removed:
323 for f in commit.removed:
293 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
324 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
294
325
295 elif detail_level == 'full':
326 elif detail_level == 'full':
296 from rhodecode.lib.diffs import DiffProcessor
327 from rhodecode.lib.diffs import DiffProcessor
297 diff_processor = DiffProcessor(commit.diff())
328 diff_processor = DiffProcessor(commit.diff())
298 for dp in diff_processor.prepare():
329 for dp in diff_processor.prepare():
299 del dp['stats']['ops']
330 del dp['stats']['ops']
300 _stats = dp['stats']
331 _stats = dp['stats']
301 parsed_diff.append(_get_commit_dict(
332 parsed_diff.append(_get_commit_dict(
302 filename=dp['filename'], op=dp['operation'],
333 filename=dp['filename'], op=dp['operation'],
303 new_revision=dp['new_revision'],
334 new_revision=dp['new_revision'],
304 old_revision=dp['old_revision'],
335 old_revision=dp['old_revision'],
305 raw_diff=dp['raw_diff'], stats=_stats))
336 raw_diff=dp['raw_diff'], stats=_stats))
306
337
307 return parsed_diff
338 return parsed_diff
308
339
309
340
310 def get_commit_or_error(ref, repo):
341 def get_commit_or_error(ref, repo):
311 try:
342 try:
312 ref_type, _, ref_hash = ref.split(':')
343 ref_type, _, ref_hash = ref.split(':')
313 except ValueError:
344 except ValueError:
314 raise JSONRPCError(
345 raise JSONRPCError(
315 'Ref `{ref}` given in a wrong format. Please check the API'
346 'Ref `{ref}` given in a wrong format. Please check the API'
316 ' documentation for more details'.format(ref=ref))
347 ' documentation for more details'.format(ref=ref))
317 try:
348 try:
318 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
349 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
319 # once get_commit supports ref_types
350 # once get_commit supports ref_types
320 return get_commit_from_ref_name(repo, ref_hash)
351 return get_commit_from_ref_name(repo, ref_hash)
321 except RepositoryError:
352 except RepositoryError:
322 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
353 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
323
354
324
355
325 def resolve_ref_or_error(ref, repo):
356 def resolve_ref_or_error(ref, repo):
326 def _parse_ref(type_, name, hash_=None):
357 def _parse_ref(type_, name, hash_=None):
327 return type_, name, hash_
358 return type_, name, hash_
328
359
329 try:
360 try:
330 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
361 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
331 except TypeError:
362 except TypeError:
332 raise JSONRPCError(
363 raise JSONRPCError(
333 'Ref `{ref}` given in a wrong format. Please check the API'
364 'Ref `{ref}` given in a wrong format. Please check the API'
334 ' documentation for more details'.format(ref=ref))
365 ' documentation for more details'.format(ref=ref))
335
366
336 try:
367 try:
337 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
368 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
338 except (KeyError, ValueError):
369 except (KeyError, ValueError):
339 raise JSONRPCError(
370 raise JSONRPCError(
340 'The specified {type} `{name}` does not exist'.format(
371 'The specified {type} `{name}` does not exist'.format(
341 type=ref_type, name=ref_name))
372 type=ref_type, name=ref_name))
342
373
343 return ':'.join([ref_type, ref_name, ref_hash])
374 return ':'.join([ref_type, ref_name, ref_hash])
344
375
345
376
346 def _get_commit_dict(
377 def _get_commit_dict(
347 filename, op, new_revision=None, old_revision=None,
378 filename, op, new_revision=None, old_revision=None,
348 raw_diff=None, stats=None):
379 raw_diff=None, stats=None):
349 if stats is None:
380 if stats is None:
350 stats = {
381 stats = {
351 "added": None,
382 "added": None,
352 "binary": None,
383 "binary": None,
353 "deleted": None
384 "deleted": None
354 }
385 }
355 return {
386 return {
356 "filename": safe_unicode(filename),
387 "filename": safe_unicode(filename),
357 "op": op,
388 "op": op,
358
389
359 # extra details
390 # extra details
360 "new_revision": new_revision,
391 "new_revision": new_revision,
361 "old_revision": old_revision,
392 "old_revision": old_revision,
362
393
363 "raw_diff": raw_diff,
394 "raw_diff": raw_diff,
364 "stats": stats
395 "stats": stats
365 }
396 }
366
397
367
398
368 # TODO: mikhail: Think about moving this function to some library
399 # TODO: mikhail: Think about moving this function to some library
369 def _get_ref_hash(repo, type_, name):
400 def _get_ref_hash(repo, type_, name):
370 vcs_repo = repo.scm_instance()
401 vcs_repo = repo.scm_instance()
371 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
402 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
372 return vcs_repo.branches[name]
403 return vcs_repo.branches[name]
373 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
404 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
374 return vcs_repo.bookmarks[name]
405 return vcs_repo.bookmarks[name]
375 else:
406 else:
376 raise ValueError()
407 raise ValueError()
General Comments 0
You need to be logged in to leave comments. Login now