##// END OF EJS Templates
release: update copyright year to 2018
marcink -
r2487:fcee5614 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,63 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 """
22 22
23 23 RhodeCode, a web based repository management software
24 24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 25 """
26 26
27 27 import os
28 28 import sys
29 29 import platform
30 30
31 31 VERSION = tuple(open(os.path.join(
32 32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33 33
34 34 BACKENDS = {
35 35 'hg': 'Mercurial repository',
36 36 'git': 'Git repository',
37 37 'svn': 'Subversion repository',
38 38 }
39 39
40 40 CELERY_ENABLED = False
41 41 CELERY_EAGER = False
42 42
43 43 # link to config for pyramid
44 44 CONFIG = {}
45 45
46 46 # Populated with the settings dictionary from application init in
47 47 # rhodecode.conf.environment.load_pyramid_environment
48 48 PYRAMID_SETTINGS = {}
49 49
50 50 # Linked module for extensions
51 51 EXTENSIONS = {}
52 52
53 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 54 __dbversion__ = 85 # defines current db version for migrations
55 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
58 58 __url__ = 'https://code.rhodecode.com'
59 59
60 60 is_windows = __platform__ in ['Windows']
61 61 is_unix = not is_windows
62 62 is_test = False
63 63 disable_error_handler = False
@@ -1,542 +1,542 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 inspect
22 22 import itertools
23 23 import logging
24 24 import types
25 25 import fnmatch
26 26
27 27 import decorator
28 28 import venusian
29 29 from collections import OrderedDict
30 30
31 31 from pyramid.exceptions import ConfigurationError
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34 from pyramid.httpexceptions import HTTPNotFound
35 35
36 36 from rhodecode.api.exc import (
37 37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.lib.auth import AuthUser
40 40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 41 from rhodecode.lib.ext_json import json
42 42 from rhodecode.lib.utils2 import safe_str
43 43 from rhodecode.lib.plugins.utils import get_plugin_settings
44 44 from rhodecode.model.db import User, UserApiKeys
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48 DEFAULT_RENDERER = 'jsonrpc_renderer'
49 49 DEFAULT_URL = '/_admin/apiv2'
50 50
51 51
52 52 def find_methods(jsonrpc_methods, pattern):
53 53 matches = OrderedDict()
54 54 if not isinstance(pattern, (list, tuple)):
55 55 pattern = [pattern]
56 56
57 57 for single_pattern in pattern:
58 58 for method_name, method in jsonrpc_methods.items():
59 59 if fnmatch.fnmatch(method_name, single_pattern):
60 60 matches[method_name] = method
61 61 return matches
62 62
63 63
64 64 class ExtJsonRenderer(object):
65 65 """
66 66 Custom renderer that mkaes use of our ext_json lib
67 67
68 68 """
69 69
70 70 def __init__(self, serializer=json.dumps, **kw):
71 71 """ Any keyword arguments will be passed to the ``serializer``
72 72 function."""
73 73 self.serializer = serializer
74 74 self.kw = kw
75 75
76 76 def __call__(self, info):
77 77 """ Returns a plain JSON-encoded string with content-type
78 78 ``application/json``. The content-type may be overridden by
79 79 setting ``request.response.content_type``."""
80 80
81 81 def _render(value, system):
82 82 request = system.get('request')
83 83 if request is not None:
84 84 response = request.response
85 85 ct = response.content_type
86 86 if ct == response.default_content_type:
87 87 response.content_type = 'application/json'
88 88
89 89 return self.serializer(value, **self.kw)
90 90
91 91 return _render
92 92
93 93
94 94 def jsonrpc_response(request, result):
95 95 rpc_id = getattr(request, 'rpc_id', None)
96 96 response = request.response
97 97
98 98 # store content_type before render is called
99 99 ct = response.content_type
100 100
101 101 ret_value = ''
102 102 if rpc_id:
103 103 ret_value = {
104 104 'id': rpc_id,
105 105 'result': result,
106 106 'error': None,
107 107 }
108 108
109 109 # fetch deprecation warnings, and store it inside results
110 110 deprecation = getattr(request, 'rpc_deprecation', None)
111 111 if deprecation:
112 112 ret_value['DEPRECATION_WARNING'] = deprecation
113 113
114 114 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
115 115 response.body = safe_str(raw_body, response.charset)
116 116
117 117 if ct == response.default_content_type:
118 118 response.content_type = 'application/json'
119 119
120 120 return response
121 121
122 122
123 123 def jsonrpc_error(request, message, retid=None, code=None):
124 124 """
125 125 Generate a Response object with a JSON-RPC error body
126 126
127 127 :param code:
128 128 :param retid:
129 129 :param message:
130 130 """
131 131 err_dict = {'id': retid, 'result': None, 'error': message}
132 132 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
133 133 return Response(
134 134 body=body,
135 135 status=code,
136 136 content_type='application/json'
137 137 )
138 138
139 139
140 140 def exception_view(exc, request):
141 141 rpc_id = getattr(request, 'rpc_id', None)
142 142
143 143 fault_message = 'undefined error'
144 144 if isinstance(exc, JSONRPCError):
145 145 fault_message = exc.message
146 146 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
147 147 elif isinstance(exc, JSONRPCValidationError):
148 148 colander_exc = exc.colander_exception
149 149 # TODO(marcink): think maybe of nicer way to serialize errors ?
150 150 fault_message = colander_exc.asdict()
151 151 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
152 152 elif isinstance(exc, JSONRPCForbidden):
153 153 fault_message = 'Access was denied to this resource.'
154 154 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
155 155 elif isinstance(exc, HTTPNotFound):
156 156 method = request.rpc_method
157 157 log.debug('json-rpc method `%s` not found in list of '
158 158 'api calls: %s, rpc_id:%s',
159 159 method, request.registry.jsonrpc_methods.keys(), rpc_id)
160 160
161 161 similar = 'none'
162 162 try:
163 163 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
164 164 similar_found = find_methods(
165 165 request.registry.jsonrpc_methods, similar_paterns)
166 166 similar = ', '.join(similar_found.keys()) or similar
167 167 except Exception:
168 168 # make the whole above block safe
169 169 pass
170 170
171 171 fault_message = "No such method: {}. Similar methods: {}".format(
172 172 method, similar)
173 173
174 174 return jsonrpc_error(request, fault_message, rpc_id)
175 175
176 176
177 177 def request_view(request):
178 178 """
179 179 Main request handling method. It handles all logic to call a specific
180 180 exposed method
181 181 """
182 182
183 183 # check if we can find this session using api_key, get_by_auth_token
184 184 # search not expired tokens only
185 185
186 186 try:
187 187 api_user = User.get_by_auth_token(request.rpc_api_key)
188 188
189 189 if api_user is None:
190 190 return jsonrpc_error(
191 191 request, retid=request.rpc_id, message='Invalid API KEY')
192 192
193 193 if not api_user.active:
194 194 return jsonrpc_error(
195 195 request, retid=request.rpc_id,
196 196 message='Request from this user not allowed')
197 197
198 198 # check if we are allowed to use this IP
199 199 auth_u = AuthUser(
200 200 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
201 201 if not auth_u.ip_allowed:
202 202 return jsonrpc_error(
203 203 request, retid=request.rpc_id,
204 204 message='Request from IP:%s not allowed' % (
205 205 request.rpc_ip_addr,))
206 206 else:
207 207 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
208 208
209 209 # register our auth-user
210 210 request.rpc_user = auth_u
211 211
212 212 # now check if token is valid for API
213 213 auth_token = request.rpc_api_key
214 214 token_match = api_user.authenticate_by_token(
215 215 auth_token, roles=[UserApiKeys.ROLE_API])
216 216 invalid_token = not token_match
217 217
218 218 log.debug('Checking if API KEY is valid with proper role')
219 219 if invalid_token:
220 220 return jsonrpc_error(
221 221 request, retid=request.rpc_id,
222 222 message='API KEY invalid or, has bad role for an API call')
223 223
224 224 except Exception:
225 225 log.exception('Error on API AUTH')
226 226 return jsonrpc_error(
227 227 request, retid=request.rpc_id, message='Invalid API KEY')
228 228
229 229 method = request.rpc_method
230 230 func = request.registry.jsonrpc_methods[method]
231 231
232 232 # now that we have a method, add request._req_params to
233 233 # self.kargs and dispatch control to WGIController
234 234 argspec = inspect.getargspec(func)
235 235 arglist = argspec[0]
236 236 defaults = map(type, argspec[3] or [])
237 237 default_empty = types.NotImplementedType
238 238
239 239 # kw arguments required by this method
240 240 func_kwargs = dict(itertools.izip_longest(
241 241 reversed(arglist), reversed(defaults), fillvalue=default_empty))
242 242
243 243 # This attribute will need to be first param of a method that uses
244 244 # api_key, which is translated to instance of user at that name
245 245 user_var = 'apiuser'
246 246 request_var = 'request'
247 247
248 248 for arg in [user_var, request_var]:
249 249 if arg not in arglist:
250 250 return jsonrpc_error(
251 251 request,
252 252 retid=request.rpc_id,
253 253 message='This method [%s] does not support '
254 254 'required parameter `%s`' % (func.__name__, arg))
255 255
256 256 # get our arglist and check if we provided them as args
257 257 for arg, default in func_kwargs.items():
258 258 if arg in [user_var, request_var]:
259 259 # user_var and request_var are pre-hardcoded parameters and we
260 260 # don't need to do any translation
261 261 continue
262 262
263 263 # skip the required param check if it's default value is
264 264 # NotImplementedType (default_empty)
265 265 if default == default_empty and arg not in request.rpc_params:
266 266 return jsonrpc_error(
267 267 request,
268 268 retid=request.rpc_id,
269 269 message=('Missing non optional `%s` arg in JSON DATA' % arg)
270 270 )
271 271
272 272 # sanitize extra passed arguments
273 273 for k in request.rpc_params.keys()[:]:
274 274 if k not in func_kwargs:
275 275 del request.rpc_params[k]
276 276
277 277 call_params = request.rpc_params
278 278 call_params.update({
279 279 'request': request,
280 280 'apiuser': auth_u
281 281 })
282 282
283 283 # register some common functions for usage
284 284 attach_context_attributes(
285 285 TemplateArgs(), request, request.rpc_user.user_id)
286 286
287 287 try:
288 288 ret_value = func(**call_params)
289 289 return jsonrpc_response(request, ret_value)
290 290 except JSONRPCBaseError:
291 291 raise
292 292 except Exception:
293 293 log.exception('Unhandled exception occurred on api call: %s', func)
294 294 return jsonrpc_error(request, retid=request.rpc_id,
295 295 message='Internal server error')
296 296
297 297
298 298 def setup_request(request):
299 299 """
300 300 Parse a JSON-RPC request body. It's used inside the predicates method
301 301 to validate and bootstrap requests for usage in rpc calls.
302 302
303 303 We need to raise JSONRPCError here if we want to return some errors back to
304 304 user.
305 305 """
306 306
307 307 log.debug('Executing setup request: %r', request)
308 308 request.rpc_ip_addr = get_ip_addr(request.environ)
309 309 # TODO(marcink): deprecate GET at some point
310 310 if request.method not in ['POST', 'GET']:
311 311 log.debug('unsupported request method "%s"', request.method)
312 312 raise JSONRPCError(
313 313 'unsupported request method "%s". Please use POST' % request.method)
314 314
315 315 if 'CONTENT_LENGTH' not in request.environ:
316 316 log.debug("No Content-Length")
317 317 raise JSONRPCError("Empty body, No Content-Length in request")
318 318
319 319 else:
320 320 length = request.environ['CONTENT_LENGTH']
321 321 log.debug('Content-Length: %s', length)
322 322
323 323 if length == 0:
324 324 log.debug("Content-Length is 0")
325 325 raise JSONRPCError("Content-Length is 0")
326 326
327 327 raw_body = request.body
328 328 try:
329 329 json_body = json.loads(raw_body)
330 330 except ValueError as e:
331 331 # catch JSON errors Here
332 332 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
333 333
334 334 request.rpc_id = json_body.get('id')
335 335 request.rpc_method = json_body.get('method')
336 336
337 337 # check required base parameters
338 338 try:
339 339 api_key = json_body.get('api_key')
340 340 if not api_key:
341 341 api_key = json_body.get('auth_token')
342 342
343 343 if not api_key:
344 344 raise KeyError('api_key or auth_token')
345 345
346 346 # TODO(marcink): support passing in token in request header
347 347
348 348 request.rpc_api_key = api_key
349 349 request.rpc_id = json_body['id']
350 350 request.rpc_method = json_body['method']
351 351 request.rpc_params = json_body['args'] \
352 352 if isinstance(json_body['args'], dict) else {}
353 353
354 354 log.debug(
355 355 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
356 356 except KeyError as e:
357 357 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
358 358
359 359 log.debug('setup complete, now handling method:%s rpcid:%s',
360 360 request.rpc_method, request.rpc_id, )
361 361
362 362
363 363 class RoutePredicate(object):
364 364 def __init__(self, val, config):
365 365 self.val = val
366 366
367 367 def text(self):
368 368 return 'jsonrpc route = %s' % self.val
369 369
370 370 phash = text
371 371
372 372 def __call__(self, info, request):
373 373 if self.val:
374 374 # potentially setup and bootstrap our call
375 375 setup_request(request)
376 376
377 377 # Always return True so that even if it isn't a valid RPC it
378 378 # will fall through to the underlaying handlers like notfound_view
379 379 return True
380 380
381 381
382 382 class NotFoundPredicate(object):
383 383 def __init__(self, val, config):
384 384 self.val = val
385 385 self.methods = config.registry.jsonrpc_methods
386 386
387 387 def text(self):
388 388 return 'jsonrpc method not found = {}.'.format(self.val)
389 389
390 390 phash = text
391 391
392 392 def __call__(self, info, request):
393 393 return hasattr(request, 'rpc_method')
394 394
395 395
396 396 class MethodPredicate(object):
397 397 def __init__(self, val, config):
398 398 self.method = val
399 399
400 400 def text(self):
401 401 return 'jsonrpc method = %s' % self.method
402 402
403 403 phash = text
404 404
405 405 def __call__(self, context, request):
406 406 # we need to explicitly return False here, so pyramid doesn't try to
407 407 # execute our view directly. We need our main handler to execute things
408 408 return getattr(request, 'rpc_method') == self.method
409 409
410 410
411 411 def add_jsonrpc_method(config, view, **kwargs):
412 412 # pop the method name
413 413 method = kwargs.pop('method', None)
414 414
415 415 if method is None:
416 416 raise ConfigurationError(
417 417 'Cannot register a JSON-RPC method without specifying the '
418 418 '"method"')
419 419
420 420 # we define custom predicate, to enable to detect conflicting methods,
421 421 # those predicates are kind of "translation" from the decorator variables
422 422 # to internal predicates names
423 423
424 424 kwargs['jsonrpc_method'] = method
425 425
426 426 # register our view into global view store for validation
427 427 config.registry.jsonrpc_methods[method] = view
428 428
429 429 # we're using our main request_view handler, here, so each method
430 430 # has a unified handler for itself
431 431 config.add_view(request_view, route_name='apiv2', **kwargs)
432 432
433 433
434 434 class jsonrpc_method(object):
435 435 """
436 436 decorator that works similar to @add_view_config decorator,
437 437 but tailored for our JSON RPC
438 438 """
439 439
440 440 venusian = venusian # for testing injection
441 441
442 442 def __init__(self, method=None, **kwargs):
443 443 self.method = method
444 444 self.kwargs = kwargs
445 445
446 446 def __call__(self, wrapped):
447 447 kwargs = self.kwargs.copy()
448 448 kwargs['method'] = self.method or wrapped.__name__
449 449 depth = kwargs.pop('_depth', 0)
450 450
451 451 def callback(context, name, ob):
452 452 config = context.config.with_package(info.module)
453 453 config.add_jsonrpc_method(view=ob, **kwargs)
454 454
455 455 info = venusian.attach(wrapped, callback, category='pyramid',
456 456 depth=depth + 1)
457 457 if info.scope == 'class':
458 458 # ensure that attr is set if decorating a class method
459 459 kwargs.setdefault('attr', wrapped.__name__)
460 460
461 461 kwargs['_info'] = info.codeinfo # fbo action_method
462 462 return wrapped
463 463
464 464
465 465 class jsonrpc_deprecated_method(object):
466 466 """
467 467 Marks method as deprecated, adds log.warning, and inject special key to
468 468 the request variable to mark method as deprecated.
469 469 Also injects special docstring that extract_docs will catch to mark
470 470 method as deprecated.
471 471
472 472 :param use_method: specify which method should be used instead of
473 473 the decorated one
474 474
475 475 Use like::
476 476
477 477 @jsonrpc_method()
478 478 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
479 479 def old_func(request, apiuser, arg1, arg2):
480 480 ...
481 481 """
482 482
483 483 def __init__(self, use_method, deprecated_at_version):
484 484 self.use_method = use_method
485 485 self.deprecated_at_version = deprecated_at_version
486 486 self.deprecated_msg = ''
487 487
488 488 def __call__(self, func):
489 489 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
490 490 method=self.use_method)
491 491
492 492 docstring = """\n
493 493 .. deprecated:: {version}
494 494
495 495 {deprecation_message}
496 496
497 497 {original_docstring}
498 498 """
499 499 func.__doc__ = docstring.format(
500 500 version=self.deprecated_at_version,
501 501 deprecation_message=self.deprecated_msg,
502 502 original_docstring=func.__doc__)
503 503 return decorator.decorator(self.__wrapper, func)
504 504
505 505 def __wrapper(self, func, *fargs, **fkwargs):
506 506 log.warning('DEPRECATED API CALL on function %s, please '
507 507 'use `%s` instead', func, self.use_method)
508 508 # alter function docstring to mark as deprecated, this is picked up
509 509 # via fabric file that generates API DOC.
510 510 result = func(*fargs, **fkwargs)
511 511
512 512 request = fargs[0]
513 513 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
514 514 return result
515 515
516 516
517 517 def includeme(config):
518 518 plugin_module = 'rhodecode.api'
519 519 plugin_settings = get_plugin_settings(
520 520 plugin_module, config.registry.settings)
521 521
522 522 if not hasattr(config.registry, 'jsonrpc_methods'):
523 523 config.registry.jsonrpc_methods = OrderedDict()
524 524
525 525 # match filter by given method only
526 526 config.add_view_predicate('jsonrpc_method', MethodPredicate)
527 527
528 528 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
529 529 serializer=json.dumps, indent=4))
530 530 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
531 531
532 532 config.add_route_predicate(
533 533 'jsonrpc_call', RoutePredicate)
534 534
535 535 config.add_route(
536 536 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
537 537
538 538 config.scan(plugin_module, ignore='rhodecode.api.tests')
539 539 # register some exception handling view
540 540 config.add_view(exception_view, context=JSONRPCBaseError)
541 541 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
542 542 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,39 +1,39 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
22 22 class JSONRPCBaseError(Exception):
23 23 pass
24 24
25 25
26 26 class JSONRPCError(JSONRPCBaseError):
27 27 pass
28 28
29 29
30 30 class JSONRPCValidationError(JSONRPCBaseError):
31 31
32 32 def __init__(self, *args, **kwargs):
33 33 self.colander_exception = kwargs.pop('colander_exc')
34 34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
35 35
36 36
37 37 class JSONRPCForbidden(JSONRPCBaseError):
38 38 pass
39 39
@@ -1,19 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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/
@@ -1,52 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.auth_token import AuthTokenModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27
28 28
29 29 @pytest.fixture(scope="class")
30 30 def testuser_api(request, baseapp):
31 31 cls = request.cls
32 32
33 33 # ADMIN USER
34 34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
35 35 cls.apikey = cls.usr.api_key
36 36
37 37 # REGULAR USER
38 38 cls.test_user = UserModel().create_or_update(
39 39 username='test-api',
40 40 password='test',
41 41 email='test@api.rhodecode.org',
42 42 firstname='first',
43 43 lastname='last'
44 44 )
45 45 # create TOKEN for user, if he doesn't have one
46 46 if not cls.test_user.api_key:
47 47 AuthTokenModel().create(
48 48 user=cls.test_user, description=u'TEST_USER_TOKEN')
49 49
50 50 Session().commit()
51 51 cls.TEST_USER_LOGIN = cls.test_user.username
52 52 cls.apikey_regular = cls.test_user.api_key
@@ -1,62 +1,62 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import Repository, RepositoryField
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestAddFieldToRepo(object):
30 30 def test_api_add_field_to_repo(self, backend):
31 31 repo = backend.create_repo()
32 32 repo_name = repo.repo_name
33 33 id_, params = build_data(
34 34 self.apikey, 'add_field_to_repo',
35 35 repoid=repo_name,
36 36 key='extra_field',
37 37 label='extra_field_label',
38 38 description='extra_field_desc')
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'Added new repository field `extra_field`',
42 42 'success': True,
43 43 }
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 repo = Repository.get_by_repo_name(repo_name)
47 47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 48 _data = repo_field.get_dict()
49 49 assert _data['field_desc'] == 'extra_field_desc'
50 50 assert _data['field_key'] == 'extra_field'
51 51 assert _data['field_label'] == 'extra_field_label'
52 52
53 53 id_, params = build_data(
54 54 self.apikey, 'add_field_to_repo',
55 55 repoid=repo_name,
56 56 key='extra_field',
57 57 label='extra_field_label',
58 58 description='extra_field_desc')
59 59 response = api_call(self.app, params)
60 60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
61 61 repo_name)
62 62 assert_error(id_, expected, given=response.body)
@@ -1,72 +1,72 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user_group import UserGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestAddUserToUserGroup(object):
31 31 def test_api_add_user_to_user_group(self, user_util):
32 32 group = user_util.create_user_group()
33 33 user = user_util.create_user()
34 34 group_name = group.users_group_name
35 35 user_name = user.username
36 36 id_, params = build_data(
37 37 self.apikey, 'add_user_to_user_group',
38 38 usergroupid=group_name, userid=user_name)
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'added member `%s` to user group `%s`' % (
42 42 user_name, group_name
43 43 ),
44 44 'success': True
45 45 }
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
49 49 user = user_util.create_user()
50 50 user_name = user.username
51 51 id_, params = build_data(
52 52 self.apikey, 'add_user_to_user_group',
53 53 usergroupid='false-group',
54 54 userid=user_name)
55 55 response = api_call(self.app, params)
56 56
57 57 expected = 'user group `%s` does not exist' % 'false-group'
58 58 assert_error(id_, expected, given=response.body)
59 59
60 60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
61 61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
62 62 group = user_util.create_user_group()
63 63 user = user_util.create_user()
64 64 group_name = group.users_group_name
65 65 user_name = user.username
66 66 id_, params = build_data(
67 67 self.apikey, 'add_user_to_user_group',
68 68 usergroupid=group_name, userid=user_name)
69 69 response = api_call(self.app, params)
70 70
71 71 expected = 'failed to add member to user group `%s`' % (group_name,)
72 72 assert_error(id_, expected, given=response.body)
@@ -1,131 +1,131 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.api.utils import Optional, OAttr
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApi(object):
30 30 maxDiff = None
31 31
32 32 def test_Optional_object(self):
33 33
34 34 option1 = Optional(None)
35 35 assert '<Optional:%s>' % (None,) == repr(option1)
36 36 assert option1() is None
37 37
38 38 assert 1 == Optional.extract(Optional(1))
39 39 assert 'example' == Optional.extract('example')
40 40
41 41 def test_Optional_OAttr(self):
42 42 option1 = Optional(OAttr('apiuser'))
43 43 assert 'apiuser' == Optional.extract(option1)
44 44
45 45 def test_OAttr_object(self):
46 46 oattr1 = OAttr('apiuser')
47 47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 48 assert oattr1() == oattr1
49 49
50 50 def test_api_wrong_key(self):
51 51 id_, params = build_data('trololo', 'get_user')
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Invalid API KEY'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 def test_api_missing_non_optional_param(self):
58 58 id_, params = build_data(self.apikey, 'get_repo')
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_missing_non_optional_param_args_null(self):
65 65 id_, params = build_data(self.apikey, 'get_repo')
66 66 params = params.replace('"args": {}', '"args": null')
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 def test_api_missing_non_optional_param_args_bad(self):
73 73 id_, params = build_data(self.apikey, 'get_repo')
74 74 params = params.replace('"args": {}', '"args": 1')
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 78 assert_error(id_, expected, given=response.body)
79 79
80 80 def test_api_non_existing_method(self, request):
81 81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 82 response = api_call(self.app, params)
83 83 expected = 'No such method: not_existing. Similar methods: none'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_non_existing_method_have_similar(self, request):
87 87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 88 response = api_call(self.app, params)
89 89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
90 90 assert_error(id_, expected, given=response.body)
91 91
92 92 def test_api_disabled_user(self, request):
93 93
94 94 def set_active(active):
95 95 from rhodecode.model.db import Session, User
96 96 user = User.get_by_auth_token(self.apikey)
97 97 user.active = active
98 98 Session().add(user)
99 99 Session().commit()
100 100
101 101 request.addfinalizer(lambda: set_active(True))
102 102
103 103 set_active(False)
104 104 id_, params = build_data(self.apikey, 'test', args='xx')
105 105 response = api_call(self.app, params)
106 106 expected = 'Request from this user not allowed'
107 107 assert_error(id_, expected, given=response.body)
108 108
109 109 def test_api_args_is_null(self):
110 110 __, params = build_data(self.apikey, 'get_users', )
111 111 params = params.replace('"args": {}', '"args": null')
112 112 response = api_call(self.app, params)
113 113 assert response.status == '200 OK'
114 114
115 115 def test_api_args_is_bad(self):
116 116 __, params = build_data(self.apikey, 'get_users', )
117 117 params = params.replace('"args": {}', '"args": 1')
118 118 response = api_call(self.app, params)
119 119 assert response.status == '200 OK'
120 120
121 121 def test_api_args_different_args(self):
122 122 import string
123 123 expected = {
124 124 'ascii_letters': string.ascii_letters,
125 125 'ws': string.whitespace,
126 126 'printables': string.printable
127 127 }
128 128 id_, params = build_data(self.apikey, 'test', args=expected)
129 129 response = api_call(self.app, params)
130 130 assert response.status == '200 OK'
131 131 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.user_sessions import FileAuthSessions
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestCleanupSessions(object):
31 31 def test_api_cleanup_sessions(self):
32 32 id_, params = build_data(self.apikey, 'cleanup_sessions')
33 33 response = api_call(self.app, params)
34 34
35 35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
39 39 def test_api_cleanup_error(self):
40 40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
41 41 response = api_call(self.app, params)
42 42
43 43 expected = 'Error occurred during session cleanup'
44 44 assert_error(id_, expected, given=response.body)
@@ -1,113 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import UserLog
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestClosePullRequest(object):
32 32
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_close_pull_request(self, pr_util):
35 35 pull_request = pr_util.create_pull_request()
36 36 pull_request_id = pull_request.pull_request_id
37 37 author = pull_request.user_id
38 38 repo = pull_request.target_repo.repo_id
39 39 id_, params = build_data(
40 40 self.apikey, 'close_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id)
43 43 response = api_call(self.app, params)
44 44 expected = {
45 45 'pull_request_id': pull_request_id,
46 46 'close_status': 'Rejected',
47 47 'closed': True,
48 48 }
49 49 assert_ok(id_, expected, response.body)
50 50 journal = UserLog.query()\
51 51 .filter(UserLog.user_id == author) \
52 52 .order_by('user_log_id') \
53 53 .filter(UserLog.repository_id == repo)\
54 54 .all()
55 55 assert journal[-1].action == 'repo.pull_request.close'
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 59 pull_request = pr_util.create_pull_request()
60 60 pull_request_id = pull_request.pull_request_id
61 61 pull_request_repo = pull_request.target_repo.repo_name
62 62 PullRequestModel().close_pull_request(
63 63 pull_request, pull_request.author)
64 64 id_, params = build_data(
65 65 self.apikey, 'close_pull_request',
66 66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'pull request `%s` is already closed' % pull_request_id
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_close_pull_request_repo_error(self, pr_util):
74 74 pull_request = pr_util.create_pull_request()
75 75 id_, params = build_data(
76 76 self.apikey, 'close_pull_request',
77 77 repoid=666, pullrequestid=pull_request.pull_request_id)
78 78 response = api_call(self.app, params)
79 79
80 80 expected = 'repository `666` does not exist'
81 81 assert_error(id_, expected, given=response.body)
82 82
83 83 @pytest.mark.backends("git", "hg")
84 84 def test_api_close_pull_request_non_admin_with_userid_error(self,
85 85 pr_util):
86 86 pull_request = pr_util.create_pull_request()
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'close_pull_request',
89 89 repoid=pull_request.target_repo.repo_name,
90 90 pullrequestid=pull_request.pull_request_id,
91 91 userid=TEST_USER_ADMIN_LOGIN)
92 92 response = api_call(self.app, params)
93 93
94 94 expected = 'userid is not the same as your user'
95 95 assert_error(id_, expected, given=response.body)
96 96
97 97 @pytest.mark.backends("git", "hg")
98 98 def test_api_close_pull_request_no_perms_to_close(
99 99 self, user_util, pr_util):
100 100 user = user_util.create_user()
101 101 pull_request = pr_util.create_pull_request()
102 102
103 103 id_, params = build_data(
104 104 user.api_key, 'close_pull_request',
105 105 repoid=pull_request.target_repo.repo_name,
106 106 pullrequestid=pull_request.pull_request_id,)
107 107 response = api_call(self.app, params)
108 108
109 109 expected = ('pull request `%s` close failed, '
110 110 'no permission to close.') % pull_request.pull_request_id
111 111
112 112 response_json = response.json['error']
113 113 assert response_json == expected
@@ -1,81 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import ChangesetStatus
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestCommentCommit(object):
30 30 def test_api_comment_commit_on_empty_repo(self, backend):
31 31 repo = backend.create_repo()
32 32 id_, params = build_data(
33 33 self.apikey, 'comment_commit', repoid=repo.repo_name,
34 34 commit_id='tip', message='message', status_change=None)
35 35 response = api_call(self.app, params)
36 36 expected = 'There are no commits yet'
37 37 assert_error(id_, expected, given=response.body)
38 38
39 39 @pytest.mark.parametrize("commit_id, expected_err", [
40 40 ('abcabca', {'hg': 'Commit {commit} does not exist for {repo}',
41 41 'git': 'Commit {commit} does not exist for {repo}',
42 42 'svn': 'Commit id {commit} not understood.'}),
43 43 ('idontexist', {'hg': 'Commit {commit} does not exist for {repo}',
44 44 'git': 'Commit {commit} does not exist for {repo}',
45 45 'svn': 'Commit id {commit} not understood.'}),
46 46 ])
47 47 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
48 48 repo_name = backend.repo.repo_name
49 49 id_, params = build_data(
50 50 self.apikey, 'comment_commit', repoid=repo_name,
51 51 commit_id=commit_id, message='message', status_change=None)
52 52 response = api_call(self.app, params)
53 53
54 54 expected_err = expected_err[backend.alias]
55 55 expected_err = expected_err.format(
56 56 repo=backend.repo.scm_instance(), commit=commit_id)
57 57 assert_error(id_, expected_err, given=response.body)
58 58
59 59 @pytest.mark.parametrize("status_change, message, commit_id", [
60 60 (None, 'Hallo', 'tip'),
61 61 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
62 62 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
63 63 ])
64 64 def test_api_comment_commit(
65 65 self, backend, status_change, message, commit_id,
66 66 no_notifications):
67 67
68 68 commit_id = backend.repo.scm_instance().get_changeset(commit_id).raw_id
69 69
70 70 id_, params = build_data(
71 71 self.apikey, 'comment_commit', repoid=backend.repo_name,
72 72 commit_id=commit_id, message=message, status=status_change)
73 73 response = api_call(self.app, params)
74 74 repo = backend.repo.scm_instance()
75 75 expected = {
76 76 'msg': 'Commented on commit `%s` for repository `%s`' % (
77 77 repo.get_changeset().raw_id, backend.repo_name),
78 78 'status_change': status_change,
79 79 'success': True
80 80 }
81 81 assert_ok(id_, expected, given=response.body)
@@ -1,209 +1,209 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.comment import CommentsModel
24 24 from rhodecode.model.db import UserLog
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCommentPullRequest(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 @pytest.mark.backends("git", "hg")
42 42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 43 pull_request = pr_util.create_pull_request()
44 44 pull_request_id = pull_request.pull_request_id
45 45 author = pull_request.user_id
46 46 repo = pull_request.target_repo.repo_id
47 47 id_, params = build_data(
48 48 self.apikey, 'comment_pull_request',
49 49 repoid=pull_request.target_repo.repo_name,
50 50 pullrequestid=pull_request.pull_request_id,
51 51 message='test message')
52 52 response = api_call(self.app, params)
53 53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54 54
55 55 comments = CommentsModel().get_comments(
56 56 pull_request.target_repo.repo_id, pull_request=pull_request)
57 57
58 58 expected = {
59 59 'pull_request_id': pull_request.pull_request_id,
60 60 'comment_id': comments[-1].comment_id,
61 61 'status': {'given': None, 'was_changed': None}
62 62 }
63 63 assert_ok(id_, expected, response.body)
64 64
65 65 journal = UserLog.query()\
66 66 .filter(UserLog.user_id == author)\
67 67 .filter(UserLog.repository_id == repo) \
68 68 .order_by('user_log_id') \
69 69 .all()
70 70 assert journal[-1].action == 'repo.pull_request.comment.create'
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_comment_pull_request_change_status(
74 74 self, pr_util, no_notifications):
75 75 pull_request = pr_util.create_pull_request()
76 76 pull_request_id = pull_request.pull_request_id
77 77 id_, params = build_data(
78 78 self.apikey, 'comment_pull_request',
79 79 repoid=pull_request.target_repo.repo_name,
80 80 pullrequestid=pull_request.pull_request_id,
81 81 status='rejected')
82 82 response = api_call(self.app, params)
83 83 pull_request = PullRequestModel().get(pull_request_id)
84 84
85 85 comments = CommentsModel().get_comments(
86 86 pull_request.target_repo.repo_id, pull_request=pull_request)
87 87 expected = {
88 88 'pull_request_id': pull_request.pull_request_id,
89 89 'comment_id': comments[-1].comment_id,
90 90 'status': {'given': 'rejected', 'was_changed': True}
91 91 }
92 92 assert_ok(id_, expected, response.body)
93 93
94 94 @pytest.mark.backends("git", "hg")
95 95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 96 self, pr_util, no_notifications):
97 97 pull_request = pr_util.create_pull_request()
98 98 pull_request_id = pull_request.pull_request_id
99 99 latest_commit_id = 'test_commit'
100 100 # inject additional revision, to fail test the status change on
101 101 # non-latest commit
102 102 pull_request.revisions = pull_request.revisions + ['test_commit']
103 103
104 104 id_, params = build_data(
105 105 self.apikey, 'comment_pull_request',
106 106 repoid=pull_request.target_repo.repo_name,
107 107 pullrequestid=pull_request.pull_request_id,
108 108 status='approved', commit_id=latest_commit_id)
109 109 response = api_call(self.app, params)
110 110 pull_request = PullRequestModel().get(pull_request_id)
111 111
112 112 expected = {
113 113 'pull_request_id': pull_request.pull_request_id,
114 114 'comment_id': None,
115 115 'status': {'given': 'approved', 'was_changed': False}
116 116 }
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 121 self, pr_util, no_notifications):
122 122 pull_request = pr_util.create_pull_request()
123 123 pull_request_id = pull_request.pull_request_id
124 124 latest_commit_id = pull_request.revisions[0]
125 125
126 126 id_, params = build_data(
127 127 self.apikey, 'comment_pull_request',
128 128 repoid=pull_request.target_repo.repo_name,
129 129 pullrequestid=pull_request.pull_request_id,
130 130 status='approved', commit_id=latest_commit_id)
131 131 response = api_call(self.app, params)
132 132 pull_request = PullRequestModel().get(pull_request_id)
133 133
134 134 comments = CommentsModel().get_comments(
135 135 pull_request.target_repo.repo_id, pull_request=pull_request)
136 136 expected = {
137 137 'pull_request_id': pull_request.pull_request_id,
138 138 'comment_id': comments[-1].comment_id,
139 139 'status': {'given': 'approved', 'was_changed': True}
140 140 }
141 141 assert_ok(id_, expected, response.body)
142 142
143 143 @pytest.mark.backends("git", "hg")
144 144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 145 pull_request = pr_util.create_pull_request()
146 146 pull_request_id = pull_request.pull_request_id
147 147 pull_request_repo = pull_request.target_repo.repo_name
148 148 id_, params = build_data(
149 149 self.apikey, 'comment_pull_request',
150 150 repoid=pull_request_repo,
151 151 pullrequestid=pull_request_id)
152 152 response = api_call(self.app, params)
153 153
154 154 expected = 'Both message and status parameters are missing. At least one is required.'
155 155 assert_error(id_, expected, given=response.body)
156 156
157 157 @pytest.mark.backends("git", "hg")
158 158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 159 pull_request = pr_util.create_pull_request()
160 160 pull_request_id = pull_request.pull_request_id
161 161 pull_request_repo = pull_request.target_repo.repo_name
162 162 id_, params = build_data(
163 163 self.apikey, 'comment_pull_request',
164 164 repoid=pull_request_repo,
165 165 pullrequestid=pull_request_id,
166 166 status='42')
167 167 response = api_call(self.app, params)
168 168
169 169 expected = 'Unknown comment status: `42`'
170 170 assert_error(id_, expected, given=response.body)
171 171
172 172 @pytest.mark.backends("git", "hg")
173 173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 174 pull_request = pr_util.create_pull_request()
175 175 id_, params = build_data(
176 176 self.apikey, 'comment_pull_request',
177 177 repoid=666, pullrequestid=pull_request.pull_request_id)
178 178 response = api_call(self.app, params)
179 179
180 180 expected = 'repository `666` does not exist'
181 181 assert_error(id_, expected, given=response.body)
182 182
183 183 @pytest.mark.backends("git", "hg")
184 184 def test_api_comment_pull_request_non_admin_with_userid_error(
185 185 self, pr_util):
186 186 pull_request = pr_util.create_pull_request()
187 187 id_, params = build_data(
188 188 self.apikey_regular, 'comment_pull_request',
189 189 repoid=pull_request.target_repo.repo_name,
190 190 pullrequestid=pull_request.pull_request_id,
191 191 userid=TEST_USER_ADMIN_LOGIN)
192 192 response = api_call(self.app, params)
193 193
194 194 expected = 'userid is not the same as your user'
195 195 assert_error(id_, expected, given=response.body)
196 196
197 197 @pytest.mark.backends("git", "hg")
198 198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
199 199 pull_request = pr_util.create_pull_request()
200 200 id_, params = build_data(
201 201 self.apikey_regular, 'comment_pull_request',
202 202 repoid=pull_request.target_repo.repo_name,
203 203 status='approved',
204 204 pullrequestid=pull_request.pull_request_id,
205 205 commit_id='XXX')
206 206 response = api_call(self.app, params)
207 207
208 208 expected = 'Invalid commit_id `XXX` for this pull request.'
209 209 assert_error(id_, expected, given=response.body)
@@ -1,102 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.db import Gist
25 25 from rhodecode.model.gist import GistModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28 from rhodecode.tests.fixture import Fixture
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestApiCreateGist(object):
33 33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
34 34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
35 35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
36 36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
37 37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
38 38 ])
39 39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
40 40 id_, params = build_data(
41 41 self.apikey_regular, 'create_gist',
42 42 lifetime=lifetime,
43 43 description='foobar-gist',
44 44 gist_type=gist_type,
45 45 acl_level=gist_acl_level,
46 46 files={'foobar_ąć': {'content': 'foo'}})
47 47 response = api_call(self.app, params)
48 48 response_json = response.json
49 49 gist = response_json['result']['gist']
50 50 expected = {
51 51 'gist': {
52 52 'access_id': gist['access_id'],
53 53 'created_on': gist['created_on'],
54 54 'modified_at': gist['modified_at'],
55 55 'description': 'foobar-gist',
56 56 'expires': gist['expires'],
57 57 'gist_id': gist['gist_id'],
58 58 'type': gist_type,
59 59 'url': gist['url'],
60 60 # content is empty since we don't show it here
61 61 'content': None,
62 62 'acl_level': gist_acl_level,
63 63 },
64 64 'msg': 'created new gist'
65 65 }
66 66 try:
67 67 assert_ok(id_, expected, given=response.body)
68 68 finally:
69 69 Fixture().destroy_gists()
70 70
71 71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
72 72 ({'gist_type': '"ups" is not one of private, public'},
73 73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
74 74
75 75 ({'lifetime': '-120 is less than minimum value -1'},
76 76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
77 77
78 78 ({'0.content': 'Required'},
79 79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
80 80 ])
81 81 def test_api_try_create_gist(
82 82 self, expected, lifetime, gist_type, gist_acl_level, files):
83 83 id_, params = build_data(
84 84 self.apikey_regular, 'create_gist',
85 85 lifetime=lifetime,
86 86 description='foobar-gist',
87 87 gist_type=gist_type,
88 88 acl_level=gist_acl_level,
89 89 files=files)
90 90 response = api_call(self.app, params)
91 91
92 92 try:
93 93 assert_error(id_, expected, given=response.body)
94 94 finally:
95 95 Fixture().destroy_gists()
96 96
97 97 @mock.patch.object(GistModel, 'create', crash)
98 98 def test_api_create_gist_exception_occurred(self):
99 99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
100 100 response = api_call(self.app, params)
101 101 expected = 'failed to create gist'
102 102 assert_error(id_, expected, given=response.body)
@@ -1,297 +1,297 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import User
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
28 28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCreatePullRequestApi(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 def test_create_with_wrong_data(self):
42 42 required_data = {
43 43 'source_repo': 'tests/source_repo',
44 44 'target_repo': 'tests/target_repo',
45 45 'source_ref': 'branch:default:initial',
46 46 'target_ref': 'branch:default:new-feature',
47 47 'title': 'Test PR 1'
48 48 }
49 49 for key in required_data:
50 50 data = required_data.copy()
51 51 data.pop(key)
52 52 id_, params = build_data(
53 53 self.apikey, 'create_pull_request', **data)
54 54 response = api_call(self.app, params)
55 55
56 56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
57 57 assert_error(id_, expected, given=response.body)
58 58
59 59 @pytest.mark.backends("git", "hg")
60 60 def test_create_with_correct_data(self, backend):
61 61 data = self._prepare_data(backend)
62 62 RepoModel().revoke_user_permission(
63 63 self.source.repo_name, User.DEFAULT_USER)
64 64 id_, params = build_data(
65 65 self.apikey_regular, 'create_pull_request', **data)
66 66 response = api_call(self.app, params)
67 67 expected_message = "Created new pull request `{title}`".format(
68 68 title=data['title'])
69 69 result = response.json
70 70 assert result['result']['msg'] == expected_message
71 71 pull_request_id = result['result']['pull_request_id']
72 72 pull_request = PullRequestModel().get(pull_request_id)
73 73 assert pull_request.title == data['title']
74 74 assert pull_request.description == data['description']
75 75 assert pull_request.source_ref == data['source_ref']
76 76 assert pull_request.target_ref == data['target_ref']
77 77 assert pull_request.source_repo.repo_name == data['source_repo']
78 78 assert pull_request.target_repo.repo_name == data['target_repo']
79 79 assert pull_request.revisions == [self.commit_ids['change']]
80 80 assert len(pull_request.reviewers) == 1
81 81
82 82 @pytest.mark.backends("git", "hg")
83 83 def test_create_with_empty_description(self, backend):
84 84 data = self._prepare_data(backend)
85 85 data.pop('description')
86 86 id_, params = build_data(
87 87 self.apikey_regular, 'create_pull_request', **data)
88 88 response = api_call(self.app, params)
89 89 expected_message = "Created new pull request `{title}`".format(
90 90 title=data['title'])
91 91 result = response.json
92 92 assert result['result']['msg'] == expected_message
93 93 pull_request_id = result['result']['pull_request_id']
94 94 pull_request = PullRequestModel().get(pull_request_id)
95 95 assert pull_request.description == ''
96 96
97 97 @pytest.mark.backends("git", "hg")
98 98 def test_create_with_reviewers_specified_by_names(
99 99 self, backend, no_notifications):
100 100 data = self._prepare_data(backend)
101 101 reviewers = [
102 102 {'username': TEST_USER_REGULAR_LOGIN,
103 103 'reasons': ['added manually']},
104 104 {'username': TEST_USER_ADMIN_LOGIN,
105 105 'reasons': ['added manually']},
106 106 ]
107 107 data['reviewers'] = reviewers
108 108 id_, params = build_data(
109 109 self.apikey_regular, 'create_pull_request', **data)
110 110 response = api_call(self.app, params)
111 111
112 112 expected_message = "Created new pull request `{title}`".format(
113 113 title=data['title'])
114 114 result = response.json
115 115 assert result['result']['msg'] == expected_message
116 116 pull_request_id = result['result']['pull_request_id']
117 117 pull_request = PullRequestModel().get(pull_request_id)
118 118 actual_reviewers = [
119 119 {'username': r.user.username,
120 120 'reasons': ['added manually'],
121 121 } for r in pull_request.reviewers
122 122 ]
123 123 assert sorted(actual_reviewers) == sorted(reviewers)
124 124
125 125 @pytest.mark.backends("git", "hg")
126 126 def test_create_with_reviewers_specified_by_ids(
127 127 self, backend, no_notifications):
128 128 data = self._prepare_data(backend)
129 129 reviewers = [
130 130 {'username': UserModel().get_by_username(
131 131 TEST_USER_REGULAR_LOGIN).user_id,
132 132 'reasons': ['added manually']},
133 133 {'username': UserModel().get_by_username(
134 134 TEST_USER_ADMIN_LOGIN).user_id,
135 135 'reasons': ['added manually']},
136 136 ]
137 137
138 138 data['reviewers'] = reviewers
139 139 id_, params = build_data(
140 140 self.apikey_regular, 'create_pull_request', **data)
141 141 response = api_call(self.app, params)
142 142
143 143 expected_message = "Created new pull request `{title}`".format(
144 144 title=data['title'])
145 145 result = response.json
146 146 assert result['result']['msg'] == expected_message
147 147 pull_request_id = result['result']['pull_request_id']
148 148 pull_request = PullRequestModel().get(pull_request_id)
149 149 actual_reviewers = [
150 150 {'username': r.user.user_id,
151 151 'reasons': ['added manually'],
152 152 } for r in pull_request.reviewers
153 153 ]
154 154 assert sorted(actual_reviewers) == sorted(reviewers)
155 155
156 156 @pytest.mark.backends("git", "hg")
157 157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
158 158 data = self._prepare_data(backend)
159 159 data['reviewers'] = [{'username': 'somebody'}]
160 160 id_, params = build_data(
161 161 self.apikey_regular, 'create_pull_request', **data)
162 162 response = api_call(self.app, params)
163 163 expected_message = 'user `somebody` does not exist'
164 164 assert_error(id_, expected_message, given=response.body)
165 165
166 166 @pytest.mark.backends("git", "hg")
167 167 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
168 168 data = self._prepare_data(backend)
169 169 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
170 170 data['reviewers'] = reviewers
171 171 id_, params = build_data(
172 172 self.apikey_regular, 'create_pull_request', **data)
173 173 response = api_call(self.app, params)
174 174 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
175 175 assert_error(id_, expected_message, given=response.body)
176 176
177 177 @pytest.mark.backends("git", "hg")
178 178 def test_create_with_no_commit_hashes(self, backend):
179 179 data = self._prepare_data(backend)
180 180 expected_source_ref = data['source_ref']
181 181 expected_target_ref = data['target_ref']
182 182 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
183 183 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
184 184 id_, params = build_data(
185 185 self.apikey_regular, 'create_pull_request', **data)
186 186 response = api_call(self.app, params)
187 187 expected_message = "Created new pull request `{title}`".format(
188 188 title=data['title'])
189 189 result = response.json
190 190 assert result['result']['msg'] == expected_message
191 191 pull_request_id = result['result']['pull_request_id']
192 192 pull_request = PullRequestModel().get(pull_request_id)
193 193 assert pull_request.source_ref == expected_source_ref
194 194 assert pull_request.target_ref == expected_target_ref
195 195
196 196 @pytest.mark.backends("git", "hg")
197 197 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
198 198 def test_create_fails_with_wrong_repo(self, backend, data_key):
199 199 repo_name = 'fake-repo'
200 200 data = self._prepare_data(backend)
201 201 data[data_key] = repo_name
202 202 id_, params = build_data(
203 203 self.apikey_regular, 'create_pull_request', **data)
204 204 response = api_call(self.app, params)
205 205 expected_message = 'repository `{}` does not exist'.format(repo_name)
206 206 assert_error(id_, expected_message, given=response.body)
207 207
208 208 @pytest.mark.backends("git", "hg")
209 209 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
210 210 def test_create_fails_with_non_existing_branch(self, backend, data_key):
211 211 branch_name = 'test-branch'
212 212 data = self._prepare_data(backend)
213 213 data[data_key] = "branch:{}".format(branch_name)
214 214 id_, params = build_data(
215 215 self.apikey_regular, 'create_pull_request', **data)
216 216 response = api_call(self.app, params)
217 217 expected_message = 'The specified branch `{}` does not exist'.format(
218 218 branch_name)
219 219 assert_error(id_, expected_message, given=response.body)
220 220
221 221 @pytest.mark.backends("git", "hg")
222 222 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
223 223 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
224 224 data = self._prepare_data(backend)
225 225 ref = 'stange-ref'
226 226 data[data_key] = ref
227 227 id_, params = build_data(
228 228 self.apikey_regular, 'create_pull_request', **data)
229 229 response = api_call(self.app, params)
230 230 expected_message = (
231 231 'Ref `{ref}` given in a wrong format. Please check the API'
232 232 ' documentation for more details'.format(ref=ref))
233 233 assert_error(id_, expected_message, given=response.body)
234 234
235 235 @pytest.mark.backends("git", "hg")
236 236 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
237 237 def test_create_fails_with_non_existing_ref(self, backend, data_key):
238 238 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
239 239 ref = self._get_full_ref(backend, commit_id)
240 240 data = self._prepare_data(backend)
241 241 data[data_key] = ref
242 242 id_, params = build_data(
243 243 self.apikey_regular, 'create_pull_request', **data)
244 244 response = api_call(self.app, params)
245 245 expected_message = 'Ref `{}` does not exist'.format(ref)
246 246 assert_error(id_, expected_message, given=response.body)
247 247
248 248 @pytest.mark.backends("git", "hg")
249 249 def test_create_fails_when_no_revisions(self, backend):
250 250 data = self._prepare_data(backend, source_head='initial')
251 251 id_, params = build_data(
252 252 self.apikey_regular, 'create_pull_request', **data)
253 253 response = api_call(self.app, params)
254 254 expected_message = 'no commits found'
255 255 assert_error(id_, expected_message, given=response.body)
256 256
257 257 @pytest.mark.backends("git", "hg")
258 258 def test_create_fails_when_no_permissions(self, backend):
259 259 data = self._prepare_data(backend)
260 260 RepoModel().revoke_user_permission(
261 261 self.source.repo_name, User.DEFAULT_USER)
262 262 RepoModel().revoke_user_permission(
263 263 self.source.repo_name, self.test_user)
264 264 id_, params = build_data(
265 265 self.apikey_regular, 'create_pull_request', **data)
266 266 response = api_call(self.app, params)
267 267 expected_message = 'repository `{}` does not exist'.format(
268 268 self.source.repo_name)
269 269 assert_error(id_, expected_message, given=response.body)
270 270
271 271 def _prepare_data(
272 272 self, backend, source_head='change', target_head='initial'):
273 273 commits = [
274 274 {'message': 'initial'},
275 275 {'message': 'change'},
276 276 {'message': 'new-feature', 'parents': ['initial']},
277 277 ]
278 278 self.commit_ids = backend.create_master_repo(commits)
279 279 self.source = backend.create_repo(heads=[source_head])
280 280 self.target = backend.create_repo(heads=[target_head])
281 281 data = {
282 282 'source_repo': self.source.repo_name,
283 283 'target_repo': self.target.repo_name,
284 284 'source_ref': self._get_full_ref(
285 285 backend, self.commit_ids[source_head]),
286 286 'target_ref': self._get_full_ref(
287 287 backend, self.commit_ids[target_head]),
288 288 'title': 'Test PR 1',
289 289 'description': 'Test'
290 290 }
291 291 RepoModel().grant_user_permission(
292 292 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
293 293 return data
294 294
295 295 def _get_full_ref(self, backend, commit_id):
296 296 return 'branch:{branch}:{commit_id}'.format(
297 297 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,350 +1,350 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 json
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib.utils2 import safe_unicode
27 27 from rhodecode.lib.vcs import settings
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.repo import RepoModel
30 30 from rhodecode.model.user import UserModel
31 31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 32 from rhodecode.api.tests.utils import (
33 33 build_data, api_call, assert_ok, assert_error, crash)
34 34 from rhodecode.tests.fixture import Fixture
35 35
36 36
37 37 fixture = Fixture()
38 38
39 39
40 40 @pytest.mark.usefixtures("testuser_api", "app")
41 41 class TestCreateRepo(object):
42 42
43 43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
44 44 ('api repo-1', 'api-repo-1', False),
45 45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
46 46 (u'unicode-ąć', u'unicode-ąć', False),
47 47 ('some repo v1.2', 'some-repo-v1.2', False),
48 48 ('v2.0', 'v2.0', False),
49 49 ])
50 50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
51 51
52 52 id_, params = build_data(
53 53 self.apikey,
54 54 'create_repo',
55 55 repo_name=given,
56 56 owner=TEST_USER_ADMIN_LOGIN,
57 57 repo_type=backend.alias,
58 58 )
59 59 response = api_call(self.app, params)
60 60
61 61 ret = {
62 62 'msg': 'Created new repository `%s`' % (expected_name,),
63 63 'success': True,
64 64 'task': None,
65 65 }
66 66 expected = ret
67 67 assert_ok(id_, expected, given=response.body)
68 68
69 69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
70 70 assert repo is not None
71 71
72 72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
73 73 response = api_call(self.app, params)
74 74 body = json.loads(response.body)
75 75
76 76 assert body['result']['enable_downloads'] is False
77 77 assert body['result']['enable_locking'] is False
78 78 assert body['result']['enable_statistics'] is False
79 79
80 80 fixture.destroy_repo(safe_unicode(expected_name))
81 81
82 82 def test_api_create_restricted_repo_type(self, backend):
83 83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
84 84 id_, params = build_data(
85 85 self.apikey,
86 86 'create_repo',
87 87 repo_name=repo_name,
88 88 owner=TEST_USER_ADMIN_LOGIN,
89 89 repo_type=backend.alias,
90 90 )
91 91 git_backend = settings.BACKENDS['git']
92 92 with mock.patch(
93 93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
94 94 response = api_call(self.app, params)
95 95
96 96 repo = RepoModel().get_by_repo_name(repo_name)
97 97
98 98 if backend.alias == 'git':
99 99 assert repo is not None
100 100 expected = {
101 101 'msg': 'Created new repository `{0}`'.format(repo_name,),
102 102 'success': True,
103 103 'task': None,
104 104 }
105 105 assert_ok(id_, expected, given=response.body)
106 106 else:
107 107 assert repo is None
108 108
109 109 fixture.destroy_repo(repo_name)
110 110
111 111 def test_api_create_repo_with_booleans(self, backend):
112 112 repo_name = 'api-repo-2'
113 113 id_, params = build_data(
114 114 self.apikey,
115 115 'create_repo',
116 116 repo_name=repo_name,
117 117 owner=TEST_USER_ADMIN_LOGIN,
118 118 repo_type=backend.alias,
119 119 enable_statistics=True,
120 120 enable_locking=True,
121 121 enable_downloads=True
122 122 )
123 123 response = api_call(self.app, params)
124 124
125 125 repo = RepoModel().get_by_repo_name(repo_name)
126 126
127 127 assert repo is not None
128 128 ret = {
129 129 'msg': 'Created new repository `%s`' % (repo_name,),
130 130 'success': True,
131 131 'task': None,
132 132 }
133 133 expected = ret
134 134 assert_ok(id_, expected, given=response.body)
135 135
136 136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
137 137 response = api_call(self.app, params)
138 138 body = json.loads(response.body)
139 139
140 140 assert body['result']['enable_downloads'] is True
141 141 assert body['result']['enable_locking'] is True
142 142 assert body['result']['enable_statistics'] is True
143 143
144 144 fixture.destroy_repo(repo_name)
145 145
146 146 def test_api_create_repo_in_group(self, backend):
147 147 repo_group_name = 'my_gr'
148 148 # create the parent
149 149 fixture.create_repo_group(repo_group_name)
150 150
151 151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
152 152 id_, params = build_data(
153 153 self.apikey, 'create_repo',
154 154 repo_name=repo_name,
155 155 owner=TEST_USER_ADMIN_LOGIN,
156 156 repo_type=backend.alias,)
157 157 response = api_call(self.app, params)
158 158 repo = RepoModel().get_by_repo_name(repo_name)
159 159 assert repo is not None
160 160 assert repo.group is not None
161 161
162 162 ret = {
163 163 'msg': 'Created new repository `%s`' % (repo_name,),
164 164 'success': True,
165 165 'task': None,
166 166 }
167 167 expected = ret
168 168 assert_ok(id_, expected, given=response.body)
169 169 fixture.destroy_repo(repo_name)
170 170 fixture.destroy_repo_group(repo_group_name)
171 171
172 172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
173 173 repo_group_name = 'fake_group'
174 174
175 175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
176 176 id_, params = build_data(
177 177 self.apikey, 'create_repo',
178 178 repo_name=repo_name,
179 179 owner=TEST_USER_ADMIN_LOGIN,
180 180 repo_type=backend.alias,)
181 181 response = api_call(self.app, params)
182 182
183 183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
184 184 repo_group_name)}
185 185 assert_error(id_, expected, given=response.body)
186 186
187 187 def test_api_create_repo_unknown_owner(self, backend):
188 188 repo_name = 'api-repo-2'
189 189 owner = 'i-dont-exist'
190 190 id_, params = build_data(
191 191 self.apikey, 'create_repo',
192 192 repo_name=repo_name,
193 193 owner=owner,
194 194 repo_type=backend.alias)
195 195 response = api_call(self.app, params)
196 196 expected = 'user `%s` does not exist' % (owner,)
197 197 assert_error(id_, expected, given=response.body)
198 198
199 199 def test_api_create_repo_dont_specify_owner(self, backend):
200 200 repo_name = 'api-repo-3'
201 201 id_, params = build_data(
202 202 self.apikey, 'create_repo',
203 203 repo_name=repo_name,
204 204 repo_type=backend.alias)
205 205 response = api_call(self.app, params)
206 206
207 207 repo = RepoModel().get_by_repo_name(repo_name)
208 208 assert repo is not None
209 209 ret = {
210 210 'msg': 'Created new repository `%s`' % (repo_name,),
211 211 'success': True,
212 212 'task': None,
213 213 }
214 214 expected = ret
215 215 assert_ok(id_, expected, given=response.body)
216 216 fixture.destroy_repo(repo_name)
217 217
218 218 def test_api_create_repo_by_non_admin(self, backend):
219 219 repo_name = 'api-repo-4'
220 220 id_, params = build_data(
221 221 self.apikey_regular, 'create_repo',
222 222 repo_name=repo_name,
223 223 repo_type=backend.alias)
224 224 response = api_call(self.app, params)
225 225
226 226 repo = RepoModel().get_by_repo_name(repo_name)
227 227 assert repo is not None
228 228 ret = {
229 229 'msg': 'Created new repository `%s`' % (repo_name,),
230 230 'success': True,
231 231 'task': None,
232 232 }
233 233 expected = ret
234 234 assert_ok(id_, expected, given=response.body)
235 235 fixture.destroy_repo(repo_name)
236 236
237 237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
238 238 repo_name = 'api-repo-5'
239 239 owner = 'i-dont-exist'
240 240 id_, params = build_data(
241 241 self.apikey_regular, 'create_repo',
242 242 repo_name=repo_name,
243 243 repo_type=backend.alias,
244 244 owner=owner)
245 245 response = api_call(self.app, params)
246 246
247 247 expected = 'Only RhodeCode super-admin can specify `owner` param'
248 248 assert_error(id_, expected, given=response.body)
249 249 fixture.destroy_repo(repo_name)
250 250
251 251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
252 252 repo_group_name = 'no-access'
253 253 fixture.create_repo_group(repo_group_name)
254 254 repo_name = 'no-access/api-repo'
255 255
256 256 id_, params = build_data(
257 257 self.apikey_regular, 'create_repo',
258 258 repo_name=repo_name,
259 259 repo_type=backend.alias)
260 260 response = api_call(self.app, params)
261 261
262 262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
263 263 repo_group_name)}
264 264 assert_error(id_, expected, given=response.body)
265 265 fixture.destroy_repo_group(repo_group_name)
266 266 fixture.destroy_repo(repo_name)
267 267
268 268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
269 269 self, backend, user_util):
270 270
271 271 regular_user = user_util.create_user()
272 272 regular_user_api_key = regular_user.api_key
273 273
274 274 usr = UserModel().get_by_username(regular_user.username)
275 275 usr.inherit_default_permissions = False
276 276 Session().add(usr)
277 277
278 278 repo_name = backend.new_repo_name()
279 279 id_, params = build_data(
280 280 regular_user_api_key, 'create_repo',
281 281 repo_name=repo_name,
282 282 repo_type=backend.alias)
283 283 response = api_call(self.app, params)
284 284 expected = {
285 285 "repo_name": "You do not have the permission to "
286 286 "store repositories in the root location."}
287 287 assert_error(id_, expected, given=response.body)
288 288
289 289 def test_api_create_repo_exists(self, backend):
290 290 repo_name = backend.repo_name
291 291 id_, params = build_data(
292 292 self.apikey, 'create_repo',
293 293 repo_name=repo_name,
294 294 owner=TEST_USER_ADMIN_LOGIN,
295 295 repo_type=backend.alias,)
296 296 response = api_call(self.app, params)
297 297 expected = {
298 298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
299 299 repo_name)}
300 300 assert_error(id_, expected, given=response.body)
301 301
302 302 @mock.patch.object(RepoModel, 'create', crash)
303 303 def test_api_create_repo_exception_occurred(self, backend):
304 304 repo_name = 'api-repo-6'
305 305 id_, params = build_data(
306 306 self.apikey, 'create_repo',
307 307 repo_name=repo_name,
308 308 owner=TEST_USER_ADMIN_LOGIN,
309 309 repo_type=backend.alias,)
310 310 response = api_call(self.app, params)
311 311 expected = 'failed to create repository `%s`' % (repo_name,)
312 312 assert_error(id_, expected, given=response.body)
313 313
314 314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
315 315 (None, 'foo bar x', 'foo-bar-x'),
316 316 ('foo', '/foo//bar x', 'foo/bar-x'),
317 317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
318 318 ])
319 319 def test_create_repo_with_extra_slashes_in_name(
320 320 self, backend, parent_group, dirty_name, expected_name):
321 321
322 322 if parent_group:
323 323 gr = fixture.create_repo_group(parent_group)
324 324 assert gr.group_name == parent_group
325 325
326 326 id_, params = build_data(
327 327 self.apikey, 'create_repo',
328 328 repo_name=dirty_name,
329 329 repo_type=backend.alias,
330 330 owner=TEST_USER_ADMIN_LOGIN,)
331 331 response = api_call(self.app, params)
332 332 expected ={
333 333 "msg": "Created new repository `{}`".format(expected_name),
334 334 "task": None,
335 335 "success": True
336 336 }
337 337 assert_ok(id_, expected, response.body)
338 338
339 339 repo = RepoModel().get_by_repo_name(expected_name)
340 340 assert repo is not None
341 341
342 342 expected = {
343 343 'msg': 'Created new repository `%s`' % (expected_name,),
344 344 'success': True,
345 345 'task': None,
346 346 }
347 347 assert_ok(id_, expected, given=response.body)
348 348 fixture.destroy_repo(expected_name)
349 349 if parent_group:
350 350 fixture.destroy_repo_group(parent_group)
@@ -1,289 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31
32 32
33 33 fixture = Fixture()
34 34
35 35
36 36 @pytest.mark.usefixtures("testuser_api", "app")
37 37 class TestCreateRepoGroup(object):
38 38 def test_api_create_repo_group(self):
39 39 repo_group_name = 'api-repo-group'
40 40
41 41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 42 assert repo_group is None
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'create_repo_group',
46 46 group_name=repo_group_name,
47 47 owner=TEST_USER_ADMIN_LOGIN,)
48 48 response = api_call(self.app, params)
49 49
50 50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 51 assert repo_group is not None
52 52 ret = {
53 53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 54 'repo_group': repo_group.get_api_data()
55 55 }
56 56 expected = ret
57 57 try:
58 58 assert_ok(id_, expected, given=response.body)
59 59 finally:
60 60 fixture.destroy_repo_group(repo_group_name)
61 61
62 62 def test_api_create_repo_group_in_another_group(self):
63 63 repo_group_name = 'api-repo-group'
64 64
65 65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 66 assert repo_group is None
67 67 # create the parent
68 68 fixture.create_repo_group(repo_group_name)
69 69
70 70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 71 id_, params = build_data(
72 72 self.apikey, 'create_repo_group',
73 73 group_name=full_repo_group_name,
74 74 owner=TEST_USER_ADMIN_LOGIN,
75 75 copy_permissions=True)
76 76 response = api_call(self.app, params)
77 77
78 78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 79 assert repo_group is not None
80 80 ret = {
81 81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 82 'repo_group': repo_group.get_api_data()
83 83 }
84 84 expected = ret
85 85 try:
86 86 assert_ok(id_, expected, given=response.body)
87 87 finally:
88 88 fixture.destroy_repo_group(full_repo_group_name)
89 89 fixture.destroy_repo_group(repo_group_name)
90 90
91 91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 92 repo_group_name = 'api-repo-group-no'
93 93
94 94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 95 assert repo_group is None
96 96
97 97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 98 id_, params = build_data(
99 99 self.apikey, 'create_repo_group',
100 100 group_name=full_repo_group_name,
101 101 owner=TEST_USER_ADMIN_LOGIN,
102 102 copy_permissions=True)
103 103 response = api_call(self.app, params)
104 104 expected = {
105 105 'repo_group':
106 106 'Parent repository group `{}` does not exist'.format(
107 107 repo_group_name)}
108 108 assert_error(id_, expected, given=response.body)
109 109
110 110 def test_api_create_repo_group_that_exists(self):
111 111 repo_group_name = 'api-repo-group'
112 112
113 113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 114 assert repo_group is None
115 115
116 116 fixture.create_repo_group(repo_group_name)
117 117 id_, params = build_data(
118 118 self.apikey, 'create_repo_group',
119 119 group_name=repo_group_name,
120 120 owner=TEST_USER_ADMIN_LOGIN,)
121 121 response = api_call(self.app, params)
122 122 expected = {
123 123 'unique_repo_group_name':
124 124 'Repository group with name `{}` already exists'.format(
125 125 repo_group_name)}
126 126 try:
127 127 assert_error(id_, expected, given=response.body)
128 128 finally:
129 129 fixture.destroy_repo_group(repo_group_name)
130 130
131 131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 132 self, user_util):
133 133 regular_user = user_util.create_user()
134 134 regular_user_api_key = regular_user.api_key
135 135
136 136 repo_group_name = 'api-repo-group-by-regular-user'
137 137
138 138 usr = UserModel().get_by_username(regular_user.username)
139 139 usr.inherit_default_permissions = False
140 140 Session().add(usr)
141 141
142 142 UserModel().grant_perm(
143 143 regular_user.username, 'hg.repogroup.create.true')
144 144 Session().commit()
145 145
146 146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 147 assert repo_group is None
148 148
149 149 id_, params = build_data(
150 150 regular_user_api_key, 'create_repo_group',
151 151 group_name=repo_group_name)
152 152 response = api_call(self.app, params)
153 153
154 154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 155 assert repo_group is not None
156 156 expected = {
157 157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 158 'repo_group': repo_group.get_api_data()
159 159 }
160 160 try:
161 161 assert_ok(id_, expected, given=response.body)
162 162 finally:
163 163 fixture.destroy_repo_group(repo_group_name)
164 164
165 165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 166 self, user_util):
167 167
168 168 repo_group_name = 'api-repo-group-parent'
169 169
170 170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 171 assert repo_group is None
172 172 # create the parent
173 173 fixture.create_repo_group(repo_group_name)
174 174
175 175 # user perms
176 176 regular_user = user_util.create_user()
177 177 regular_user_api_key = regular_user.api_key
178 178
179 179 usr = UserModel().get_by_username(regular_user.username)
180 180 usr.inherit_default_permissions = False
181 181 Session().add(usr)
182 182
183 183 RepoGroupModel().grant_user_permission(
184 184 repo_group_name, regular_user.username, 'group.admin')
185 185 Session().commit()
186 186
187 187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 188 id_, params = build_data(
189 189 regular_user_api_key, 'create_repo_group',
190 190 group_name=full_repo_group_name)
191 191 response = api_call(self.app, params)
192 192
193 193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 194 assert repo_group is not None
195 195 expected = {
196 196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 197 'repo_group': repo_group.get_api_data()
198 198 }
199 199 try:
200 200 assert_ok(id_, expected, given=response.body)
201 201 finally:
202 202 fixture.destroy_repo_group(full_repo_group_name)
203 203 fixture.destroy_repo_group(repo_group_name)
204 204
205 205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 206 repo_group_name = 'api-repo-group'
207 207
208 208 id_, params = build_data(
209 209 self.apikey_regular, 'create_repo_group',
210 210 group_name=repo_group_name)
211 211 response = api_call(self.app, params)
212 212
213 213 expected = {
214 214 'repo_group':
215 215 u'You do not have the permission to store '
216 216 u'repository groups in the root location.'}
217 217 assert_error(id_, expected, given=response.body)
218 218
219 219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 220 repo_group_name = 'api-repo-group-regular-user'
221 221
222 222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 223 assert repo_group is None
224 224 # create the parent
225 225 fixture.create_repo_group(repo_group_name)
226 226
227 227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228 228
229 229 id_, params = build_data(
230 230 self.apikey_regular, 'create_repo_group',
231 231 group_name=full_repo_group_name)
232 232 response = api_call(self.app, params)
233 233
234 234 expected = {
235 235 'repo_group':
236 236 'Parent repository group `{}` does not exist'.format(
237 237 repo_group_name)}
238 238 try:
239 239 assert_error(id_, expected, given=response.body)
240 240 finally:
241 241 fixture.destroy_repo_group(repo_group_name)
242 242
243 243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 244 self):
245 245 repo_group_name = 'api-repo-group'
246 246
247 247 id_, params = build_data(
248 248 self.apikey_regular, 'create_repo_group',
249 249 group_name=repo_group_name,
250 250 owner=TEST_USER_ADMIN_LOGIN,)
251 251 response = api_call(self.app, params)
252 252
253 253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 254 assert_error(id_, expected, given=response.body)
255 255
256 256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 257 def test_api_create_repo_group_exception_occurred(self):
258 258 repo_group_name = 'api-repo-group'
259 259
260 260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 261 assert repo_group is None
262 262
263 263 id_, params = build_data(
264 264 self.apikey, 'create_repo_group',
265 265 group_name=repo_group_name,
266 266 owner=TEST_USER_ADMIN_LOGIN,)
267 267 response = api_call(self.app, params)
268 268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 269 assert_error(id_, expected, given=response.body)
270 270
271 271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 272 existing_repo_group = user_util.create_repo_group()
273 273 dirty_group_name = '//{}//group2//'.format(
274 274 existing_repo_group.group_name)
275 275 cleaned_group_name = '{}/group2'.format(
276 276 existing_repo_group.group_name)
277 277
278 278 id_, params = build_data(
279 279 self.apikey, 'create_repo_group',
280 280 group_name=dirty_group_name,
281 281 owner=TEST_USER_ADMIN_LOGIN,)
282 282 response = api_call(self.app, params)
283 283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 284 expected = {
285 285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 286 'repo_group': repo_group.get_api_data()
287 287 }
288 288 assert_ok(id_, expected, given=response.body)
289 289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,206 +1,206 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.auth import check_password
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import (
27 27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31 from rhodecode.model.db import RepoGroup
32 32
33 33
34 34 # TODO: mikhail: remove fixture from here
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestCreateUser(object):
40 40 def test_api_create_existing_user(self):
41 41 id_, params = build_data(
42 42 self.apikey, 'create_user',
43 43 username=TEST_USER_ADMIN_LOGIN,
44 44 email='test@foo.com',
45 45 password='trololo')
46 46 response = api_call(self.app, params)
47 47
48 48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 49 assert_error(id_, expected, given=response.body)
50 50
51 51 def test_api_create_user_with_existing_email(self):
52 52 id_, params = build_data(
53 53 self.apikey, 'create_user',
54 54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 55 email=TEST_USER_REGULAR_EMAIL,
56 56 password='trololo')
57 57 response = api_call(self.app, params)
58 58
59 59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 60 assert_error(id_, expected, given=response.body)
61 61
62 62 def test_api_create_user_with_wrong_username(self):
63 63 bad_username = '<> HELLO WORLD <>'
64 64 id_, params = build_data(
65 65 self.apikey, 'create_user',
66 66 username=bad_username,
67 67 email='new@email.com',
68 68 password='trololo')
69 69 response = api_call(self.app, params)
70 70
71 71 expected = {'username':
72 72 "Username may only contain alphanumeric characters "
73 73 "underscores, periods or dashes and must begin with "
74 74 "alphanumeric character or underscore"}
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 def test_api_create_user(self):
78 78 username = 'test_new_api_user'
79 79 email = username + "@foo.com"
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'create_user',
83 83 username=username,
84 84 email=email,
85 85 password='example')
86 86 response = api_call(self.app, params)
87 87
88 88 usr = UserModel().get_by_username(username)
89 89 ret = {
90 90 'msg': 'created new user `%s`' % (username,),
91 91 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 92 }
93 93 try:
94 94 expected = ret
95 95 assert check_password('example', usr.password)
96 96 assert_ok(id_, expected, given=response.body)
97 97 finally:
98 98 fixture.destroy_user(usr.user_id)
99 99
100 100 def test_api_create_user_without_password(self):
101 101 username = 'test_new_api_user_passwordless'
102 102 email = username + "@foo.com"
103 103
104 104 id_, params = build_data(
105 105 self.apikey, 'create_user',
106 106 username=username,
107 107 email=email)
108 108 response = api_call(self.app, params)
109 109
110 110 usr = UserModel().get_by_username(username)
111 111 ret = {
112 112 'msg': 'created new user `%s`' % (username,),
113 113 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 114 }
115 115 try:
116 116 expected = ret
117 117 assert_ok(id_, expected, given=response.body)
118 118 finally:
119 119 fixture.destroy_user(usr.user_id)
120 120
121 121 def test_api_create_user_with_extern_name(self):
122 122 username = 'test_new_api_user_passwordless'
123 123 email = username + "@foo.com"
124 124
125 125 id_, params = build_data(
126 126 self.apikey, 'create_user',
127 127 username=username,
128 128 email=email, extern_name='rhodecode')
129 129 response = api_call(self.app, params)
130 130
131 131 usr = UserModel().get_by_username(username)
132 132 ret = {
133 133 'msg': 'created new user `%s`' % (username,),
134 134 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 135 }
136 136 try:
137 137 expected = ret
138 138 assert_ok(id_, expected, given=response.body)
139 139 finally:
140 140 fixture.destroy_user(usr.user_id)
141 141
142 142 def test_api_create_user_with_password_change(self):
143 143 username = 'test_new_api_user_password_change'
144 144 email = username + "@foo.com"
145 145
146 146 id_, params = build_data(
147 147 self.apikey, 'create_user',
148 148 username=username,
149 149 email=email, extern_name='rhodecode',
150 150 force_password_change=True)
151 151 response = api_call(self.app, params)
152 152
153 153 usr = UserModel().get_by_username(username)
154 154 ret = {
155 155 'msg': 'created new user `%s`' % (username,),
156 156 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 157 }
158 158 try:
159 159 expected = ret
160 160 assert_ok(id_, expected, given=response.body)
161 161 finally:
162 162 fixture.destroy_user(usr.user_id)
163 163
164 164 def test_api_create_user_with_personal_repo_group(self):
165 165 username = 'test_new_api_user_personal_group'
166 166 email = username + "@foo.com"
167 167
168 168 id_, params = build_data(
169 169 self.apikey, 'create_user',
170 170 username=username,
171 171 email=email, extern_name='rhodecode',
172 172 create_personal_repo_group=True)
173 173 response = api_call(self.app, params)
174 174
175 175 usr = UserModel().get_by_username(username)
176 176 ret = {
177 177 'msg': 'created new user `%s`' % (username,),
178 178 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 179 }
180 180
181 181 personal_group = RepoGroup.get_by_group_name(username)
182 182 assert personal_group
183 183 assert personal_group.personal == True
184 184 assert personal_group.user.username == username
185 185
186 186 try:
187 187 expected = ret
188 188 assert_ok(id_, expected, given=response.body)
189 189 finally:
190 190 fixture.destroy_repo_group(username)
191 191 fixture.destroy_user(usr.user_id)
192 192
193 193 @mock.patch.object(UserModel, 'create_or_update', crash)
194 194 def test_api_create_user_when_exception_happened(self):
195 195
196 196 username = 'test_new_api_user'
197 197 email = username + "@foo.com"
198 198
199 199 id_, params = build_data(
200 200 self.apikey, 'create_user',
201 201 username=username,
202 202 email=email,
203 203 password='trololo')
204 204 response = api_call(self.app, params)
205 205 expected = 'failed to create user `%s`' % (username,)
206 206 assert_error(id_, expected, given=response.body)
@@ -1,127 +1,127 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.model.user_group import UserGroupModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 29 from rhodecode.tests.fixture import Fixture
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestCreateUserGroup(object):
34 34 fixture = Fixture()
35 35
36 36 def test_api_create_user_group(self):
37 37 group_name = 'some_new_group'
38 38 id_, params = build_data(
39 39 self.apikey, 'create_user_group', group_name=group_name)
40 40 response = api_call(self.app, params)
41 41
42 42 ret = {
43 43 'msg': 'created new user group `%s`' % (group_name,),
44 44 'user_group': jsonify(
45 45 UserGroupModel()
46 46 .get_by_name(group_name)
47 47 .get_api_data()
48 48 )
49 49 }
50 50 expected = ret
51 51 assert_ok(id_, expected, given=response.body)
52 52 self.fixture.destroy_user_group(group_name)
53 53
54 54 def test_api_create_user_group_regular_user(self):
55 55 group_name = 'some_new_group'
56 56
57 57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 58 usr.inherit_default_permissions = False
59 59 Session().add(usr)
60 60 UserModel().grant_perm(
61 61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
62 62 Session().commit()
63 63
64 64 id_, params = build_data(
65 65 self.apikey_regular, 'create_user_group', group_name=group_name)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = {
69 69 'msg': 'created new user group `%s`' % (group_name,),
70 70 'user_group': jsonify(
71 71 UserGroupModel()
72 72 .get_by_name(group_name)
73 73 .get_api_data()
74 74 )
75 75 }
76 76 try:
77 77 assert_ok(id_, expected, given=response.body)
78 78 finally:
79 79 self.fixture.destroy_user_group(group_name)
80 80 UserModel().revoke_perm(
81 81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
82 82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
83 83 usr.inherit_default_permissions = True
84 84 Session().add(usr)
85 85 Session().commit()
86 86
87 87 def test_api_create_user_group_regular_user_no_permission(self):
88 88 group_name = 'some_new_group'
89 89 id_, params = build_data(
90 90 self.apikey_regular, 'create_user_group', group_name=group_name)
91 91 response = api_call(self.app, params)
92 92 expected = "Access was denied to this resource."
93 93 assert_error(id_, expected, given=response.body)
94 94
95 95 def test_api_create_user_group_that_exist(self, user_util):
96 96 group = user_util.create_user_group()
97 97 group_name = group.users_group_name
98 98
99 99 id_, params = build_data(
100 100 self.apikey, 'create_user_group', group_name=group_name)
101 101 response = api_call(self.app, params)
102 102
103 103 expected = "user group `%s` already exist" % (group_name,)
104 104 assert_error(id_, expected, given=response.body)
105 105
106 106 @mock.patch.object(UserGroupModel, 'create', crash)
107 107 def test_api_create_user_group_exception_occurred(self):
108 108 group_name = 'exception_happens'
109 109 id_, params = build_data(
110 110 self.apikey, 'create_user_group', group_name=group_name)
111 111 response = api_call(self.app, params)
112 112
113 113 expected = 'failed to create group `%s`' % (group_name,)
114 114 assert_error(id_, expected, given=response.body)
115 115
116 116 def test_api_create_user_group_with_wrong_name(self, user_util):
117 117
118 118 group_name = 'wrong NAME <>'
119 119 id_, params = build_data(
120 120 self.apikey, 'create_user_group', group_name=group_name)
121 121 response = api_call(self.app, params)
122 122
123 123 expected = {"user_group_name":
124 124 "Allowed in name are letters, numbers, and `-`, `_`, "
125 125 "`.` Name must start with a letter or number. "
126 126 "Got `{}`".format(group_name)}
127 127 assert_error(id_, expected, given=response.body)
@@ -1,61 +1,61 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.gist import GistModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiDeleteGist(object):
31 31 def test_api_delete_gist(self, gist_util):
32 32 gist_id = gist_util.create_gist().gist_access_id
33 33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
34 34 response = api_call(self.app, params)
35 35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 def test_api_delete_gist_regular_user(self, gist_util):
39 39 gist_id = gist_util.create_gist(
40 40 owner=self.TEST_USER_LOGIN).gist_access_id
41 41 id_, params = build_data(
42 42 self.apikey_regular, 'delete_gist', gistid=gist_id)
43 43 response = api_call(self.app, params)
44 44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
48 48 gist_id = gist_util.create_gist().gist_access_id
49 49 id_, params = build_data(
50 50 self.apikey_regular, 'delete_gist', gistid=gist_id)
51 51 response = api_call(self.app, params)
52 52 expected = 'gist `%s` does not exist' % (gist_id,)
53 53 assert_error(id_, expected, given=response.body)
54 54
55 55 @mock.patch.object(GistModel, 'delete', crash)
56 56 def test_api_delete_gist_exception_occurred(self, gist_util):
57 57 gist_id = gist_util.create_gist().gist_access_id
58 58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
59 59 response = api_call(self.app, params)
60 60 expected = 'failed to delete gist ID:%s' % (gist_id,)
61 61 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiDeleteRepo(object):
31 31 def test_api_delete_repo(self, backend):
32 32 repo = backend.create_repo()
33 33 repo_name = repo.repo_name
34 34 id_, params = build_data(
35 35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 36 response = api_call(self.app, params)
37 37
38 38 expected = {
39 39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 40 'success': True
41 41 }
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 45 repo = backend.create_repo(cur_user=user_regular.username)
46 46 repo_name = repo.repo_name
47 47 id_, params = build_data(
48 48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': 'Deleted repository `%s`' % (repo_name,),
53 53 'success': True
54 54 }
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
58 58 repo = backend.create_repo()
59 59 repo_name = repo.repo_name
60 60 id_, params = build_data(
61 61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
62 62 response = api_call(self.app, params)
63 63 expected = 'repository `%s` does not exist' % (repo_name)
64 64 assert_error(id_, expected, given=response.body)
65 65
66 66 def test_api_delete_repo_exception_occurred(self, backend):
67 67 repo = backend.create_repo()
68 68 repo_name = repo.repo_name
69 69 id_, params = build_data(
70 70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
71 71 with mock.patch.object(RepoModel, 'delete', crash):
72 72 response = api_call(self.app, params)
73 73 expected = 'failed to delete repository `%s`' % (repo_name,)
74 74 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestApiDeleteRepoGroup(object):
32 32 def test_api_delete_repo_group(self, user_util):
33 33 repo_group = user_util.create_repo_group(auto_cleanup=False)
34 34 repo_group_name = repo_group.group_name
35 35 repo_group_id = repo_group.group_id
36 36 id_, params = build_data(
37 37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
38 38 response = api_call(self.app, params)
39 39
40 40 ret = {
41 41 'msg': 'deleted repo group ID:%s %s' % (
42 42 repo_group_id, repo_group_name
43 43 ),
44 44 'repo_group': None
45 45 }
46 46 expected = ret
47 47 assert_ok(id_, expected, given=response.body)
48 48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
49 49 assert gr is None
50 50
51 51 def test_api_delete_repo_group_regular_user(self, user_util):
52 52 repo_group = user_util.create_repo_group(auto_cleanup=False)
53 53 repo_group_name = repo_group.group_name
54 54 repo_group_id = repo_group.group_id
55 55
56 56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 57 user_util.grant_user_permission_to_repo_group(
58 58 repo_group, user, 'group.admin')
59 59
60 60 id_, params = build_data(
61 61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
62 62 response = api_call(self.app, params)
63 63
64 64 ret = {
65 65 'msg': 'deleted repo group ID:%s %s' % (
66 66 repo_group_id, repo_group_name
67 67 ),
68 68 'repo_group': None
69 69 }
70 70 expected = ret
71 71 assert_ok(id_, expected, given=response.body)
72 72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
73 73 assert gr is None
74 74
75 75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
76 76 repo_group = user_util.create_repo_group()
77 77 repo_group_name = repo_group.group_name
78 78
79 79 id_, params = build_data(
80 80 self.apikey_regular, 'delete_repo_group',
81 81 repogroupid=repo_group_name, )
82 82 response = api_call(self.app, params)
83 83
84 84 expected = 'repository group `%s` does not exist' % (
85 85 repo_group_name,)
86 86 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestDeleteUser(object):
32 32 def test_api_delete_user(self, user_util):
33 33 usr = user_util.create_user(auto_cleanup=False)
34 34
35 35 username = usr.username
36 36 usr_id = usr.user_id
37 37
38 38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
39 39 response = api_call(self.app, params)
40 40
41 41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
42 42 'user': None}
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 @mock.patch.object(UserModel, 'delete', crash)
47 47 def test_api_delete_user_when_exception_happened(self, user_util):
48 48 usr = user_util.create_user()
49 49 username = usr.username
50 50
51 51 id_, params = build_data(
52 52 self.apikey, 'delete_user', userid=username, )
53 53 response = api_call(self.app, params)
54 54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
55 55 usr.username)
56 56 expected = ret
57 57 assert_error(id_, expected, given=response.body)
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.model.user_group import UserGroupModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestDeleteUserGroup(object):
33 33 def test_api_delete_user_group(self, user_util):
34 34 user_group = user_util.create_user_group(auto_cleanup=False)
35 35 group_name = user_group.users_group_name
36 36 group_id = user_group.users_group_id
37 37 id_, params = build_data(
38 38 self.apikey, 'delete_user_group', usergroupid=group_name)
39 39 response = api_call(self.app, params)
40 40
41 41 expected = {
42 42 'user_group': None,
43 43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
44 44 }
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_delete_user_group_regular_user(self, user_util):
48 48 ugroup = user_util.create_user_group(auto_cleanup=False)
49 49 group_name = ugroup.users_group_name
50 50 group_id = ugroup.users_group_id
51 51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
52 52
53 53 user_util.grant_user_permission_to_user_group(
54 54 ugroup, user, 'usergroup.admin')
55 55
56 56 id_, params = build_data(
57 57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
58 58 response = api_call(self.app, params)
59 59
60 60 expected = {
61 61 'user_group': None,
62 62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
63 63 }
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
67 67 user_group = user_util.create_user_group()
68 68 group_name = user_group.users_group_name
69 69
70 70 id_, params = build_data(
71 71 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
72 72 response = api_call(self.app, params)
73 73
74 74 expected = 'user group `%s` does not exist' % (group_name)
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
78 78 ugroup = user_util.create_user_group()
79 79 group_name = ugroup.users_group_name
80 80 repo = backend.create_repo()
81 81
82 82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
83 83 repo, ugroup, 'repository.write')
84 84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
85 85
86 86 id_, params = build_data(
87 87 self.apikey, 'delete_user_group',
88 88 usergroupid=group_name)
89 89 response = api_call(self.app, params)
90 90
91 91 expected = msg
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_delete_user_group_exception_occurred(self, user_util):
95 95 ugroup = user_util.create_user_group()
96 96 group_name = ugroup.users_group_name
97 97 group_id = ugroup.users_group_id
98 98 id_, params = build_data(
99 99 self.apikey, 'delete_user_group',
100 100 usergroupid=group_name)
101 101
102 102 with mock.patch.object(UserGroupModel, 'delete', crash):
103 103 response = api_call(self.app, params)
104 104 expected = 'failed to delete user group ID:%s %s' % (
105 105 group_id, group_name)
106 106 assert_error(id_, expected, given=response.body)
@@ -1,79 +1,79 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.api.views import deprecated_api
25 25 from rhodecode.lib.ext_json import json
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestCommitComment(object):
32 32 def test_deprecated_message_in_docstring(self):
33 33 docstring = deprecated_api.changeset_comment.__doc__
34 34 assert '.. deprecated:: 3.4.0' in docstring
35 35 assert 'Please use method `comment_commit` instead.' in docstring
36 36
37 37 def test_deprecated_message_in_retvalue(self):
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'show_ip')
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'id': id_,
45 45 'error': None,
46 46 'result': json.loads(response.body)['result'],
47 47 'DEPRECATION_WARNING':
48 48 'DEPRECATED METHOD Please use method `get_ip` instead.'
49 49 }
50 50 assert expected == json.loads(response.body)
51 51
52 52 # def test_calls_comment_commit(self, backend, no_notifications):
53 53 # data = {
54 54 # 'repoid': backend.repo_name,
55 55 # 'status': ChangesetStatus.STATUS_APPROVED,
56 56 # 'message': 'Approved',
57 57 # 'revision': 'tip'
58 58 # }
59 59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
60 60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
61 61 # api_call(self.app, params)
62 62 #
63 63 # _, call_args = comment_mock.call_args
64 64 # data['commit_id'] = data.pop('revision')
65 65 # for key in data:
66 66 # assert call_args[key] == data[key]
67 67
68 68 # def test_warning_log_contains_deprecation_message(self):
69 69 # api = self.SampleApi()
70 70 # with patch.object(utils, 'log') as log_mock:
71 71 # api.api_method()
72 72 #
73 73 # assert log_mock.warning.call_count == 1
74 74 # call_args = log_mock.warning.call_args[0]
75 75 # assert (
76 76 # call_args[0] ==
77 77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
78 78 # assert call_args[1].__name__ == 'api_method'
79 79 # assert call_args[2] == 'new_method' No newline at end of file
@@ -1,279 +1,279 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.repo import RepoModel
27 27 from rhodecode.model.repo_group import RepoGroupModel
28 28 from rhodecode.model.user import UserModel
29 29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30 30 from rhodecode.api.tests.utils import (
31 31 build_data, api_call, assert_error, assert_ok, crash)
32 32 from rhodecode.tests.fixture import Fixture
33 33
34 34
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestApiForkRepo(object):
40 40 def test_api_fork_repo(self, backend):
41 41 source_name = backend['minimal'].repo_name
42 42 fork_name = backend.new_repo_name()
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'fork_repo',
46 46 repoid=source_name,
47 47 fork_name=fork_name,
48 48 owner=TEST_USER_ADMIN_LOGIN)
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
53 53 'success': True,
54 54 'task': None,
55 55 }
56 56 try:
57 57 assert_ok(id_, expected, given=response.body)
58 58 finally:
59 59 fixture.destroy_repo(fork_name)
60 60
61 61 def test_api_fork_repo_into_group(self, backend, user_util):
62 62 source_name = backend['minimal'].repo_name
63 63 repo_group = user_util.create_repo_group()
64 64 fork_name = '%s/api-repo-fork' % repo_group.group_name
65 65 id_, params = build_data(
66 66 self.apikey, 'fork_repo',
67 67 repoid=source_name,
68 68 fork_name=fork_name,
69 69 owner=TEST_USER_ADMIN_LOGIN)
70 70 response = api_call(self.app, params)
71 71
72 72 ret = {
73 73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
74 74 'success': True,
75 75 'task': None,
76 76 }
77 77 expected = ret
78 78 try:
79 79 assert_ok(id_, expected, given=response.body)
80 80 finally:
81 81 fixture.destroy_repo(fork_name)
82 82
83 83 def test_api_fork_repo_non_admin(self, backend):
84 84 source_name = backend['minimal'].repo_name
85 85 fork_name = backend.new_repo_name()
86 86
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'fork_repo',
89 89 repoid=source_name,
90 90 fork_name=fork_name)
91 91 response = api_call(self.app, params)
92 92
93 93 expected = {
94 94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
95 95 'success': True,
96 96 'task': None,
97 97 }
98 98 try:
99 99 assert_ok(id_, expected, given=response.body)
100 100 finally:
101 101 fixture.destroy_repo(fork_name)
102 102
103 103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
104 104 source_name = backend['minimal'].repo_name
105 105 repo_group = user_util.create_repo_group()
106 106 repo_group_name = repo_group.group_name
107 107 fork_name = '%s/api-repo-fork' % repo_group_name
108 108
109 109 id_, params = build_data(
110 110 self.apikey_regular, 'fork_repo',
111 111 repoid=source_name,
112 112 fork_name=fork_name)
113 113 response = api_call(self.app, params)
114 114
115 115 expected = {
116 116 'repo_group': 'Repository group `{}` does not exist'.format(
117 117 repo_group_name)}
118 118 try:
119 119 assert_error(id_, expected, given=response.body)
120 120 finally:
121 121 fixture.destroy_repo(fork_name)
122 122
123 123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
124 124 source_name = backend['minimal'].repo_name
125 125 repo_group = user_util.create_repo_group()
126 126 fork_name = '%s/api-repo-fork' % repo_group.group_name
127 127
128 128 RepoGroupModel().grant_user_permission(
129 129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
130 130 Session().commit()
131 131
132 132 id_, params = build_data(
133 133 self.apikey_regular, 'fork_repo',
134 134 repoid=source_name,
135 135 fork_name=fork_name)
136 136 response = api_call(self.app, params)
137 137
138 138 expected = {
139 139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
140 140 'success': True,
141 141 'task': None,
142 142 }
143 143 try:
144 144 assert_ok(id_, expected, given=response.body)
145 145 finally:
146 146 fixture.destroy_repo(fork_name)
147 147
148 148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
149 149 source_name = backend['minimal'].repo_name
150 150 fork_name = backend.new_repo_name()
151 151 id_, params = build_data(
152 152 self.apikey_regular, 'fork_repo',
153 153 repoid=source_name,
154 154 fork_name=fork_name,
155 155 owner=TEST_USER_ADMIN_LOGIN)
156 156 response = api_call(self.app, params)
157 157 expected = 'Only RhodeCode super-admin can specify `owner` param'
158 158 assert_error(id_, expected, given=response.body)
159 159
160 160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
161 161 self, backend):
162 162 source_name = backend['minimal'].repo_name
163 163 RepoModel().grant_user_permission(repo=source_name,
164 164 user=self.TEST_USER_LOGIN,
165 165 perm='repository.none')
166 166 fork_name = backend.new_repo_name()
167 167 id_, params = build_data(
168 168 self.apikey_regular, 'fork_repo',
169 169 repoid=backend.repo_name,
170 170 fork_name=fork_name)
171 171 response = api_call(self.app, params)
172 172 expected = 'repository `%s` does not exist' % (backend.repo_name)
173 173 assert_error(id_, expected, given=response.body)
174 174
175 175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
176 176 self, backend, user_util):
177 177
178 178 regular_user = user_util.create_user()
179 179 regular_user_api_key = regular_user.api_key
180 180 usr = UserModel().get_by_username(regular_user.username)
181 181 usr.inherit_default_permissions = False
182 182 Session().add(usr)
183 183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
184 184
185 185 source_name = backend['minimal'].repo_name
186 186 fork_name = backend.new_repo_name()
187 187 id_, params = build_data(
188 188 regular_user_api_key, 'fork_repo',
189 189 repoid=source_name,
190 190 fork_name=fork_name)
191 191 response = api_call(self.app, params)
192 192 expected = {
193 193 "repo_name": "You do not have the permission to "
194 194 "store repositories in the root location."}
195 195 assert_error(id_, expected, given=response.body)
196 196
197 197 def test_api_fork_repo_non_admin_no_permission_to_fork(
198 198 self, backend, user_util):
199 199
200 200 regular_user = user_util.create_user()
201 201 regular_user_api_key = regular_user.api_key
202 202 usr = UserModel().get_by_username(regular_user.username)
203 203 usr.inherit_default_permissions = False
204 204 Session().add(usr)
205 205
206 206 source_name = backend['minimal'].repo_name
207 207 fork_name = backend.new_repo_name()
208 208 id_, params = build_data(
209 209 regular_user_api_key, 'fork_repo',
210 210 repoid=source_name,
211 211 fork_name=fork_name)
212 212 response = api_call(self.app, params)
213 213
214 214 expected = "Access was denied to this resource."
215 215 assert_error(id_, expected, given=response.body)
216 216
217 217 def test_api_fork_repo_unknown_owner(self, backend):
218 218 source_name = backend['minimal'].repo_name
219 219 fork_name = backend.new_repo_name()
220 220 owner = 'i-dont-exist'
221 221 id_, params = build_data(
222 222 self.apikey, 'fork_repo',
223 223 repoid=source_name,
224 224 fork_name=fork_name,
225 225 owner=owner)
226 226 response = api_call(self.app, params)
227 227 expected = 'user `%s` does not exist' % (owner,)
228 228 assert_error(id_, expected, given=response.body)
229 229
230 230 def test_api_fork_repo_fork_exists(self, backend):
231 231 source_name = backend['minimal'].repo_name
232 232 fork_name = backend.new_repo_name()
233 233 fork_repo = fixture.create_fork(source_name, fork_name)
234 234
235 235 id_, params = build_data(
236 236 self.apikey, 'fork_repo',
237 237 repoid=source_name,
238 238 fork_name=fork_name,
239 239 owner=TEST_USER_ADMIN_LOGIN)
240 240 response = api_call(self.app, params)
241 241
242 242 try:
243 243 expected = {
244 244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
245 245 fork_name)}
246 246 assert_error(id_, expected, given=response.body)
247 247 finally:
248 248 fixture.destroy_repo(fork_repo.repo_name)
249 249
250 250 def test_api_fork_repo_repo_exists(self, backend):
251 251 source_name = backend['minimal'].repo_name
252 252 fork_name = source_name
253 253
254 254 id_, params = build_data(
255 255 self.apikey, 'fork_repo',
256 256 repoid=source_name,
257 257 fork_name=fork_name,
258 258 owner=TEST_USER_ADMIN_LOGIN)
259 259 response = api_call(self.app, params)
260 260
261 261 expected = {
262 262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
263 263 fork_name)}
264 264 assert_error(id_, expected, given=response.body)
265 265
266 266 @mock.patch.object(RepoModel, 'create_fork', crash)
267 267 def test_api_fork_repo_exception_occurred(self, backend):
268 268 source_name = backend['minimal'].repo_name
269 269 fork_name = backend.new_repo_name()
270 270 id_, params = build_data(
271 271 self.apikey, 'fork_repo',
272 272 repoid=source_name,
273 273 fork_name=fork_name,
274 274 owner=TEST_USER_ADMIN_LOGIN)
275 275 response = api_call(self.app, params)
276 276
277 277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
278 278 fork_name)
279 279 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.db import Gist
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetGist(object):
31 31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 32 gist = gist_util.create_gist()
33 33 gist_id = gist.gist_access_id
34 34 gist_created_on = gist.created_on
35 35 gist_modified_at = gist.modified_at
36 36 id_, params = build_data(
37 37 self.apikey, 'get_gist', gistid=gist_id, )
38 38 response = api_call(self.app, params)
39 39
40 40 expected = {
41 41 'access_id': gist_id,
42 42 'created_on': gist_created_on,
43 43 'modified_at': gist_modified_at,
44 44 'description': 'new-gist',
45 45 'expires': -1.0,
46 46 'gist_id': int(gist_id),
47 47 'type': 'public',
48 48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 50 'content': None,
51 51 }
52 52
53 53 assert_ok(id_, expected, given=response.body)
54 54
55 55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 56 mapping = {
57 57 u'filename1.txt': {'content': u'hello world'},
58 58 u'filename1ą.txt': {'content': u'hello worldę'}
59 59 }
60 60 gist = gist_util.create_gist(gist_mapping=mapping)
61 61 gist_id = gist.gist_access_id
62 62 gist_created_on = gist.created_on
63 63 gist_modified_at = gist.modified_at
64 64 id_, params = build_data(
65 65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = {
69 69 'access_id': gist_id,
70 70 'created_on': gist_created_on,
71 71 'modified_at': gist_modified_at,
72 72 'description': 'new-gist',
73 73 'expires': -1.0,
74 74 'gist_id': int(gist_id),
75 75 'type': 'public',
76 76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 78 'content': {
79 79 u'filename1.txt': u'hello world',
80 80 u'filename1ą.txt': u'hello worldę'
81 81 },
82 82 }
83 83
84 84 assert_ok(id_, expected, given=response.body)
85 85
86 86 def test_api_get_gist_not_existing(self):
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'get_gist', gistid='12345', )
89 89 response = api_call(self.app, params)
90 90 expected = 'gist `%s` does not exist' % ('12345',)
91 91 assert_error(id_, expected, given=response.body)
92 92
93 93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 94 gist = gist_util.create_gist()
95 95 gist_id = gist.gist_access_id
96 96 id_, params = build_data(
97 97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 98 response = api_call(self.app, params)
99 99
100 100 expected = 'gist `%s` does not exist' % (gist_id,)
101 101 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetGist(object):
31 31 def test_api_get_gists(self, gist_util):
32 32 gist_util.create_gist()
33 33 gist_util.create_gist()
34 34
35 35 id_, params = build_data(self.apikey, 'get_gists')
36 36 response = api_call(self.app, params)
37 37 assert len(response.json['result']) == 2
38 38
39 39 def test_api_get_gists_regular_user(self, gist_util):
40 40 # by admin
41 41 gist_util.create_gist()
42 42 gist_util.create_gist()
43 43
44 44 # by reg user
45 45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
48 48
49 49 id_, params = build_data(self.apikey_regular, 'get_gists')
50 50 response = api_call(self.app, params)
51 51 assert len(response.json['result']) == 3
52 52
53 53 def test_api_get_gists_only_for_regular_user(self, gist_util):
54 54 # by admin
55 55 gist_util.create_gist()
56 56 gist_util.create_gist()
57 57
58 58 # by reg user
59 59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
62 62
63 63 id_, params = build_data(
64 64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
65 65 response = api_call(self.app, params)
66 66 assert len(response.json['result']) == 3
67 67
68 68 def test_api_get_gists_regular_user_with_different_userid(self):
69 69 id_, params = build_data(
70 70 self.apikey_regular, 'get_gists',
71 71 userid=TEST_USER_ADMIN_LOGIN)
72 72 response = api_call(self.app, params)
73 73 expected = 'userid is not the same as your user'
74 74 assert_error(id_, expected, given=response.body)
@@ -1,36 +1,36 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetIp(object):
29 29 def test_api_get_ip(self):
30 30 id_, params = build_data(self.apikey, 'get_ip')
31 31 response = api_call(self.app, params)
32 32 expected = {
33 33 'server_ip_addr': '0.0.0.0',
34 34 'user_ips': []
35 35 }
36 36 assert_ok(id_, expected, given=response.body)
@@ -1,91 +1,91 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.db import Repository, User
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetLocks(object):
32 32 def test_api_get_user_locks_regular_user(self):
33 33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
34 34 response = api_call(self.app, params)
35 35 expected = []
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 def test_api_get_user_locks_with_userid_regular_user(self):
39 39 id_, params = build_data(
40 40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
41 41 response = api_call(self.app, params)
42 42 expected = 'userid is not the same as your user'
43 43 assert_error(id_, expected, given=response.body)
44 44
45 45 def test_api_get_user_locks(self):
46 46 id_, params = build_data(self.apikey, 'get_user_locks')
47 47 response = api_call(self.app, params)
48 48 expected = []
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
52 52 ('apikey', True),
53 53 ('apikey_regular', False),
54 54 ])
55 55 def test_api_get_user_locks_with_one_locked_repo(
56 56 self, apikey_attr, expect_secrets, backend):
57 57
58 58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
59 59 Repository.lock(
60 60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
61 61
62 62 apikey = getattr(self, apikey_attr)
63 63
64 64 id_, params = build_data(apikey, 'get_user_locks')
65 65 if apikey_attr == 'apikey':
66 66 # super-admin should call in specific user
67 67 id_, params = build_data(apikey, 'get_user_locks',
68 68 userid=self.TEST_USER_LOGIN)
69 69
70 70 response = api_call(self.app, params)
71 71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
75 75 self, backend):
76 76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
77 77
78 78 Repository.lock(repo, User.get_by_username(
79 79 self.TEST_USER_LOGIN).user_id)
80 80 id_, params = build_data(
81 81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
82 82 response = api_call(self.app, params)
83 83 expected = [repo.get_api_data(include_secrets=True)]
84 84 assert_ok(id_, expected, given=response.body)
85 85
86 86 def test_api_get_user_locks_with_userid(self):
87 87 id_, params = build_data(
88 88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
89 89 response = api_call(self.app, params)
90 90 expected = []
91 91 assert_ok(id_, expected, given=response.body)
@@ -1,59 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetMethod(object):
29 29 def test_get_methods_no_matches(self):
30 30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 31 response = api_call(self.app, params)
32 32
33 33 expected = []
34 34 assert_ok(id_, expected, given=response.body)
35 35
36 36 def test_get_methods(self):
37 37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 38 response = api_call(self.app, params)
39 39
40 40 expected = ['changeset_comment', 'comment_pull_request',
41 41 'get_pull_request_comments', 'comment_commit']
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_get_methods_on_single_match(self):
45 45 id_, params = build_data(self.apikey, 'get_method',
46 46 pattern='*comment_commit*')
47 47 response = api_call(self.app, params)
48 48
49 49 expected = ['comment_commit',
50 50 {'apiuser': '<RequiredType>',
51 51 'comment_type': "<Optional:u'note'>",
52 52 'commit_id': '<RequiredType>',
53 53 'message': '<RequiredType>',
54 54 'repoid': '<RequiredType>',
55 55 'request': '<RequiredType>',
56 56 'resolves_comment_id': '<Optional:None>',
57 57 'status': '<Optional:None>',
58 58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
59 59 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestGetPullRequest(object):
35 35
36 36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38 pull_request = pr_util.create_pull_request(mergeable=True)
39 39 id_, params = build_data(
40 40 self.apikey, 'get_pull_request',
41 41 pullrequestid=pull_request.pull_request_id)
42 42
43 43 response = api_call(self.app, params)
44 44
45 45 assert response.status == '200 OK'
46 46
47 47 url_obj = urlobject.URLObject(
48 48 h.route_url(
49 49 'pullrequest_show',
50 50 repo_name=pull_request.target_repo.repo_name,
51 51 pull_request_id=pull_request.pull_request_id))
52 52
53 53 pr_url = safe_unicode(
54 54 url_obj.with_netloc(http_host_only_stub))
55 55 source_url = safe_unicode(
56 56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
57 57 target_url = safe_unicode(
58 58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
59 59 shadow_url = safe_unicode(
60 60 PullRequestModel().get_shadow_clone_url(pull_request))
61 61
62 62 expected = {
63 63 'pull_request_id': pull_request.pull_request_id,
64 64 'url': pr_url,
65 65 'title': pull_request.title,
66 66 'description': pull_request.description,
67 67 'status': pull_request.status,
68 68 'created_on': pull_request.created_on,
69 69 'updated_on': pull_request.updated_on,
70 70 'commit_ids': pull_request.revisions,
71 71 'review_status': pull_request.calculated_review_status(),
72 72 'mergeable': {
73 73 'status': True,
74 74 'message': 'This pull request can be automatically merged.',
75 75 },
76 76 'source': {
77 77 'clone_url': source_url,
78 78 'repository': pull_request.source_repo.repo_name,
79 79 'reference': {
80 80 'name': pull_request.source_ref_parts.name,
81 81 'type': pull_request.source_ref_parts.type,
82 82 'commit_id': pull_request.source_ref_parts.commit_id,
83 83 },
84 84 },
85 85 'target': {
86 86 'clone_url': target_url,
87 87 'repository': pull_request.target_repo.repo_name,
88 88 'reference': {
89 89 'name': pull_request.target_ref_parts.name,
90 90 'type': pull_request.target_ref_parts.type,
91 91 'commit_id': pull_request.target_ref_parts.commit_id,
92 92 },
93 93 },
94 94 'merge': {
95 95 'clone_url': shadow_url,
96 96 'reference': {
97 97 'name': pull_request.shadow_merge_ref.name,
98 98 'type': pull_request.shadow_merge_ref.type,
99 99 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 100 },
101 101 },
102 102 'author': pull_request.author.get_api_data(include_secrets=False,
103 103 details='basic'),
104 104 'reviewers': [
105 105 {
106 106 'user': reviewer.get_api_data(include_secrets=False,
107 107 details='basic'),
108 108 'reasons': reasons,
109 109 'review_status': st[0][1].status if st else 'not_reviewed',
110 110 }
111 111 for obj, reviewer, reasons, mandatory, st in
112 112 pull_request.reviewers_statuses()
113 113 ]
114 114 }
115 115 assert_ok(id_, expected, response.body)
116 116
117 117 def test_api_get_pull_request_repo_error(self, pr_util):
118 118 pull_request = pr_util.create_pull_request()
119 119 id_, params = build_data(
120 120 self.apikey, 'get_pull_request',
121 121 repoid=666, pullrequestid=pull_request.pull_request_id)
122 122 response = api_call(self.app, params)
123 123
124 124 expected = 'repository `666` does not exist'
125 125 assert_error(id_, expected, given=response.body)
126 126
127 127 def test_api_get_pull_request_pull_request_error(self):
128 128 id_, params = build_data(
129 129 self.apikey, 'get_pull_request', pullrequestid=666)
130 130 response = api_call(self.app, params)
131 131
132 132 expected = 'pull request `666` does not exist'
133 133 assert_error(id_, expected, given=response.body)
134 134
135 135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 136 id_, params = build_data(
137 137 self.apikey, 'get_pull_request',
138 138 pullrequestid=666)
139 139 response = api_call(self.app, params)
140 140
141 141 expected = 'pull request `666` does not exist'
142 142 assert_error(id_, expected, given=response.body)
@@ -1,82 +1,82 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestGetPullRequestComments(object):
35 35
36 36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38
39 39 pull_request = pr_util.create_pull_request(mergeable=True)
40 40 id_, params = build_data(
41 41 self.apikey, 'get_pull_request_comments',
42 42 pullrequestid=pull_request.pull_request_id)
43 43
44 44 response = api_call(self.app, params)
45 45
46 46 assert response.status == '200 OK'
47 47 resp_date = response.json['result'][0]['comment_created_on']
48 48 resp_comment_id = response.json['result'][0]['comment_id']
49 49
50 50 expected = [
51 51 {'comment_author': {'active': True,
52 52 'full_name_or_username': 'RhodeCode Admin',
53 53 'username': 'test_admin'},
54 54 'comment_created_on': resp_date,
55 55 'comment_f_path': None,
56 56 'comment_id': resp_comment_id,
57 57 'comment_lineno': None,
58 58 'comment_status': {'status': 'under_review',
59 59 'status_lbl': 'Under Review'},
60 60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 61 'comment_type': 'note',
62 62 'pull_request_version': None}
63 63 ]
64 64 assert_ok(id_, expected, response.body)
65 65
66 66 def test_api_get_pull_request_comments_repo_error(self, pr_util):
67 67 pull_request = pr_util.create_pull_request()
68 68 id_, params = build_data(
69 69 self.apikey, 'get_pull_request_comments',
70 70 repoid=666, pullrequestid=pull_request.pull_request_id)
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'repository `666` does not exist'
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_get_pull_request_comments_pull_request_error(self):
77 77 id_, params = build_data(
78 78 self.apikey, 'get_pull_request_comments', pullrequestid=666)
79 79 response = api_call(self.app, params)
80 80
81 81 expected = 'pull request `666` does not exist'
82 82 assert_error(id_, expected, given=response.body)
@@ -1,80 +1,80 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetPullRequest(object):
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_get_pull_requests(self, pr_util):
34 34 pull_request = pr_util.create_pull_request()
35 35 pull_request_2 = PullRequestModel().create(
36 36 created_by=pull_request.author,
37 37 source_repo=pull_request.source_repo,
38 38 source_ref=pull_request.source_ref,
39 39 target_repo=pull_request.target_repo,
40 40 target_ref=pull_request.target_ref,
41 41 revisions=pull_request.revisions,
42 42 reviewers=(),
43 43 title=pull_request.title,
44 44 description=pull_request.description,
45 45 )
46 46 Session().commit()
47 47 id_, params = build_data(
48 48 self.apikey, 'get_pull_requests',
49 49 repoid=pull_request.target_repo.repo_name)
50 50 response = api_call(self.app, params)
51 51 assert response.status == '200 OK'
52 52 assert len(response.json['result']) == 2
53 53
54 54 PullRequestModel().close_pull_request(
55 55 pull_request_2, pull_request_2.author)
56 56 Session().commit()
57 57
58 58 id_, params = build_data(
59 59 self.apikey, 'get_pull_requests',
60 60 repoid=pull_request.target_repo.repo_name,
61 61 status='new')
62 62 response = api_call(self.app, params)
63 63 assert response.status == '200 OK'
64 64 assert len(response.json['result']) == 1
65 65
66 66 id_, params = build_data(
67 67 self.apikey, 'get_pull_requests',
68 68 repoid=pull_request.target_repo.repo_name,
69 69 status='closed')
70 70 response = api_call(self.app, params)
71 71 assert response.status == '200 OK'
72 72 assert len(response.json['result']) == 1
73 73
74 74 @pytest.mark.backends("git", "hg")
75 75 def test_api_get_pull_requests_repo_error(self):
76 76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
77 77 response = api_call(self.app, params)
78 78
79 79 expected = 'repository `666` does not exist'
80 80 assert_error(id_, expected, given=response.body)
@@ -1,137 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestGetRepo(object):
34 34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
35 35 ('apikey', True),
36 36 ('apikey_regular', False),
37 37 ])
38 38 @pytest.mark.parametrize("cache_param", [
39 39 True,
40 40 False,
41 41 None,
42 42 ])
43 43 def test_api_get_repo(
44 44 self, apikey_attr, expect_secrets, cache_param, backend,
45 45 user_util):
46 46 repo = backend.create_repo()
47 47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 48 group = user_util.create_user_group(members=[usr])
49 49 user_util.grant_user_group_permission_to_repo(
50 50 repo=repo, user_group=group, permission_name='repository.read')
51 51 Session().commit()
52 52 kwargs = {
53 53 'repoid': repo.repo_name,
54 54 }
55 55 if cache_param is not None:
56 56 kwargs['cache'] = cache_param
57 57
58 58 apikey = getattr(self, apikey_attr)
59 59 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 60 response = api_call(self.app, params)
61 61
62 62 ret = repo.get_api_data()
63 63
64 64 permissions = expected_permissions(repo)
65 65
66 66 followers = []
67 67 for user in repo.followers:
68 68 followers.append(user.user.get_api_data(
69 69 include_secrets=expect_secrets))
70 70
71 71 ret['permissions'] = permissions
72 72 ret['followers'] = followers
73 73
74 74 expected = ret
75 75
76 76 assert_ok(id_, expected, given=response.body)
77 77
78 78 @pytest.mark.parametrize("grant_perm", [
79 79 'repository.admin',
80 80 'repository.write',
81 81 'repository.read',
82 82 ])
83 83 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
84 84 # TODO: Depending on which tests are running before this one, we
85 85 # start with a different number of permissions in the database.
86 86 repo = RepoModel().get_by_repo_name(backend.repo_name)
87 87 permission_count = len(repo.repo_to_perm)
88 88
89 89 RepoModel().grant_user_permission(repo=backend.repo_name,
90 90 user=self.TEST_USER_LOGIN,
91 91 perm=grant_perm)
92 92 Session().commit()
93 93 id_, params = build_data(
94 94 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
95 95 response = api_call(self.app, params)
96 96
97 97 repo = RepoModel().get_by_repo_name(backend.repo_name)
98 98 ret = repo.get_api_data()
99 99
100 100 assert permission_count + 1, len(repo.repo_to_perm)
101 101
102 102 permissions = expected_permissions(repo)
103 103
104 104 followers = []
105 105 for user in repo.followers:
106 106 followers.append(user.user.get_api_data())
107 107
108 108 ret['permissions'] = permissions
109 109 ret['followers'] = followers
110 110
111 111 expected = ret
112 112 try:
113 113 assert_ok(id_, expected, given=response.body)
114 114 finally:
115 115 RepoModel().revoke_user_permission(
116 116 backend.repo_name, self.TEST_USER_LOGIN)
117 117
118 118 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
119 119 RepoModel().grant_user_permission(repo=backend.repo_name,
120 120 user=self.TEST_USER_LOGIN,
121 121 perm='repository.none')
122 122
123 123 id_, params = build_data(
124 124 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
125 125 response = api_call(self.app, params)
126 126
127 127 expected = 'repository `%s` does not exist' % (backend.repo_name)
128 128 assert_error(id_, expected, given=response.body)
129 129
130 130 def test_api_get_repo_not_existing(self):
131 131 id_, params = build_data(
132 132 self.apikey, 'get_repo', repoid='no-such-repo')
133 133 response = api_call(self.app, params)
134 134
135 135 ret = 'repository `%s` does not exist' % 'no-such-repo'
136 136 expected = ret
137 137 assert_error(id_, expected, given=response.body)
@@ -1,140 +1,140 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetRepoChangeset(object):
29 29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
30 30 def test_get_repo_changeset(self, details, backend):
31 31 commit = backend.repo.get_commit(commit_idx=0)
32 32 __, params = build_data(
33 33 self.apikey, 'get_repo_changeset',
34 34 repoid=backend.repo_name, revision=commit.raw_id,
35 35 details=details,
36 36 )
37 37 response = api_call(self.app, params)
38 38 result = response.json['result']
39 39 assert result['revision'] == 0
40 40 assert result['raw_id'] == commit.raw_id
41 41
42 42 if details == 'full':
43 43 assert result['refs']['bookmarks'] == getattr(
44 44 commit, 'bookmarks', [])
45 45 assert result['refs']['branches'] == [commit.branch]
46 46 assert result['refs']['tags'] == commit.tags
47 47
48 48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 49 def test_get_repo_changeset_bad_type(self, details, backend):
50 50 id_, params = build_data(
51 51 self.apikey, 'get_repo_changeset',
52 52 repoid=backend.repo_name, revision=0,
53 53 details=details,
54 54 )
55 55 response = api_call(self.app, params)
56 56 expected = 'commit_id must be a string value'
57 57 assert_error(id_, expected, given=response.body)
58 58
59 59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 60 def test_get_repo_changesets(self, details, backend):
61 61 limit = 2
62 62 commit = backend.repo.get_commit(commit_idx=0)
63 63 __, params = build_data(
64 64 self.apikey, 'get_repo_changesets',
65 65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 66 details=details,
67 67 )
68 68 response = api_call(self.app, params)
69 69 result = response.json['result']
70 70 assert result
71 71 assert len(result) == limit
72 72 for x in xrange(limit):
73 73 assert result[x]['revision'] == x
74 74
75 75 if details == 'full':
76 76 for x in xrange(limit):
77 77 assert 'bookmarks' in result[x]['refs']
78 78 assert 'branches' in result[x]['refs']
79 79 assert 'tags' in result[x]['refs']
80 80
81 81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 82 @pytest.mark.parametrize("start_rev, expected_revision", [
83 83 ("0", 0),
84 84 ("10", 10),
85 85 ("20", 20),
86 86 ])
87 87 @pytest.mark.backends("hg", "git")
88 88 def test_get_repo_changesets_commit_range(
89 89 self, details, backend, start_rev, expected_revision):
90 90 limit = 10
91 91 __, params = build_data(
92 92 self.apikey, 'get_repo_changesets',
93 93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 94 details=details,
95 95 )
96 96 response = api_call(self.app, params)
97 97 result = response.json['result']
98 98 assert result
99 99 assert len(result) == limit
100 100 for i in xrange(limit):
101 101 assert result[i]['revision'] == int(expected_revision) + i
102 102
103 103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 104 @pytest.mark.parametrize("start_rev, expected_revision", [
105 105 ("0", 0),
106 106 ("10", 9),
107 107 ("20", 19),
108 108 ])
109 109 def test_get_repo_changesets_commit_range_svn(
110 110 self, details, backend_svn, start_rev, expected_revision):
111 111
112 112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 113 # in our API allows to pass in a "Commit ID" as well as a
114 114 # "Commit Index". In the case of Subversion it is not possible to
115 115 # distinguish these cases. As a workaround we implemented this
116 116 # behavior which gives a preference to see it as a "Commit ID".
117 117
118 118 limit = 10
119 119 __, params = build_data(
120 120 self.apikey, 'get_repo_changesets',
121 121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 122 details=details,
123 123 )
124 124 response = api_call(self.app, params)
125 125 result = response.json['result']
126 126 assert result
127 127 assert len(result) == limit
128 128 for i in xrange(limit):
129 129 assert result[i]['revision'] == int(expected_revision) + i
130 130
131 131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 132 def test_get_repo_changesets_bad_type(self, details, backend):
133 133 id_, params = build_data(
134 134 self.apikey, 'get_repo_changesets',
135 135 repoid=backend.repo_name, start_rev=0, limit=2,
136 136 details=details,
137 137 )
138 138 response = api_call(self.app, params)
139 139 expected = 'commit_id must be a string value'
140 140 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, expected_permissions)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetRepoGroup(object):
31 31 def test_api_get_repo_group(self, user_util):
32 32 repo_group = user_util.create_repo_group()
33 33 repo_group_name = repo_group.group_name
34 34
35 35 id_, params = build_data(
36 36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
37 37 response = api_call(self.app, params)
38 38
39 39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
40 40 ret = repo_group.get_api_data()
41 41
42 42 permissions = expected_permissions(repo_group)
43 43
44 44 ret['permissions'] = permissions
45 45 expected = ret
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 def test_api_get_repo_group_not_existing(self):
49 49 id_, params = build_data(
50 50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
51 51 response = api_call(self.app, params)
52 52
53 53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
54 54 expected = ret
55 55 assert_error(id_, expected, given=response.body)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiGetRepoGroups(object):
30 30 def test_api_get_repo_groups(self):
31 31 id_, params = build_data(self.apikey, 'get_repo_groups')
32 32 response = api_call(self.app, params)
33 33
34 34 result = []
35 35 for repo in RepoGroupModel().get_all():
36 36 result.append(repo.get_api_data())
37 37 ret = jsonify(result)
38 38
39 39 expected = ret
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetRepoNodes(object):
32 32 @pytest.mark.parametrize("name, ret_type", [
33 33 ('all', 'all'),
34 34 ('dirs', 'dirs'),
35 35 ('files', 'files'),
36 36 ])
37 37 def test_api_get_repo_nodes(self, name, ret_type, backend):
38 38 commit_id = 'tip'
39 39 path = '/'
40 40 id_, params = build_data(
41 41 self.apikey, 'get_repo_nodes',
42 42 repoid=backend.repo_name, revision=commit_id,
43 43 root_path=path,
44 44 ret_type=ret_type)
45 45 response = api_call(self.app, params)
46 46
47 47 # we don't the actual return types here since it's tested somewhere
48 48 # else
49 49 expected = response.json['result']
50 50 assert_ok(id_, expected, given=response.body)
51 51
52 52 def test_api_get_repo_nodes_bad_commits(self, backend):
53 53 commit_id = 'i-dont-exist'
54 54 path = '/'
55 55 id_, params = build_data(
56 56 self.apikey, 'get_repo_nodes',
57 57 repoid=backend.repo_name, revision=commit_id,
58 58 root_path=path, )
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_get_repo_nodes_bad_path(self, backend):
65 65 commit_id = 'tip'
66 66 path = '/idontexits'
67 67 id_, params = build_data(
68 68 self.apikey, 'get_repo_nodes',
69 69 repoid=backend.repo_name, revision=commit_id,
70 70 root_path=path, )
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 77 commit_id = 'tip'
78 78 path = '/'
79 79 max_file_bytes = 500
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'get_repo_nodes',
83 83 repoid=backend.repo_name, revision=commit_id, details='full',
84 84 root_path=path)
85 85 response = api_call(self.app, params)
86 86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 87 for file in response.json['result'])
88 88
89 89 id_, params = build_data(
90 90 self.apikey, 'get_repo_nodes',
91 91 repoid=backend.repo_name, revision=commit_id,
92 92 root_path=path, details='full',
93 93 max_file_bytes=max_file_bytes)
94 94 response = api_call(self.app, params)
95 95 assert all(
96 96 file['content'] is None if file['size'] > max_file_bytes else True
97 97 for file in response.json['result'])
98 98
99 99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
100 100 commit_id = 'tip'
101 101 path = '/'
102 102 ret_type = 'error'
103 103 id_, params = build_data(
104 104 self.apikey, 'get_repo_nodes',
105 105 repoid=backend.repo_name, revision=commit_id,
106 106 root_path=path,
107 107 ret_type=ret_type)
108 108 response = api_call(self.app, params)
109 109
110 110 expected = ('ret_type must be one of %s'
111 111 % (','.join(['all', 'dirs', 'files'])))
112 112 assert_error(id_, expected, given=response.body)
113 113
114 114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
115 115 ('all', 'all', 'repository.write'),
116 116 ('dirs', 'dirs', 'repository.admin'),
117 117 ('files', 'files', 'repository.read'),
118 118 ])
119 119 def test_api_get_repo_nodes_by_regular_user(
120 120 self, name, ret_type, grant_perm, backend):
121 121 RepoModel().grant_user_permission(repo=backend.repo_name,
122 122 user=self.TEST_USER_LOGIN,
123 123 perm=grant_perm)
124 124 Session().commit()
125 125
126 126 commit_id = 'tip'
127 127 path = '/'
128 128 id_, params = build_data(
129 129 self.apikey_regular, 'get_repo_nodes',
130 130 repoid=backend.repo_name, revision=commit_id,
131 131 root_path=path,
132 132 ret_type=ret_type)
133 133 response = api_call(self.app, params)
134 134
135 135 # we don't the actual return types here since it's tested somewhere
136 136 # else
137 137 expected = response.json['result']
138 138 try:
139 139 assert_ok(id_, expected, given=response.body)
140 140 finally:
141 141 RepoModel().revoke_user_permission(
142 142 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestGetRepo(object):
34 34 def test_api_get_repo_refs(self, backend, user_util):
35 35 repo = backend.create_repo()
36 36 id_, params = build_data(self.apikey, 'get_repo_refs',
37 37 **{'repoid': repo.repo_name,})
38 38 response = api_call(self.app, params)
39 39 expected = repo.scm_instance().refs()
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, jsonify)
27 27 from rhodecode.model.db import User
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetRepos(object):
32 32 def test_api_get_repos(self):
33 33 id_, params = build_data(self.apikey, 'get_repos')
34 34 response = api_call(self.app, params)
35 35
36 36 result = []
37 37 for repo in RepoModel().get_all():
38 38 result.append(repo.get_api_data(include_secrets=True))
39 39 ret = jsonify(result)
40 40
41 41 expected = ret
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_api_get_repos_only_toplevel(self, user_util):
45 45 repo_group = user_util.create_repo_group(auto_cleanup=True)
46 46 user_util.create_repo(parent=repo_group)
47 47
48 48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
49 49 response = api_call(self.app, params)
50 50
51 51 result = []
52 52 for repo in RepoModel().get_repos_for_root(root=None):
53 53 result.append(repo.get_api_data(include_secrets=True))
54 54 expected = jsonify(result)
55 55
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 def test_api_get_repos_with_wrong_root(self):
59 59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
60 60 response = api_call(self.app, params)
61 61
62 62 expected = 'Root repository group `abracadabra` does not exist'
63 63 assert_error(id_, expected, given=response.body)
64 64
65 65 def test_api_get_repos_with_root(self, user_util):
66 66 repo_group = user_util.create_repo_group(auto_cleanup=True)
67 67 repo_group_name = repo_group.group_name
68 68
69 69 user_util.create_repo(parent=repo_group)
70 70 user_util.create_repo(parent=repo_group)
71 71
72 72 # nested, should not show up
73 73 user_util._test_name = '{}/'.format(repo_group_name)
74 74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
75 75 user_util.create_repo(parent=sub_repo_group)
76 76
77 77 id_, params = build_data(self.apikey, 'get_repos',
78 78 root=repo_group_name, traverse=0)
79 79 response = api_call(self.app, params)
80 80
81 81 result = []
82 82 for repo in RepoModel().get_repos_for_root(repo_group):
83 83 result.append(repo.get_api_data(include_secrets=True))
84 84
85 85 assert len(result) == 2
86 86 expected = jsonify(result)
87 87 assert_ok(id_, expected, given=response.body)
88 88
89 89 def test_api_get_repos_with_root_and_traverse(self, user_util):
90 90 repo_group = user_util.create_repo_group(auto_cleanup=True)
91 91 repo_group_name = repo_group.group_name
92 92
93 93 user_util.create_repo(parent=repo_group)
94 94 user_util.create_repo(parent=repo_group)
95 95
96 96 # nested, should not show up
97 97 user_util._test_name = '{}/'.format(repo_group_name)
98 98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
99 99 user_util.create_repo(parent=sub_repo_group)
100 100
101 101 id_, params = build_data(self.apikey, 'get_repos',
102 102 root=repo_group_name, traverse=1)
103 103 response = api_call(self.app, params)
104 104
105 105 result = []
106 106 for repo in RepoModel().get_repos_for_root(
107 107 repo_group_name, traverse=True):
108 108 result.append(repo.get_api_data(include_secrets=True))
109 109
110 110 assert len(result) == 3
111 111 expected = jsonify(result)
112 112 assert_ok(id_, expected, given=response.body)
113 113
114 114 def test_api_get_repos_non_admin(self):
115 115 id_, params = build_data(self.apikey_regular, 'get_repos')
116 116 response = api_call(self.app, params)
117 117
118 118 user = User.get_by_username(self.TEST_USER_LOGIN)
119 119 allowed_repos = user.AuthUser().permissions['repositories']
120 120
121 121 result = []
122 122 for repo in RepoModel().get_all():
123 123 perm = allowed_repos[repo.repo_name]
124 124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
125 125 result.append(repo.get_api_data())
126 126 ret = jsonify(result)
127 127
128 128 expected = ret
129 129 assert_ok(id_, expected, given=response.body)
@@ -1,84 +1,84 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23
24 24 from rhodecode.model.scm import ScmModel
25 25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
26 26
27 27
28 28 @pytest.fixture
29 29 def http_host_stub():
30 30 """
31 31 To ensure that we can get an IP address, this test shall run with a
32 32 hostname set to "localhost".
33 33 """
34 34 return 'localhost:80'
35 35
36 36
37 37 @pytest.mark.usefixtures("testuser_api", "app")
38 38 class TestGetServerInfo(object):
39 39 def test_api_get_server_info(self):
40 40 id_, params = build_data(self.apikey, 'get_server_info')
41 41 response = api_call(self.app, params)
42 42 resp = response.json
43 43 expected = ScmModel().get_server_info()
44 44 expected['memory'] = resp['result']['memory']
45 45 expected['uptime'] = resp['result']['uptime']
46 46 expected['load'] = resp['result']['load']
47 47 expected['cpu'] = resp['result']['cpu']
48 48 expected['storage'] = resp['result']['storage']
49 49 expected['storage_temp'] = resp['result']['storage_temp']
50 50 expected['storage_inodes'] = resp['result']['storage_inodes']
51 51 expected['server'] = resp['result']['server']
52 52
53 53 expected['index_storage'] = resp['result']['index_storage']
54 54 expected['storage'] = resp['result']['storage']
55 55
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 def test_api_get_server_info_ip(self):
59 59 id_, params = build_data(self.apikey, 'get_server_info')
60 60 response = api_call(self.app, params)
61 61 resp = response.json
62 62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
63 63 expected['memory'] = resp['result']['memory']
64 64 expected['uptime'] = resp['result']['uptime']
65 65 expected['load'] = resp['result']['load']
66 66 expected['cpu'] = resp['result']['cpu']
67 67 expected['storage'] = resp['result']['storage']
68 68 expected['storage_temp'] = resp['result']['storage_temp']
69 69 expected['storage_inodes'] = resp['result']['storage_inodes']
70 70 expected['server'] = resp['result']['server']
71 71
72 72 expected['index_storage'] = resp['result']['index_storage']
73 73 expected['storage'] = resp['result']['storage']
74 74
75 75 assert_ok(id_, expected, given=response.body)
76 76
77 77 def test_api_get_server_info_data_for_search_index_build(self):
78 78 id_, params = build_data(self.apikey, 'get_server_info')
79 79 response = api_call(self.app, params)
80 80 resp = response.json
81 81
82 82 # required by indexer
83 83 assert resp['result']['index_storage']
84 84 assert resp['result']['storage']
@@ -1,86 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.lib.auth import AuthUser
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetUser(object):
32 32 def test_api_get_user(self):
33 33 id_, params = build_data(
34 34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
35 35 response = api_call(self.app, params)
36 36
37 37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
38 38 ret = usr.get_api_data(include_secrets=True)
39 39 permissions = AuthUser(usr.user_id).permissions
40 40 ret['permissions'] = permissions
41 41 ret['permissions_summary'] = permissions
42 42
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_get_user_not_existing(self):
47 47 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
48 48 response = api_call(self.app, params)
49 49
50 50 expected = "user `%s` does not exist" % 'trololo'
51 51 assert_error(id_, expected, given=response.body)
52 52
53 53 def test_api_get_user_without_giving_userid(self):
54 54 id_, params = build_data(self.apikey, 'get_user')
55 55 response = api_call(self.app, params)
56 56
57 57 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
58 58 ret = usr.get_api_data(include_secrets=True)
59 59 permissions = AuthUser(usr.user_id).permissions
60 60 ret['permissions'] = permissions
61 61 ret['permissions_summary'] = permissions
62 62
63 63 expected = ret
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_get_user_without_giving_userid_non_admin(self):
67 67 id_, params = build_data(self.apikey_regular, 'get_user')
68 68 response = api_call(self.app, params)
69 69
70 70 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
71 71 ret = usr.get_api_data(include_secrets=True)
72 72 permissions = AuthUser(usr.user_id).permissions
73 73 ret['permissions'] = permissions
74 74 ret['permissions_summary'] = permissions
75 75
76 76 expected = ret
77 77 assert_ok(id_, expected, given=response.body)
78 78
79 79 def test_api_get_user_with_giving_userid_non_admin(self):
80 80 id_, params = build_data(
81 81 self.apikey_regular, 'get_user',
82 82 userid=self.TEST_USER_LOGIN)
83 83 response = api_call(self.app, params)
84 84
85 85 expected = 'userid is not the same as your user'
86 86 assert_error(id_, expected, given=response.body)
@@ -1,76 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error, expected_permissions)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGetUserGroups(object):
30 30 def test_api_get_user_group(self, user_util):
31 31 user, group = user_util.create_user_with_group()
32 32 id_, params = build_data(
33 33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
34 34 response = api_call(self.app, params)
35 35
36 36 ret = group.get_api_data()
37 37 ret['users'] = [user.get_api_data()]
38 38
39 39 permissions = expected_permissions(group)
40 40
41 41 ret['permissions'] = permissions
42 42 ret['permissions_summary'] = response.json['result']['permissions_summary']
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_get_user_group_regular_user(self, user_util):
47 47 user, group = user_util.create_user_with_group()
48 48 id_, params = build_data(
49 49 self.apikey_regular, 'get_user_group',
50 50 usergroupid=group.users_group_name)
51 51 response = api_call(self.app, params)
52 52
53 53 ret = group.get_api_data()
54 54 ret['users'] = [user.get_api_data()]
55 55
56 56 permissions = expected_permissions(group)
57 57
58 58 ret['permissions'] = permissions
59 59 ret['permissions_summary'] = response.json['result']['permissions_summary']
60 60 expected = ret
61 61 assert_ok(id_, expected, given=response.body)
62 62
63 63 def test_api_get_user_group_regular_user_permission_denied(
64 64 self, user_util):
65 65 group = user_util.create_user_group()
66 66 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
67 67 group_name = group.users_group_name
68 68 user_util.grant_user_permission_to_user_group(
69 69 group, user, 'usergroup.none')
70 70
71 71 id_, params = build_data(
72 72 self.apikey_regular, 'get_user_group', usergroupid=group_name)
73 73 response = api_call(self.app, params)
74 74
75 75 expected = 'user group `%s` does not exist' % (group_name,)
76 76 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import json
23 23
24 24 import pytest
25 25
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.api.tests.utils import build_data, api_call
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetUserGroups(object):
32 32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
33 33 ('apikey', True),
34 34 ('apikey_regular', False),
35 35 ])
36 36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
37 37 first_group = user_util.create_user_group()
38 38 second_group = user_util.create_user_group()
39 39 expected = [
40 40 g.get_api_data(include_secrets=expect_secrets)
41 41 for g in (first_group, second_group)]
42 42
43 43 apikey = getattr(self, apikey_attr)
44 44 id_, params = build_data(apikey, 'get_user_groups', )
45 45 response = api_call(self.app, params)
46 46 self._assert_ok(id_, expected, response)
47 47
48 48 def test_api_get_user_groups_regular_user(self, user_util):
49 49 first_group = user_util.create_user_group()
50 50 second_group = user_util.create_user_group()
51 51 expected = [g.get_api_data() for g in (first_group, second_group)]
52 52
53 53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
54 54 response = api_call(self.app, params)
55 55 self._assert_ok(id_, expected, response)
56 56
57 57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
58 58 group = user_util.create_user_group()
59 59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
60 60 user_util.grant_user_permission_to_user_group(
61 61 group, user, 'usergroup.none')
62 62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
63 63 response = api_call(self.app, params)
64 64 expected = []
65 65 self._assert_ok(id_, expected, response)
66 66
67 67 def _assert_ok(self, id_, expected_list, response):
68 68 result = json.loads(response.body)
69 69 assert result['id'] == id_
70 70 assert result['error'] is None
71 71 assert sorted(result['result']) == sorted(expected_list)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import User
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, jsonify)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGetUsers(object):
30 30 def test_api_get_users(self):
31 31 id_, params = build_data(self.apikey, 'get_users', )
32 32 response = api_call(self.app, params)
33 33 ret_all = []
34 34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
35 35 .order_by(User.username).all()
36 36 for usr in _users:
37 37 ret = usr.get_api_data(include_secrets=True)
38 38 ret_all.append(jsonify(ret))
39 39 expected = ret_all
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGrantUserGroupPermission(object):
31 31 @pytest.mark.parametrize("name, perm", [
32 32 ('none', 'repository.none'),
33 33 ('read', 'repository.read'),
34 34 ('write', 'repository.write'),
35 35 ('admin', 'repository.admin')
36 36 ])
37 37 def test_api_grant_user_group_permission(
38 38 self, name, perm, backend, user_util):
39 39 user_group = user_util.create_user_group()
40 40 id_, params = build_data(
41 41 self.apikey,
42 42 'grant_user_group_permission',
43 43 repoid=backend.repo_name,
44 44 usergroupid=user_group.users_group_name,
45 45 perm=perm)
46 46 response = api_call(self.app, params)
47 47
48 48 ret = {
49 49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
50 50 perm, user_group.users_group_name, backend.repo_name
51 51 ),
52 52 'success': True
53 53 }
54 54 expected = ret
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_grant_user_group_permission_wrong_permission(
58 58 self, backend, user_util):
59 59 perm = 'haha.no.permission'
60 60 user_group = user_util.create_user_group()
61 61 id_, params = build_data(
62 62 self.apikey,
63 63 'grant_user_group_permission',
64 64 repoid=backend.repo_name,
65 65 usergroupid=user_group.users_group_name,
66 66 perm=perm)
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'permission `%s` does not exist' % (perm,)
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
73 73 def test_api_grant_user_group_permission_exception_when_adding(
74 74 self, backend, user_util):
75 75 perm = 'repository.read'
76 76 user_group = user_util.create_user_group()
77 77 id_, params = build_data(
78 78 self.apikey,
79 79 'grant_user_group_permission',
80 80 repoid=backend.repo_name,
81 81 usergroupid=user_group.users_group_name,
82 82 perm=perm)
83 83 response = api_call(self.app, params)
84 84
85 85 expected = (
86 86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
87 87 user_group.users_group_name, backend.repo_name
88 88 )
89 89 )
90 90 assert_error(id_, expected, given=response.body)
@@ -1,172 +1,172 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGrantUserGroupPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 33 ('none', 'group.none', 'none'),
34 34 ('read', 'group.read', 'none'),
35 35 ('write', 'group.write', 'none'),
36 36 ('admin', 'group.admin', 'none'),
37 37
38 38 ('none', 'group.none', 'all'),
39 39 ('read', 'group.read', 'all'),
40 40 ('write', 'group.write', 'all'),
41 41 ('admin', 'group.admin', 'all'),
42 42
43 43 ('none', 'group.none', 'repos'),
44 44 ('read', 'group.read', 'repos'),
45 45 ('write', 'group.write', 'repos'),
46 46 ('admin', 'group.admin', 'repos'),
47 47
48 48 ('none', 'group.none', 'groups'),
49 49 ('read', 'group.read', 'groups'),
50 50 ('write', 'group.write', 'groups'),
51 51 ('admin', 'group.admin', 'groups'),
52 52 ])
53 53 def test_api_grant_user_group_permission_to_repo_group(
54 54 self, name, perm, apply_to_children, user_util):
55 55 user_group = user_util.create_user_group()
56 56 repo_group = user_util.create_repo_group()
57 57 id_, params = build_data(
58 58 self.apikey,
59 59 'grant_user_group_permission_to_repo_group',
60 60 repogroupid=repo_group.name,
61 61 usergroupid=user_group.users_group_name,
62 62 perm=perm,
63 63 apply_to_children=apply_to_children,)
64 64 response = api_call(self.app, params)
65 65
66 66 ret = {
67 67 'msg': (
68 68 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
69 69 ' in repo group: `%s`' % (
70 70 perm, apply_to_children, user_group.users_group_name,
71 71 repo_group.name
72 72 )
73 73 ),
74 74 'success': True
75 75 }
76 76 expected = ret
77 77 try:
78 78 assert_ok(id_, expected, given=response.body)
79 79 finally:
80 80 RepoGroupModel().revoke_user_group_permission(
81 81 repo_group.group_id, user_group.users_group_id)
82 82
83 83 @pytest.mark.parametrize(
84 84 "name, perm, apply_to_children, grant_admin, access_ok", [
85 85 ('none_fails', 'group.none', 'none', False, False),
86 86 ('read_fails', 'group.read', 'none', False, False),
87 87 ('write_fails', 'group.write', 'none', False, False),
88 88 ('admin_fails', 'group.admin', 'none', False, False),
89 89
90 90 # with granted perms
91 91 ('none_ok', 'group.none', 'none', True, True),
92 92 ('read_ok', 'group.read', 'none', True, True),
93 93 ('write_ok', 'group.write', 'none', True, True),
94 94 ('admin_ok', 'group.admin', 'none', True, True),
95 95 ]
96 96 )
97 97 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
98 98 self, name, perm, apply_to_children, grant_admin, access_ok,
99 99 user_util):
100 100 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
101 101 user_group = user_util.create_user_group()
102 102 repo_group = user_util.create_repo_group()
103 103 if grant_admin:
104 104 user_util.grant_user_permission_to_repo_group(
105 105 repo_group, user, 'group.admin')
106 106
107 107 id_, params = build_data(
108 108 self.apikey_regular,
109 109 'grant_user_group_permission_to_repo_group',
110 110 repogroupid=repo_group.name,
111 111 usergroupid=user_group.users_group_name,
112 112 perm=perm,
113 113 apply_to_children=apply_to_children,)
114 114 response = api_call(self.app, params)
115 115 if access_ok:
116 116 ret = {
117 117 'msg': (
118 118 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
119 119 ' in repo group: `%s`' % (
120 120 perm, apply_to_children, user_group.users_group_name,
121 121 repo_group.name
122 122 )
123 123 ),
124 124 'success': True
125 125 }
126 126 expected = ret
127 127 try:
128 128 assert_ok(id_, expected, given=response.body)
129 129 finally:
130 130 RepoGroupModel().revoke_user_group_permission(
131 131 repo_group.group_id, user_group.users_group_id)
132 132 else:
133 133 expected = 'repository group `%s` does not exist' % (
134 134 repo_group.name,)
135 135 assert_error(id_, expected, given=response.body)
136 136
137 137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 138 self, user_util):
139 139 user_group = user_util.create_user_group()
140 140 repo_group = user_util.create_repo_group()
141 141 perm = 'haha.no.permission'
142 142 id_, params = build_data(
143 143 self.apikey,
144 144 'grant_user_group_permission_to_repo_group',
145 145 repogroupid=repo_group.name,
146 146 usergroupid=user_group.users_group_name,
147 147 perm=perm)
148 148 response = api_call(self.app, params)
149 149
150 150 expected = 'permission `%s` does not exist' % (perm,)
151 151 assert_error(id_, expected, given=response.body)
152 152
153 153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 154 def test_api_grant_user_group_permission_exception_when_adding_2(
155 155 self, user_util):
156 156 user_group = user_util.create_user_group()
157 157 repo_group = user_util.create_repo_group()
158 158 perm = 'group.read'
159 159 id_, params = build_data(
160 160 self.apikey,
161 161 'grant_user_group_permission_to_repo_group',
162 162 repogroupid=repo_group.name,
163 163 usergroupid=user_group.users_group_name,
164 164 perm=perm)
165 165 response = api_call(self.app, params)
166 166
167 167 expected = (
168 168 'failed to edit permission for user group: `%s`'
169 169 ' in repo group: `%s`' % (
170 170 user_group.users_group_name, repo_group.name)
171 171 )
172 172 assert_error(id_, expected, given=response.body)
@@ -1,97 +1,97 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.user_group import UserGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGrantUserGroupPermissionFromUserGroup(object):
30 30 @pytest.mark.parametrize("name, perm", [
31 31 ('none', 'usergroup.none'),
32 32 ('read', 'usergroup.read'),
33 33 ('write', 'usergroup.write'),
34 34 ('admin', 'usergroup.admin'),
35 35
36 36 ('none', 'usergroup.none'),
37 37 ('read', 'usergroup.read'),
38 38 ('write', 'usergroup.write'),
39 39 ('admin', 'usergroup.admin'),
40 40
41 41 ('none', 'usergroup.none'),
42 42 ('read', 'usergroup.read'),
43 43 ('write', 'usergroup.write'),
44 44 ('admin', 'usergroup.admin'),
45 45
46 46 ('none', 'usergroup.none'),
47 47 ('read', 'usergroup.read'),
48 48 ('write', 'usergroup.write'),
49 49 ('admin', 'usergroup.admin'),
50 50 ])
51 51 def test_api_grant_user_group_permission_to_user_group(
52 52 self, name, perm, user_util):
53 53 group = user_util.create_user_group()
54 54 target_group = user_util.create_user_group()
55 55
56 56 id_, params = build_data(
57 57 self.apikey,
58 58 'grant_user_group_permission_to_user_group',
59 59 usergroupid=target_group.users_group_name,
60 60 sourceusergroupid=group.users_group_name,
61 61 perm=perm)
62 62 response = api_call(self.app, params)
63 63
64 64 expected = {
65 65 'msg': (
66 66 'Granted perm: `%s` for user group: `%s`'
67 67 ' in user group: `%s`' % (
68 68 perm, group.users_group_name,
69 69 target_group.users_group_name
70 70 )
71 71 ),
72 72 'success': True
73 73 }
74 74 try:
75 75 assert_ok(id_, expected, given=response.body)
76 76 finally:
77 77 UserGroupModel().revoke_user_group_permission(
78 78 target_group.users_group_id, group.users_group_id)
79 79
80 80 def test_api_grant_user_group_permission_to_user_group_same_failure(
81 81 self, user_util):
82 82 group = user_util.create_user_group()
83 83
84 84 id_, params = build_data(
85 85 self.apikey,
86 86 'grant_user_group_permission_to_user_group',
87 87 usergroupid=group.users_group_name,
88 88 sourceusergroupid=group.users_group_name,
89 89 perm='usergroup.none')
90 90 response = api_call(self.app, params)
91 91
92 92 expected = (
93 93 'failed to edit permission for user group: `%s`'
94 94 ' in user group: `%s`' % (
95 95 group.users_group_name, group.users_group_name)
96 96 )
97 97 assert_error(id_, expected, given=response.body)
@@ -1,87 +1,87 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGrantUserPermission(object):
31 31 @pytest.mark.parametrize("name, perm", [
32 32 ('none', 'repository.none'),
33 33 ('read', 'repository.read'),
34 34 ('write', 'repository.write'),
35 35 ('admin', 'repository.admin')
36 36 ])
37 37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
38 38 user = user_util.create_user()
39 39 id_, params = build_data(
40 40 self.apikey,
41 41 'grant_user_permission',
42 42 repoid=backend.repo_name,
43 43 userid=user.username,
44 44 perm=perm)
45 45 response = api_call(self.app, params)
46 46
47 47 ret = {
48 48 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
49 49 perm, user.username, backend.repo_name
50 50 ),
51 51 'success': True
52 52 }
53 53 expected = ret
54 54 assert_ok(id_, expected, given=response.body)
55 55
56 56 def test_api_grant_user_permission_wrong_permission(
57 57 self, backend, user_util):
58 58 user = user_util.create_user()
59 59 perm = 'haha.no.permission'
60 60 id_, params = build_data(
61 61 self.apikey,
62 62 'grant_user_permission',
63 63 repoid=backend.repo_name,
64 64 userid=user.username,
65 65 perm=perm)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = 'permission `%s` does not exist' % (perm,)
69 69 assert_error(id_, expected, given=response.body)
70 70
71 71 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
72 72 def test_api_grant_user_permission_exception_when_adding(
73 73 self, backend, user_util):
74 74 user = user_util.create_user()
75 75 perm = 'repository.read'
76 76 id_, params = build_data(
77 77 self.apikey,
78 78 'grant_user_permission',
79 79 repoid=backend.repo_name,
80 80 userid=user.username,
81 81 perm=perm)
82 82 response = api_call(self.app, params)
83 83
84 84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
85 85 user.username, backend.repo_name
86 86 )
87 87 assert_error(id_, expected, given=response.body)
@@ -1,157 +1,157 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGrantUserPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 33 ('none', 'group.none', 'none'),
34 34 ('read', 'group.read', 'none'),
35 35 ('write', 'group.write', 'none'),
36 36 ('admin', 'group.admin', 'none'),
37 37
38 38 ('none', 'group.none', 'all'),
39 39 ('read', 'group.read', 'all'),
40 40 ('write', 'group.write', 'all'),
41 41 ('admin', 'group.admin', 'all'),
42 42
43 43 ('none', 'group.none', 'repos'),
44 44 ('read', 'group.read', 'repos'),
45 45 ('write', 'group.write', 'repos'),
46 46 ('admin', 'group.admin', 'repos'),
47 47
48 48 ('none', 'group.none', 'groups'),
49 49 ('read', 'group.read', 'groups'),
50 50 ('write', 'group.write', 'groups'),
51 51 ('admin', 'group.admin', 'groups'),
52 52 ])
53 53 def test_api_grant_user_permission_to_repo_group(
54 54 self, name, perm, apply_to_children, user_util):
55 55 user = user_util.create_user()
56 56 repo_group = user_util.create_repo_group()
57 57 id_, params = build_data(
58 58 self.apikey, 'grant_user_permission_to_repo_group',
59 59 repogroupid=repo_group.name, userid=user.username,
60 60 perm=perm, apply_to_children=apply_to_children)
61 61 response = api_call(self.app, params)
62 62
63 63 ret = {
64 64 'msg': (
65 65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
66 66 ' in repo group: `%s`' % (
67 67 perm, apply_to_children, user.username, repo_group.name
68 68 )
69 69 ),
70 70 'success': True
71 71 }
72 72 expected = ret
73 73 assert_ok(id_, expected, given=response.body)
74 74
75 75 @pytest.mark.parametrize(
76 76 "name, perm, apply_to_children, grant_admin, access_ok", [
77 77 ('none_fails', 'group.none', 'none', False, False),
78 78 ('read_fails', 'group.read', 'none', False, False),
79 79 ('write_fails', 'group.write', 'none', False, False),
80 80 ('admin_fails', 'group.admin', 'none', False, False),
81 81
82 82 # with granted perms
83 83 ('none_ok', 'group.none', 'none', True, True),
84 84 ('read_ok', 'group.read', 'none', True, True),
85 85 ('write_ok', 'group.write', 'none', True, True),
86 86 ('admin_ok', 'group.admin', 'none', True, True),
87 87 ]
88 88 )
89 89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
90 90 self, name, perm, apply_to_children, grant_admin, access_ok,
91 91 user_util):
92 92 user = user_util.create_user()
93 93 repo_group = user_util.create_repo_group()
94 94
95 95 if grant_admin:
96 96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
97 97 user_util.grant_user_permission_to_repo_group(
98 98 repo_group, test_user, 'group.admin')
99 99
100 100 id_, params = build_data(
101 101 self.apikey_regular, 'grant_user_permission_to_repo_group',
102 102 repogroupid=repo_group.name, userid=user.username,
103 103 perm=perm, apply_to_children=apply_to_children)
104 104 response = api_call(self.app, params)
105 105 if access_ok:
106 106 ret = {
107 107 'msg': (
108 108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
109 109 ' in repo group: `%s`' % (
110 110 perm, apply_to_children, user.username, repo_group.name
111 111 )
112 112 ),
113 113 'success': True
114 114 }
115 115 expected = ret
116 116 assert_ok(id_, expected, given=response.body)
117 117 else:
118 118 expected = 'repository group `%s` does not exist' % (
119 119 repo_group.name, )
120 120 assert_error(id_, expected, given=response.body)
121 121
122 122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
123 123 self, user_util):
124 124 user = user_util.create_user()
125 125 repo_group = user_util.create_repo_group()
126 126 perm = 'haha.no.permission'
127 127 id_, params = build_data(
128 128 self.apikey,
129 129 'grant_user_permission_to_repo_group',
130 130 repogroupid=repo_group.name,
131 131 userid=user.username,
132 132 perm=perm)
133 133 response = api_call(self.app, params)
134 134
135 135 expected = 'permission `%s` does not exist' % (perm,)
136 136 assert_error(id_, expected, given=response.body)
137 137
138 138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
139 139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
140 140 self, user_util):
141 141 user = user_util.create_user()
142 142 repo_group = user_util.create_repo_group()
143 143 perm = 'group.read'
144 144 id_, params = build_data(
145 145 self.apikey,
146 146 'grant_user_permission_to_repo_group',
147 147 repogroupid=repo_group.name,
148 148 userid=user.username,
149 149 perm=perm)
150 150 response = api_call(self.app, params)
151 151
152 152 expected = (
153 153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
154 154 user.username, repo_group.name
155 155 )
156 156 )
157 157 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.user_group import UserGroupModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGrantUserPermissionFromUserGroup(object):
32 32 @pytest.mark.parametrize("name, perm", [
33 33 ('none', 'usergroup.none'),
34 34 ('read', 'usergroup.read'),
35 35 ('write', 'usergroup.write'),
36 36 ('admin', 'usergroup.admin'),
37 37
38 38 ('none', 'usergroup.none'),
39 39 ('read', 'usergroup.read'),
40 40 ('write', 'usergroup.write'),
41 41 ('admin', 'usergroup.admin'),
42 42
43 43 ('none', 'usergroup.none'),
44 44 ('read', 'usergroup.read'),
45 45 ('write', 'usergroup.write'),
46 46 ('admin', 'usergroup.admin'),
47 47
48 48 ('none', 'usergroup.none'),
49 49 ('read', 'usergroup.read'),
50 50 ('write', 'usergroup.write'),
51 51 ('admin', 'usergroup.admin'),
52 52 ])
53 53 def test_api_grant_user_permission_to_user_group(
54 54 self, name, perm, user_util):
55 55 user = user_util.create_user()
56 56 group = user_util.create_user_group()
57 57 id_, params = build_data(
58 58 self.apikey,
59 59 'grant_user_permission_to_user_group',
60 60 usergroupid=group.users_group_name,
61 61 userid=user.username,
62 62 perm=perm)
63 63 response = api_call(self.app, params)
64 64
65 65 ret = {
66 66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
67 67 perm, user.username, group.users_group_name
68 68 ),
69 69 'success': True
70 70 }
71 71 expected = ret
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
75 75 ('none_fails', 'usergroup.none', False, False),
76 76 ('read_fails', 'usergroup.read', False, False),
77 77 ('write_fails', 'usergroup.write', False, False),
78 78 ('admin_fails', 'usergroup.admin', False, False),
79 79
80 80 # with granted perms
81 81 ('none_ok', 'usergroup.none', True, True),
82 82 ('read_ok', 'usergroup.read', True, True),
83 83 ('write_ok', 'usergroup.write', True, True),
84 84 ('admin_ok', 'usergroup.admin', True, True),
85 85 ])
86 86 def test_api_grant_user_permission_to_user_group_by_regular_user(
87 87 self, name, perm, grant_admin, access_ok, user_util):
88 88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
89 89 user = user_util.create_user()
90 90 group = user_util.create_user_group()
91 91 # grant the user ability to at least read the group
92 92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
93 93 user_util.grant_user_permission_to_user_group(
94 94 group, api_user, permission)
95 95
96 96 id_, params = build_data(
97 97 self.apikey_regular,
98 98 'grant_user_permission_to_user_group',
99 99 usergroupid=group.users_group_name,
100 100 userid=user.username,
101 101 perm=perm)
102 102 response = api_call(self.app, params)
103 103
104 104 if access_ok:
105 105 ret = {
106 106 'msg': (
107 107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
108 108 perm, user.username, group.users_group_name
109 109 )
110 110 ),
111 111 'success': True
112 112 }
113 113 expected = ret
114 114 assert_ok(id_, expected, given=response.body)
115 115 else:
116 116 expected = 'user group `%s` does not exist' % (
117 117 group.users_group_name)
118 118 assert_error(id_, expected, given=response.body)
119 119
120 120 def test_api_grant_user_permission_to_user_group_wrong_permission(
121 121 self, user_util):
122 122 user = user_util.create_user()
123 123 group = user_util.create_user_group()
124 124 perm = 'haha.no.permission'
125 125 id_, params = build_data(
126 126 self.apikey,
127 127 'grant_user_permission_to_user_group',
128 128 usergroupid=group.users_group_name,
129 129 userid=user.username,
130 130 perm=perm)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'permission `%s` does not exist' % perm
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
137 137 self, user_util):
138 138 user = user_util.create_user()
139 139 group = user_util.create_user_group()
140 140
141 141 perm = 'usergroup.read'
142 142 id_, params = build_data(
143 143 self.apikey,
144 144 'grant_user_permission_to_user_group',
145 145 usergroupid=group.users_group_name,
146 146 userid=user.username,
147 147 perm=perm)
148 148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
149 149 response = api_call(self.app, params)
150 150
151 151 expected = (
152 152 'failed to edit permission for user: `%s` in user group: `%s`' % (
153 153 user.username, group.users_group_name
154 154 )
155 155 )
156 156 assert_error(id_, expected, given=response.body)
@@ -1,68 +1,68 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.scm import ScmModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27 from rhodecode.model.repo import RepoModel
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestInvalidateCache(object):
32 32
33 33 def _set_cache(self, repo_name):
34 34 repo = RepoModel().get_by_repo_name(repo_name)
35 35 repo.scm_instance(cache=True)
36 36
37 37 def test_api_invalidate_cache(self, backend):
38 38 self._set_cache(backend.repo_name)
39 39
40 40 id_, params = build_data(
41 41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
42 42 response = api_call(self.app, params)
43 43
44 44 expected = {
45 45 'msg': "Cache for repository `%s` was invalidated" % (
46 46 backend.repo_name,),
47 47 'repository': backend.repo_name,
48 48 }
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
52 52 def test_api_invalidate_cache_error(self, backend):
53 53 id_, params = build_data(
54 54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
55 55 response = api_call(self.app, params)
56 56
57 57 expected = 'Error occurred during cache invalidation action'
58 58 assert_error(id_, expected, given=response.body)
59 59
60 60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
61 61 self._set_cache(backend.repo_name)
62 62
63 63 id_, params = build_data(
64 64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
65 65 response = api_call(self.app, params)
66 66
67 67 expected = "repository `%s` does not exist" % (backend.repo_name,)
68 68 assert_error(id_, expected, given=response.body)
@@ -1,137 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import UserLog, PullRequest
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestMergePullRequest(object):
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 34 pull_request = pr_util.create_pull_request(mergeable=True)
35 35 author = pull_request.user_id
36 36 repo = pull_request.target_repo.repo_id
37 37 pull_request_id = pull_request.pull_request_id
38 38 pull_request_repo = pull_request.target_repo.repo_name
39 39
40 40 id_, params = build_data(
41 41 self.apikey, 'merge_pull_request',
42 42 repoid=pull_request_repo,
43 43 pullrequestid=pull_request_id)
44 44
45 45 response = api_call(self.app, params)
46 46
47 47 # The above api call detaches the pull request DB object from the
48 48 # session because of an unconditional transaction rollback in our
49 49 # middleware. Therefore we need to add it back here if we want to use
50 50 # it.
51 51 Session().add(pull_request)
52 52
53 53 expected = 'merge not possible for following reasons: ' \
54 54 'Pull request reviewer approval is pending.'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 60 author = pull_request.user_id
61 61 repo = pull_request.target_repo.repo_id
62 62 pull_request_id = pull_request.pull_request_id
63 63 pull_request_repo = pull_request.target_repo.repo_name
64 64
65 65 id_, params = build_data(
66 66 self.apikey, 'comment_pull_request',
67 67 repoid=pull_request_repo,
68 68 pullrequestid=pull_request_id,
69 69 status='approved')
70 70
71 71 response = api_call(self.app, params)
72 72 expected = {
73 73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 74 'pull_request_id': pull_request_id,
75 75 'status': {'given': 'approved', 'was_changed': True}
76 76 }
77 77 assert_ok(id_, expected, given=response.body)
78 78
79 79 id_, params = build_data(
80 80 self.apikey, 'merge_pull_request',
81 81 repoid=pull_request_repo,
82 82 pullrequestid=pull_request_id)
83 83
84 84 response = api_call(self.app, params)
85 85
86 86 pull_request = PullRequest.get(pull_request_id)
87 87
88 88 expected = {
89 89 'executed': True,
90 90 'failure_reason': 0,
91 91 'possible': True,
92 92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 94 }
95 95
96 96 assert_ok(id_, expected, response.body)
97 97
98 98 journal = UserLog.query()\
99 99 .filter(UserLog.user_id == author)\
100 100 .filter(UserLog.repository_id == repo) \
101 101 .order_by('user_log_id') \
102 102 .all()
103 103 assert journal[-2].action == 'repo.pull_request.merge'
104 104 assert journal[-1].action == 'repo.pull_request.close'
105 105
106 106 id_, params = build_data(
107 107 self.apikey, 'merge_pull_request',
108 108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 109 response = api_call(self.app, params)
110 110
111 111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 112 assert_error(id_, expected, given=response.body)
113 113
114 114 @pytest.mark.backends("git", "hg")
115 115 def test_api_merge_pull_request_repo_error(self, pr_util):
116 116 pull_request = pr_util.create_pull_request()
117 117 id_, params = build_data(
118 118 self.apikey, 'merge_pull_request',
119 119 repoid=666, pullrequestid=pull_request.pull_request_id)
120 120 response = api_call(self.app, params)
121 121
122 122 expected = 'repository `666` does not exist'
123 123 assert_error(id_, expected, given=response.body)
124 124
125 125 @pytest.mark.backends("git", "hg")
126 126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
127 127 pr_util):
128 128 pull_request = pr_util.create_pull_request(mergeable=True)
129 129 id_, params = build_data(
130 130 self.apikey_regular, 'merge_pull_request',
131 131 repoid=pull_request.target_repo.repo_name,
132 132 pullrequestid=pull_request.pull_request_id,
133 133 userid=TEST_USER_ADMIN_LOGIN)
134 134 response = api_call(self.app, params)
135 135
136 136 expected = 'userid is not the same as your user'
137 137 assert_error(id_, expected, given=response.body)
@@ -1,51 +1,51 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 os
22 22
23 23 import pytest
24 24
25 25 from rhodecode.tests import TESTS_TMP_PATH
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestPull(object):
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_pull(self, backend):
34 34 r = backend.create_repo()
35 35 repo_name = r.repo_name
36 36 r.clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37 37
38 38 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
39 39 response = api_call(self.app, params)
40 40
41 41 expected = {'msg': 'Pulled from `%s`' % (repo_name,),
42 42 'repository': repo_name}
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 def test_api_pull_error(self, backend):
46 46 id_, params = build_data(
47 47 self.apikey, 'pull', repoid=backend.repo_name)
48 48 response = api_call(self.app, params)
49 49
50 50 expected = 'Unable to pull changes from `%s`' % (backend.repo_name,)
51 51 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.db import Repository, RepositoryField
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestRemoveFieldFromRepo(object):
29 29 def test_api_remove_field_from_repo(self, backend):
30 30 repo = backend.create_repo()
31 31 repo_name = repo.repo_name
32 32
33 33 id_, params = build_data(
34 34 self.apikey, 'add_field_to_repo',
35 35 repoid=repo_name,
36 36 key='extra_field',
37 37 label='extra_field_label',
38 38 description='extra_field_desc')
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'Added new repository field `extra_field`',
42 42 'success': True,
43 43 }
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 repo = Repository.get_by_repo_name(repo_name)
47 47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 48 _data = repo_field.get_dict()
49 49 assert _data['field_desc'] == 'extra_field_desc'
50 50 assert _data['field_key'] == 'extra_field'
51 51 assert _data['field_label'] == 'extra_field_label'
52 52
53 53 id_, params = build_data(
54 54 self.apikey, 'remove_field_from_repo',
55 55 repoid=repo_name,
56 56 key='extra_field')
57 57 response = api_call(self.app, params)
58 58 expected = {
59 59 'msg': 'Deleted repository field `extra_field`',
60 60 'success': True,
61 61 }
62 62 assert_ok(id_, expected, given=response.body)
63 63 repo = Repository.get_by_repo_name(repo_name)
64 64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
65 65
66 66 assert repo_field is None
@@ -1,58 +1,58 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user_group import UserGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRemoveUserFromUserGroup(object):
31 31 def test_api_remove_user_from_user_group(self, user_util):
32 32 user, group = user_util.create_user_with_group()
33 33 user_name = user.username
34 34 group_name = group.users_group_name
35 35 id_, params = build_data(
36 36 self.apikey, 'remove_user_from_user_group',
37 37 usergroupid=group_name,
38 38 userid=user.username)
39 39 response = api_call(self.app, params)
40 40
41 41 expected = {
42 42 'msg': 'removed member `%s` from user group `%s`' % (
43 43 user_name, group_name
44 44 ),
45 45 'success': True}
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
49 49 def test_api_remove_user_from_user_group_exception_occurred(
50 50 self, user_util):
51 51 user, group = user_util.create_user_with_group()
52 52 id_, params = build_data(
53 53 self.apikey, 'remove_user_from_user_group',
54 54 usergroupid=group.users_group_name, userid=user.username)
55 55 response = api_call(self.app, params)
56 56 expected = 'failed to remove member from user group `%s`' % (
57 57 group.users_group_name)
58 58 assert_error(id_, expected, given=response.body)
@@ -1,184 +1,184 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.model.db import Repository
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.lib.ext_json import json
28 28 from rhodecode.lib.utils2 import time_to_datetime
29 29 from rhodecode.api.tests.utils import (
30 30 build_data, api_call, assert_ok, assert_error, crash)
31 31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 32
33 33
34 34 @pytest.mark.usefixtures("testuser_api", "app")
35 35 class TestLock(object):
36 36 def test_api_lock_repo_lock_aquire(self, backend):
37 37 id_, params = build_data(
38 38 self.apikey, 'lock',
39 39 userid=TEST_USER_ADMIN_LOGIN,
40 40 repoid=backend.repo_name,
41 41 locked=True)
42 42 response = api_call(self.app, params)
43 43 expected = {
44 44 'repo': backend.repo_name, 'locked': True,
45 45 'locked_since': response.json['result']['locked_since'],
46 46 'locked_by': TEST_USER_ADMIN_LOGIN,
47 47 'lock_state_changed': True,
48 48 'lock_reason': Repository.LOCK_API,
49 49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
50 50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
51 51 }
52 52 assert_ok(id_, expected, given=response.body)
53 53
54 54 def test_repo_lock_aquire_by_non_admin(self, backend):
55 55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
56 56 repo_name = repo.repo_name
57 57 id_, params = build_data(
58 58 self.apikey_regular, 'lock',
59 59 repoid=repo_name,
60 60 locked=True)
61 61 response = api_call(self.app, params)
62 62 expected = {
63 63 'repo': repo_name,
64 64 'locked': True,
65 65 'locked_since': response.json['result']['locked_since'],
66 66 'locked_by': self.TEST_USER_LOGIN,
67 67 'lock_state_changed': True,
68 68 'lock_reason': Repository.LOCK_API,
69 69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
70 70 % (self.TEST_USER_LOGIN, repo_name, True))
71 71 }
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
75 75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 76 repo_name = repo.repo_name
77 77 id_, params = build_data(
78 78 self.apikey_regular, 'lock',
79 79 userid=TEST_USER_ADMIN_LOGIN,
80 80 repoid=repo_name,
81 81 locked=True)
82 82 response = api_call(self.app, params)
83 83 expected = 'userid is not the same as your user'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'lock',
89 89 repoid=backend.repo_name,
90 90 locked=True)
91 91 response = api_call(self.app, params)
92 92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
93 93 assert_error(id_, expected, given=response.body)
94 94
95 95 def test_api_lock_repo_lock_release(self, backend):
96 96 id_, params = build_data(
97 97 self.apikey, 'lock',
98 98 userid=TEST_USER_ADMIN_LOGIN,
99 99 repoid=backend.repo_name,
100 100 locked=False)
101 101 response = api_call(self.app, params)
102 102 expected = {
103 103 'repo': backend.repo_name,
104 104 'locked': False,
105 105 'locked_since': None,
106 106 'locked_by': TEST_USER_ADMIN_LOGIN,
107 107 'lock_state_changed': True,
108 108 'lock_reason': Repository.LOCK_API,
109 109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
110 110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
111 111 }
112 112 assert_ok(id_, expected, given=response.body)
113 113
114 114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
115 115 id_, params = build_data(
116 116 self.apikey, 'lock',
117 117 repoid=backend.repo_name,
118 118 locked=True)
119 119 response = api_call(self.app, params)
120 120 time_ = response.json['result']['locked_since']
121 121 expected = {
122 122 'repo': backend.repo_name,
123 123 'locked': True,
124 124 'locked_since': time_,
125 125 'locked_by': TEST_USER_ADMIN_LOGIN,
126 126 'lock_state_changed': True,
127 127 'lock_reason': Repository.LOCK_API,
128 128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
129 129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
130 130 }
131 131
132 132 assert_ok(id_, expected, given=response.body)
133 133
134 134 def test_api_lock_repo_lock_optional_locked(self, backend):
135 135 # TODO: Provide a fixture locked_repository or similar
136 136 repo = Repository.get_by_repo_name(backend.repo_name)
137 137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
138 138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
139 139
140 140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
141 141 response = api_call(self.app, params)
142 142 time_ = response.json['result']['locked_since']
143 143 expected = {
144 144 'repo': backend.repo_name,
145 145 'locked': True,
146 146 'locked_since': time_,
147 147 'locked_by': TEST_USER_ADMIN_LOGIN,
148 148 'lock_state_changed': False,
149 149 'lock_reason': Repository.LOCK_API,
150 150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
151 151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
152 152 json.dumps(time_to_datetime(time_))))
153 153 }
154 154 assert_ok(id_, expected, given=response.body)
155 155
156 156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
157 157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
158 158 repo_name = repo.repo_name
159 159 assert repo.locked == [None, None, None]
160 160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
161 161 response = api_call(self.app, params)
162 162 expected = {
163 163 'repo': repo_name,
164 164 'locked': False,
165 165 'locked_since': None,
166 166 'locked_by': None,
167 167 'lock_state_changed': False,
168 168 'lock_reason': None,
169 169 'msg': ('Repo `%s` not locked.' % (repo_name,))
170 170 }
171 171 assert_ok(id_, expected, given=response.body)
172 172
173 173 @mock.patch.object(Repository, 'lock', crash)
174 174 def test_api_lock_error(self, backend):
175 175 id_, params = build_data(
176 176 self.apikey, 'lock',
177 177 userid=TEST_USER_ADMIN_LOGIN,
178 178 repoid=backend.repo_name,
179 179 locked=True)
180 180 response = api_call(self.app, params)
181 181
182 182 expected = 'Error occurred locking repository `%s`' % (
183 183 backend.repo_name,)
184 184 assert_error(id_, expected, given=response.body)
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.scm import ScmModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRescanRepos(object):
31 31 def test_api_rescan_repos(self):
32 32 id_, params = build_data(self.apikey, 'rescan_repos')
33 33 response = api_call(self.app, params)
34 34
35 35 expected = {'added': [], 'removed': []}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 @mock.patch.object(ScmModel, 'repo_scan', crash)
39 39 def test_api_rescann_error(self):
40 40 id_, params = build_data(self.apikey, 'rescan_repos', )
41 41 response = api_call(self.app, params)
42 42
43 43 expected = 'Error occurred during rescan repositories action'
44 44 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRevokeUserGroupPermission(object):
31 31 def test_api_revoke_user_group_permission(self, backend, user_util):
32 32 repo = backend.create_repo()
33 33 user_group = user_util.create_user_group()
34 34 user_util.grant_user_group_permission_to_repo(
35 35 repo, user_group, 'repository.read')
36 36 id_, params = build_data(
37 37 self.apikey,
38 38 'revoke_user_group_permission',
39 39 repoid=backend.repo_name,
40 40 usergroupid=user_group.users_group_name)
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
45 45 user_group.users_group_name, backend.repo_name
46 46 ),
47 47 'success': True
48 48 }
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
52 52 def test_api_revoke_user_group_permission_exception_when_adding(
53 53 self, backend, user_util):
54 54 user_group = user_util.create_user_group()
55 55 id_, params = build_data(
56 56 self.apikey,
57 57 'revoke_user_group_permission',
58 58 repoid=backend.repo_name,
59 59 usergroupid=user_group.users_group_name)
60 60 response = api_call(self.app, params)
61 61
62 62 expected = (
63 63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
64 64 user_group.users_group_name, backend.repo_name
65 65 )
66 66 )
67 67 assert_error(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, apply_to_children", [
33 33 ('none', 'none'),
34 34 ('all', 'all'),
35 35 ('repos', 'repos'),
36 36 ('groups', 'groups'),
37 37 ])
38 38 def test_api_revoke_user_group_permission_from_repo_group(
39 39 self, name, apply_to_children, user_util):
40 40 user_group = user_util.create_user_group()
41 41 repo_group = user_util.create_repo_group()
42 42 user_util.grant_user_group_permission_to_repo_group(
43 43 repo_group, user_group, 'group.read')
44 44
45 45 id_, params = build_data(
46 46 self.apikey, 'revoke_user_group_permission_from_repo_group',
47 47 repogroupid=repo_group.name,
48 48 usergroupid=user_group.users_group_name,
49 49 apply_to_children=apply_to_children,)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': (
54 54 'Revoked perm (recursive:%s) for user group: `%s`'
55 55 ' in repo group: `%s`' % (
56 56 apply_to_children, user_group.users_group_name,
57 57 repo_group.name
58 58 )
59 59 ),
60 60 'success': True
61 61 }
62 62 assert_ok(id_, expected, given=response.body)
63 63
64 64 @pytest.mark.parametrize(
65 65 "name, apply_to_children, grant_admin, access_ok", [
66 66 ('none', 'none', False, False),
67 67 ('all', 'all', False, False),
68 68 ('repos', 'repos', False, False),
69 69 ('groups', 'groups', False, False),
70 70
71 71 # after granting admin rights
72 72 ('none', 'none', False, False),
73 73 ('all', 'all', False, False),
74 74 ('repos', 'repos', False, False),
75 75 ('groups', 'groups', False, False),
76 76 ]
77 77 )
78 78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
79 79 self, name, apply_to_children, grant_admin, access_ok, user_util):
80 80 user_group = user_util.create_user_group()
81 81 repo_group = user_util.create_repo_group()
82 82 user_util.grant_user_group_permission_to_repo_group(
83 83 repo_group, user_group, 'group.read')
84 84
85 85 if grant_admin:
86 86 user_util.grant_user_permission_to_repo_group(
87 87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
88 88
89 89 id_, params = build_data(
90 90 self.apikey_regular,
91 91 'revoke_user_group_permission_from_repo_group',
92 92 repogroupid=repo_group.name,
93 93 usergroupid=user_group.users_group_name,
94 94 apply_to_children=apply_to_children,)
95 95 response = api_call(self.app, params)
96 96 if access_ok:
97 97 expected = {
98 98 'msg': (
99 99 'Revoked perm (recursive:%s) for user group: `%s`'
100 100 ' in repo group: `%s`' % (
101 101 apply_to_children, TEST_USER_ADMIN_LOGIN,
102 102 repo_group.name
103 103 )
104 104 ),
105 105 'success': True
106 106 }
107 107 assert_ok(id_, expected, given=response.body)
108 108 else:
109 109 expected = 'repository group `%s` does not exist' % (
110 110 repo_group.name,)
111 111 assert_error(id_, expected, given=response.body)
112 112
113 113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
114 114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
115 115 self, user_util):
116 116 user_group = user_util.create_user_group()
117 117 repo_group = user_util.create_repo_group()
118 118 id_, params = build_data(
119 119 self.apikey, 'revoke_user_group_permission_from_repo_group',
120 120 repogroupid=repo_group.name,
121 121 usergroupid=user_group.users_group_name)
122 122 response = api_call(self.app, params)
123 123
124 124 expected = (
125 125 'failed to edit permission for user group: `%s`'
126 126 ' in repo group: `%s`' % (
127 127 user_group.users_group_name, repo_group.name)
128 128 )
129 129 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestRevokeUserGroupPermissionFromUserGroup(object):
29 29 @pytest.mark.parametrize("name", [
30 30 ('none',),
31 31 ('all',),
32 32 ('repos',),
33 33 ('groups',),
34 34 ])
35 35 def test_api_revoke_user_group_permission_from_user_group(
36 36 self, name, user_util):
37 37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
38 38 group = user_util.create_user_group()
39 39 source_group = user_util.create_user_group()
40 40
41 41 user_util.grant_user_permission_to_user_group(
42 42 group, user, 'usergroup.read')
43 43 user_util.grant_user_group_permission_to_user_group(
44 44 source_group, group, 'usergroup.read')
45 45
46 46 id_, params = build_data(
47 47 self.apikey, 'revoke_user_group_permission_from_user_group',
48 48 usergroupid=group.users_group_name,
49 49 sourceusergroupid=source_group.users_group_name)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
54 54 source_group.users_group_name, group.users_group_name
55 55 ),
56 56 'success': True
57 57 }
58 58 assert_ok(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRevokeUserPermission(object):
31 31 def test_api_revoke_user_permission(self, backend, user_util):
32 32 repo = backend.create_repo()
33 33 user = user_util.create_user()
34 34 user_util.grant_user_permission_to_repo(
35 35 repo, user, 'repository.read')
36 36
37 37 id_, params = build_data(
38 38 self.apikey,
39 39 'revoke_user_permission',
40 40 repoid=repo.repo_name,
41 41 userid=user.username)
42 42 response = api_call(self.app, params)
43 43
44 44 expected = {
45 45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
46 46 user.username, backend.repo_name
47 47 ),
48 48 'success': True
49 49 }
50 50 assert_ok(id_, expected, given=response.body)
51 51
52 52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
53 53 def test_api_revoke_user_permission_exception_when_adding(
54 54 self, backend, user_util):
55 55 user = user_util.create_user()
56 56 id_, params = build_data(
57 57 self.apikey,
58 58 'revoke_user_permission',
59 59 repoid=backend.repo_name,
60 60 userid=user.username)
61 61 response = api_call(self.app, params)
62 62
63 63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
64 64 user.username, backend.repo_name
65 65 )
66 66 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRevokeUserPermissionFromRepoGroup(object):
31 31 @pytest.mark.parametrize("name, apply_to_children", [
32 32 ('none', 'none'),
33 33 ('all', 'all'),
34 34 ('repos', 'repos'),
35 35 ('groups', 'groups'),
36 36 ])
37 37 def test_api_revoke_user_permission_from_repo_group(
38 38 self, name, apply_to_children, user_util):
39 39 user = user_util.create_user()
40 40 repo_group = user_util.create_repo_group()
41 41 user_util.grant_user_permission_to_repo_group(
42 42 repo_group, user, 'group.read')
43 43
44 44 id_, params = build_data(
45 45 self.apikey,
46 46 'revoke_user_permission_from_repo_group',
47 47 repogroupid=repo_group.name,
48 48 userid=user.username,
49 49 apply_to_children=apply_to_children,)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': (
54 54 'Revoked perm (recursive:%s) for user: `%s`'
55 55 ' in repo group: `%s`' % (
56 56 apply_to_children, user.username, repo_group.name
57 57 )
58 58 ),
59 59 'success': True
60 60 }
61 61 assert_ok(id_, expected, given=response.body)
62 62
63 63 @pytest.mark.parametrize(
64 64 "name, apply_to_children, grant_admin, access_ok", [
65 65 ('none', 'none', False, False),
66 66 ('all', 'all', False, False),
67 67 ('repos', 'repos', False, False),
68 68 ('groups', 'groups', False, False),
69 69
70 70 # after granting admin rights
71 71 ('none', 'none', False, False),
72 72 ('all', 'all', False, False),
73 73 ('repos', 'repos', False, False),
74 74 ('groups', 'groups', False, False),
75 75 ]
76 76 )
77 77 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
78 78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 79 user = user_util.create_user()
80 80 repo_group = user_util.create_repo_group()
81 81 permission = 'group.admin' if grant_admin else 'group.read'
82 82 user_util.grant_user_permission_to_repo_group(
83 83 repo_group, user, permission)
84 84
85 85 id_, params = build_data(
86 86 self.apikey_regular,
87 87 'revoke_user_permission_from_repo_group',
88 88 repogroupid=repo_group.name,
89 89 userid=user.username,
90 90 apply_to_children=apply_to_children,)
91 91 response = api_call(self.app, params)
92 92 if access_ok:
93 93 expected = {
94 94 'msg': (
95 95 'Revoked perm (recursive:%s) for user: `%s`'
96 96 ' in repo group: `%s`' % (
97 97 apply_to_children, user.username, repo_group.name
98 98 )
99 99 ),
100 100 'success': True
101 101 }
102 102 assert_ok(id_, expected, given=response.body)
103 103 else:
104 104 expected = 'repository group `%s` does not exist' % (
105 105 repo_group.name)
106 106 assert_error(id_, expected, given=response.body)
107 107
108 108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
109 109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
110 110 self, user_util):
111 111 user = user_util.create_user()
112 112 repo_group = user_util.create_repo_group()
113 113 id_, params = build_data(
114 114 self.apikey,
115 115 'revoke_user_permission_from_repo_group',
116 116 repogroupid=repo_group.name,
117 117 userid=user.username
118 118 )
119 119 response = api_call(self.app, params)
120 120
121 121 expected = (
122 122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
123 123 user.username, repo_group.name
124 124 )
125 125 )
126 126 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user_group import UserGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRevokeUserPermissionFromUserGroup(object):
31 31 @pytest.mark.parametrize("name", [
32 32 ('none',),
33 33 ('all',),
34 34 ('repos',),
35 35 ('groups',),
36 36 ])
37 37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
38 38 user = user_util.create_user()
39 39 group = user_util.create_user_group()
40 40 user_util.grant_user_permission_to_user_group(
41 41 group, user, 'usergroup.admin')
42 42
43 43 id_, params = build_data(
44 44 self.apikey,
45 45 'revoke_user_permission_from_user_group',
46 46 usergroupid=group.users_group_name,
47 47 userid=user.username)
48 48 response = api_call(self.app, params)
49 49
50 50 expected = {
51 51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
52 52 user.username, group.users_group_name
53 53 ),
54 54 'success': True
55 55 }
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
59 59 ('none', False, False),
60 60 ('all', False, False),
61 61 ('repos', False, False),
62 62 ('groups', False, False),
63 63
64 64 # after granting admin rights
65 65 ('none', False, False),
66 66 ('all', False, False),
67 67 ('repos', False, False),
68 68 ('groups', False, False),
69 69 ])
70 70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
71 71 self, name, grant_admin, access_ok, user_util):
72 72 user = user_util.create_user()
73 73 group = user_util.create_user_group()
74 74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
75 75 user_util.grant_user_permission_to_user_group(group, user, permission)
76 76
77 77 id_, params = build_data(
78 78 self.apikey_regular,
79 79 'revoke_user_permission_from_user_group',
80 80 usergroupid=group.users_group_name,
81 81 userid=user.username)
82 82 response = api_call(self.app, params)
83 83 if access_ok:
84 84 expected = {
85 85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
86 86 user.username, group.users_group_name
87 87 ),
88 88 'success': True
89 89 }
90 90 assert_ok(id_, expected, given=response.body)
91 91 else:
92 92 expected = 'user group `%s` does not exist' % (
93 93 group.users_group_name)
94 94 assert_error(id_, expected, given=response.body)
95 95
96 96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
97 97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
98 98 self, user_util):
99 99 user = user_util.create_user()
100 100 group = user_util.create_user_group()
101 101 id_, params = build_data(
102 102 self.apikey,
103 103 'revoke_user_permission_from_user_group',
104 104 usergroupid=group.users_group_name,
105 105 userid=user.username)
106 106 response = api_call(self.app, params)
107 107
108 108 expected = (
109 109 'failed to edit permission for user: `%s` in user group: `%s`' % (
110 110 user.username, group.users_group_name)
111 111 )
112 112 assert_error(id_, expected, given=response.body)
@@ -1,213 +1,213 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 pytest
22 22
23 23 from rhodecode.lib.vcs.nodes import FileNode
24 24 from rhodecode.model.db import User
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdatePullRequest(object):
33 33
34 34 @pytest.mark.backends("git", "hg")
35 35 def test_api_update_pull_request_title_or_description(
36 36 self, pr_util, no_notifications):
37 37 pull_request = pr_util.create_pull_request()
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'update_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id,
43 43 title='New TITLE OF A PR',
44 44 description='New DESC OF A PR',
45 45 )
46 46 response = api_call(self.app, params)
47 47
48 48 expected = {
49 49 "msg": "Updated pull request `{}`".format(
50 50 pull_request.pull_request_id),
51 51 "pull_request": response.json['result']['pull_request'],
52 52 "updated_commits": {"added": [], "common": [], "removed": []},
53 53 "updated_reviewers": {"added": [], "removed": []},
54 54 }
55 55
56 56 response_json = response.json['result']
57 57 assert response_json == expected
58 58 pr = response_json['pull_request']
59 59 assert pr['title'] == 'New TITLE OF A PR'
60 60 assert pr['description'] == 'New DESC OF A PR'
61 61
62 62 @pytest.mark.backends("git", "hg")
63 63 def test_api_try_update_closed_pull_request(
64 64 self, pr_util, no_notifications):
65 65 pull_request = pr_util.create_pull_request()
66 66 PullRequestModel().close_pull_request(
67 67 pull_request, TEST_USER_ADMIN_LOGIN)
68 68
69 69 id_, params = build_data(
70 70 self.apikey, 'update_pull_request',
71 71 repoid=pull_request.target_repo.repo_name,
72 72 pullrequestid=pull_request.pull_request_id)
73 73 response = api_call(self.app, params)
74 74
75 75 expected = 'pull request `{}` update failed, pull request ' \
76 76 'is closed'.format(pull_request.pull_request_id)
77 77
78 78 assert_error(id_, expected, response.body)
79 79
80 80 @pytest.mark.backends("git", "hg")
81 81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 82 commits = [
83 83 {'message': 'a'},
84 84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 86 ]
87 87 pull_request = pr_util.create_pull_request(
88 88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 89 pr_util.update_source_repository(head='c')
90 90 repo = pull_request.source_repo.scm_instance()
91 91 commits = [x for x in repo.get_commits()]
92 92 print commits
93 93
94 94 added_commit_id = commits[-1].raw_id # c commit
95 95 common_commit_id = commits[1].raw_id # b commit is common ancestor
96 96 total_commits = [added_commit_id, common_commit_id]
97 97
98 98 id_, params = build_data(
99 99 self.apikey, 'update_pull_request',
100 100 repoid=pull_request.target_repo.repo_name,
101 101 pullrequestid=pull_request.pull_request_id,
102 102 update_commits=True
103 103 )
104 104 response = api_call(self.app, params)
105 105
106 106 expected = {
107 107 "msg": "Updated pull request `{}`".format(
108 108 pull_request.pull_request_id),
109 109 "pull_request": response.json['result']['pull_request'],
110 110 "updated_commits": {"added": [added_commit_id],
111 111 "common": [common_commit_id],
112 112 "total": total_commits,
113 113 "removed": []},
114 114 "updated_reviewers": {"added": [], "removed": []},
115 115 }
116 116
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_update_change_reviewers(
121 121 self, user_util, pr_util, no_notifications):
122 122 a = user_util.create_user()
123 123 b = user_util.create_user()
124 124 c = user_util.create_user()
125 125 new_reviewers = [
126 126 {'username': b.username,'reasons': ['updated via API'],
127 127 'mandatory':False},
128 128 {'username': c.username, 'reasons': ['updated via API'],
129 129 'mandatory':False},
130 130 ]
131 131
132 132 added = [b.username, c.username]
133 133 removed = [a.username]
134 134
135 135 pull_request = pr_util.create_pull_request(
136 136 reviewers=[(a.username, ['added via API'], False, [])])
137 137
138 138 id_, params = build_data(
139 139 self.apikey, 'update_pull_request',
140 140 repoid=pull_request.target_repo.repo_name,
141 141 pullrequestid=pull_request.pull_request_id,
142 142 reviewers=new_reviewers)
143 143 response = api_call(self.app, params)
144 144 expected = {
145 145 "msg": "Updated pull request `{}`".format(
146 146 pull_request.pull_request_id),
147 147 "pull_request": response.json['result']['pull_request'],
148 148 "updated_commits": {"added": [], "common": [], "removed": []},
149 149 "updated_reviewers": {"added": added, "removed": removed},
150 150 }
151 151
152 152 assert_ok(id_, expected, response.body)
153 153
154 154 @pytest.mark.backends("git", "hg")
155 155 def test_api_update_bad_user_in_reviewers(self, pr_util):
156 156 pull_request = pr_util.create_pull_request()
157 157
158 158 id_, params = build_data(
159 159 self.apikey, 'update_pull_request',
160 160 repoid=pull_request.target_repo.repo_name,
161 161 pullrequestid=pull_request.pull_request_id,
162 162 reviewers=[{'username': 'bad_name'}])
163 163 response = api_call(self.app, params)
164 164
165 165 expected = 'user `bad_name` does not exist'
166 166
167 167 assert_error(id_, expected, response.body)
168 168
169 169 @pytest.mark.backends("git", "hg")
170 170 def test_api_update_repo_error(self, pr_util):
171 171 pull_request = pr_util.create_pull_request()
172 172 id_, params = build_data(
173 173 self.apikey, 'update_pull_request',
174 174 repoid='fake',
175 175 pullrequestid=pull_request.pull_request_id,
176 176 reviewers=[{'username': 'bad_name'}])
177 177 response = api_call(self.app, params)
178 178
179 179 expected = 'repository `fake` does not exist'
180 180
181 181 response_json = response.json['error']
182 182 assert response_json == expected
183 183
184 184 @pytest.mark.backends("git", "hg")
185 185 def test_api_update_pull_request_error(self, pr_util):
186 186 pull_request = pr_util.create_pull_request()
187 187
188 188 id_, params = build_data(
189 189 self.apikey, 'update_pull_request',
190 190 repoid=pull_request.target_repo.repo_name,
191 191 pullrequestid=999999,
192 192 reviewers=[{'username': 'bad_name'}])
193 193 response = api_call(self.app, params)
194 194
195 195 expected = 'pull request `999999` does not exist'
196 196 assert_error(id_, expected, response.body)
197 197
198 198 @pytest.mark.backends("git", "hg")
199 199 def test_api_update_pull_request_no_perms_to_update(
200 200 self, user_util, pr_util):
201 201 user = user_util.create_user()
202 202 pull_request = pr_util.create_pull_request()
203 203
204 204 id_, params = build_data(
205 205 user.api_key, 'update_pull_request',
206 206 repoid=pull_request.target_repo.repo_name,
207 207 pullrequestid=pull_request.pull_request_id,)
208 208 response = api_call(self.app, params)
209 209
210 210 expected = ('pull request `%s` update failed, '
211 211 'no permission to update.') % pull_request.pull_request_id
212 212
213 213 assert_error(id_, expected, response.body)
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 28 from rhodecode.tests.fixture import Fixture
29 29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
30 30
31 31 fixture = Fixture()
32 32
33 33 UPDATE_REPO_NAME = 'api_update_me'
34 34
35 35
36 36 class SAME_AS_UPDATES(object):
37 37 """ Constant used for tests below """
38 38
39 39
40 40 @pytest.mark.usefixtures("testuser_api", "app")
41 41 class TestApiUpdateRepo(object):
42 42
43 43 @pytest.mark.parametrize("updates, expected", [
44 44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 45 SAME_AS_UPDATES),
46 46
47 47 ({'description': 'new description'},
48 48 SAME_AS_UPDATES),
49 49
50 50 ({'clone_uri': 'http://foo.com/repo'},
51 51 SAME_AS_UPDATES),
52 52
53 53 ({'clone_uri': None},
54 54 {'clone_uri': ''}),
55 55
56 56 ({'clone_uri': ''},
57 57 {'clone_uri': ''}),
58 58
59 59 ({'landing_rev': 'rev:tip'},
60 60 {'landing_rev': ['rev', 'tip']}),
61 61
62 62 ({'enable_statistics': True},
63 63 SAME_AS_UPDATES),
64 64
65 65 ({'enable_locking': True},
66 66 SAME_AS_UPDATES),
67 67
68 68 ({'enable_downloads': True},
69 69 SAME_AS_UPDATES),
70 70
71 71 ({'repo_name': 'new_repo_name'},
72 72 {
73 73 'repo_name': 'new_repo_name',
74 74 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
75 75 }),
76 76
77 77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 78 '_group': 'test_group_for_update'},
79 79 {
80 80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
81 81 'url': 'http://{}/test_group_for_update/{}'.format(
82 82 http_host_only_stub(), UPDATE_REPO_NAME)
83 83 }),
84 84 ])
85 85 def test_api_update_repo(self, updates, expected, backend):
86 86 repo_name = UPDATE_REPO_NAME
87 87 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
88 88 if updates.get('_group'):
89 89 fixture.create_repo_group(updates['_group'])
90 90
91 91 expected_api_data = repo.get_api_data(include_secrets=True)
92 92 if expected is SAME_AS_UPDATES:
93 93 expected_api_data.update(updates)
94 94 else:
95 95 expected_api_data.update(expected)
96 96
97 97 id_, params = build_data(
98 98 self.apikey, 'update_repo', repoid=repo_name, **updates)
99 99 response = api_call(self.app, params)
100 100
101 101 if updates.get('repo_name'):
102 102 repo_name = updates['repo_name']
103 103
104 104 try:
105 105 expected = {
106 106 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
107 107 'repository': jsonify(expected_api_data)
108 108 }
109 109 assert_ok(id_, expected, given=response.body)
110 110 finally:
111 111 fixture.destroy_repo(repo_name)
112 112 if updates.get('_group'):
113 113 fixture.destroy_repo_group(updates['_group'])
114 114
115 115 def test_api_update_repo_fork_of_field(self, backend):
116 116 master_repo = backend.create_repo()
117 117 repo = backend.create_repo()
118 118 updates = {
119 119 'fork_of': master_repo.repo_name,
120 120 'fork_of_id': master_repo.repo_id
121 121 }
122 122 expected_api_data = repo.get_api_data(include_secrets=True)
123 123 expected_api_data.update(updates)
124 124
125 125 id_, params = build_data(
126 126 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
127 127 response = api_call(self.app, params)
128 128 expected = {
129 129 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
130 130 'repository': jsonify(expected_api_data)
131 131 }
132 132 assert_ok(id_, expected, given=response.body)
133 133 result = response.json['result']['repository']
134 134 assert result['fork_of'] == master_repo.repo_name
135 135 assert result['fork_of_id'] == master_repo.repo_id
136 136
137 137 def test_api_update_repo_fork_of_not_found(self, backend):
138 138 master_repo_name = 'fake-parent-repo'
139 139 repo = backend.create_repo()
140 140 updates = {
141 141 'fork_of': master_repo_name
142 142 }
143 143 id_, params = build_data(
144 144 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
145 145 response = api_call(self.app, params)
146 146 expected = {
147 147 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
148 148 master_repo_name)}
149 149 assert_error(id_, expected, given=response.body)
150 150
151 151 def test_api_update_repo_with_repo_group_not_existing(self):
152 152 repo_name = 'admin_owned'
153 153 fake_repo_group = 'test_group_for_update'
154 154 fixture.create_repo(repo_name)
155 155 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
156 156 id_, params = build_data(
157 157 self.apikey, 'update_repo', repoid=repo_name, **updates)
158 158 response = api_call(self.app, params)
159 159 try:
160 160 expected = {
161 161 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
162 162 }
163 163 assert_error(id_, expected, given=response.body)
164 164 finally:
165 165 fixture.destroy_repo(repo_name)
166 166
167 167 def test_api_update_repo_regular_user_not_allowed(self):
168 168 repo_name = 'admin_owned'
169 169 fixture.create_repo(repo_name)
170 170 updates = {'active': False}
171 171 id_, params = build_data(
172 172 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
173 173 response = api_call(self.app, params)
174 174 try:
175 175 expected = 'repository `%s` does not exist' % (repo_name,)
176 176 assert_error(id_, expected, given=response.body)
177 177 finally:
178 178 fixture.destroy_repo(repo_name)
179 179
180 180 @mock.patch.object(RepoModel, 'update', crash)
181 181 def test_api_update_repo_exception_occurred(self, backend):
182 182 repo_name = UPDATE_REPO_NAME
183 183 fixture.create_repo(repo_name, repo_type=backend.alias)
184 184 id_, params = build_data(
185 185 self.apikey, 'update_repo', repoid=repo_name,
186 186 owner=TEST_USER_ADMIN_LOGIN,)
187 187 response = api_call(self.app, params)
188 188 try:
189 189 expected = 'failed to update repo `%s`' % (repo_name,)
190 190 assert_error(id_, expected, given=response.body)
191 191 finally:
192 192 fixture.destroy_repo(repo_name)
@@ -1,150 +1,150 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 os
22 22
23 23 import pytest
24 24
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestApiUpdateRepoGroup(object):
33 33
34 34 def test_update_group_name(self, user_util):
35 35 new_group_name = 'new-group'
36 36 initial_name = self._update(user_util, group_name=new_group_name)
37 37 assert RepoGroupModel()._get_repo_group(initial_name) is None
38 38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
39 39 assert new_group is not None
40 40 assert new_group.full_path == new_group_name
41 41
42 42 def test_update_group_name_change_parent(self, user_util):
43 43
44 44 parent_group = user_util.create_repo_group()
45 45 parent_group_name = parent_group.name
46 46
47 47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
48 48 initial_name = self._update(user_util, group_name=expected_group_name)
49 49
50 50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
51 51
52 52 assert repo_group is not None
53 53 assert repo_group.group_name == expected_group_name
54 54 assert repo_group.full_path == expected_group_name
55 55 assert RepoGroupModel()._get_repo_group(initial_name) is None
56 56
57 57 new_path = os.path.join(
58 58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
59 59 assert os.path.exists(new_path)
60 60
61 61 def test_update_enable_locking(self, user_util):
62 62 initial_name = self._update(user_util, enable_locking=True)
63 63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
64 64 assert repo_group.enable_locking is True
65 65
66 66 def test_update_description(self, user_util):
67 67 description = 'New description'
68 68 initial_name = self._update(user_util, description=description)
69 69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
70 70 assert repo_group.group_description == description
71 71
72 72 def test_update_owner(self, user_util):
73 73 owner = self.TEST_USER_LOGIN
74 74 initial_name = self._update(user_util, owner=owner)
75 75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
76 76 assert repo_group.user.username == owner
77 77
78 78 def test_update_group_name_conflict_with_existing(self, user_util):
79 79 group_1 = user_util.create_repo_group()
80 80 group_2 = user_util.create_repo_group()
81 81 repo_group_name_1 = group_1.group_name
82 82 repo_group_name_2 = group_2.group_name
83 83
84 84 id_, params = build_data(
85 85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
86 86 group_name=repo_group_name_2)
87 87 response = api_call(self.app, params)
88 88 expected = {
89 89 'unique_repo_group_name':
90 90 'Repository group with name `{}` already exists'.format(
91 91 repo_group_name_2)}
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
95 95 temp_user = user_util.create_user()
96 96 temp_user_api_key = temp_user.api_key
97 97 parent_group = user_util.create_repo_group()
98 98 repo_group_name = parent_group.group_name
99 99 id_, params = build_data(
100 100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
101 101 response = api_call(self.app, params)
102 102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
103 103 assert_error(id_, expected, given=response.body)
104 104
105 105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
106 106 self, user_util):
107 107 temp_user = user_util.create_user()
108 108 temp_user_api_key = temp_user.api_key
109 109 parent_group = user_util.create_repo_group(owner=temp_user.username)
110 110 repo_group_name = parent_group.group_name
111 111
112 112 id_, params = build_data(
113 113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
114 114 group_name='at-root-level')
115 115 response = api_call(self.app, params)
116 116 expected = {
117 117 'repo_group': 'You do not have the permission to store '
118 118 'repository groups in the root location.'}
119 119 assert_error(id_, expected, given=response.body)
120 120
121 121 def _update(self, user_util, **kwargs):
122 122 repo_group = user_util.create_repo_group()
123 123 initial_name = repo_group.name
124 124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
125 125 user_util.grant_user_permission_to_repo_group(
126 126 repo_group, user, 'group.admin')
127 127
128 128 id_, params = build_data(
129 129 self.apikey, 'update_repo_group', repogroupid=initial_name,
130 130 **kwargs)
131 131 response = api_call(self.app, params)
132 132
133 133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
134 134
135 135 expected = {
136 136 'msg': 'updated repository group ID:{} {}'.format(
137 137 repo_group.group_id, repo_group.group_name),
138 138 'repo_group': {
139 139 'repositories': [],
140 140 'group_name': repo_group.group_name,
141 141 'group_description': repo_group.group_description,
142 142 'owner': repo_group.user.username,
143 143 'group_id': repo_group.group_id,
144 144 'parent_group': (
145 145 repo_group.parent_group.name
146 146 if repo_group.parent_group else None)
147 147 }
148 148 }
149 149 assert_ok(id_, expected, given=response.body)
150 150 return initial_name
@@ -1,116 +1,116 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.db import User
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdateUser(object):
33 33 @pytest.mark.parametrize("name, expected", [
34 34 ('firstname', 'new_username'),
35 35 ('lastname', 'new_username'),
36 36 ('email', 'new_username'),
37 37 ('admin', True),
38 38 ('admin', False),
39 39 ('extern_type', 'ldap'),
40 40 ('extern_type', None),
41 41 ('extern_name', 'test'),
42 42 ('extern_name', None),
43 43 ('active', False),
44 44 ('active', True),
45 45 ('password', 'newpass')
46 46 ])
47 47 def test_api_update_user(self, name, expected, user_util):
48 48 usr = user_util.create_user()
49 49
50 50 kw = {name: expected, 'userid': usr.user_id}
51 51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 52 response = api_call(self.app, params)
53 53
54 54 ret = {
55 55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 56 'user': jsonify(
57 57 UserModel()
58 58 .get_by_username(usr.username)
59 59 .get_api_data(include_secrets=True)
60 60 )
61 61 }
62 62
63 63 expected = ret
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_update_user_no_changed_params(self):
67 67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 69 id_, params = build_data(
70 70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71 71
72 72 response = api_call(self.app, params)
73 73 ret = {
74 74 'msg': 'updated user ID:%s %s' % (
75 75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 76 'user': ret
77 77 }
78 78 expected = ret
79 79 assert_ok(id_, expected, given=response.body)
80 80
81 81 def test_api_update_user_by_user_id(self):
82 82 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
83 83 ret = jsonify(usr.get_api_data(include_secrets=True))
84 84 id_, params = build_data(
85 85 self.apikey, 'update_user', userid=usr.user_id)
86 86
87 87 response = api_call(self.app, params)
88 88 ret = {
89 89 'msg': 'updated user ID:%s %s' % (
90 90 usr.user_id, TEST_USER_ADMIN_LOGIN),
91 91 'user': ret
92 92 }
93 93 expected = ret
94 94 assert_ok(id_, expected, given=response.body)
95 95
96 96 def test_api_update_user_default_user(self):
97 97 usr = User.get_default_user()
98 98 id_, params = build_data(
99 99 self.apikey, 'update_user', userid=usr.user_id)
100 100
101 101 response = api_call(self.app, params)
102 102 expected = 'editing default user is forbidden'
103 103 assert_error(id_, expected, given=response.body)
104 104
105 105 @mock.patch.object(UserModel, 'update_user', crash)
106 106 def test_api_update_user_when_exception_happens(self):
107 107 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
108 108 ret = jsonify(usr.get_api_data(include_secrets=True))
109 109 id_, params = build_data(
110 110 self.apikey, 'update_user', userid=usr.user_id)
111 111
112 112 response = api_call(self.app, params)
113 113 ret = 'failed to update user `%s`' % (usr.user_id,)
114 114
115 115 expected = ret
116 116 assert_error(id_, expected, given=response.body)
@@ -1,110 +1,110 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.user_group import UserGroupModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdateUserGroup(object):
33 33 @pytest.mark.parametrize("changing_attr, updates", [
34 34 ('group_name', {'group_name': 'new_group_name'}),
35 35 ('group_name', {'group_name': 'test_group_for_update'}),
36 36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
38 38 ('active', {'active': False}),
39 39 ('active', {'active': True})
40 40 ])
41 41 def test_api_update_user_group(self, changing_attr, updates, user_util):
42 42 user_group = user_util.create_user_group()
43 43 group_name = user_group.users_group_name
44 44 expected_api_data = user_group.get_api_data()
45 45 expected_api_data.update(updates)
46 46
47 47 id_, params = build_data(
48 48 self.apikey, 'update_user_group', usergroupid=group_name,
49 49 **updates)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': 'updated user group ID:%s %s' % (
54 54 user_group.users_group_id, user_group.users_group_name),
55 55 'user_group': jsonify(expected_api_data)
56 56 }
57 57 assert_ok(id_, expected, given=response.body)
58 58
59 59 @pytest.mark.parametrize("changing_attr, updates", [
60 60 # TODO: mikhail: decide if we need to test against the commented params
61 61 # ('group_name', {'group_name': 'new_group_name'}),
62 62 # ('group_name', {'group_name': 'test_group_for_update'}),
63 63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
65 65 ('active', {'active': False}),
66 66 ('active', {'active': True})
67 67 ])
68 68 def test_api_update_user_group_regular_user(
69 69 self, changing_attr, updates, user_util):
70 70 user_group = user_util.create_user_group()
71 71 group_name = user_group.users_group_name
72 72 expected_api_data = user_group.get_api_data()
73 73 expected_api_data.update(updates)
74 74
75 75
76 76 # grant permission to this user
77 77 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
78 78
79 79 user_util.grant_user_permission_to_user_group(
80 80 user_group, user, 'usergroup.admin')
81 81 id_, params = build_data(
82 82 self.apikey_regular, 'update_user_group',
83 83 usergroupid=group_name, **updates)
84 84 response = api_call(self.app, params)
85 85 expected = {
86 86 'msg': 'updated user group ID:%s %s' % (
87 87 user_group.users_group_id, user_group.users_group_name),
88 88 'user_group': jsonify(expected_api_data)
89 89 }
90 90 assert_ok(id_, expected, given=response.body)
91 91
92 92 def test_api_update_user_group_regular_user_no_permission(self, user_util):
93 93 user_group = user_util.create_user_group()
94 94 group_name = user_group.users_group_name
95 95 id_, params = build_data(
96 96 self.apikey_regular, 'update_user_group', usergroupid=group_name)
97 97 response = api_call(self.app, params)
98 98
99 99 expected = 'user group `%s` does not exist' % (group_name)
100 100 assert_error(id_, expected, given=response.body)
101 101
102 102 @mock.patch.object(UserGroupModel, 'update', crash)
103 103 def test_api_update_user_group_exception_occurred(self, user_util):
104 104 user_group = user_util.create_user_group()
105 105 group_name = user_group.users_group_name
106 106 id_, params = build_data(
107 107 self.apikey, 'update_user_group', usergroupid=group_name)
108 108 response = api_call(self.app, params)
109 109 expected = 'failed to update user group `%s`' % (group_name,)
110 110 assert_error(id_, expected, given=response.body)
@@ -1,294 +1,294 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import pytest
23 23 from mock import Mock, patch
24 24
25 25 from rhodecode.api import utils
26 26 from rhodecode.api import JSONRPCError
27 27 from rhodecode.lib.vcs.exceptions import RepositoryError
28 28
29 29
30 30 class TestGetCommitOrError(object):
31 31 def setup(self):
32 32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
33 33
34 34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
35 35 def test_ref_cannot_be_parsed(self, ref):
36 36 repo = Mock()
37 37 with pytest.raises(JSONRPCError) as excinfo:
38 38 utils.get_commit_or_error(ref, repo)
39 39 expected_message = (
40 40 'Ref `{ref}` given in a wrong format. Please check the API'
41 41 ' documentation for more details'.format(ref=ref)
42 42 )
43 43 assert excinfo.value.message == expected_message
44 44
45 45 def test_success_with_hash_specified(self):
46 46 repo = Mock()
47 47 ref_type = 'branch'
48 48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
49 49
50 50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
51 51 result = utils.get_commit_or_error(ref, repo)
52 52 get_commit.assert_called_once_with(
53 53 repo, self.commit_hash)
54 54 assert result == get_commit()
55 55
56 56 def test_raises_an_error_when_commit_not_found(self):
57 57 repo = Mock()
58 58 ref = 'branch:master:{}'.format(self.commit_hash)
59 59
60 60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
61 61 get_commit.side_effect = RepositoryError('Commit not found')
62 62 with pytest.raises(JSONRPCError) as excinfo:
63 63 utils.get_commit_or_error(ref, repo)
64 64 expected_message = 'Ref `{}` does not exist'.format(ref)
65 65 assert excinfo.value.message == expected_message
66 66
67 67
68 68 class TestResolveRefOrError(object):
69 69 def setup(self):
70 70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
71 71
72 72 def test_success_with_no_hash_specified(self):
73 73 repo = Mock()
74 74 ref_type = 'branch'
75 75 ref_name = 'master'
76 76 ref = '{}:{}'.format(ref_type, ref_name)
77 77
78 78 with patch('rhodecode.api.utils._get_ref_hash') \
79 79 as _get_ref_hash:
80 80 _get_ref_hash.return_value = self.commit_hash
81 81 result = utils.resolve_ref_or_error(ref, repo)
82 82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
83 83 assert result == '{}:{}'.format(ref, self.commit_hash)
84 84
85 85 def test_non_supported_refs(self):
86 86 repo = Mock()
87 87 ref = 'ancestor:ref'
88 88 with pytest.raises(JSONRPCError) as excinfo:
89 89 utils.resolve_ref_or_error(ref, repo)
90 90 expected_message = 'The specified ancestor `ref` does not exist'
91 91 assert excinfo.value.message == expected_message
92 92
93 93 def test_branch_is_not_found(self):
94 94 repo = Mock()
95 95 ref = 'branch:non-existing-one'
96 96 with patch('rhodecode.api.utils._get_ref_hash')\
97 97 as _get_ref_hash:
98 98 _get_ref_hash.side_effect = KeyError()
99 99 with pytest.raises(JSONRPCError) as excinfo:
100 100 utils.resolve_ref_or_error(ref, repo)
101 101 expected_message = (
102 102 'The specified branch `non-existing-one` does not exist')
103 103 assert excinfo.value.message == expected_message
104 104
105 105 def test_bookmark_is_not_found(self):
106 106 repo = Mock()
107 107 ref = 'bookmark:non-existing-one'
108 108 with patch('rhodecode.api.utils._get_ref_hash')\
109 109 as _get_ref_hash:
110 110 _get_ref_hash.side_effect = KeyError()
111 111 with pytest.raises(JSONRPCError) as excinfo:
112 112 utils.resolve_ref_or_error(ref, repo)
113 113 expected_message = (
114 114 'The specified bookmark `non-existing-one` does not exist')
115 115 assert excinfo.value.message == expected_message
116 116
117 117 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
118 118 def test_ref_cannot_be_parsed(self, ref):
119 119 repo = Mock()
120 120 with pytest.raises(JSONRPCError) as excinfo:
121 121 utils.resolve_ref_or_error(ref, repo)
122 122 expected_message = (
123 123 'Ref `{ref}` given in a wrong format. Please check the API'
124 124 ' documentation for more details'.format(ref=ref)
125 125 )
126 126 assert excinfo.value.message == expected_message
127 127
128 128
129 129 class TestGetRefHash(object):
130 130 def setup(self):
131 131 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
132 132 self.bookmark_name = 'test-bookmark'
133 133
134 134 @pytest.mark.parametrize("alias, branch_name", [
135 135 ("git", "master"),
136 136 ("hg", "default")
137 137 ])
138 138 def test_returns_hash_by_branch_name(self, alias, branch_name):
139 139 with patch('rhodecode.model.db.Repository') as repo:
140 140 repo.scm_instance().alias = alias
141 141 repo.scm_instance().branches = {branch_name: self.commit_hash}
142 142 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
143 143 assert result_hash == self.commit_hash
144 144
145 145 @pytest.mark.parametrize("alias, branch_name", [
146 146 ("git", "master"),
147 147 ("hg", "default")
148 148 ])
149 149 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
150 150 with patch('rhodecode.model.db.Repository') as repo:
151 151 repo.scm_instance().alias = alias
152 152 repo.scm_instance().branches = {}
153 153 with pytest.raises(KeyError):
154 154 utils._get_ref_hash(repo, 'branch', branch_name)
155 155
156 156 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
157 157 with patch('rhodecode.model.db.Repository') as repo:
158 158 repo.scm_instance().alias = 'hg'
159 159 repo.scm_instance().bookmarks = {
160 160 self.bookmark_name: self.commit_hash}
161 161 result_hash = utils._get_ref_hash(
162 162 repo, 'bookmark', self.bookmark_name)
163 163 assert result_hash == self.commit_hash
164 164
165 165 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
166 166 with patch('rhodecode.model.db.Repository') as repo:
167 167 repo.scm_instance().alias = 'hg'
168 168 repo.scm_instance().bookmarks = {}
169 169 with pytest.raises(KeyError):
170 170 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
171 171
172 172 def test_raises_error_when_bookmark_is_specified_for_git(self):
173 173 with patch('rhodecode.model.db.Repository') as repo:
174 174 repo.scm_instance().alias = 'git'
175 175 repo.scm_instance().bookmarks = {
176 176 self.bookmark_name: self.commit_hash}
177 177 with pytest.raises(ValueError):
178 178 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
179 179
180 180
181 181 class TestUserByNameOrError(object):
182 182 def test_user_found_by_id(self):
183 183 fake_user = Mock(id=123)
184 184
185 185 patcher = patch('rhodecode.model.user.UserModel.get_user')
186 186 with patcher as get_user:
187 187 get_user.return_value = fake_user
188 188
189 189 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
190 190 with patcher as get_by_username:
191 191 result = utils.get_user_or_error(123)
192 192 assert result == fake_user
193 193
194 194 def test_user_not_found_by_id_as_str(self):
195 195 fake_user = Mock(id=123)
196 196
197 197 patcher = patch('rhodecode.model.user.UserModel.get_user')
198 198 with patcher as get_user:
199 199 get_user.return_value = fake_user
200 200 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
201 201 with patcher as get_by_username:
202 202 get_by_username.return_value = None
203 203
204 204 with pytest.raises(JSONRPCError):
205 205 utils.get_user_or_error('123')
206 206
207 207 def test_user_found_by_name(self):
208 208 fake_user = Mock(id=123)
209 209
210 210 patcher = patch('rhodecode.model.user.UserModel.get_user')
211 211 with patcher as get_user:
212 212 get_user.return_value = None
213 213
214 214 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
215 215 with patcher as get_by_username:
216 216 get_by_username.return_value = fake_user
217 217
218 218 result = utils.get_user_or_error('test')
219 219 assert result == fake_user
220 220
221 221 def test_user_not_found_by_id(self):
222 222 patcher = patch('rhodecode.model.user.UserModel.get_user')
223 223 with patcher as get_user:
224 224 get_user.return_value = None
225 225 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
226 226 with patcher as get_by_username:
227 227 get_by_username.return_value = None
228 228
229 229 with pytest.raises(JSONRPCError) as excinfo:
230 230 utils.get_user_or_error(123)
231 231
232 232 expected_message = 'user `123` does not exist'
233 233 assert excinfo.value.message == expected_message
234 234
235 235 def test_user_not_found_by_name(self):
236 236 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
237 237 with patcher as get_by_username:
238 238 get_by_username.return_value = None
239 239 with pytest.raises(JSONRPCError) as excinfo:
240 240 utils.get_user_or_error('test')
241 241
242 242 expected_message = 'user `test` does not exist'
243 243 assert excinfo.value.message == expected_message
244 244
245 245
246 246 class TestGetCommitDict(object):
247 247 @pytest.mark.parametrize('filename, expected', [
248 248 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
249 249 (b'sp\xa4cial', u'sp\ufffdcial'),
250 250 ])
251 251 def test_decodes_filenames_to_unicode(self, filename, expected):
252 252 result = utils._get_commit_dict(filename=filename, op='A')
253 253 assert result['filename'] == expected
254 254
255 255
256 256 class TestRepoAccess(object):
257 257 def setup_method(self, method):
258 258
259 259 self.admin_perm_patch = patch(
260 260 'rhodecode.api.utils.HasPermissionAnyApi')
261 261 self.repo_perm_patch = patch(
262 262 'rhodecode.api.utils.HasRepoPermissionAnyApi')
263 263
264 264 def test_has_superadmin_permission_checks_for_admin(self):
265 265 admin_mock = Mock()
266 266 with self.admin_perm_patch as amock:
267 267 amock.return_value = admin_mock
268 268 assert utils.has_superadmin_permission('fake_user')
269 269 amock.assert_called_once_with('hg.admin')
270 270
271 271 admin_mock.assert_called_once_with(user='fake_user')
272 272
273 273 def test_has_repo_permissions_checks_for_repo_access(self):
274 274 repo_mock = Mock()
275 275 fake_repo = Mock()
276 276 with self.repo_perm_patch as rmock:
277 277 rmock.return_value = repo_mock
278 278 assert utils.validate_repo_permissions(
279 279 'fake_user', 'fake_repo_id', fake_repo,
280 280 ['perm1', 'perm2'])
281 281 rmock.assert_called_once_with(*['perm1', 'perm2'])
282 282
283 283 repo_mock.assert_called_once_with(
284 284 user='fake_user', repo_name=fake_repo.repo_name)
285 285
286 286 def test_has_repo_permissions_raises_not_found(self):
287 287 repo_mock = Mock(return_value=False)
288 288 fake_repo = Mock()
289 289 with self.repo_perm_patch as rmock:
290 290 rmock.return_value = repo_mock
291 291 with pytest.raises(JSONRPCError) as excinfo:
292 292 utils.validate_repo_permissions(
293 293 'fake_user', 'fake_repo_id', fake_repo, 'perms')
294 294 assert 'fake_repo_id' in excinfo
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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
22 22 import random
23 23
24 24 from rhodecode.api.utils import get_origin
25 25 from rhodecode.lib.ext_json import json
26 26
27 27
28 28 API_URL = '/_admin/api'
29 29
30 30
31 31 def assert_ok(id_, expected, given):
32 32 expected = jsonify({
33 33 'id': id_,
34 34 'error': None,
35 35 'result': expected
36 36 })
37 37 given = json.loads(given)
38 38 assert expected == given
39 39
40 40
41 41 def assert_error(id_, expected, given):
42 42 expected = jsonify({
43 43 'id': id_,
44 44 'error': expected,
45 45 'result': None
46 46 })
47 47 given = json.loads(given)
48 48 assert expected == given
49 49
50 50
51 51 def jsonify(obj):
52 52 return json.loads(json.dumps(obj))
53 53
54 54
55 55 def build_data(apikey, method, **kw):
56 56 """
57 57 Builds API data with given random ID
58 58
59 59 :param random_id:
60 60 """
61 61 random_id = random.randrange(1, 9999)
62 62 return random_id, json.dumps({
63 63 "id": random_id,
64 64 "api_key": apikey,
65 65 "method": method,
66 66 "args": kw
67 67 })
68 68
69 69
70 70 def api_call(app, params, status=None):
71 71 response = app.post(
72 72 API_URL, content_type='application/json', params=params, status=status)
73 73 return response
74 74
75 75
76 76 def crash(*args, **kwargs):
77 77 raise Exception('Total Crash !')
78 78
79 79
80 80 def expected_permissions(object_with_permissions):
81 81 """
82 82 Returns the expected permissions structure for the given object.
83 83
84 84 The object is expected to be a `Repository`, `RepositoryGroup`,
85 85 or `UserGroup`. They all implement the same permission handling
86 86 API.
87 87 """
88 88 permissions = []
89 89 for _user in object_with_permissions.permissions():
90 90 user_data = {
91 91 'name': _user.username,
92 92 'permission': _user.permission,
93 93 'origin': get_origin(_user),
94 94 'type': "user",
95 95 }
96 96 permissions.append(user_data)
97 97
98 98 for _user_group in object_with_permissions.permission_user_groups():
99 99 user_group_data = {
100 100 'name': _user_group.users_group_name,
101 101 'permission': _user_group.permission,
102 102 'origin': get_origin(_user_group),
103 103 'type': "user_group",
104 104 }
105 105 permissions.append(user_group_data)
106 106 return permissions
@@ -1,442 +1,442 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2014-2017 RhodeCode GmbH
3 # Copyright (C) 2014-2018 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 """
22 22 JSON RPC utils
23 23 """
24 24
25 25 import collections
26 26 import logging
27 27
28 28 from rhodecode.api.exc import JSONRPCError
29 29 from rhodecode.lib.auth import (
30 30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 31 from rhodecode.lib.utils import safe_unicode
32 32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 33 from rhodecode.controllers.utils import get_commit_from_ref_name
34 34 from rhodecode.lib.utils2 import str2bool
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class OAttr(object):
40 40 """
41 41 Special Option that defines other attribute, and can default to them
42 42
43 43 Example::
44 44
45 45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 46 user = Optional.extract(userid, evaluate_locals=local())
47 47 #if we pass in userid, we get it, else it will default to apiuser
48 48 #attribute
49 49 """
50 50
51 51 def __init__(self, attr_name):
52 52 self.attr_name = attr_name
53 53
54 54 def __repr__(self):
55 55 return '<OptionalAttr:%s>' % self.attr_name
56 56
57 57 def __call__(self):
58 58 return self
59 59
60 60
61 61 class Optional(object):
62 62 """
63 63 Defines an optional parameter::
64 64
65 65 param = param.getval() if isinstance(param, Optional) else param
66 66 param = param() if isinstance(param, Optional) else param
67 67
68 68 is equivalent of::
69 69
70 70 param = Optional.extract(param)
71 71
72 72 """
73 73
74 74 def __init__(self, type_):
75 75 self.type_ = type_
76 76
77 77 def __repr__(self):
78 78 return '<Optional:%s>' % self.type_.__repr__()
79 79
80 80 def __call__(self):
81 81 return self.getval()
82 82
83 83 def getval(self, evaluate_locals=None):
84 84 """
85 85 returns value from this Optional instance
86 86 """
87 87 if isinstance(self.type_, OAttr):
88 88 param_name = self.type_.attr_name
89 89 if evaluate_locals:
90 90 return evaluate_locals[param_name]
91 91 # use params name
92 92 return param_name
93 93 return self.type_
94 94
95 95 @classmethod
96 96 def extract(cls, val, evaluate_locals=None, binary=None):
97 97 """
98 98 Extracts value from Optional() instance
99 99
100 100 :param val:
101 101 :return: original value if it's not Optional instance else
102 102 value of instance
103 103 """
104 104 if isinstance(val, cls):
105 105 val = val.getval(evaluate_locals)
106 106
107 107 if binary:
108 108 val = str2bool(val)
109 109
110 110 return val
111 111
112 112
113 113 def parse_args(cli_args, key_prefix=''):
114 114 from rhodecode.lib.utils2 import (escape_split)
115 115 kwargs = collections.defaultdict(dict)
116 116 for el in escape_split(cli_args, ','):
117 117 kv = escape_split(el, '=', 1)
118 118 if len(kv) == 2:
119 119 k, v = kv
120 120 kwargs[key_prefix + k] = v
121 121 return kwargs
122 122
123 123
124 124 def get_origin(obj):
125 125 """
126 126 Get origin of permission from object.
127 127
128 128 :param obj:
129 129 """
130 130 origin = 'permission'
131 131
132 132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 133 # admin and owner case, maybe we should use dual string ?
134 134 origin = 'owner'
135 135 elif getattr(obj, 'owner_row', ''):
136 136 origin = 'owner'
137 137 elif getattr(obj, 'admin_row', ''):
138 138 origin = 'super-admin'
139 139 return origin
140 140
141 141
142 142 def store_update(updates, attr, name):
143 143 """
144 144 Stores param in updates dict if it's not instance of Optional
145 145 allows easy updates of passed in params
146 146 """
147 147 if not isinstance(attr, Optional):
148 148 updates[name] = attr
149 149
150 150
151 151 def has_superadmin_permission(apiuser):
152 152 """
153 153 Return True if apiuser is admin or return False
154 154
155 155 :param apiuser:
156 156 """
157 157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 158 return True
159 159 return False
160 160
161 161
162 162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 163 """
164 164 Raise JsonRPCError if apiuser is not authorized or return True
165 165
166 166 :param apiuser:
167 167 :param repoid:
168 168 :param repo:
169 169 :param perms:
170 170 """
171 171 if not HasRepoPermissionAnyApi(*perms)(
172 172 user=apiuser, repo_name=repo.repo_name):
173 173 raise JSONRPCError(
174 174 'repository `%s` does not exist' % repoid)
175 175
176 176 return True
177 177
178 178
179 179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 180 """
181 181 Raise JsonRPCError if apiuser is not authorized or return True
182 182
183 183 :param apiuser:
184 184 :param repogroupid: just the id of repository group
185 185 :param repo_group: instance of repo_group
186 186 :param perms:
187 187 """
188 188 if not HasRepoGroupPermissionAnyApi(*perms)(
189 189 user=apiuser, group_name=repo_group.group_name):
190 190 raise JSONRPCError(
191 191 'repository group `%s` does not exist' % repogroupid)
192 192
193 193 return True
194 194
195 195
196 196 def validate_set_owner_permissions(apiuser, owner):
197 197 if isinstance(owner, Optional):
198 198 owner = get_user_or_error(apiuser.user_id)
199 199 else:
200 200 if has_superadmin_permission(apiuser):
201 201 owner = get_user_or_error(owner)
202 202 else:
203 203 # forbid setting owner for non-admins
204 204 raise JSONRPCError(
205 205 'Only RhodeCode super-admin can specify `owner` param')
206 206 return owner
207 207
208 208
209 209 def get_user_or_error(userid):
210 210 """
211 211 Get user by id or name or return JsonRPCError if not found
212 212
213 213 :param userid:
214 214 """
215 215 from rhodecode.model.user import UserModel
216 216 user_model = UserModel()
217 217
218 218 if isinstance(userid, (int, long)):
219 219 try:
220 220 user = user_model.get_user(userid)
221 221 except ValueError:
222 222 user = None
223 223 else:
224 224 user = user_model.get_by_username(userid)
225 225
226 226 if user is None:
227 227 raise JSONRPCError(
228 228 'user `%s` does not exist' % (userid,))
229 229 return user
230 230
231 231
232 232 def get_repo_or_error(repoid):
233 233 """
234 234 Get repo by id or name or return JsonRPCError if not found
235 235
236 236 :param repoid:
237 237 """
238 238 from rhodecode.model.repo import RepoModel
239 239 repo_model = RepoModel()
240 240
241 241 if isinstance(repoid, (int, long)):
242 242 try:
243 243 repo = repo_model.get_repo(repoid)
244 244 except ValueError:
245 245 repo = None
246 246 else:
247 247 repo = repo_model.get_by_repo_name(repoid)
248 248
249 249 if repo is None:
250 250 raise JSONRPCError(
251 251 'repository `%s` does not exist' % (repoid,))
252 252 return repo
253 253
254 254
255 255 def get_repo_group_or_error(repogroupid):
256 256 """
257 257 Get repo group by id or name or return JsonRPCError if not found
258 258
259 259 :param repogroupid:
260 260 """
261 261 from rhodecode.model.repo_group import RepoGroupModel
262 262 repo_group_model = RepoGroupModel()
263 263
264 264 if isinstance(repogroupid, (int, long)):
265 265 try:
266 266 repo_group = repo_group_model._get_repo_group(repogroupid)
267 267 except ValueError:
268 268 repo_group = None
269 269 else:
270 270 repo_group = repo_group_model.get_by_group_name(repogroupid)
271 271
272 272 if repo_group is None:
273 273 raise JSONRPCError(
274 274 'repository group `%s` does not exist' % (repogroupid,))
275 275 return repo_group
276 276
277 277
278 278 def get_user_group_or_error(usergroupid):
279 279 """
280 280 Get user group by id or name or return JsonRPCError if not found
281 281
282 282 :param usergroupid:
283 283 """
284 284 from rhodecode.model.user_group import UserGroupModel
285 285 user_group_model = UserGroupModel()
286 286
287 287 if isinstance(usergroupid, (int, long)):
288 288 try:
289 289 user_group = user_group_model.get_group(usergroupid)
290 290 except ValueError:
291 291 user_group = None
292 292 else:
293 293 user_group = user_group_model.get_by_name(usergroupid)
294 294
295 295 if user_group is None:
296 296 raise JSONRPCError(
297 297 'user group `%s` does not exist' % (usergroupid,))
298 298 return user_group
299 299
300 300
301 301 def get_perm_or_error(permid, prefix=None):
302 302 """
303 303 Get permission by id or name or return JsonRPCError if not found
304 304
305 305 :param permid:
306 306 """
307 307 from rhodecode.model.permission import PermissionModel
308 308
309 309 perm = PermissionModel.cls.get_by_key(permid)
310 310 if perm is None:
311 311 raise JSONRPCError('permission `%s` does not exist' % (permid,))
312 312 if prefix:
313 313 if not perm.permission_name.startswith(prefix):
314 314 raise JSONRPCError('permission `%s` is invalid, '
315 315 'should start with %s' % (permid, prefix))
316 316 return perm
317 317
318 318
319 319 def get_gist_or_error(gistid):
320 320 """
321 321 Get gist by id or gist_access_id or return JsonRPCError if not found
322 322
323 323 :param gistid:
324 324 """
325 325 from rhodecode.model.gist import GistModel
326 326
327 327 gist = GistModel.cls.get_by_access_id(gistid)
328 328 if gist is None:
329 329 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
330 330 return gist
331 331
332 332
333 333 def get_pull_request_or_error(pullrequestid):
334 334 """
335 335 Get pull request by id or return JsonRPCError if not found
336 336
337 337 :param pullrequestid:
338 338 """
339 339 from rhodecode.model.pull_request import PullRequestModel
340 340
341 341 try:
342 342 pull_request = PullRequestModel().get(int(pullrequestid))
343 343 except ValueError:
344 344 raise JSONRPCError('pullrequestid must be an integer')
345 345 if not pull_request:
346 346 raise JSONRPCError('pull request `%s` does not exist' % (
347 347 pullrequestid,))
348 348 return pull_request
349 349
350 350
351 351 def build_commit_data(commit, detail_level):
352 352 parsed_diff = []
353 353 if detail_level == 'extended':
354 354 for f in commit.added:
355 355 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
356 356 for f in commit.changed:
357 357 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
358 358 for f in commit.removed:
359 359 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
360 360
361 361 elif detail_level == 'full':
362 362 from rhodecode.lib.diffs import DiffProcessor
363 363 diff_processor = DiffProcessor(commit.diff())
364 364 for dp in diff_processor.prepare():
365 365 del dp['stats']['ops']
366 366 _stats = dp['stats']
367 367 parsed_diff.append(_get_commit_dict(
368 368 filename=dp['filename'], op=dp['operation'],
369 369 new_revision=dp['new_revision'],
370 370 old_revision=dp['old_revision'],
371 371 raw_diff=dp['raw_diff'], stats=_stats))
372 372
373 373 return parsed_diff
374 374
375 375
376 376 def get_commit_or_error(ref, repo):
377 377 try:
378 378 ref_type, _, ref_hash = ref.split(':')
379 379 except ValueError:
380 380 raise JSONRPCError(
381 381 'Ref `{ref}` given in a wrong format. Please check the API'
382 382 ' documentation for more details'.format(ref=ref))
383 383 try:
384 384 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
385 385 # once get_commit supports ref_types
386 386 return get_commit_from_ref_name(repo, ref_hash)
387 387 except RepositoryError:
388 388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
389 389
390 390
391 391 def resolve_ref_or_error(ref, repo):
392 392 def _parse_ref(type_, name, hash_=None):
393 393 return type_, name, hash_
394 394
395 395 try:
396 396 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
397 397 except TypeError:
398 398 raise JSONRPCError(
399 399 'Ref `{ref}` given in a wrong format. Please check the API'
400 400 ' documentation for more details'.format(ref=ref))
401 401
402 402 try:
403 403 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
404 404 except (KeyError, ValueError):
405 405 raise JSONRPCError(
406 406 'The specified {type} `{name}` does not exist'.format(
407 407 type=ref_type, name=ref_name))
408 408
409 409 return ':'.join([ref_type, ref_name, ref_hash])
410 410
411 411
412 412 def _get_commit_dict(
413 413 filename, op, new_revision=None, old_revision=None,
414 414 raw_diff=None, stats=None):
415 415 if stats is None:
416 416 stats = {
417 417 "added": None,
418 418 "binary": None,
419 419 "deleted": None
420 420 }
421 421 return {
422 422 "filename": safe_unicode(filename),
423 423 "op": op,
424 424
425 425 # extra details
426 426 "new_revision": new_revision,
427 427 "old_revision": old_revision,
428 428
429 429 "raw_diff": raw_diff,
430 430 "stats": stats
431 431 }
432 432
433 433
434 434 # TODO: mikhail: Think about moving this function to some library
435 435 def _get_ref_hash(repo, type_, name):
436 436 vcs_repo = repo.scm_instance()
437 437 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
438 438 return vcs_repo.branches[name]
439 439 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
440 440 return vcs_repo.bookmarks[name]
441 441 else:
442 442 raise ValueError()
@@ -1,19 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2015-2017 RhodeCode GmbH
3 # Copyright (C) 2015-2018 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/
@@ -1,102 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
22 22 """
23 23 NOTE:
24 24 Place for deprecated APIs here, if a call needs to be deprecated, please
25 25 put it here, and point to a new version
26 26 """
27 27 import logging
28 28
29 29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
30 30 from rhodecode.api.utils import Optional, OAttr
31 31
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 # permission check inside
37 37 @jsonrpc_method()
38 38 @jsonrpc_deprecated_method(
39 39 use_method='comment_commit', deprecated_at_version='3.4.0')
40 40 def changeset_comment(request, apiuser, repoid, revision, message,
41 41 userid=Optional(OAttr('apiuser')),
42 42 status=Optional(None)):
43 43 """
44 44 Set a changeset comment, and optionally change the status of the
45 45 changeset.
46 46
47 47 This command can only be run using an |authtoken| with admin
48 48 permissions on the |repo|.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 52 :param repoid: Set the repository name or repository ID.
53 53 :type repoid: str or int
54 54 :param revision: Specify the revision for which to set a comment.
55 55 :type revision: str
56 56 :param message: The comment text.
57 57 :type message: str
58 58 :param userid: Set the user name of the comment creator.
59 59 :type userid: Optional(str or int)
60 60 :param status: Set the comment status. The following are valid options:
61 61 * not_reviewed
62 62 * approved
63 63 * rejected
64 64 * under_review
65 65 :type status: str
66 66
67 67 Example error output:
68 68
69 69 .. code-block:: json
70 70
71 71 {
72 72 "id" : <id_given_in_input>,
73 73 "result" : {
74 74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
75 75 "status_change": null or <status>,
76 76 "success": true
77 77 },
78 78 "error" : null
79 79 }
80 80
81 81 """
82 82 from .repo_api import comment_commit
83 83
84 84 return comment_commit(request=request,
85 85 apiuser=apiuser, repoid=repoid, commit_id=revision,
86 86 message=message, userid=userid, status=status)
87 87
88 88
89 89 @jsonrpc_method()
90 90 @jsonrpc_deprecated_method(
91 91 use_method='get_ip', deprecated_at_version='4.0.0')
92 92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
93 93 from .server_api import get_ip
94 94 return get_ip(request=request, apiuser=apiuser, userid=userid)
95 95
96 96
97 97 @jsonrpc_method()
98 98 @jsonrpc_deprecated_method(
99 99 use_method='get_user_locks', deprecated_at_version='4.0.0')
100 100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
101 101 from .user_api import get_user_locks
102 102 return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file
@@ -1,255 +1,255 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
22 22 import logging
23 23 import time
24 24
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 26 from rhodecode.api.exc import JSONRPCValidationError
27 27 from rhodecode.api.utils import (
28 28 Optional, OAttr, get_gist_or_error, get_user_or_error,
29 29 has_superadmin_permission)
30 30 from rhodecode.model.db import Session, or_
31 31 from rhodecode.model.gist import Gist, GistModel
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 @jsonrpc_method()
37 37 def get_gist(request, apiuser, gistid, content=Optional(False)):
38 38 """
39 39 Get the specified gist, based on the gist ID.
40 40
41 41 :param apiuser: This is filled automatically from the |authtoken|.
42 42 :type apiuser: AuthUser
43 43 :param gistid: Set the id of the private or public gist
44 44 :type gistid: str
45 45 :param content: Return the gist content. Default is false.
46 46 :type content: Optional(bool)
47 47 """
48 48
49 49 gist = get_gist_or_error(gistid)
50 50 content = Optional.extract(content)
51 51 if not has_superadmin_permission(apiuser):
52 52 if gist.gist_owner != apiuser.user_id:
53 53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
54 54 data = gist.get_api_data()
55 55 if content:
56 56 from rhodecode.model.gist import GistModel
57 57 rev, gist_files = GistModel().get_gist_files(gistid)
58 58 data['content'] = dict([(x.path, x.content) for x in gist_files])
59 59 return data
60 60
61 61
62 62 @jsonrpc_method()
63 63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
64 64 """
65 65 Get all gists for given user. If userid is empty returned gists
66 66 are for user who called the api
67 67
68 68 :param apiuser: This is filled automatically from the |authtoken|.
69 69 :type apiuser: AuthUser
70 70 :param userid: user to get gists for
71 71 :type userid: Optional(str or int)
72 72 """
73 73
74 74 if not has_superadmin_permission(apiuser):
75 75 # make sure normal user does not pass someone else userid,
76 76 # he is not allowed to do that
77 77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
78 78 raise JSONRPCError(
79 79 'userid is not the same as your user'
80 80 )
81 81
82 82 if isinstance(userid, Optional):
83 83 user_id = apiuser.user_id
84 84 else:
85 85 user_id = get_user_or_error(userid).user_id
86 86
87 87 gists = []
88 88 _gists = Gist().query() \
89 89 .filter(or_(
90 90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
91 91 .filter(Gist.gist_owner == user_id) \
92 92 .order_by(Gist.created_on.desc())
93 93 for gist in _gists:
94 94 gists.append(gist.get_api_data())
95 95 return gists
96 96
97 97
98 98 @jsonrpc_method()
99 99 def create_gist(
100 100 request, apiuser, files, gistid=Optional(None),
101 101 owner=Optional(OAttr('apiuser')),
102 102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
103 103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
104 104 description=Optional('')):
105 105 """
106 106 Creates a new Gist.
107 107
108 108 :param apiuser: This is filled automatically from the |authtoken|.
109 109 :type apiuser: AuthUser
110 110 :param files: files to be added to the gist. The data structure has
111 111 to match the following example::
112 112
113 113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
114 114
115 115 :type files: dict
116 116 :param gistid: Set a custom id for the gist
117 117 :type gistid: Optional(str)
118 118 :param owner: Set the gist owner, defaults to api method caller
119 119 :type owner: Optional(str or int)
120 120 :param gist_type: type of gist ``public`` or ``private``
121 121 :type gist_type: Optional(str)
122 122 :param lifetime: time in minutes of gist lifetime
123 123 :type lifetime: Optional(int)
124 124 :param acl_level: acl level for this gist, can be
125 125 ``acl_public`` or ``acl_private`` If the value is set to
126 126 ``acl_private`` only logged in users are able to access this gist.
127 127 If not set it defaults to ``acl_public``.
128 128 :type acl_level: Optional(str)
129 129 :param description: gist description
130 130 :type description: Optional(str)
131 131
132 132 Example output:
133 133
134 134 .. code-block:: bash
135 135
136 136 id : <id_given_in_input>
137 137 result : {
138 138 "msg": "created new gist",
139 139 "gist": {}
140 140 }
141 141 error : null
142 142
143 143 Example error output:
144 144
145 145 .. code-block:: bash
146 146
147 147 id : <id_given_in_input>
148 148 result : null
149 149 error : {
150 150 "failed to create gist"
151 151 }
152 152
153 153 """
154 154 from rhodecode.model import validation_schema
155 155 from rhodecode.model.validation_schema.schemas import gist_schema
156 156
157 157 if isinstance(owner, Optional):
158 158 owner = apiuser.user_id
159 159
160 160 owner = get_user_or_error(owner)
161 161
162 162 lifetime = Optional.extract(lifetime)
163 163 schema = gist_schema.GistSchema().bind(
164 164 # bind the given values if it's allowed, however the deferred
165 165 # validator will still validate it according to other rules
166 166 lifetime_options=[lifetime])
167 167
168 168 try:
169 169 nodes = gist_schema.nodes_to_sequence(
170 170 files, colander_node=schema.get('nodes'))
171 171
172 172 schema_data = schema.deserialize(dict(
173 173 gistid=Optional.extract(gistid),
174 174 description=Optional.extract(description),
175 175 gist_type=Optional.extract(gist_type),
176 176 lifetime=lifetime,
177 177 gist_acl_level=Optional.extract(acl_level),
178 178 nodes=nodes
179 179 ))
180 180
181 181 # convert to safer format with just KEYs so we sure no duplicates
182 182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
183 183 schema_data['nodes'], colander_node=schema.get('nodes'))
184 184
185 185 except validation_schema.Invalid as err:
186 186 raise JSONRPCValidationError(colander_exc=err)
187 187
188 188 try:
189 189 gist = GistModel().create(
190 190 owner=owner,
191 191 gist_id=schema_data['gistid'],
192 192 description=schema_data['description'],
193 193 gist_mapping=schema_data['nodes'],
194 194 gist_type=schema_data['gist_type'],
195 195 lifetime=schema_data['lifetime'],
196 196 gist_acl_level=schema_data['gist_acl_level'])
197 197 Session().commit()
198 198 return {
199 199 'msg': 'created new gist',
200 200 'gist': gist.get_api_data()
201 201 }
202 202 except Exception:
203 203 log.exception('Error occurred during creation of gist')
204 204 raise JSONRPCError('failed to create gist')
205 205
206 206
207 207 @jsonrpc_method()
208 208 def delete_gist(request, apiuser, gistid):
209 209 """
210 210 Deletes existing gist
211 211
212 212 :param apiuser: filled automatically from apikey
213 213 :type apiuser: AuthUser
214 214 :param gistid: id of gist to delete
215 215 :type gistid: str
216 216
217 217 Example output:
218 218
219 219 .. code-block:: bash
220 220
221 221 id : <id_given_in_input>
222 222 result : {
223 223 "deleted gist ID: <gist_id>",
224 224 "gist": null
225 225 }
226 226 error : null
227 227
228 228 Example error output:
229 229
230 230 .. code-block:: bash
231 231
232 232 id : <id_given_in_input>
233 233 result : null
234 234 error : {
235 235 "failed to delete gist ID:<gist_id>"
236 236 }
237 237
238 238 """
239 239
240 240 gist = get_gist_or_error(gistid)
241 241 if not has_superadmin_permission(apiuser):
242 242 if gist.gist_owner != apiuser.user_id:
243 243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
244 244
245 245 try:
246 246 GistModel().delete(gist)
247 247 Session().commit()
248 248 return {
249 249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
250 250 'gist': None
251 251 }
252 252 except Exception:
253 253 log.exception('Error occured during gist deletion')
254 254 raise JSONRPCError('failed to delete gist ID:%s'
255 255 % (gist.gist_access_id,)) No newline at end of file
@@ -1,903 +1,903 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
22 22 import logging
23 23
24 24 from rhodecode import events
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 26 from rhodecode.api.utils import (
27 27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 29 validate_repo_permissions, resolve_ref_or_error)
30 30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 31 from rhodecode.lib.base import vcs_operation_context
32 32 from rhodecode.lib.utils2 import str2bool
33 33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 34 from rhodecode.model.comment import CommentsModel
35 35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
36 36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 37 from rhodecode.model.settings import SettingsModel
38 38 from rhodecode.model.validation_schema import Invalid
39 39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 40 ReviewerListSchema)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 @jsonrpc_method()
46 46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
47 47 """
48 48 Get a pull request based on the given ID.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 52 :param repoid: Optional, repository name or repository ID from where
53 53 the pull request was opened.
54 54 :type repoid: str or int
55 55 :param pullrequestid: ID of the requested pull request.
56 56 :type pullrequestid: int
57 57
58 58 Example output:
59 59
60 60 .. code-block:: bash
61 61
62 62 "id": <id_given_in_input>,
63 63 "result":
64 64 {
65 65 "pull_request_id": "<pull_request_id>",
66 66 "url": "<url>",
67 67 "title": "<title>",
68 68 "description": "<description>",
69 69 "status" : "<status>",
70 70 "created_on": "<date_time_created>",
71 71 "updated_on": "<date_time_updated>",
72 72 "commit_ids": [
73 73 ...
74 74 "<commit_id>",
75 75 "<commit_id>",
76 76 ...
77 77 ],
78 78 "review_status": "<review_status>",
79 79 "mergeable": {
80 80 "status": "<bool>",
81 81 "message": "<message>",
82 82 },
83 83 "source": {
84 84 "clone_url": "<clone_url>",
85 85 "repository": "<repository_name>",
86 86 "reference":
87 87 {
88 88 "name": "<name>",
89 89 "type": "<type>",
90 90 "commit_id": "<commit_id>",
91 91 }
92 92 },
93 93 "target": {
94 94 "clone_url": "<clone_url>",
95 95 "repository": "<repository_name>",
96 96 "reference":
97 97 {
98 98 "name": "<name>",
99 99 "type": "<type>",
100 100 "commit_id": "<commit_id>",
101 101 }
102 102 },
103 103 "merge": {
104 104 "clone_url": "<clone_url>",
105 105 "reference":
106 106 {
107 107 "name": "<name>",
108 108 "type": "<type>",
109 109 "commit_id": "<commit_id>",
110 110 }
111 111 },
112 112 "author": <user_obj>,
113 113 "reviewers": [
114 114 ...
115 115 {
116 116 "user": "<user_obj>",
117 117 "review_status": "<review_status>",
118 118 }
119 119 ...
120 120 ]
121 121 },
122 122 "error": null
123 123 """
124 124
125 125 pull_request = get_pull_request_or_error(pullrequestid)
126 126 if Optional.extract(repoid):
127 127 repo = get_repo_or_error(repoid)
128 128 else:
129 129 repo = pull_request.target_repo
130 130
131 131 if not PullRequestModel().check_user_read(
132 132 pull_request, apiuser, api=True):
133 133 raise JSONRPCError('repository `%s` or pull request `%s` '
134 134 'does not exist' % (repoid, pullrequestid))
135 135 data = pull_request.get_api_data()
136 136 return data
137 137
138 138
139 139 @jsonrpc_method()
140 140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
141 141 """
142 142 Get all pull requests from the repository specified in `repoid`.
143 143
144 144 :param apiuser: This is filled automatically from the |authtoken|.
145 145 :type apiuser: AuthUser
146 146 :param repoid: Optional repository name or repository ID.
147 147 :type repoid: str or int
148 148 :param status: Only return pull requests with the specified status.
149 149 Valid options are.
150 150 * ``new`` (default)
151 151 * ``open``
152 152 * ``closed``
153 153 :type status: str
154 154
155 155 Example output:
156 156
157 157 .. code-block:: bash
158 158
159 159 "id": <id_given_in_input>,
160 160 "result":
161 161 [
162 162 ...
163 163 {
164 164 "pull_request_id": "<pull_request_id>",
165 165 "url": "<url>",
166 166 "title" : "<title>",
167 167 "description": "<description>",
168 168 "status": "<status>",
169 169 "created_on": "<date_time_created>",
170 170 "updated_on": "<date_time_updated>",
171 171 "commit_ids": [
172 172 ...
173 173 "<commit_id>",
174 174 "<commit_id>",
175 175 ...
176 176 ],
177 177 "review_status": "<review_status>",
178 178 "mergeable": {
179 179 "status": "<bool>",
180 180 "message: "<message>",
181 181 },
182 182 "source": {
183 183 "clone_url": "<clone_url>",
184 184 "reference":
185 185 {
186 186 "name": "<name>",
187 187 "type": "<type>",
188 188 "commit_id": "<commit_id>",
189 189 }
190 190 },
191 191 "target": {
192 192 "clone_url": "<clone_url>",
193 193 "reference":
194 194 {
195 195 "name": "<name>",
196 196 "type": "<type>",
197 197 "commit_id": "<commit_id>",
198 198 }
199 199 },
200 200 "merge": {
201 201 "clone_url": "<clone_url>",
202 202 "reference":
203 203 {
204 204 "name": "<name>",
205 205 "type": "<type>",
206 206 "commit_id": "<commit_id>",
207 207 }
208 208 },
209 209 "author": <user_obj>,
210 210 "reviewers": [
211 211 ...
212 212 {
213 213 "user": "<user_obj>",
214 214 "review_status": "<review_status>",
215 215 }
216 216 ...
217 217 ]
218 218 }
219 219 ...
220 220 ],
221 221 "error": null
222 222
223 223 """
224 224 repo = get_repo_or_error(repoid)
225 225 if not has_superadmin_permission(apiuser):
226 226 _perms = (
227 227 'repository.admin', 'repository.write', 'repository.read',)
228 228 validate_repo_permissions(apiuser, repoid, repo, _perms)
229 229
230 230 status = Optional.extract(status)
231 231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
232 232 data = [pr.get_api_data() for pr in pull_requests]
233 233 return data
234 234
235 235
236 236 @jsonrpc_method()
237 237 def merge_pull_request(
238 238 request, apiuser, pullrequestid, repoid=Optional(None),
239 239 userid=Optional(OAttr('apiuser'))):
240 240 """
241 241 Merge the pull request specified by `pullrequestid` into its target
242 242 repository.
243 243
244 244 :param apiuser: This is filled automatically from the |authtoken|.
245 245 :type apiuser: AuthUser
246 246 :param repoid: Optional, repository name or repository ID of the
247 247 target repository to which the |pr| is to be merged.
248 248 :type repoid: str or int
249 249 :param pullrequestid: ID of the pull request which shall be merged.
250 250 :type pullrequestid: int
251 251 :param userid: Merge the pull request as this user.
252 252 :type userid: Optional(str or int)
253 253
254 254 Example output:
255 255
256 256 .. code-block:: bash
257 257
258 258 "id": <id_given_in_input>,
259 259 "result": {
260 260 "executed": "<bool>",
261 261 "failure_reason": "<int>",
262 262 "merge_commit_id": "<merge_commit_id>",
263 263 "possible": "<bool>",
264 264 "merge_ref": {
265 265 "commit_id": "<commit_id>",
266 266 "type": "<type>",
267 267 "name": "<name>"
268 268 }
269 269 },
270 270 "error": null
271 271 """
272 272 pull_request = get_pull_request_or_error(pullrequestid)
273 273 if Optional.extract(repoid):
274 274 repo = get_repo_or_error(repoid)
275 275 else:
276 276 repo = pull_request.target_repo
277 277
278 278 if not isinstance(userid, Optional):
279 279 if (has_superadmin_permission(apiuser) or
280 280 HasRepoPermissionAnyApi('repository.admin')(
281 281 user=apiuser, repo_name=repo.repo_name)):
282 282 apiuser = get_user_or_error(userid)
283 283 else:
284 284 raise JSONRPCError('userid is not the same as your user')
285 285
286 286 check = MergeCheck.validate(
287 287 pull_request, user=apiuser, translator=request.translate)
288 288 merge_possible = not check.failed
289 289
290 290 if not merge_possible:
291 291 error_messages = []
292 292 for err_type, error_msg in check.errors:
293 293 error_msg = request.translate(error_msg)
294 294 error_messages.append(error_msg)
295 295
296 296 reasons = ','.join(error_messages)
297 297 raise JSONRPCError(
298 298 'merge not possible for following reasons: {}'.format(reasons))
299 299
300 300 target_repo = pull_request.target_repo
301 301 extras = vcs_operation_context(
302 302 request.environ, repo_name=target_repo.repo_name,
303 303 username=apiuser.username, action='push',
304 304 scm=target_repo.repo_type)
305 305 merge_response = PullRequestModel().merge(
306 306 pull_request, apiuser, extras=extras)
307 307 if merge_response.executed:
308 308 PullRequestModel().close_pull_request(
309 309 pull_request.pull_request_id, apiuser)
310 310
311 311 Session().commit()
312 312
313 313 # In previous versions the merge response directly contained the merge
314 314 # commit id. It is now contained in the merge reference object. To be
315 315 # backwards compatible we have to extract it again.
316 316 merge_response = merge_response._asdict()
317 317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
318 318
319 319 return merge_response
320 320
321 321
322 322 @jsonrpc_method()
323 323 def get_pull_request_comments(
324 324 request, apiuser, pullrequestid, repoid=Optional(None)):
325 325 """
326 326 Get all comments of pull request specified with the `pullrequestid`
327 327
328 328 :param apiuser: This is filled automatically from the |authtoken|.
329 329 :type apiuser: AuthUser
330 330 :param repoid: Optional repository name or repository ID.
331 331 :type repoid: str or int
332 332 :param pullrequestid: The pull request ID.
333 333 :type pullrequestid: int
334 334
335 335 Example output:
336 336
337 337 .. code-block:: bash
338 338
339 339 id : <id_given_in_input>
340 340 result : [
341 341 {
342 342 "comment_author": {
343 343 "active": true,
344 344 "full_name_or_username": "Tom Gore",
345 345 "username": "admin"
346 346 },
347 347 "comment_created_on": "2017-01-02T18:43:45.533",
348 348 "comment_f_path": null,
349 349 "comment_id": 25,
350 350 "comment_lineno": null,
351 351 "comment_status": {
352 352 "status": "under_review",
353 353 "status_lbl": "Under Review"
354 354 },
355 355 "comment_text": "Example text",
356 356 "comment_type": null,
357 357 "pull_request_version": null
358 358 }
359 359 ],
360 360 error : null
361 361 """
362 362
363 363 pull_request = get_pull_request_or_error(pullrequestid)
364 364 if Optional.extract(repoid):
365 365 repo = get_repo_or_error(repoid)
366 366 else:
367 367 repo = pull_request.target_repo
368 368
369 369 if not PullRequestModel().check_user_read(
370 370 pull_request, apiuser, api=True):
371 371 raise JSONRPCError('repository `%s` or pull request `%s` '
372 372 'does not exist' % (repoid, pullrequestid))
373 373
374 374 (pull_request_latest,
375 375 pull_request_at_ver,
376 376 pull_request_display_obj,
377 377 at_version) = PullRequestModel().get_pr_version(
378 378 pull_request.pull_request_id, version=None)
379 379
380 380 versions = pull_request_display_obj.versions()
381 381 ver_map = {
382 382 ver.pull_request_version_id: cnt
383 383 for cnt, ver in enumerate(versions, 1)
384 384 }
385 385
386 386 # GENERAL COMMENTS with versions #
387 387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
388 388 q = q.order_by(ChangesetComment.comment_id.asc())
389 389 general_comments = q.all()
390 390
391 391 # INLINE COMMENTS with versions #
392 392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
393 393 q = q.order_by(ChangesetComment.comment_id.asc())
394 394 inline_comments = q.all()
395 395
396 396 data = []
397 397 for comment in inline_comments + general_comments:
398 398 full_data = comment.get_api_data()
399 399 pr_version_id = None
400 400 if comment.pull_request_version_id:
401 401 pr_version_id = 'v{}'.format(
402 402 ver_map[comment.pull_request_version_id])
403 403
404 404 # sanitize some entries
405 405
406 406 full_data['pull_request_version'] = pr_version_id
407 407 full_data['comment_author'] = {
408 408 'username': full_data['comment_author'].username,
409 409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
410 410 'active': full_data['comment_author'].active,
411 411 }
412 412
413 413 if full_data['comment_status']:
414 414 full_data['comment_status'] = {
415 415 'status': full_data['comment_status'][0].status,
416 416 'status_lbl': full_data['comment_status'][0].status_lbl,
417 417 }
418 418 else:
419 419 full_data['comment_status'] = {}
420 420
421 421 data.append(full_data)
422 422 return data
423 423
424 424
425 425 @jsonrpc_method()
426 426 def comment_pull_request(
427 427 request, apiuser, pullrequestid, repoid=Optional(None),
428 428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
429 429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
430 430 resolves_comment_id=Optional(None),
431 431 userid=Optional(OAttr('apiuser'))):
432 432 """
433 433 Comment on the pull request specified with the `pullrequestid`,
434 434 in the |repo| specified by the `repoid`, and optionally change the
435 435 review status.
436 436
437 437 :param apiuser: This is filled automatically from the |authtoken|.
438 438 :type apiuser: AuthUser
439 439 :param repoid: Optional repository name or repository ID.
440 440 :type repoid: str or int
441 441 :param pullrequestid: The pull request ID.
442 442 :type pullrequestid: int
443 443 :param commit_id: Specify the commit_id for which to set a comment. If
444 444 given commit_id is different than latest in the PR status
445 445 change won't be performed.
446 446 :type commit_id: str
447 447 :param message: The text content of the comment.
448 448 :type message: str
449 449 :param status: (**Optional**) Set the approval status of the pull
450 450 request. One of: 'not_reviewed', 'approved', 'rejected',
451 451 'under_review'
452 452 :type status: str
453 453 :param comment_type: Comment type, one of: 'note', 'todo'
454 454 :type comment_type: Optional(str), default: 'note'
455 455 :param userid: Comment on the pull request as this user
456 456 :type userid: Optional(str or int)
457 457
458 458 Example output:
459 459
460 460 .. code-block:: bash
461 461
462 462 id : <id_given_in_input>
463 463 result : {
464 464 "pull_request_id": "<Integer>",
465 465 "comment_id": "<Integer>",
466 466 "status": {"given": <given_status>,
467 467 "was_changed": <bool status_was_actually_changed> },
468 468 },
469 469 error : null
470 470 """
471 471 pull_request = get_pull_request_or_error(pullrequestid)
472 472 if Optional.extract(repoid):
473 473 repo = get_repo_or_error(repoid)
474 474 else:
475 475 repo = pull_request.target_repo
476 476
477 477 if not isinstance(userid, Optional):
478 478 if (has_superadmin_permission(apiuser) or
479 479 HasRepoPermissionAnyApi('repository.admin')(
480 480 user=apiuser, repo_name=repo.repo_name)):
481 481 apiuser = get_user_or_error(userid)
482 482 else:
483 483 raise JSONRPCError('userid is not the same as your user')
484 484
485 485 if not PullRequestModel().check_user_read(
486 486 pull_request, apiuser, api=True):
487 487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
488 488 message = Optional.extract(message)
489 489 status = Optional.extract(status)
490 490 commit_id = Optional.extract(commit_id)
491 491 comment_type = Optional.extract(comment_type)
492 492 resolves_comment_id = Optional.extract(resolves_comment_id)
493 493
494 494 if not message and not status:
495 495 raise JSONRPCError(
496 496 'Both message and status parameters are missing. '
497 497 'At least one is required.')
498 498
499 499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
500 500 status is not None):
501 501 raise JSONRPCError('Unknown comment status: `%s`' % status)
502 502
503 503 if commit_id and commit_id not in pull_request.revisions:
504 504 raise JSONRPCError(
505 505 'Invalid commit_id `%s` for this pull request.' % commit_id)
506 506
507 507 allowed_to_change_status = PullRequestModel().check_user_change_status(
508 508 pull_request, apiuser)
509 509
510 510 # if commit_id is passed re-validated if user is allowed to change status
511 511 # based on latest commit_id from the PR
512 512 if commit_id:
513 513 commit_idx = pull_request.revisions.index(commit_id)
514 514 if commit_idx != 0:
515 515 allowed_to_change_status = False
516 516
517 517 if resolves_comment_id:
518 518 comment = ChangesetComment.get(resolves_comment_id)
519 519 if not comment:
520 520 raise JSONRPCError(
521 521 'Invalid resolves_comment_id `%s` for this pull request.'
522 522 % resolves_comment_id)
523 523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
524 524 raise JSONRPCError(
525 525 'Comment `%s` is wrong type for setting status to resolved.'
526 526 % resolves_comment_id)
527 527
528 528 text = message
529 529 status_label = ChangesetStatus.get_status_lbl(status)
530 530 if status and allowed_to_change_status:
531 531 st_message = ('Status change %(transition_icon)s %(status)s'
532 532 % {'transition_icon': '>', 'status': status_label})
533 533 text = message or st_message
534 534
535 535 rc_config = SettingsModel().get_all_settings()
536 536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
537 537
538 538 status_change = status and allowed_to_change_status
539 539 comment = CommentsModel().create(
540 540 text=text,
541 541 repo=pull_request.target_repo.repo_id,
542 542 user=apiuser.user_id,
543 543 pull_request=pull_request.pull_request_id,
544 544 f_path=None,
545 545 line_no=None,
546 546 status_change=(status_label if status_change else None),
547 547 status_change_type=(status if status_change else None),
548 548 closing_pr=False,
549 549 renderer=renderer,
550 550 comment_type=comment_type,
551 551 resolves_comment_id=resolves_comment_id
552 552 )
553 553
554 554 if allowed_to_change_status and status:
555 555 ChangesetStatusModel().set_status(
556 556 pull_request.target_repo.repo_id,
557 557 status,
558 558 apiuser.user_id,
559 559 comment,
560 560 pull_request=pull_request.pull_request_id
561 561 )
562 562 Session().flush()
563 563
564 564 Session().commit()
565 565 data = {
566 566 'pull_request_id': pull_request.pull_request_id,
567 567 'comment_id': comment.comment_id if comment else None,
568 568 'status': {'given': status, 'was_changed': status_change},
569 569 }
570 570 return data
571 571
572 572
573 573 @jsonrpc_method()
574 574 def create_pull_request(
575 575 request, apiuser, source_repo, target_repo, source_ref, target_ref,
576 576 title, description=Optional(''), reviewers=Optional(None)):
577 577 """
578 578 Creates a new pull request.
579 579
580 580 Accepts refs in the following formats:
581 581
582 582 * branch:<branch_name>:<sha>
583 583 * branch:<branch_name>
584 584 * bookmark:<bookmark_name>:<sha> (Mercurial only)
585 585 * bookmark:<bookmark_name> (Mercurial only)
586 586
587 587 :param apiuser: This is filled automatically from the |authtoken|.
588 588 :type apiuser: AuthUser
589 589 :param source_repo: Set the source repository name.
590 590 :type source_repo: str
591 591 :param target_repo: Set the target repository name.
592 592 :type target_repo: str
593 593 :param source_ref: Set the source ref name.
594 594 :type source_ref: str
595 595 :param target_ref: Set the target ref name.
596 596 :type target_ref: str
597 597 :param title: Set the pull request title.
598 598 :type title: str
599 599 :param description: Set the pull request description.
600 600 :type description: Optional(str)
601 601 :param reviewers: Set the new pull request reviewers list.
602 602 Reviewer defined by review rules will be added automatically to the
603 603 defined list.
604 604 :type reviewers: Optional(list)
605 605 Accepts username strings or objects of the format:
606 606
607 607 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
608 608 """
609 609
610 610 source_db_repo = get_repo_or_error(source_repo)
611 611 target_db_repo = get_repo_or_error(target_repo)
612 612 if not has_superadmin_permission(apiuser):
613 613 _perms = ('repository.admin', 'repository.write', 'repository.read',)
614 614 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
615 615
616 616 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
617 617 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
618 618 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
619 619 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
620 620 source_scm = source_db_repo.scm_instance()
621 621 target_scm = target_db_repo.scm_instance()
622 622
623 623 commit_ranges = target_scm.compare(
624 624 target_commit.raw_id, source_commit.raw_id, source_scm,
625 625 merge=True, pre_load=[])
626 626
627 627 ancestor = target_scm.get_common_ancestor(
628 628 target_commit.raw_id, source_commit.raw_id, source_scm)
629 629
630 630 if not commit_ranges:
631 631 raise JSONRPCError('no commits found')
632 632
633 633 if not ancestor:
634 634 raise JSONRPCError('no common ancestor found')
635 635
636 636 reviewer_objects = Optional.extract(reviewers) or []
637 637
638 638 if reviewer_objects:
639 639 schema = ReviewerListSchema()
640 640 try:
641 641 reviewer_objects = schema.deserialize(reviewer_objects)
642 642 except Invalid as err:
643 643 raise JSONRPCValidationError(colander_exc=err)
644 644
645 645 # validate users
646 646 for reviewer_object in reviewer_objects:
647 647 user = get_user_or_error(reviewer_object['username'])
648 648 reviewer_object['user_id'] = user.user_id
649 649
650 650 get_default_reviewers_data, get_validated_reviewers = \
651 651 PullRequestModel().get_reviewer_functions()
652 652
653 653 reviewer_rules = get_default_reviewers_data(
654 654 apiuser.get_instance(), source_db_repo,
655 655 source_commit, target_db_repo, target_commit)
656 656
657 657 # specified rules are later re-validated, thus we can assume users will
658 658 # eventually provide those that meet the reviewer criteria.
659 659 if not reviewer_objects:
660 660 reviewer_objects = reviewer_rules['reviewers']
661 661
662 662 try:
663 663 reviewers = get_validated_reviewers(
664 664 reviewer_objects, reviewer_rules)
665 665 except ValueError as e:
666 666 raise JSONRPCError('Reviewers Validation: {}'.format(e))
667 667
668 668 pull_request_model = PullRequestModel()
669 669 pull_request = pull_request_model.create(
670 670 created_by=apiuser.user_id,
671 671 source_repo=source_repo,
672 672 source_ref=full_source_ref,
673 673 target_repo=target_repo,
674 674 target_ref=full_target_ref,
675 675 revisions=reversed(
676 676 [commit.raw_id for commit in reversed(commit_ranges)]),
677 677 reviewers=reviewers,
678 678 title=title,
679 679 description=Optional.extract(description)
680 680 )
681 681
682 682 Session().commit()
683 683 data = {
684 684 'msg': 'Created new pull request `{}`'.format(title),
685 685 'pull_request_id': pull_request.pull_request_id,
686 686 }
687 687 return data
688 688
689 689
690 690 @jsonrpc_method()
691 691 def update_pull_request(
692 692 request, apiuser, pullrequestid, repoid=Optional(None),
693 693 title=Optional(''), description=Optional(''), reviewers=Optional(None),
694 694 update_commits=Optional(None)):
695 695 """
696 696 Updates a pull request.
697 697
698 698 :param apiuser: This is filled automatically from the |authtoken|.
699 699 :type apiuser: AuthUser
700 700 :param repoid: Optional repository name or repository ID.
701 701 :type repoid: str or int
702 702 :param pullrequestid: The pull request ID.
703 703 :type pullrequestid: int
704 704 :param title: Set the pull request title.
705 705 :type title: str
706 706 :param description: Update pull request description.
707 707 :type description: Optional(str)
708 708 :param reviewers: Update pull request reviewers list with new value.
709 709 :type reviewers: Optional(list)
710 710 Accepts username strings or objects of the format:
711 711
712 712 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
713 713
714 714 :param update_commits: Trigger update of commits for this pull request
715 715 :type: update_commits: Optional(bool)
716 716
717 717 Example output:
718 718
719 719 .. code-block:: bash
720 720
721 721 id : <id_given_in_input>
722 722 result : {
723 723 "msg": "Updated pull request `63`",
724 724 "pull_request": <pull_request_object>,
725 725 "updated_reviewers": {
726 726 "added": [
727 727 "username"
728 728 ],
729 729 "removed": []
730 730 },
731 731 "updated_commits": {
732 732 "added": [
733 733 "<sha1_hash>"
734 734 ],
735 735 "common": [
736 736 "<sha1_hash>",
737 737 "<sha1_hash>",
738 738 ],
739 739 "removed": []
740 740 }
741 741 }
742 742 error : null
743 743 """
744 744
745 745 pull_request = get_pull_request_or_error(pullrequestid)
746 746 if Optional.extract(repoid):
747 747 repo = get_repo_or_error(repoid)
748 748 else:
749 749 repo = pull_request.target_repo
750 750
751 751 if not PullRequestModel().check_user_update(
752 752 pull_request, apiuser, api=True):
753 753 raise JSONRPCError(
754 754 'pull request `%s` update failed, no permission to update.' % (
755 755 pullrequestid,))
756 756 if pull_request.is_closed():
757 757 raise JSONRPCError(
758 758 'pull request `%s` update failed, pull request is closed' % (
759 759 pullrequestid,))
760 760
761 761 reviewer_objects = Optional.extract(reviewers) or []
762 762
763 763 if reviewer_objects:
764 764 schema = ReviewerListSchema()
765 765 try:
766 766 reviewer_objects = schema.deserialize(reviewer_objects)
767 767 except Invalid as err:
768 768 raise JSONRPCValidationError(colander_exc=err)
769 769
770 770 # validate users
771 771 for reviewer_object in reviewer_objects:
772 772 user = get_user_or_error(reviewer_object['username'])
773 773 reviewer_object['user_id'] = user.user_id
774 774
775 775 get_default_reviewers_data, get_validated_reviewers = \
776 776 PullRequestModel().get_reviewer_functions()
777 777
778 778 # re-use stored rules
779 779 reviewer_rules = pull_request.reviewer_data
780 780 try:
781 781 reviewers = get_validated_reviewers(
782 782 reviewer_objects, reviewer_rules)
783 783 except ValueError as e:
784 784 raise JSONRPCError('Reviewers Validation: {}'.format(e))
785 785 else:
786 786 reviewers = []
787 787
788 788 title = Optional.extract(title)
789 789 description = Optional.extract(description)
790 790 if title or description:
791 791 PullRequestModel().edit(
792 792 pull_request, title or pull_request.title,
793 793 description or pull_request.description, apiuser)
794 794 Session().commit()
795 795
796 796 commit_changes = {"added": [], "common": [], "removed": []}
797 797 if str2bool(Optional.extract(update_commits)):
798 798 if PullRequestModel().has_valid_update_type(pull_request):
799 799 update_response = PullRequestModel().update_commits(
800 800 pull_request)
801 801 commit_changes = update_response.changes or commit_changes
802 802 Session().commit()
803 803
804 804 reviewers_changes = {"added": [], "removed": []}
805 805 if reviewers:
806 806 added_reviewers, removed_reviewers = \
807 807 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
808 808
809 809 reviewers_changes['added'] = sorted(
810 810 [get_user_or_error(n).username for n in added_reviewers])
811 811 reviewers_changes['removed'] = sorted(
812 812 [get_user_or_error(n).username for n in removed_reviewers])
813 813 Session().commit()
814 814
815 815 data = {
816 816 'msg': 'Updated pull request `{}`'.format(
817 817 pull_request.pull_request_id),
818 818 'pull_request': pull_request.get_api_data(),
819 819 'updated_commits': commit_changes,
820 820 'updated_reviewers': reviewers_changes
821 821 }
822 822
823 823 return data
824 824
825 825
826 826 @jsonrpc_method()
827 827 def close_pull_request(
828 828 request, apiuser, pullrequestid, repoid=Optional(None),
829 829 userid=Optional(OAttr('apiuser')), message=Optional('')):
830 830 """
831 831 Close the pull request specified by `pullrequestid`.
832 832
833 833 :param apiuser: This is filled automatically from the |authtoken|.
834 834 :type apiuser: AuthUser
835 835 :param repoid: Repository name or repository ID to which the pull
836 836 request belongs.
837 837 :type repoid: str or int
838 838 :param pullrequestid: ID of the pull request to be closed.
839 839 :type pullrequestid: int
840 840 :param userid: Close the pull request as this user.
841 841 :type userid: Optional(str or int)
842 842 :param message: Optional message to close the Pull Request with. If not
843 843 specified it will be generated automatically.
844 844 :type message: Optional(str)
845 845
846 846 Example output:
847 847
848 848 .. code-block:: bash
849 849
850 850 "id": <id_given_in_input>,
851 851 "result": {
852 852 "pull_request_id": "<int>",
853 853 "close_status": "<str:status_lbl>,
854 854 "closed": "<bool>"
855 855 },
856 856 "error": null
857 857
858 858 """
859 859 _ = request.translate
860 860
861 861 pull_request = get_pull_request_or_error(pullrequestid)
862 862 if Optional.extract(repoid):
863 863 repo = get_repo_or_error(repoid)
864 864 else:
865 865 repo = pull_request.target_repo
866 866
867 867 if not isinstance(userid, Optional):
868 868 if (has_superadmin_permission(apiuser) or
869 869 HasRepoPermissionAnyApi('repository.admin')(
870 870 user=apiuser, repo_name=repo.repo_name)):
871 871 apiuser = get_user_or_error(userid)
872 872 else:
873 873 raise JSONRPCError('userid is not the same as your user')
874 874
875 875 if pull_request.is_closed():
876 876 raise JSONRPCError(
877 877 'pull request `%s` is already closed' % (pullrequestid,))
878 878
879 879 # only owner or admin or person with write permissions
880 880 allowed_to_close = PullRequestModel().check_user_update(
881 881 pull_request, apiuser, api=True)
882 882
883 883 if not allowed_to_close:
884 884 raise JSONRPCError(
885 885 'pull request `%s` close failed, no permission to close.' % (
886 886 pullrequestid,))
887 887
888 888 # message we're using to close the PR, else it's automatically generated
889 889 message = Optional.extract(message)
890 890
891 891 # finally close the PR, with proper message comment
892 892 comment, status = PullRequestModel().close_pull_request_with_comment(
893 893 pull_request, apiuser, repo, message=message)
894 894 status_lbl = ChangesetStatus.get_status_lbl(status)
895 895
896 896 Session().commit()
897 897
898 898 data = {
899 899 'pull_request_id': pull_request.pull_request_id,
900 900 'close_status': status_lbl,
901 901 'closed': True,
902 902 }
903 903 return data
@@ -1,2042 +1,2042 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.celerylib.utils import get_task_id
36 36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 40 from rhodecode.model.comment import CommentsModel
41 41 from rhodecode.model.db import (
42 42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
43 43 ChangesetComment)
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 47 from rhodecode.model import validation_schema
48 48 from rhodecode.model.validation_schema.schemas import repo_schema
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 @jsonrpc_method()
54 54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
55 55 """
56 56 Gets an existing repository by its name or repository_id.
57 57
58 58 The members section so the output returns users groups or users
59 59 associated with that repository.
60 60
61 61 This command can only be run using an |authtoken| with admin rights,
62 62 or users with at least read rights to the |repo|.
63 63
64 64 :param apiuser: This is filled automatically from the |authtoken|.
65 65 :type apiuser: AuthUser
66 66 :param repoid: The repository name or repository id.
67 67 :type repoid: str or int
68 68 :param cache: use the cached value for last changeset
69 69 :type: cache: Optional(bool)
70 70
71 71 Example output:
72 72
73 73 .. code-block:: bash
74 74
75 75 {
76 76 "error": null,
77 77 "id": <repo_id>,
78 78 "result": {
79 79 "clone_uri": null,
80 80 "created_on": "timestamp",
81 81 "description": "repo description",
82 82 "enable_downloads": false,
83 83 "enable_locking": false,
84 84 "enable_statistics": false,
85 85 "followers": [
86 86 {
87 87 "active": true,
88 88 "admin": false,
89 89 "api_key": "****************************************",
90 90 "api_keys": [
91 91 "****************************************"
92 92 ],
93 93 "email": "user@example.com",
94 94 "emails": [
95 95 "user@example.com"
96 96 ],
97 97 "extern_name": "rhodecode",
98 98 "extern_type": "rhodecode",
99 99 "firstname": "username",
100 100 "ip_addresses": [],
101 101 "language": null,
102 102 "last_login": "2015-09-16T17:16:35.854",
103 103 "lastname": "surname",
104 104 "user_id": <user_id>,
105 105 "username": "name"
106 106 }
107 107 ],
108 108 "fork_of": "parent-repo",
109 109 "landing_rev": [
110 110 "rev",
111 111 "tip"
112 112 ],
113 113 "last_changeset": {
114 114 "author": "User <user@example.com>",
115 115 "branch": "default",
116 116 "date": "timestamp",
117 117 "message": "last commit message",
118 118 "parents": [
119 119 {
120 120 "raw_id": "commit-id"
121 121 }
122 122 ],
123 123 "raw_id": "commit-id",
124 124 "revision": <revision number>,
125 125 "short_id": "short id"
126 126 },
127 127 "lock_reason": null,
128 128 "locked_by": null,
129 129 "locked_date": null,
130 130 "owner": "owner-name",
131 131 "permissions": [
132 132 {
133 133 "name": "super-admin-name",
134 134 "origin": "super-admin",
135 135 "permission": "repository.admin",
136 136 "type": "user"
137 137 },
138 138 {
139 139 "name": "owner-name",
140 140 "origin": "owner",
141 141 "permission": "repository.admin",
142 142 "type": "user"
143 143 },
144 144 {
145 145 "name": "user-group-name",
146 146 "origin": "permission",
147 147 "permission": "repository.write",
148 148 "type": "user_group"
149 149 }
150 150 ],
151 151 "private": true,
152 152 "repo_id": 676,
153 153 "repo_name": "user-group/repo-name",
154 154 "repo_type": "hg"
155 155 }
156 156 }
157 157 """
158 158
159 159 repo = get_repo_or_error(repoid)
160 160 cache = Optional.extract(cache)
161 161
162 162 include_secrets = False
163 163 if has_superadmin_permission(apiuser):
164 164 include_secrets = True
165 165 else:
166 166 # check if we have at least read permission for this repo !
167 167 _perms = (
168 168 'repository.admin', 'repository.write', 'repository.read',)
169 169 validate_repo_permissions(apiuser, repoid, repo, _perms)
170 170
171 171 permissions = []
172 172 for _user in repo.permissions():
173 173 user_data = {
174 174 'name': _user.username,
175 175 'permission': _user.permission,
176 176 'origin': get_origin(_user),
177 177 'type': "user",
178 178 }
179 179 permissions.append(user_data)
180 180
181 181 for _user_group in repo.permission_user_groups():
182 182 user_group_data = {
183 183 'name': _user_group.users_group_name,
184 184 'permission': _user_group.permission,
185 185 'origin': get_origin(_user_group),
186 186 'type': "user_group",
187 187 }
188 188 permissions.append(user_group_data)
189 189
190 190 following_users = [
191 191 user.user.get_api_data(include_secrets=include_secrets)
192 192 for user in repo.followers]
193 193
194 194 if not cache:
195 195 repo.update_commit_cache()
196 196 data = repo.get_api_data(include_secrets=include_secrets)
197 197 data['permissions'] = permissions
198 198 data['followers'] = following_users
199 199 return data
200 200
201 201
202 202 @jsonrpc_method()
203 203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
204 204 """
205 205 Lists all existing repositories.
206 206
207 207 This command can only be run using an |authtoken| with admin rights,
208 208 or users with at least read rights to |repos|.
209 209
210 210 :param apiuser: This is filled automatically from the |authtoken|.
211 211 :type apiuser: AuthUser
212 212 :param root: specify root repository group to fetch repositories.
213 213 filters the returned repositories to be members of given root group.
214 214 :type root: Optional(None)
215 215 :param traverse: traverse given root into subrepositories. With this flag
216 216 set to False, it will only return top-level repositories from `root`.
217 217 if root is empty it will return just top-level repositories.
218 218 :type traverse: Optional(True)
219 219
220 220
221 221 Example output:
222 222
223 223 .. code-block:: bash
224 224
225 225 id : <id_given_in_input>
226 226 result: [
227 227 {
228 228 "repo_id" : "<repo_id>",
229 229 "repo_name" : "<reponame>"
230 230 "repo_type" : "<repo_type>",
231 231 "clone_uri" : "<clone_uri>",
232 232 "private": : "<bool>",
233 233 "created_on" : "<datetimecreated>",
234 234 "description" : "<description>",
235 235 "landing_rev": "<landing_rev>",
236 236 "owner": "<repo_owner>",
237 237 "fork_of": "<name_of_fork_parent>",
238 238 "enable_downloads": "<bool>",
239 239 "enable_locking": "<bool>",
240 240 "enable_statistics": "<bool>",
241 241 },
242 242 ...
243 243 ]
244 244 error: null
245 245 """
246 246
247 247 include_secrets = has_superadmin_permission(apiuser)
248 248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
249 249 extras = {'user': apiuser}
250 250
251 251 root = Optional.extract(root)
252 252 traverse = Optional.extract(traverse, binary=True)
253 253
254 254 if root:
255 255 # verify parent existance, if it's empty return an error
256 256 parent = RepoGroup.get_by_group_name(root)
257 257 if not parent:
258 258 raise JSONRPCError(
259 259 'Root repository group `{}` does not exist'.format(root))
260 260
261 261 if traverse:
262 262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
263 263 else:
264 264 repos = RepoModel().get_repos_for_root(root=parent)
265 265 else:
266 266 if traverse:
267 267 repos = RepoModel().get_all()
268 268 else:
269 269 # return just top-level
270 270 repos = RepoModel().get_repos_for_root(root=None)
271 271
272 272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
273 273 return [repo.get_api_data(include_secrets=include_secrets)
274 274 for repo in repo_list]
275 275
276 276
277 277 @jsonrpc_method()
278 278 def get_repo_changeset(request, apiuser, repoid, revision,
279 279 details=Optional('basic')):
280 280 """
281 281 Returns information about a changeset.
282 282
283 283 Additionally parameters define the amount of details returned by
284 284 this function.
285 285
286 286 This command can only be run using an |authtoken| with admin rights,
287 287 or users with at least read rights to the |repo|.
288 288
289 289 :param apiuser: This is filled automatically from the |authtoken|.
290 290 :type apiuser: AuthUser
291 291 :param repoid: The repository name or repository id
292 292 :type repoid: str or int
293 293 :param revision: revision for which listing should be done
294 294 :type revision: str
295 295 :param details: details can be 'basic|extended|full' full gives diff
296 296 info details like the diff itself, and number of changed files etc.
297 297 :type details: Optional(str)
298 298
299 299 """
300 300 repo = get_repo_or_error(repoid)
301 301 if not has_superadmin_permission(apiuser):
302 302 _perms = (
303 303 'repository.admin', 'repository.write', 'repository.read',)
304 304 validate_repo_permissions(apiuser, repoid, repo, _perms)
305 305
306 306 changes_details = Optional.extract(details)
307 307 _changes_details_types = ['basic', 'extended', 'full']
308 308 if changes_details not in _changes_details_types:
309 309 raise JSONRPCError(
310 310 'ret_type must be one of %s' % (
311 311 ','.join(_changes_details_types)))
312 312
313 313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
314 314 'status', '_commit', '_file_paths']
315 315
316 316 try:
317 317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
318 318 except TypeError as e:
319 319 raise JSONRPCError(e.message)
320 320 _cs_json = cs.__json__()
321 321 _cs_json['diff'] = build_commit_data(cs, changes_details)
322 322 if changes_details == 'full':
323 323 _cs_json['refs'] = cs._get_refs()
324 324 return _cs_json
325 325
326 326
327 327 @jsonrpc_method()
328 328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
329 329 details=Optional('basic')):
330 330 """
331 331 Returns a set of commits limited by the number starting
332 332 from the `start_rev` option.
333 333
334 334 Additional parameters define the amount of details returned by this
335 335 function.
336 336
337 337 This command can only be run using an |authtoken| with admin rights,
338 338 or users with at least read rights to |repos|.
339 339
340 340 :param apiuser: This is filled automatically from the |authtoken|.
341 341 :type apiuser: AuthUser
342 342 :param repoid: The repository name or repository ID.
343 343 :type repoid: str or int
344 344 :param start_rev: The starting revision from where to get changesets.
345 345 :type start_rev: str
346 346 :param limit: Limit the number of commits to this amount
347 347 :type limit: str or int
348 348 :param details: Set the level of detail returned. Valid option are:
349 349 ``basic``, ``extended`` and ``full``.
350 350 :type details: Optional(str)
351 351
352 352 .. note::
353 353
354 354 Setting the parameter `details` to the value ``full`` is extensive
355 355 and returns details like the diff itself, and the number
356 356 of changed files.
357 357
358 358 """
359 359 repo = get_repo_or_error(repoid)
360 360 if not has_superadmin_permission(apiuser):
361 361 _perms = (
362 362 'repository.admin', 'repository.write', 'repository.read',)
363 363 validate_repo_permissions(apiuser, repoid, repo, _perms)
364 364
365 365 changes_details = Optional.extract(details)
366 366 _changes_details_types = ['basic', 'extended', 'full']
367 367 if changes_details not in _changes_details_types:
368 368 raise JSONRPCError(
369 369 'ret_type must be one of %s' % (
370 370 ','.join(_changes_details_types)))
371 371
372 372 limit = int(limit)
373 373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
374 374 'status', '_commit', '_file_paths']
375 375
376 376 vcs_repo = repo.scm_instance()
377 377 # SVN needs a special case to distinguish its index and commit id
378 378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
379 379 start_rev = vcs_repo.commit_ids[0]
380 380
381 381 try:
382 382 commits = vcs_repo.get_commits(
383 383 start_id=start_rev, pre_load=pre_load)
384 384 except TypeError as e:
385 385 raise JSONRPCError(e.message)
386 386 except Exception:
387 387 log.exception('Fetching of commits failed')
388 388 raise JSONRPCError('Error occurred during commit fetching')
389 389
390 390 ret = []
391 391 for cnt, commit in enumerate(commits):
392 392 if cnt >= limit != -1:
393 393 break
394 394 _cs_json = commit.__json__()
395 395 _cs_json['diff'] = build_commit_data(commit, changes_details)
396 396 if changes_details == 'full':
397 397 _cs_json['refs'] = {
398 398 'branches': [commit.branch],
399 399 'bookmarks': getattr(commit, 'bookmarks', []),
400 400 'tags': commit.tags
401 401 }
402 402 ret.append(_cs_json)
403 403 return ret
404 404
405 405
406 406 @jsonrpc_method()
407 407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
408 408 ret_type=Optional('all'), details=Optional('basic'),
409 409 max_file_bytes=Optional(None)):
410 410 """
411 411 Returns a list of nodes and children in a flat list for a given
412 412 path at given revision.
413 413
414 414 It's possible to specify ret_type to show only `files` or `dirs`.
415 415
416 416 This command can only be run using an |authtoken| with admin rights,
417 417 or users with at least read rights to |repos|.
418 418
419 419 :param apiuser: This is filled automatically from the |authtoken|.
420 420 :type apiuser: AuthUser
421 421 :param repoid: The repository name or repository ID.
422 422 :type repoid: str or int
423 423 :param revision: The revision for which listing should be done.
424 424 :type revision: str
425 425 :param root_path: The path from which to start displaying.
426 426 :type root_path: str
427 427 :param ret_type: Set the return type. Valid options are
428 428 ``all`` (default), ``files`` and ``dirs``.
429 429 :type ret_type: Optional(str)
430 430 :param details: Returns extended information about nodes, such as
431 431 md5, binary, and or content. The valid options are ``basic`` and
432 432 ``full``.
433 433 :type details: Optional(str)
434 434 :param max_file_bytes: Only return file content under this file size bytes
435 435 :type details: Optional(int)
436 436
437 437 Example output:
438 438
439 439 .. code-block:: bash
440 440
441 441 id : <id_given_in_input>
442 442 result: [
443 443 {
444 444 "name" : "<name>"
445 445 "type" : "<type>",
446 446 "binary": "<true|false>" (only in extended mode)
447 447 "md5" : "<md5 of file content>" (only in extended mode)
448 448 },
449 449 ...
450 450 ]
451 451 error: null
452 452 """
453 453
454 454 repo = get_repo_or_error(repoid)
455 455 if not has_superadmin_permission(apiuser):
456 456 _perms = (
457 457 'repository.admin', 'repository.write', 'repository.read',)
458 458 validate_repo_permissions(apiuser, repoid, repo, _perms)
459 459
460 460 ret_type = Optional.extract(ret_type)
461 461 details = Optional.extract(details)
462 462 _extended_types = ['basic', 'full']
463 463 if details not in _extended_types:
464 464 raise JSONRPCError(
465 465 'ret_type must be one of %s' % (','.join(_extended_types)))
466 466 extended_info = False
467 467 content = False
468 468 if details == 'basic':
469 469 extended_info = True
470 470
471 471 if details == 'full':
472 472 extended_info = content = True
473 473
474 474 _map = {}
475 475 try:
476 476 # check if repo is not empty by any chance, skip quicker if it is.
477 477 _scm = repo.scm_instance()
478 478 if _scm.is_empty():
479 479 return []
480 480
481 481 _d, _f = ScmModel().get_nodes(
482 482 repo, revision, root_path, flat=False,
483 483 extended_info=extended_info, content=content,
484 484 max_file_bytes=max_file_bytes)
485 485 _map = {
486 486 'all': _d + _f,
487 487 'files': _f,
488 488 'dirs': _d,
489 489 }
490 490 return _map[ret_type]
491 491 except KeyError:
492 492 raise JSONRPCError(
493 493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
494 494 except Exception:
495 495 log.exception("Exception occurred while trying to get repo nodes")
496 496 raise JSONRPCError(
497 497 'failed to get repo: `%s` nodes' % repo.repo_name
498 498 )
499 499
500 500
501 501 @jsonrpc_method()
502 502 def get_repo_refs(request, apiuser, repoid):
503 503 """
504 504 Returns a dictionary of current references. It returns
505 505 bookmarks, branches, closed_branches, and tags for given repository
506 506
507 507 It's possible to specify ret_type to show only `files` or `dirs`.
508 508
509 509 This command can only be run using an |authtoken| with admin rights,
510 510 or users with at least read rights to |repos|.
511 511
512 512 :param apiuser: This is filled automatically from the |authtoken|.
513 513 :type apiuser: AuthUser
514 514 :param repoid: The repository name or repository ID.
515 515 :type repoid: str or int
516 516
517 517 Example output:
518 518
519 519 .. code-block:: bash
520 520
521 521 id : <id_given_in_input>
522 522 "result": {
523 523 "bookmarks": {
524 524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
525 525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
526 526 },
527 527 "branches": {
528 528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
529 529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
530 530 },
531 531 "branches_closed": {},
532 532 "tags": {
533 533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
534 534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
535 535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
536 536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
537 537 }
538 538 }
539 539 error: null
540 540 """
541 541
542 542 repo = get_repo_or_error(repoid)
543 543 if not has_superadmin_permission(apiuser):
544 544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
545 545 validate_repo_permissions(apiuser, repoid, repo, _perms)
546 546
547 547 try:
548 548 # check if repo is not empty by any chance, skip quicker if it is.
549 549 vcs_instance = repo.scm_instance()
550 550 refs = vcs_instance.refs()
551 551 return refs
552 552 except Exception:
553 553 log.exception("Exception occurred while trying to get repo refs")
554 554 raise JSONRPCError(
555 555 'failed to get repo: `%s` references' % repo.repo_name
556 556 )
557 557
558 558
559 559 @jsonrpc_method()
560 560 def create_repo(
561 561 request, apiuser, repo_name, repo_type,
562 562 owner=Optional(OAttr('apiuser')),
563 563 description=Optional(''),
564 564 private=Optional(False),
565 565 clone_uri=Optional(None),
566 566 landing_rev=Optional('rev:tip'),
567 567 enable_statistics=Optional(False),
568 568 enable_locking=Optional(False),
569 569 enable_downloads=Optional(False),
570 570 copy_permissions=Optional(False)):
571 571 """
572 572 Creates a repository.
573 573
574 574 * If the repository name contains "/", repository will be created inside
575 575 a repository group or nested repository groups
576 576
577 577 For example "foo/bar/repo1" will create |repo| called "repo1" inside
578 578 group "foo/bar". You have to have permissions to access and write to
579 579 the last repository group ("bar" in this example)
580 580
581 581 This command can only be run using an |authtoken| with at least
582 582 permissions to create repositories, or write permissions to
583 583 parent repository groups.
584 584
585 585 :param apiuser: This is filled automatically from the |authtoken|.
586 586 :type apiuser: AuthUser
587 587 :param repo_name: Set the repository name.
588 588 :type repo_name: str
589 589 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
590 590 :type repo_type: str
591 591 :param owner: user_id or username
592 592 :type owner: Optional(str)
593 593 :param description: Set the repository description.
594 594 :type description: Optional(str)
595 595 :param private: set repository as private
596 596 :type private: bool
597 597 :param clone_uri: set clone_uri
598 598 :type clone_uri: str
599 599 :param landing_rev: <rev_type>:<rev>
600 600 :type landing_rev: str
601 601 :param enable_locking:
602 602 :type enable_locking: bool
603 603 :param enable_downloads:
604 604 :type enable_downloads: bool
605 605 :param enable_statistics:
606 606 :type enable_statistics: bool
607 607 :param copy_permissions: Copy permission from group in which the
608 608 repository is being created.
609 609 :type copy_permissions: bool
610 610
611 611
612 612 Example output:
613 613
614 614 .. code-block:: bash
615 615
616 616 id : <id_given_in_input>
617 617 result: {
618 618 "msg": "Created new repository `<reponame>`",
619 619 "success": true,
620 620 "task": "<celery task id or None if done sync>"
621 621 }
622 622 error: null
623 623
624 624
625 625 Example error output:
626 626
627 627 .. code-block:: bash
628 628
629 629 id : <id_given_in_input>
630 630 result : null
631 631 error : {
632 632 'failed to create repository `<repo_name>`'
633 633 }
634 634
635 635 """
636 636
637 637 owner = validate_set_owner_permissions(apiuser, owner)
638 638
639 639 description = Optional.extract(description)
640 640 copy_permissions = Optional.extract(copy_permissions)
641 641 clone_uri = Optional.extract(clone_uri)
642 642 landing_commit_ref = Optional.extract(landing_rev)
643 643
644 644 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
645 645 if isinstance(private, Optional):
646 646 private = defs.get('repo_private') or Optional.extract(private)
647 647 if isinstance(repo_type, Optional):
648 648 repo_type = defs.get('repo_type')
649 649 if isinstance(enable_statistics, Optional):
650 650 enable_statistics = defs.get('repo_enable_statistics')
651 651 if isinstance(enable_locking, Optional):
652 652 enable_locking = defs.get('repo_enable_locking')
653 653 if isinstance(enable_downloads, Optional):
654 654 enable_downloads = defs.get('repo_enable_downloads')
655 655
656 656 schema = repo_schema.RepoSchema().bind(
657 657 repo_type_options=rhodecode.BACKENDS.keys(),
658 658 # user caller
659 659 user=apiuser)
660 660
661 661 try:
662 662 schema_data = schema.deserialize(dict(
663 663 repo_name=repo_name,
664 664 repo_type=repo_type,
665 665 repo_owner=owner.username,
666 666 repo_description=description,
667 667 repo_landing_commit_ref=landing_commit_ref,
668 668 repo_clone_uri=clone_uri,
669 669 repo_private=private,
670 670 repo_copy_permissions=copy_permissions,
671 671 repo_enable_statistics=enable_statistics,
672 672 repo_enable_downloads=enable_downloads,
673 673 repo_enable_locking=enable_locking))
674 674 except validation_schema.Invalid as err:
675 675 raise JSONRPCValidationError(colander_exc=err)
676 676
677 677 try:
678 678 data = {
679 679 'owner': owner,
680 680 'repo_name': schema_data['repo_group']['repo_name_without_group'],
681 681 'repo_name_full': schema_data['repo_name'],
682 682 'repo_group': schema_data['repo_group']['repo_group_id'],
683 683 'repo_type': schema_data['repo_type'],
684 684 'repo_description': schema_data['repo_description'],
685 685 'repo_private': schema_data['repo_private'],
686 686 'clone_uri': schema_data['repo_clone_uri'],
687 687 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
688 688 'enable_statistics': schema_data['repo_enable_statistics'],
689 689 'enable_locking': schema_data['repo_enable_locking'],
690 690 'enable_downloads': schema_data['repo_enable_downloads'],
691 691 'repo_copy_permissions': schema_data['repo_copy_permissions'],
692 692 }
693 693
694 694 task = RepoModel().create(form_data=data, cur_user=owner)
695 695 task_id = get_task_id(task)
696 696 # no commit, it's done in RepoModel, or async via celery
697 697 return {
698 698 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
699 699 'success': True, # cannot return the repo data here since fork
700 700 # can be done async
701 701 'task': task_id
702 702 }
703 703 except Exception:
704 704 log.exception(
705 705 u"Exception while trying to create the repository %s",
706 706 schema_data['repo_name'])
707 707 raise JSONRPCError(
708 708 'failed to create repository `%s`' % (schema_data['repo_name'],))
709 709
710 710
711 711 @jsonrpc_method()
712 712 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
713 713 description=Optional('')):
714 714 """
715 715 Adds an extra field to a repository.
716 716
717 717 This command can only be run using an |authtoken| with at least
718 718 write permissions to the |repo|.
719 719
720 720 :param apiuser: This is filled automatically from the |authtoken|.
721 721 :type apiuser: AuthUser
722 722 :param repoid: Set the repository name or repository id.
723 723 :type repoid: str or int
724 724 :param key: Create a unique field key for this repository.
725 725 :type key: str
726 726 :param label:
727 727 :type label: Optional(str)
728 728 :param description:
729 729 :type description: Optional(str)
730 730 """
731 731 repo = get_repo_or_error(repoid)
732 732 if not has_superadmin_permission(apiuser):
733 733 _perms = ('repository.admin',)
734 734 validate_repo_permissions(apiuser, repoid, repo, _perms)
735 735
736 736 label = Optional.extract(label) or key
737 737 description = Optional.extract(description)
738 738
739 739 field = RepositoryField.get_by_key_name(key, repo)
740 740 if field:
741 741 raise JSONRPCError('Field with key '
742 742 '`%s` exists for repo `%s`' % (key, repoid))
743 743
744 744 try:
745 745 RepoModel().add_repo_field(repo, key, field_label=label,
746 746 field_desc=description)
747 747 Session().commit()
748 748 return {
749 749 'msg': "Added new repository field `%s`" % (key,),
750 750 'success': True,
751 751 }
752 752 except Exception:
753 753 log.exception("Exception occurred while trying to add field to repo")
754 754 raise JSONRPCError(
755 755 'failed to create new field for repository `%s`' % (repoid,))
756 756
757 757
758 758 @jsonrpc_method()
759 759 def remove_field_from_repo(request, apiuser, repoid, key):
760 760 """
761 761 Removes an extra field from a repository.
762 762
763 763 This command can only be run using an |authtoken| with at least
764 764 write permissions to the |repo|.
765 765
766 766 :param apiuser: This is filled automatically from the |authtoken|.
767 767 :type apiuser: AuthUser
768 768 :param repoid: Set the repository name or repository ID.
769 769 :type repoid: str or int
770 770 :param key: Set the unique field key for this repository.
771 771 :type key: str
772 772 """
773 773
774 774 repo = get_repo_or_error(repoid)
775 775 if not has_superadmin_permission(apiuser):
776 776 _perms = ('repository.admin',)
777 777 validate_repo_permissions(apiuser, repoid, repo, _perms)
778 778
779 779 field = RepositoryField.get_by_key_name(key, repo)
780 780 if not field:
781 781 raise JSONRPCError('Field with key `%s` does not '
782 782 'exists for repo `%s`' % (key, repoid))
783 783
784 784 try:
785 785 RepoModel().delete_repo_field(repo, field_key=key)
786 786 Session().commit()
787 787 return {
788 788 'msg': "Deleted repository field `%s`" % (key,),
789 789 'success': True,
790 790 }
791 791 except Exception:
792 792 log.exception(
793 793 "Exception occurred while trying to delete field from repo")
794 794 raise JSONRPCError(
795 795 'failed to delete field for repository `%s`' % (repoid,))
796 796
797 797
798 798 @jsonrpc_method()
799 799 def update_repo(
800 800 request, apiuser, repoid, repo_name=Optional(None),
801 801 owner=Optional(OAttr('apiuser')), description=Optional(''),
802 802 private=Optional(False), clone_uri=Optional(None),
803 803 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
804 804 enable_statistics=Optional(False),
805 805 enable_locking=Optional(False),
806 806 enable_downloads=Optional(False), fields=Optional('')):
807 807 """
808 808 Updates a repository with the given information.
809 809
810 810 This command can only be run using an |authtoken| with at least
811 811 admin permissions to the |repo|.
812 812
813 813 * If the repository name contains "/", repository will be updated
814 814 accordingly with a repository group or nested repository groups
815 815
816 816 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
817 817 called "repo-test" and place it inside group "foo/bar".
818 818 You have to have permissions to access and write to the last repository
819 819 group ("bar" in this example)
820 820
821 821 :param apiuser: This is filled automatically from the |authtoken|.
822 822 :type apiuser: AuthUser
823 823 :param repoid: repository name or repository ID.
824 824 :type repoid: str or int
825 825 :param repo_name: Update the |repo| name, including the
826 826 repository group it's in.
827 827 :type repo_name: str
828 828 :param owner: Set the |repo| owner.
829 829 :type owner: str
830 830 :param fork_of: Set the |repo| as fork of another |repo|.
831 831 :type fork_of: str
832 832 :param description: Update the |repo| description.
833 833 :type description: str
834 834 :param private: Set the |repo| as private. (True | False)
835 835 :type private: bool
836 836 :param clone_uri: Update the |repo| clone URI.
837 837 :type clone_uri: str
838 838 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
839 839 :type landing_rev: str
840 840 :param enable_statistics: Enable statistics on the |repo|, (True | False).
841 841 :type enable_statistics: bool
842 842 :param enable_locking: Enable |repo| locking.
843 843 :type enable_locking: bool
844 844 :param enable_downloads: Enable downloads from the |repo|, (True | False).
845 845 :type enable_downloads: bool
846 846 :param fields: Add extra fields to the |repo|. Use the following
847 847 example format: ``field_key=field_val,field_key2=fieldval2``.
848 848 Escape ', ' with \,
849 849 :type fields: str
850 850 """
851 851
852 852 repo = get_repo_or_error(repoid)
853 853
854 854 include_secrets = False
855 855 if not has_superadmin_permission(apiuser):
856 856 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
857 857 else:
858 858 include_secrets = True
859 859
860 860 updates = dict(
861 861 repo_name=repo_name
862 862 if not isinstance(repo_name, Optional) else repo.repo_name,
863 863
864 864 fork_id=fork_of
865 865 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
866 866
867 867 user=owner
868 868 if not isinstance(owner, Optional) else repo.user.username,
869 869
870 870 repo_description=description
871 871 if not isinstance(description, Optional) else repo.description,
872 872
873 873 repo_private=private
874 874 if not isinstance(private, Optional) else repo.private,
875 875
876 876 clone_uri=clone_uri
877 877 if not isinstance(clone_uri, Optional) else repo.clone_uri,
878 878
879 879 repo_landing_rev=landing_rev
880 880 if not isinstance(landing_rev, Optional) else repo._landing_revision,
881 881
882 882 repo_enable_statistics=enable_statistics
883 883 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
884 884
885 885 repo_enable_locking=enable_locking
886 886 if not isinstance(enable_locking, Optional) else repo.enable_locking,
887 887
888 888 repo_enable_downloads=enable_downloads
889 889 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
890 890
891 891 ref_choices, _labels = ScmModel().get_repo_landing_revs(
892 892 request.translate, repo=repo)
893 893
894 894 old_values = repo.get_api_data()
895 895 schema = repo_schema.RepoSchema().bind(
896 896 repo_type_options=rhodecode.BACKENDS.keys(),
897 897 repo_ref_options=ref_choices,
898 898 # user caller
899 899 user=apiuser,
900 900 old_values=old_values)
901 901 try:
902 902 schema_data = schema.deserialize(dict(
903 903 # we save old value, users cannot change type
904 904 repo_type=repo.repo_type,
905 905
906 906 repo_name=updates['repo_name'],
907 907 repo_owner=updates['user'],
908 908 repo_description=updates['repo_description'],
909 909 repo_clone_uri=updates['clone_uri'],
910 910 repo_fork_of=updates['fork_id'],
911 911 repo_private=updates['repo_private'],
912 912 repo_landing_commit_ref=updates['repo_landing_rev'],
913 913 repo_enable_statistics=updates['repo_enable_statistics'],
914 914 repo_enable_downloads=updates['repo_enable_downloads'],
915 915 repo_enable_locking=updates['repo_enable_locking']))
916 916 except validation_schema.Invalid as err:
917 917 raise JSONRPCValidationError(colander_exc=err)
918 918
919 919 # save validated data back into the updates dict
920 920 validated_updates = dict(
921 921 repo_name=schema_data['repo_group']['repo_name_without_group'],
922 922 repo_group=schema_data['repo_group']['repo_group_id'],
923 923
924 924 user=schema_data['repo_owner'],
925 925 repo_description=schema_data['repo_description'],
926 926 repo_private=schema_data['repo_private'],
927 927 clone_uri=schema_data['repo_clone_uri'],
928 928 repo_landing_rev=schema_data['repo_landing_commit_ref'],
929 929 repo_enable_statistics=schema_data['repo_enable_statistics'],
930 930 repo_enable_locking=schema_data['repo_enable_locking'],
931 931 repo_enable_downloads=schema_data['repo_enable_downloads'],
932 932 )
933 933
934 934 if schema_data['repo_fork_of']:
935 935 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
936 936 validated_updates['fork_id'] = fork_repo.repo_id
937 937
938 938 # extra fields
939 939 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
940 940 if fields:
941 941 validated_updates.update(fields)
942 942
943 943 try:
944 944 RepoModel().update(repo, **validated_updates)
945 945 audit_logger.store_api(
946 946 'repo.edit', action_data={'old_data': old_values},
947 947 user=apiuser, repo=repo)
948 948 Session().commit()
949 949 return {
950 950 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
951 951 'repository': repo.get_api_data(include_secrets=include_secrets)
952 952 }
953 953 except Exception:
954 954 log.exception(
955 955 u"Exception while trying to update the repository %s",
956 956 repoid)
957 957 raise JSONRPCError('failed to update repo `%s`' % repoid)
958 958
959 959
960 960 @jsonrpc_method()
961 961 def fork_repo(request, apiuser, repoid, fork_name,
962 962 owner=Optional(OAttr('apiuser')),
963 963 description=Optional(''),
964 964 private=Optional(False),
965 965 clone_uri=Optional(None),
966 966 landing_rev=Optional('rev:tip'),
967 967 copy_permissions=Optional(False)):
968 968 """
969 969 Creates a fork of the specified |repo|.
970 970
971 971 * If the fork_name contains "/", fork will be created inside
972 972 a repository group or nested repository groups
973 973
974 974 For example "foo/bar/fork-repo" will create fork called "fork-repo"
975 975 inside group "foo/bar". You have to have permissions to access and
976 976 write to the last repository group ("bar" in this example)
977 977
978 978 This command can only be run using an |authtoken| with minimum
979 979 read permissions of the forked repo, create fork permissions for an user.
980 980
981 981 :param apiuser: This is filled automatically from the |authtoken|.
982 982 :type apiuser: AuthUser
983 983 :param repoid: Set repository name or repository ID.
984 984 :type repoid: str or int
985 985 :param fork_name: Set the fork name, including it's repository group membership.
986 986 :type fork_name: str
987 987 :param owner: Set the fork owner.
988 988 :type owner: str
989 989 :param description: Set the fork description.
990 990 :type description: str
991 991 :param copy_permissions: Copy permissions from parent |repo|. The
992 992 default is False.
993 993 :type copy_permissions: bool
994 994 :param private: Make the fork private. The default is False.
995 995 :type private: bool
996 996 :param landing_rev: Set the landing revision. The default is tip.
997 997
998 998 Example output:
999 999
1000 1000 .. code-block:: bash
1001 1001
1002 1002 id : <id_for_response>
1003 1003 api_key : "<api_key>"
1004 1004 args: {
1005 1005 "repoid" : "<reponame or repo_id>",
1006 1006 "fork_name": "<forkname>",
1007 1007 "owner": "<username or user_id = Optional(=apiuser)>",
1008 1008 "description": "<description>",
1009 1009 "copy_permissions": "<bool>",
1010 1010 "private": "<bool>",
1011 1011 "landing_rev": "<landing_rev>"
1012 1012 }
1013 1013
1014 1014 Example error output:
1015 1015
1016 1016 .. code-block:: bash
1017 1017
1018 1018 id : <id_given_in_input>
1019 1019 result: {
1020 1020 "msg": "Created fork of `<reponame>` as `<forkname>`",
1021 1021 "success": true,
1022 1022 "task": "<celery task id or None if done sync>"
1023 1023 }
1024 1024 error: null
1025 1025
1026 1026 """
1027 1027
1028 1028 repo = get_repo_or_error(repoid)
1029 1029 repo_name = repo.repo_name
1030 1030
1031 1031 if not has_superadmin_permission(apiuser):
1032 1032 # check if we have at least read permission for
1033 1033 # this repo that we fork !
1034 1034 _perms = (
1035 1035 'repository.admin', 'repository.write', 'repository.read')
1036 1036 validate_repo_permissions(apiuser, repoid, repo, _perms)
1037 1037
1038 1038 # check if the regular user has at least fork permissions as well
1039 1039 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1040 1040 raise JSONRPCForbidden()
1041 1041
1042 1042 # check if user can set owner parameter
1043 1043 owner = validate_set_owner_permissions(apiuser, owner)
1044 1044
1045 1045 description = Optional.extract(description)
1046 1046 copy_permissions = Optional.extract(copy_permissions)
1047 1047 clone_uri = Optional.extract(clone_uri)
1048 1048 landing_commit_ref = Optional.extract(landing_rev)
1049 1049 private = Optional.extract(private)
1050 1050
1051 1051 schema = repo_schema.RepoSchema().bind(
1052 1052 repo_type_options=rhodecode.BACKENDS.keys(),
1053 1053 # user caller
1054 1054 user=apiuser)
1055 1055
1056 1056 try:
1057 1057 schema_data = schema.deserialize(dict(
1058 1058 repo_name=fork_name,
1059 1059 repo_type=repo.repo_type,
1060 1060 repo_owner=owner.username,
1061 1061 repo_description=description,
1062 1062 repo_landing_commit_ref=landing_commit_ref,
1063 1063 repo_clone_uri=clone_uri,
1064 1064 repo_private=private,
1065 1065 repo_copy_permissions=copy_permissions))
1066 1066 except validation_schema.Invalid as err:
1067 1067 raise JSONRPCValidationError(colander_exc=err)
1068 1068
1069 1069 try:
1070 1070 data = {
1071 1071 'fork_parent_id': repo.repo_id,
1072 1072
1073 1073 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1074 1074 'repo_name_full': schema_data['repo_name'],
1075 1075 'repo_group': schema_data['repo_group']['repo_group_id'],
1076 1076 'repo_type': schema_data['repo_type'],
1077 1077 'description': schema_data['repo_description'],
1078 1078 'private': schema_data['repo_private'],
1079 1079 'copy_permissions': schema_data['repo_copy_permissions'],
1080 1080 'landing_rev': schema_data['repo_landing_commit_ref'],
1081 1081 }
1082 1082
1083 1083 task = RepoModel().create_fork(data, cur_user=owner)
1084 1084 # no commit, it's done in RepoModel, or async via celery
1085 1085 task_id = get_task_id(task)
1086 1086
1087 1087 return {
1088 1088 'msg': 'Created fork of `%s` as `%s`' % (
1089 1089 repo.repo_name, schema_data['repo_name']),
1090 1090 'success': True, # cannot return the repo data here since fork
1091 1091 # can be done async
1092 1092 'task': task_id
1093 1093 }
1094 1094 except Exception:
1095 1095 log.exception(
1096 1096 u"Exception while trying to create fork %s",
1097 1097 schema_data['repo_name'])
1098 1098 raise JSONRPCError(
1099 1099 'failed to fork repository `%s` as `%s`' % (
1100 1100 repo_name, schema_data['repo_name']))
1101 1101
1102 1102
1103 1103 @jsonrpc_method()
1104 1104 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1105 1105 """
1106 1106 Deletes a repository.
1107 1107
1108 1108 * When the `forks` parameter is set it's possible to detach or delete
1109 1109 forks of deleted repository.
1110 1110
1111 1111 This command can only be run using an |authtoken| with admin
1112 1112 permissions on the |repo|.
1113 1113
1114 1114 :param apiuser: This is filled automatically from the |authtoken|.
1115 1115 :type apiuser: AuthUser
1116 1116 :param repoid: Set the repository name or repository ID.
1117 1117 :type repoid: str or int
1118 1118 :param forks: Set to `detach` or `delete` forks from the |repo|.
1119 1119 :type forks: Optional(str)
1120 1120
1121 1121 Example error output:
1122 1122
1123 1123 .. code-block:: bash
1124 1124
1125 1125 id : <id_given_in_input>
1126 1126 result: {
1127 1127 "msg": "Deleted repository `<reponame>`",
1128 1128 "success": true
1129 1129 }
1130 1130 error: null
1131 1131 """
1132 1132
1133 1133 repo = get_repo_or_error(repoid)
1134 1134 repo_name = repo.repo_name
1135 1135 if not has_superadmin_permission(apiuser):
1136 1136 _perms = ('repository.admin',)
1137 1137 validate_repo_permissions(apiuser, repoid, repo, _perms)
1138 1138
1139 1139 try:
1140 1140 handle_forks = Optional.extract(forks)
1141 1141 _forks_msg = ''
1142 1142 _forks = [f for f in repo.forks]
1143 1143 if handle_forks == 'detach':
1144 1144 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1145 1145 elif handle_forks == 'delete':
1146 1146 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1147 1147 elif _forks:
1148 1148 raise JSONRPCError(
1149 1149 'Cannot delete `%s` it still contains attached forks' %
1150 1150 (repo.repo_name,)
1151 1151 )
1152 1152 old_data = repo.get_api_data()
1153 1153 RepoModel().delete(repo, forks=forks)
1154 1154
1155 1155 repo = audit_logger.RepoWrap(repo_id=None,
1156 1156 repo_name=repo.repo_name)
1157 1157
1158 1158 audit_logger.store_api(
1159 1159 'repo.delete', action_data={'old_data': old_data},
1160 1160 user=apiuser, repo=repo)
1161 1161
1162 1162 ScmModel().mark_for_invalidation(repo_name, delete=True)
1163 1163 Session().commit()
1164 1164 return {
1165 1165 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1166 1166 'success': True
1167 1167 }
1168 1168 except Exception:
1169 1169 log.exception("Exception occurred while trying to delete repo")
1170 1170 raise JSONRPCError(
1171 1171 'failed to delete repository `%s`' % (repo_name,)
1172 1172 )
1173 1173
1174 1174
1175 1175 #TODO: marcink, change name ?
1176 1176 @jsonrpc_method()
1177 1177 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1178 1178 """
1179 1179 Invalidates the cache for the specified repository.
1180 1180
1181 1181 This command can only be run using an |authtoken| with admin rights to
1182 1182 the specified repository.
1183 1183
1184 1184 This command takes the following options:
1185 1185
1186 1186 :param apiuser: This is filled automatically from |authtoken|.
1187 1187 :type apiuser: AuthUser
1188 1188 :param repoid: Sets the repository name or repository ID.
1189 1189 :type repoid: str or int
1190 1190 :param delete_keys: This deletes the invalidated keys instead of
1191 1191 just flagging them.
1192 1192 :type delete_keys: Optional(``True`` | ``False``)
1193 1193
1194 1194 Example output:
1195 1195
1196 1196 .. code-block:: bash
1197 1197
1198 1198 id : <id_given_in_input>
1199 1199 result : {
1200 1200 'msg': Cache for repository `<repository name>` was invalidated,
1201 1201 'repository': <repository name>
1202 1202 }
1203 1203 error : null
1204 1204
1205 1205 Example error output:
1206 1206
1207 1207 .. code-block:: bash
1208 1208
1209 1209 id : <id_given_in_input>
1210 1210 result : null
1211 1211 error : {
1212 1212 'Error occurred during cache invalidation action'
1213 1213 }
1214 1214
1215 1215 """
1216 1216
1217 1217 repo = get_repo_or_error(repoid)
1218 1218 if not has_superadmin_permission(apiuser):
1219 1219 _perms = ('repository.admin', 'repository.write',)
1220 1220 validate_repo_permissions(apiuser, repoid, repo, _perms)
1221 1221
1222 1222 delete = Optional.extract(delete_keys)
1223 1223 try:
1224 1224 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1225 1225 return {
1226 1226 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1227 1227 'repository': repo.repo_name
1228 1228 }
1229 1229 except Exception:
1230 1230 log.exception(
1231 1231 "Exception occurred while trying to invalidate repo cache")
1232 1232 raise JSONRPCError(
1233 1233 'Error occurred during cache invalidation action'
1234 1234 )
1235 1235
1236 1236
1237 1237 #TODO: marcink, change name ?
1238 1238 @jsonrpc_method()
1239 1239 def lock(request, apiuser, repoid, locked=Optional(None),
1240 1240 userid=Optional(OAttr('apiuser'))):
1241 1241 """
1242 1242 Sets the lock state of the specified |repo| by the given user.
1243 1243 From more information, see :ref:`repo-locking`.
1244 1244
1245 1245 * If the ``userid`` option is not set, the repository is locked to the
1246 1246 user who called the method.
1247 1247 * If the ``locked`` parameter is not set, the current lock state of the
1248 1248 repository is displayed.
1249 1249
1250 1250 This command can only be run using an |authtoken| with admin rights to
1251 1251 the specified repository.
1252 1252
1253 1253 This command takes the following options:
1254 1254
1255 1255 :param apiuser: This is filled automatically from the |authtoken|.
1256 1256 :type apiuser: AuthUser
1257 1257 :param repoid: Sets the repository name or repository ID.
1258 1258 :type repoid: str or int
1259 1259 :param locked: Sets the lock state.
1260 1260 :type locked: Optional(``True`` | ``False``)
1261 1261 :param userid: Set the repository lock to this user.
1262 1262 :type userid: Optional(str or int)
1263 1263
1264 1264 Example error output:
1265 1265
1266 1266 .. code-block:: bash
1267 1267
1268 1268 id : <id_given_in_input>
1269 1269 result : {
1270 1270 'repo': '<reponame>',
1271 1271 'locked': <bool: lock state>,
1272 1272 'locked_since': <int: lock timestamp>,
1273 1273 'locked_by': <username of person who made the lock>,
1274 1274 'lock_reason': <str: reason for locking>,
1275 1275 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1276 1276 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1277 1277 or
1278 1278 'msg': 'Repo `<repository name>` not locked.'
1279 1279 or
1280 1280 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1281 1281 }
1282 1282 error : null
1283 1283
1284 1284 Example error output:
1285 1285
1286 1286 .. code-block:: bash
1287 1287
1288 1288 id : <id_given_in_input>
1289 1289 result : null
1290 1290 error : {
1291 1291 'Error occurred locking repository `<reponame>`'
1292 1292 }
1293 1293 """
1294 1294
1295 1295 repo = get_repo_or_error(repoid)
1296 1296 if not has_superadmin_permission(apiuser):
1297 1297 # check if we have at least write permission for this repo !
1298 1298 _perms = ('repository.admin', 'repository.write',)
1299 1299 validate_repo_permissions(apiuser, repoid, repo, _perms)
1300 1300
1301 1301 # make sure normal user does not pass someone else userid,
1302 1302 # he is not allowed to do that
1303 1303 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1304 1304 raise JSONRPCError('userid is not the same as your user')
1305 1305
1306 1306 if isinstance(userid, Optional):
1307 1307 userid = apiuser.user_id
1308 1308
1309 1309 user = get_user_or_error(userid)
1310 1310
1311 1311 if isinstance(locked, Optional):
1312 1312 lockobj = repo.locked
1313 1313
1314 1314 if lockobj[0] is None:
1315 1315 _d = {
1316 1316 'repo': repo.repo_name,
1317 1317 'locked': False,
1318 1318 'locked_since': None,
1319 1319 'locked_by': None,
1320 1320 'lock_reason': None,
1321 1321 'lock_state_changed': False,
1322 1322 'msg': 'Repo `%s` not locked.' % repo.repo_name
1323 1323 }
1324 1324 return _d
1325 1325 else:
1326 1326 _user_id, _time, _reason = lockobj
1327 1327 lock_user = get_user_or_error(userid)
1328 1328 _d = {
1329 1329 'repo': repo.repo_name,
1330 1330 'locked': True,
1331 1331 'locked_since': _time,
1332 1332 'locked_by': lock_user.username,
1333 1333 'lock_reason': _reason,
1334 1334 'lock_state_changed': False,
1335 1335 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1336 1336 % (repo.repo_name, lock_user.username,
1337 1337 json.dumps(time_to_datetime(_time))))
1338 1338 }
1339 1339 return _d
1340 1340
1341 1341 # force locked state through a flag
1342 1342 else:
1343 1343 locked = str2bool(locked)
1344 1344 lock_reason = Repository.LOCK_API
1345 1345 try:
1346 1346 if locked:
1347 1347 lock_time = time.time()
1348 1348 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1349 1349 else:
1350 1350 lock_time = None
1351 1351 Repository.unlock(repo)
1352 1352 _d = {
1353 1353 'repo': repo.repo_name,
1354 1354 'locked': locked,
1355 1355 'locked_since': lock_time,
1356 1356 'locked_by': user.username,
1357 1357 'lock_reason': lock_reason,
1358 1358 'lock_state_changed': True,
1359 1359 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1360 1360 % (user.username, repo.repo_name, locked))
1361 1361 }
1362 1362 return _d
1363 1363 except Exception:
1364 1364 log.exception(
1365 1365 "Exception occurred while trying to lock repository")
1366 1366 raise JSONRPCError(
1367 1367 'Error occurred locking repository `%s`' % repo.repo_name
1368 1368 )
1369 1369
1370 1370
1371 1371 @jsonrpc_method()
1372 1372 def comment_commit(
1373 1373 request, apiuser, repoid, commit_id, message, status=Optional(None),
1374 1374 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1375 1375 resolves_comment_id=Optional(None),
1376 1376 userid=Optional(OAttr('apiuser'))):
1377 1377 """
1378 1378 Set a commit comment, and optionally change the status of the commit.
1379 1379
1380 1380 :param apiuser: This is filled automatically from the |authtoken|.
1381 1381 :type apiuser: AuthUser
1382 1382 :param repoid: Set the repository name or repository ID.
1383 1383 :type repoid: str or int
1384 1384 :param commit_id: Specify the commit_id for which to set a comment.
1385 1385 :type commit_id: str
1386 1386 :param message: The comment text.
1387 1387 :type message: str
1388 1388 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1389 1389 'approved', 'rejected', 'under_review'
1390 1390 :type status: str
1391 1391 :param comment_type: Comment type, one of: 'note', 'todo'
1392 1392 :type comment_type: Optional(str), default: 'note'
1393 1393 :param userid: Set the user name of the comment creator.
1394 1394 :type userid: Optional(str or int)
1395 1395
1396 1396 Example error output:
1397 1397
1398 1398 .. code-block:: bash
1399 1399
1400 1400 {
1401 1401 "id" : <id_given_in_input>,
1402 1402 "result" : {
1403 1403 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1404 1404 "status_change": null or <status>,
1405 1405 "success": true
1406 1406 },
1407 1407 "error" : null
1408 1408 }
1409 1409
1410 1410 """
1411 1411 repo = get_repo_or_error(repoid)
1412 1412 if not has_superadmin_permission(apiuser):
1413 1413 _perms = ('repository.read', 'repository.write', 'repository.admin')
1414 1414 validate_repo_permissions(apiuser, repoid, repo, _perms)
1415 1415
1416 1416 try:
1417 1417 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1418 1418 except Exception as e:
1419 1419 log.exception('Failed to fetch commit')
1420 1420 raise JSONRPCError(e.message)
1421 1421
1422 1422 if isinstance(userid, Optional):
1423 1423 userid = apiuser.user_id
1424 1424
1425 1425 user = get_user_or_error(userid)
1426 1426 status = Optional.extract(status)
1427 1427 comment_type = Optional.extract(comment_type)
1428 1428 resolves_comment_id = Optional.extract(resolves_comment_id)
1429 1429
1430 1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1431 1431 if status and status not in allowed_statuses:
1432 1432 raise JSONRPCError('Bad status, must be on '
1433 1433 'of %s got %s' % (allowed_statuses, status,))
1434 1434
1435 1435 if resolves_comment_id:
1436 1436 comment = ChangesetComment.get(resolves_comment_id)
1437 1437 if not comment:
1438 1438 raise JSONRPCError(
1439 1439 'Invalid resolves_comment_id `%s` for this commit.'
1440 1440 % resolves_comment_id)
1441 1441 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1442 1442 raise JSONRPCError(
1443 1443 'Comment `%s` is wrong type for setting status to resolved.'
1444 1444 % resolves_comment_id)
1445 1445
1446 1446 try:
1447 1447 rc_config = SettingsModel().get_all_settings()
1448 1448 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1449 1449 status_change_label = ChangesetStatus.get_status_lbl(status)
1450 1450 comment = CommentsModel().create(
1451 1451 message, repo, user, commit_id=commit_id,
1452 1452 status_change=status_change_label,
1453 1453 status_change_type=status,
1454 1454 renderer=renderer,
1455 1455 comment_type=comment_type,
1456 1456 resolves_comment_id=resolves_comment_id
1457 1457 )
1458 1458 if status:
1459 1459 # also do a status change
1460 1460 try:
1461 1461 ChangesetStatusModel().set_status(
1462 1462 repo, status, user, comment, revision=commit_id,
1463 1463 dont_allow_on_closed_pull_request=True
1464 1464 )
1465 1465 except StatusChangeOnClosedPullRequestError:
1466 1466 log.exception(
1467 1467 "Exception occurred while trying to change repo commit status")
1468 1468 msg = ('Changing status on a changeset associated with '
1469 1469 'a closed pull request is not allowed')
1470 1470 raise JSONRPCError(msg)
1471 1471
1472 1472 Session().commit()
1473 1473 return {
1474 1474 'msg': (
1475 1475 'Commented on commit `%s` for repository `%s`' % (
1476 1476 comment.revision, repo.repo_name)),
1477 1477 'status_change': status,
1478 1478 'success': True,
1479 1479 }
1480 1480 except JSONRPCError:
1481 1481 # catch any inside errors, and re-raise them to prevent from
1482 1482 # below global catch to silence them
1483 1483 raise
1484 1484 except Exception:
1485 1485 log.exception("Exception occurred while trying to comment on commit")
1486 1486 raise JSONRPCError(
1487 1487 'failed to set comment on repository `%s`' % (repo.repo_name,)
1488 1488 )
1489 1489
1490 1490
1491 1491 @jsonrpc_method()
1492 1492 def grant_user_permission(request, apiuser, repoid, userid, perm):
1493 1493 """
1494 1494 Grant permissions for the specified user on the given repository,
1495 1495 or update existing permissions if found.
1496 1496
1497 1497 This command can only be run using an |authtoken| with admin
1498 1498 permissions on the |repo|.
1499 1499
1500 1500 :param apiuser: This is filled automatically from the |authtoken|.
1501 1501 :type apiuser: AuthUser
1502 1502 :param repoid: Set the repository name or repository ID.
1503 1503 :type repoid: str or int
1504 1504 :param userid: Set the user name.
1505 1505 :type userid: str
1506 1506 :param perm: Set the user permissions, using the following format
1507 1507 ``(repository.(none|read|write|admin))``
1508 1508 :type perm: str
1509 1509
1510 1510 Example output:
1511 1511
1512 1512 .. code-block:: bash
1513 1513
1514 1514 id : <id_given_in_input>
1515 1515 result: {
1516 1516 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1517 1517 "success": true
1518 1518 }
1519 1519 error: null
1520 1520 """
1521 1521
1522 1522 repo = get_repo_or_error(repoid)
1523 1523 user = get_user_or_error(userid)
1524 1524 perm = get_perm_or_error(perm)
1525 1525 if not has_superadmin_permission(apiuser):
1526 1526 _perms = ('repository.admin',)
1527 1527 validate_repo_permissions(apiuser, repoid, repo, _perms)
1528 1528
1529 1529 try:
1530 1530
1531 1531 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1532 1532
1533 1533 Session().commit()
1534 1534 return {
1535 1535 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1536 1536 perm.permission_name, user.username, repo.repo_name
1537 1537 ),
1538 1538 'success': True
1539 1539 }
1540 1540 except Exception:
1541 1541 log.exception(
1542 1542 "Exception occurred while trying edit permissions for repo")
1543 1543 raise JSONRPCError(
1544 1544 'failed to edit permission for user: `%s` in repo: `%s`' % (
1545 1545 userid, repoid
1546 1546 )
1547 1547 )
1548 1548
1549 1549
1550 1550 @jsonrpc_method()
1551 1551 def revoke_user_permission(request, apiuser, repoid, userid):
1552 1552 """
1553 1553 Revoke permission for a user on the specified repository.
1554 1554
1555 1555 This command can only be run using an |authtoken| with admin
1556 1556 permissions on the |repo|.
1557 1557
1558 1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 1559 :type apiuser: AuthUser
1560 1560 :param repoid: Set the repository name or repository ID.
1561 1561 :type repoid: str or int
1562 1562 :param userid: Set the user name of revoked user.
1563 1563 :type userid: str or int
1564 1564
1565 1565 Example error output:
1566 1566
1567 1567 .. code-block:: bash
1568 1568
1569 1569 id : <id_given_in_input>
1570 1570 result: {
1571 1571 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1572 1572 "success": true
1573 1573 }
1574 1574 error: null
1575 1575 """
1576 1576
1577 1577 repo = get_repo_or_error(repoid)
1578 1578 user = get_user_or_error(userid)
1579 1579 if not has_superadmin_permission(apiuser):
1580 1580 _perms = ('repository.admin',)
1581 1581 validate_repo_permissions(apiuser, repoid, repo, _perms)
1582 1582
1583 1583 try:
1584 1584 RepoModel().revoke_user_permission(repo=repo, user=user)
1585 1585 Session().commit()
1586 1586 return {
1587 1587 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1588 1588 user.username, repo.repo_name
1589 1589 ),
1590 1590 'success': True
1591 1591 }
1592 1592 except Exception:
1593 1593 log.exception(
1594 1594 "Exception occurred while trying revoke permissions to repo")
1595 1595 raise JSONRPCError(
1596 1596 'failed to edit permission for user: `%s` in repo: `%s`' % (
1597 1597 userid, repoid
1598 1598 )
1599 1599 )
1600 1600
1601 1601
1602 1602 @jsonrpc_method()
1603 1603 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1604 1604 """
1605 1605 Grant permission for a user group on the specified repository,
1606 1606 or update existing permissions.
1607 1607
1608 1608 This command can only be run using an |authtoken| with admin
1609 1609 permissions on the |repo|.
1610 1610
1611 1611 :param apiuser: This is filled automatically from the |authtoken|.
1612 1612 :type apiuser: AuthUser
1613 1613 :param repoid: Set the repository name or repository ID.
1614 1614 :type repoid: str or int
1615 1615 :param usergroupid: Specify the ID of the user group.
1616 1616 :type usergroupid: str or int
1617 1617 :param perm: Set the user group permissions using the following
1618 1618 format: (repository.(none|read|write|admin))
1619 1619 :type perm: str
1620 1620
1621 1621 Example output:
1622 1622
1623 1623 .. code-block:: bash
1624 1624
1625 1625 id : <id_given_in_input>
1626 1626 result : {
1627 1627 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1628 1628 "success": true
1629 1629
1630 1630 }
1631 1631 error : null
1632 1632
1633 1633 Example error output:
1634 1634
1635 1635 .. code-block:: bash
1636 1636
1637 1637 id : <id_given_in_input>
1638 1638 result : null
1639 1639 error : {
1640 1640 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1641 1641 }
1642 1642
1643 1643 """
1644 1644
1645 1645 repo = get_repo_or_error(repoid)
1646 1646 perm = get_perm_or_error(perm)
1647 1647 if not has_superadmin_permission(apiuser):
1648 1648 _perms = ('repository.admin',)
1649 1649 validate_repo_permissions(apiuser, repoid, repo, _perms)
1650 1650
1651 1651 user_group = get_user_group_or_error(usergroupid)
1652 1652 if not has_superadmin_permission(apiuser):
1653 1653 # check if we have at least read permission for this user group !
1654 1654 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1655 1655 if not HasUserGroupPermissionAnyApi(*_perms)(
1656 1656 user=apiuser, user_group_name=user_group.users_group_name):
1657 1657 raise JSONRPCError(
1658 1658 'user group `%s` does not exist' % (usergroupid,))
1659 1659
1660 1660 try:
1661 1661 RepoModel().grant_user_group_permission(
1662 1662 repo=repo, group_name=user_group, perm=perm)
1663 1663
1664 1664 Session().commit()
1665 1665 return {
1666 1666 'msg': 'Granted perm: `%s` for user group: `%s` in '
1667 1667 'repo: `%s`' % (
1668 1668 perm.permission_name, user_group.users_group_name,
1669 1669 repo.repo_name
1670 1670 ),
1671 1671 'success': True
1672 1672 }
1673 1673 except Exception:
1674 1674 log.exception(
1675 1675 "Exception occurred while trying change permission on repo")
1676 1676 raise JSONRPCError(
1677 1677 'failed to edit permission for user group: `%s` in '
1678 1678 'repo: `%s`' % (
1679 1679 usergroupid, repo.repo_name
1680 1680 )
1681 1681 )
1682 1682
1683 1683
1684 1684 @jsonrpc_method()
1685 1685 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1686 1686 """
1687 1687 Revoke the permissions of a user group on a given repository.
1688 1688
1689 1689 This command can only be run using an |authtoken| with admin
1690 1690 permissions on the |repo|.
1691 1691
1692 1692 :param apiuser: This is filled automatically from the |authtoken|.
1693 1693 :type apiuser: AuthUser
1694 1694 :param repoid: Set the repository name or repository ID.
1695 1695 :type repoid: str or int
1696 1696 :param usergroupid: Specify the user group ID.
1697 1697 :type usergroupid: str or int
1698 1698
1699 1699 Example output:
1700 1700
1701 1701 .. code-block:: bash
1702 1702
1703 1703 id : <id_given_in_input>
1704 1704 result: {
1705 1705 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1706 1706 "success": true
1707 1707 }
1708 1708 error: null
1709 1709 """
1710 1710
1711 1711 repo = get_repo_or_error(repoid)
1712 1712 if not has_superadmin_permission(apiuser):
1713 1713 _perms = ('repository.admin',)
1714 1714 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715 1715
1716 1716 user_group = get_user_group_or_error(usergroupid)
1717 1717 if not has_superadmin_permission(apiuser):
1718 1718 # check if we have at least read permission for this user group !
1719 1719 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1720 1720 if not HasUserGroupPermissionAnyApi(*_perms)(
1721 1721 user=apiuser, user_group_name=user_group.users_group_name):
1722 1722 raise JSONRPCError(
1723 1723 'user group `%s` does not exist' % (usergroupid,))
1724 1724
1725 1725 try:
1726 1726 RepoModel().revoke_user_group_permission(
1727 1727 repo=repo, group_name=user_group)
1728 1728
1729 1729 Session().commit()
1730 1730 return {
1731 1731 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1732 1732 user_group.users_group_name, repo.repo_name
1733 1733 ),
1734 1734 'success': True
1735 1735 }
1736 1736 except Exception:
1737 1737 log.exception("Exception occurred while trying revoke "
1738 1738 "user group permission on repo")
1739 1739 raise JSONRPCError(
1740 1740 'failed to edit permission for user group: `%s` in '
1741 1741 'repo: `%s`' % (
1742 1742 user_group.users_group_name, repo.repo_name
1743 1743 )
1744 1744 )
1745 1745
1746 1746
1747 1747 @jsonrpc_method()
1748 1748 def pull(request, apiuser, repoid):
1749 1749 """
1750 1750 Triggers a pull on the given repository from a remote location. You
1751 1751 can use this to keep remote repositories up-to-date.
1752 1752
1753 1753 This command can only be run using an |authtoken| with admin
1754 1754 rights to the specified repository. For more information,
1755 1755 see :ref:`config-token-ref`.
1756 1756
1757 1757 This command takes the following options:
1758 1758
1759 1759 :param apiuser: This is filled automatically from the |authtoken|.
1760 1760 :type apiuser: AuthUser
1761 1761 :param repoid: The repository name or repository ID.
1762 1762 :type repoid: str or int
1763 1763
1764 1764 Example output:
1765 1765
1766 1766 .. code-block:: bash
1767 1767
1768 1768 id : <id_given_in_input>
1769 1769 result : {
1770 1770 "msg": "Pulled from `<repository name>`"
1771 1771 "repository": "<repository name>"
1772 1772 }
1773 1773 error : null
1774 1774
1775 1775 Example error output:
1776 1776
1777 1777 .. code-block:: bash
1778 1778
1779 1779 id : <id_given_in_input>
1780 1780 result : null
1781 1781 error : {
1782 1782 "Unable to pull changes from `<reponame>`"
1783 1783 }
1784 1784
1785 1785 """
1786 1786
1787 1787 repo = get_repo_or_error(repoid)
1788 1788 if not has_superadmin_permission(apiuser):
1789 1789 _perms = ('repository.admin',)
1790 1790 validate_repo_permissions(apiuser, repoid, repo, _perms)
1791 1791
1792 1792 try:
1793 1793 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1794 1794 return {
1795 1795 'msg': 'Pulled from `%s`' % repo.repo_name,
1796 1796 'repository': repo.repo_name
1797 1797 }
1798 1798 except Exception:
1799 1799 log.exception("Exception occurred while trying to "
1800 1800 "pull changes from remote location")
1801 1801 raise JSONRPCError(
1802 1802 'Unable to pull changes from `%s`' % repo.repo_name
1803 1803 )
1804 1804
1805 1805
1806 1806 @jsonrpc_method()
1807 1807 def strip(request, apiuser, repoid, revision, branch):
1808 1808 """
1809 1809 Strips the given revision from the specified repository.
1810 1810
1811 1811 * This will remove the revision and all of its decendants.
1812 1812
1813 1813 This command can only be run using an |authtoken| with admin rights to
1814 1814 the specified repository.
1815 1815
1816 1816 This command takes the following options:
1817 1817
1818 1818 :param apiuser: This is filled automatically from the |authtoken|.
1819 1819 :type apiuser: AuthUser
1820 1820 :param repoid: The repository name or repository ID.
1821 1821 :type repoid: str or int
1822 1822 :param revision: The revision you wish to strip.
1823 1823 :type revision: str
1824 1824 :param branch: The branch from which to strip the revision.
1825 1825 :type branch: str
1826 1826
1827 1827 Example output:
1828 1828
1829 1829 .. code-block:: bash
1830 1830
1831 1831 id : <id_given_in_input>
1832 1832 result : {
1833 1833 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1834 1834 "repository": "<repository name>"
1835 1835 }
1836 1836 error : null
1837 1837
1838 1838 Example error output:
1839 1839
1840 1840 .. code-block:: bash
1841 1841
1842 1842 id : <id_given_in_input>
1843 1843 result : null
1844 1844 error : {
1845 1845 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1846 1846 }
1847 1847
1848 1848 """
1849 1849
1850 1850 repo = get_repo_or_error(repoid)
1851 1851 if not has_superadmin_permission(apiuser):
1852 1852 _perms = ('repository.admin',)
1853 1853 validate_repo_permissions(apiuser, repoid, repo, _perms)
1854 1854
1855 1855 try:
1856 1856 ScmModel().strip(repo, revision, branch)
1857 1857 audit_logger.store_api(
1858 1858 'repo.commit.strip', action_data={'commit_id': revision},
1859 1859 repo=repo,
1860 1860 user=apiuser, commit=True)
1861 1861
1862 1862 return {
1863 1863 'msg': 'Stripped commit %s from repo `%s`' % (
1864 1864 revision, repo.repo_name),
1865 1865 'repository': repo.repo_name
1866 1866 }
1867 1867 except Exception:
1868 1868 log.exception("Exception while trying to strip")
1869 1869 raise JSONRPCError(
1870 1870 'Unable to strip commit %s from repo `%s`' % (
1871 1871 revision, repo.repo_name)
1872 1872 )
1873 1873
1874 1874
1875 1875 @jsonrpc_method()
1876 1876 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1877 1877 """
1878 1878 Returns all settings for a repository. If key is given it only returns the
1879 1879 setting identified by the key or null.
1880 1880
1881 1881 :param apiuser: This is filled automatically from the |authtoken|.
1882 1882 :type apiuser: AuthUser
1883 1883 :param repoid: The repository name or repository id.
1884 1884 :type repoid: str or int
1885 1885 :param key: Key of the setting to return.
1886 1886 :type: key: Optional(str)
1887 1887
1888 1888 Example output:
1889 1889
1890 1890 .. code-block:: bash
1891 1891
1892 1892 {
1893 1893 "error": null,
1894 1894 "id": 237,
1895 1895 "result": {
1896 1896 "extensions_largefiles": true,
1897 1897 "extensions_evolve": true,
1898 1898 "hooks_changegroup_push_logger": true,
1899 1899 "hooks_changegroup_repo_size": false,
1900 1900 "hooks_outgoing_pull_logger": true,
1901 1901 "phases_publish": "True",
1902 1902 "rhodecode_hg_use_rebase_for_merging": true,
1903 1903 "rhodecode_pr_merge_enabled": true,
1904 1904 "rhodecode_use_outdated_comments": true
1905 1905 }
1906 1906 }
1907 1907 """
1908 1908
1909 1909 # Restrict access to this api method to admins only.
1910 1910 if not has_superadmin_permission(apiuser):
1911 1911 raise JSONRPCForbidden()
1912 1912
1913 1913 try:
1914 1914 repo = get_repo_or_error(repoid)
1915 1915 settings_model = VcsSettingsModel(repo=repo)
1916 1916 settings = settings_model.get_global_settings()
1917 1917 settings.update(settings_model.get_repo_settings())
1918 1918
1919 1919 # If only a single setting is requested fetch it from all settings.
1920 1920 key = Optional.extract(key)
1921 1921 if key is not None:
1922 1922 settings = settings.get(key, None)
1923 1923 except Exception:
1924 1924 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1925 1925 log.exception(msg)
1926 1926 raise JSONRPCError(msg)
1927 1927
1928 1928 return settings
1929 1929
1930 1930
1931 1931 @jsonrpc_method()
1932 1932 def set_repo_settings(request, apiuser, repoid, settings):
1933 1933 """
1934 1934 Update repository settings. Returns true on success.
1935 1935
1936 1936 :param apiuser: This is filled automatically from the |authtoken|.
1937 1937 :type apiuser: AuthUser
1938 1938 :param repoid: The repository name or repository id.
1939 1939 :type repoid: str or int
1940 1940 :param settings: The new settings for the repository.
1941 1941 :type: settings: dict
1942 1942
1943 1943 Example output:
1944 1944
1945 1945 .. code-block:: bash
1946 1946
1947 1947 {
1948 1948 "error": null,
1949 1949 "id": 237,
1950 1950 "result": true
1951 1951 }
1952 1952 """
1953 1953 # Restrict access to this api method to admins only.
1954 1954 if not has_superadmin_permission(apiuser):
1955 1955 raise JSONRPCForbidden()
1956 1956
1957 1957 if type(settings) is not dict:
1958 1958 raise JSONRPCError('Settings have to be a JSON Object.')
1959 1959
1960 1960 try:
1961 1961 settings_model = VcsSettingsModel(repo=repoid)
1962 1962
1963 1963 # Merge global, repo and incoming settings.
1964 1964 new_settings = settings_model.get_global_settings()
1965 1965 new_settings.update(settings_model.get_repo_settings())
1966 1966 new_settings.update(settings)
1967 1967
1968 1968 # Update the settings.
1969 1969 inherit_global_settings = new_settings.get(
1970 1970 'inherit_global_settings', False)
1971 1971 settings_model.create_or_update_repo_settings(
1972 1972 new_settings, inherit_global_settings=inherit_global_settings)
1973 1973 Session().commit()
1974 1974 except Exception:
1975 1975 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1976 1976 log.exception(msg)
1977 1977 raise JSONRPCError(msg)
1978 1978
1979 1979 # Indicate success.
1980 1980 return True
1981 1981
1982 1982
1983 1983 @jsonrpc_method()
1984 1984 def maintenance(request, apiuser, repoid):
1985 1985 """
1986 1986 Triggers a maintenance on the given repository.
1987 1987
1988 1988 This command can only be run using an |authtoken| with admin
1989 1989 rights to the specified repository. For more information,
1990 1990 see :ref:`config-token-ref`.
1991 1991
1992 1992 This command takes the following options:
1993 1993
1994 1994 :param apiuser: This is filled automatically from the |authtoken|.
1995 1995 :type apiuser: AuthUser
1996 1996 :param repoid: The repository name or repository ID.
1997 1997 :type repoid: str or int
1998 1998
1999 1999 Example output:
2000 2000
2001 2001 .. code-block:: bash
2002 2002
2003 2003 id : <id_given_in_input>
2004 2004 result : {
2005 2005 "msg": "executed maintenance command",
2006 2006 "executed_actions": [
2007 2007 <action_message>, <action_message2>...
2008 2008 ],
2009 2009 "repository": "<repository name>"
2010 2010 }
2011 2011 error : null
2012 2012
2013 2013 Example error output:
2014 2014
2015 2015 .. code-block:: bash
2016 2016
2017 2017 id : <id_given_in_input>
2018 2018 result : null
2019 2019 error : {
2020 2020 "Unable to execute maintenance on `<reponame>`"
2021 2021 }
2022 2022
2023 2023 """
2024 2024
2025 2025 repo = get_repo_or_error(repoid)
2026 2026 if not has_superadmin_permission(apiuser):
2027 2027 _perms = ('repository.admin',)
2028 2028 validate_repo_permissions(apiuser, repoid, repo, _perms)
2029 2029
2030 2030 try:
2031 2031 maintenance = repo_maintenance.RepoMaintenance()
2032 2032 executed_actions = maintenance.execute(repo)
2033 2033
2034 2034 return {
2035 2035 'msg': 'executed maintenance command',
2036 2036 'executed_actions': executed_actions,
2037 2037 'repository': repo.repo_name
2038 2038 }
2039 2039 except Exception:
2040 2040 log.exception("Exception occurred while trying to run maintenance")
2041 2041 raise JSONRPCError(
2042 2042 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,719 +1,719 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
22 22 import logging
23 23
24 24 from rhodecode.api import JSONRPCValidationError
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 26 from rhodecode.api.utils import (
27 27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import (
32 32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
33 33 from rhodecode.model.db import Session
34 34 from rhodecode.model.repo_group import RepoGroupModel
35 35 from rhodecode.model.scm import RepoGroupList
36 36 from rhodecode.model import validation_schema
37 37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 38
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 @jsonrpc_method()
44 44 def get_repo_group(request, apiuser, repogroupid):
45 45 """
46 46 Return the specified |repo| group, along with permissions,
47 47 and repositories inside the group
48 48
49 49 :param apiuser: This is filled automatically from the |authtoken|.
50 50 :type apiuser: AuthUser
51 51 :param repogroupid: Specify the name of ID of the repository group.
52 52 :type repogroupid: str or int
53 53
54 54
55 55 Example output:
56 56
57 57 .. code-block:: bash
58 58
59 59 {
60 60 "error": null,
61 61 "id": repo-group-id,
62 62 "result": {
63 63 "group_description": "repo group description",
64 64 "group_id": 14,
65 65 "group_name": "group name",
66 66 "permissions": [
67 67 {
68 68 "name": "super-admin-username",
69 69 "origin": "super-admin",
70 70 "permission": "group.admin",
71 71 "type": "user"
72 72 },
73 73 {
74 74 "name": "owner-name",
75 75 "origin": "owner",
76 76 "permission": "group.admin",
77 77 "type": "user"
78 78 },
79 79 {
80 80 "name": "user-group-name",
81 81 "origin": "permission",
82 82 "permission": "group.write",
83 83 "type": "user_group"
84 84 }
85 85 ],
86 86 "owner": "owner-name",
87 87 "parent_group": null,
88 88 "repositories": [ repo-list ]
89 89 }
90 90 }
91 91 """
92 92
93 93 repo_group = get_repo_group_or_error(repogroupid)
94 94 if not has_superadmin_permission(apiuser):
95 95 # check if we have at least read permission for this repo group !
96 96 _perms = ('group.admin', 'group.write', 'group.read',)
97 97 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 98 user=apiuser, group_name=repo_group.group_name):
99 99 raise JSONRPCError(
100 100 'repository group `%s` does not exist' % (repogroupid,))
101 101
102 102 permissions = []
103 103 for _user in repo_group.permissions():
104 104 user_data = {
105 105 'name': _user.username,
106 106 'permission': _user.permission,
107 107 'origin': get_origin(_user),
108 108 'type': "user",
109 109 }
110 110 permissions.append(user_data)
111 111
112 112 for _user_group in repo_group.permission_user_groups():
113 113 user_group_data = {
114 114 'name': _user_group.users_group_name,
115 115 'permission': _user_group.permission,
116 116 'origin': get_origin(_user_group),
117 117 'type': "user_group",
118 118 }
119 119 permissions.append(user_group_data)
120 120
121 121 data = repo_group.get_api_data()
122 122 data["permissions"] = permissions
123 123 return data
124 124
125 125
126 126 @jsonrpc_method()
127 127 def get_repo_groups(request, apiuser):
128 128 """
129 129 Returns all repository groups.
130 130
131 131 :param apiuser: This is filled automatically from the |authtoken|.
132 132 :type apiuser: AuthUser
133 133 """
134 134
135 135 result = []
136 136 _perms = ('group.read', 'group.write', 'group.admin',)
137 137 extras = {'user': apiuser}
138 138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 139 perm_set=_perms, extra_kwargs=extras):
140 140 result.append(repo_group.get_api_data())
141 141 return result
142 142
143 143
144 144 @jsonrpc_method()
145 145 def create_repo_group(
146 146 request, apiuser, group_name,
147 147 owner=Optional(OAttr('apiuser')),
148 148 description=Optional(''),
149 149 copy_permissions=Optional(False)):
150 150 """
151 151 Creates a repository group.
152 152
153 153 * If the repository group name contains "/", repository group will be
154 154 created inside a repository group or nested repository groups
155 155
156 156 For example "foo/bar/group1" will create repository group called "group1"
157 157 inside group "foo/bar". You have to have permissions to access and
158 158 write to the last repository group ("bar" in this example)
159 159
160 160 This command can only be run using an |authtoken| with at least
161 161 permissions to create repository groups, or admin permissions to
162 162 parent repository groups.
163 163
164 164 :param apiuser: This is filled automatically from the |authtoken|.
165 165 :type apiuser: AuthUser
166 166 :param group_name: Set the repository group name.
167 167 :type group_name: str
168 168 :param description: Set the |repo| group description.
169 169 :type description: str
170 170 :param owner: Set the |repo| group owner.
171 171 :type owner: str
172 172 :param copy_permissions:
173 173 :type copy_permissions:
174 174
175 175 Example output:
176 176
177 177 .. code-block:: bash
178 178
179 179 id : <id_given_in_input>
180 180 result : {
181 181 "msg": "Created new repo group `<repo_group_name>`"
182 182 "repo_group": <repogroup_object>
183 183 }
184 184 error : null
185 185
186 186
187 187 Example error output:
188 188
189 189 .. code-block:: bash
190 190
191 191 id : <id_given_in_input>
192 192 result : null
193 193 error : {
194 194 failed to create repo group `<repogroupid>`
195 195 }
196 196
197 197 """
198 198
199 199 owner = validate_set_owner_permissions(apiuser, owner)
200 200
201 201 description = Optional.extract(description)
202 202 copy_permissions = Optional.extract(copy_permissions)
203 203
204 204 schema = repo_group_schema.RepoGroupSchema().bind(
205 205 # user caller
206 206 user=apiuser)
207 207
208 208 try:
209 209 schema_data = schema.deserialize(dict(
210 210 repo_group_name=group_name,
211 211 repo_group_owner=owner.username,
212 212 repo_group_description=description,
213 213 repo_group_copy_permissions=copy_permissions,
214 214 ))
215 215 except validation_schema.Invalid as err:
216 216 raise JSONRPCValidationError(colander_exc=err)
217 217
218 218 validated_group_name = schema_data['repo_group_name']
219 219
220 220 try:
221 221 repo_group = RepoGroupModel().create(
222 222 owner=owner,
223 223 group_name=validated_group_name,
224 224 group_description=schema_data['repo_group_name'],
225 225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 226 Session().flush()
227 227
228 228 repo_group_data = repo_group.get_api_data()
229 229 audit_logger.store_api(
230 230 'repo_group.create', action_data={'data': repo_group_data},
231 231 user=apiuser)
232 232
233 233 Session().commit()
234 234 return {
235 235 'msg': 'Created new repo group `%s`' % validated_group_name,
236 236 'repo_group': repo_group.get_api_data()
237 237 }
238 238 except Exception:
239 239 log.exception("Exception occurred while trying create repo group")
240 240 raise JSONRPCError(
241 241 'failed to create repo group `%s`' % (validated_group_name,))
242 242
243 243
244 244 @jsonrpc_method()
245 245 def update_repo_group(
246 246 request, apiuser, repogroupid, group_name=Optional(''),
247 247 description=Optional(''), owner=Optional(OAttr('apiuser')),
248 248 enable_locking=Optional(False)):
249 249 """
250 250 Updates repository group with the details given.
251 251
252 252 This command can only be run using an |authtoken| with admin
253 253 permissions.
254 254
255 255 * If the group_name name contains "/", repository group will be updated
256 256 accordingly with a repository group or nested repository groups
257 257
258 258 For example repogroupid=group-test group_name="foo/bar/group-test"
259 259 will update repository group called "group-test" and place it
260 260 inside group "foo/bar".
261 261 You have to have permissions to access and write to the last repository
262 262 group ("bar" in this example)
263 263
264 264 :param apiuser: This is filled automatically from the |authtoken|.
265 265 :type apiuser: AuthUser
266 266 :param repogroupid: Set the ID of repository group.
267 267 :type repogroupid: str or int
268 268 :param group_name: Set the name of the |repo| group.
269 269 :type group_name: str
270 270 :param description: Set a description for the group.
271 271 :type description: str
272 272 :param owner: Set the |repo| group owner.
273 273 :type owner: str
274 274 :param enable_locking: Enable |repo| locking. The default is false.
275 275 :type enable_locking: bool
276 276 """
277 277
278 278 repo_group = get_repo_group_or_error(repogroupid)
279 279
280 280 if not has_superadmin_permission(apiuser):
281 281 validate_repo_group_permissions(
282 282 apiuser, repogroupid, repo_group, ('group.admin',))
283 283
284 284 updates = dict(
285 285 group_name=group_name
286 286 if not isinstance(group_name, Optional) else repo_group.group_name,
287 287
288 288 group_description=description
289 289 if not isinstance(description, Optional) else repo_group.group_description,
290 290
291 291 user=owner
292 292 if not isinstance(owner, Optional) else repo_group.user.username,
293 293
294 294 enable_locking=enable_locking
295 295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
296 296 )
297 297
298 298 schema = repo_group_schema.RepoGroupSchema().bind(
299 299 # user caller
300 300 user=apiuser,
301 301 old_values=repo_group.get_api_data())
302 302
303 303 try:
304 304 schema_data = schema.deserialize(dict(
305 305 repo_group_name=updates['group_name'],
306 306 repo_group_owner=updates['user'],
307 307 repo_group_description=updates['group_description'],
308 308 repo_group_enable_locking=updates['enable_locking'],
309 309 ))
310 310 except validation_schema.Invalid as err:
311 311 raise JSONRPCValidationError(colander_exc=err)
312 312
313 313 validated_updates = dict(
314 314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
315 315 group_parent_id=schema_data['repo_group']['repo_group_id'],
316 316 user=schema_data['repo_group_owner'],
317 317 group_description=schema_data['repo_group_description'],
318 318 enable_locking=schema_data['repo_group_enable_locking'],
319 319 )
320 320
321 321 old_data = repo_group.get_api_data()
322 322 try:
323 323 RepoGroupModel().update(repo_group, validated_updates)
324 324 audit_logger.store_api(
325 325 'repo_group.edit', action_data={'old_data': old_data},
326 326 user=apiuser)
327 327
328 328 Session().commit()
329 329 return {
330 330 'msg': 'updated repository group ID:%s %s' % (
331 331 repo_group.group_id, repo_group.group_name),
332 332 'repo_group': repo_group.get_api_data()
333 333 }
334 334 except Exception:
335 335 log.exception(
336 336 u"Exception occurred while trying update repo group %s",
337 337 repogroupid)
338 338 raise JSONRPCError('failed to update repository group `%s`'
339 339 % (repogroupid,))
340 340
341 341
342 342 @jsonrpc_method()
343 343 def delete_repo_group(request, apiuser, repogroupid):
344 344 """
345 345 Deletes a |repo| group.
346 346
347 347 :param apiuser: This is filled automatically from the |authtoken|.
348 348 :type apiuser: AuthUser
349 349 :param repogroupid: Set the name or ID of repository group to be
350 350 deleted.
351 351 :type repogroupid: str or int
352 352
353 353 Example output:
354 354
355 355 .. code-block:: bash
356 356
357 357 id : <id_given_in_input>
358 358 result : {
359 359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
360 360 'repo_group': null
361 361 }
362 362 error : null
363 363
364 364 Example error output:
365 365
366 366 .. code-block:: bash
367 367
368 368 id : <id_given_in_input>
369 369 result : null
370 370 error : {
371 371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
372 372 }
373 373
374 374 """
375 375
376 376 repo_group = get_repo_group_or_error(repogroupid)
377 377 if not has_superadmin_permission(apiuser):
378 378 validate_repo_group_permissions(
379 379 apiuser, repogroupid, repo_group, ('group.admin',))
380 380
381 381 old_data = repo_group.get_api_data()
382 382 try:
383 383 RepoGroupModel().delete(repo_group)
384 384 audit_logger.store_api(
385 385 'repo_group.delete', action_data={'old_data': old_data},
386 386 user=apiuser)
387 387 Session().commit()
388 388 return {
389 389 'msg': 'deleted repo group ID:%s %s' %
390 390 (repo_group.group_id, repo_group.group_name),
391 391 'repo_group': None
392 392 }
393 393 except Exception:
394 394 log.exception("Exception occurred while trying to delete repo group")
395 395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
396 396 (repo_group.group_id, repo_group.group_name))
397 397
398 398
399 399 @jsonrpc_method()
400 400 def grant_user_permission_to_repo_group(
401 401 request, apiuser, repogroupid, userid, perm,
402 402 apply_to_children=Optional('none')):
403 403 """
404 404 Grant permission for a user on the given repository group, or update
405 405 existing permissions if found.
406 406
407 407 This command can only be run using an |authtoken| with admin
408 408 permissions.
409 409
410 410 :param apiuser: This is filled automatically from the |authtoken|.
411 411 :type apiuser: AuthUser
412 412 :param repogroupid: Set the name or ID of repository group.
413 413 :type repogroupid: str or int
414 414 :param userid: Set the user name.
415 415 :type userid: str
416 416 :param perm: (group.(none|read|write|admin))
417 417 :type perm: str
418 418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
419 419 :type apply_to_children: str
420 420
421 421 Example output:
422 422
423 423 .. code-block:: bash
424 424
425 425 id : <id_given_in_input>
426 426 result: {
427 427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
428 428 "success": true
429 429 }
430 430 error: null
431 431
432 432 Example error output:
433 433
434 434 .. code-block:: bash
435 435
436 436 id : <id_given_in_input>
437 437 result : null
438 438 error : {
439 439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
440 440 }
441 441
442 442 """
443 443
444 444 repo_group = get_repo_group_or_error(repogroupid)
445 445
446 446 if not has_superadmin_permission(apiuser):
447 447 validate_repo_group_permissions(
448 448 apiuser, repogroupid, repo_group, ('group.admin',))
449 449
450 450 user = get_user_or_error(userid)
451 451 perm = get_perm_or_error(perm, prefix='group.')
452 452 apply_to_children = Optional.extract(apply_to_children)
453 453
454 454 perm_additions = [[user.user_id, perm, "user"]]
455 455 try:
456 456 RepoGroupModel().update_permissions(repo_group=repo_group,
457 457 perm_additions=perm_additions,
458 458 recursive=apply_to_children,
459 459 cur_user=apiuser)
460 460 Session().commit()
461 461 return {
462 462 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
463 463 '`%s` in repo group: `%s`' % (
464 464 perm.permission_name, apply_to_children, user.username,
465 465 repo_group.name
466 466 ),
467 467 'success': True
468 468 }
469 469 except Exception:
470 470 log.exception("Exception occurred while trying to grant "
471 471 "user permissions to repo group")
472 472 raise JSONRPCError(
473 473 'failed to edit permission for user: '
474 474 '`%s` in repo group: `%s`' % (userid, repo_group.name))
475 475
476 476
477 477 @jsonrpc_method()
478 478 def revoke_user_permission_from_repo_group(
479 479 request, apiuser, repogroupid, userid,
480 480 apply_to_children=Optional('none')):
481 481 """
482 482 Revoke permission for a user in a given repository group.
483 483
484 484 This command can only be run using an |authtoken| with admin
485 485 permissions on the |repo| group.
486 486
487 487 :param apiuser: This is filled automatically from the |authtoken|.
488 488 :type apiuser: AuthUser
489 489 :param repogroupid: Set the name or ID of the repository group.
490 490 :type repogroupid: str or int
491 491 :param userid: Set the user name to revoke.
492 492 :type userid: str
493 493 :param apply_to_children: 'none', 'repos', 'groups', 'all'
494 494 :type apply_to_children: str
495 495
496 496 Example output:
497 497
498 498 .. code-block:: bash
499 499
500 500 id : <id_given_in_input>
501 501 result: {
502 502 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
503 503 "success": true
504 504 }
505 505 error: null
506 506
507 507 Example error output:
508 508
509 509 .. code-block:: bash
510 510
511 511 id : <id_given_in_input>
512 512 result : null
513 513 error : {
514 514 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
515 515 }
516 516
517 517 """
518 518
519 519 repo_group = get_repo_group_or_error(repogroupid)
520 520
521 521 if not has_superadmin_permission(apiuser):
522 522 validate_repo_group_permissions(
523 523 apiuser, repogroupid, repo_group, ('group.admin',))
524 524
525 525 user = get_user_or_error(userid)
526 526 apply_to_children = Optional.extract(apply_to_children)
527 527
528 528 perm_deletions = [[user.user_id, None, "user"]]
529 529 try:
530 530 RepoGroupModel().update_permissions(repo_group=repo_group,
531 531 perm_deletions=perm_deletions,
532 532 recursive=apply_to_children,
533 533 cur_user=apiuser)
534 534 Session().commit()
535 535 return {
536 536 'msg': 'Revoked perm (recursive:%s) for user: '
537 537 '`%s` in repo group: `%s`' % (
538 538 apply_to_children, user.username, repo_group.name
539 539 ),
540 540 'success': True
541 541 }
542 542 except Exception:
543 543 log.exception("Exception occurred while trying revoke user "
544 544 "permission from repo group")
545 545 raise JSONRPCError(
546 546 'failed to edit permission for user: '
547 547 '`%s` in repo group: `%s`' % (userid, repo_group.name))
548 548
549 549
550 550 @jsonrpc_method()
551 551 def grant_user_group_permission_to_repo_group(
552 552 request, apiuser, repogroupid, usergroupid, perm,
553 553 apply_to_children=Optional('none'), ):
554 554 """
555 555 Grant permission for a user group on given repository group, or update
556 556 existing permissions if found.
557 557
558 558 This command can only be run using an |authtoken| with admin
559 559 permissions on the |repo| group.
560 560
561 561 :param apiuser: This is filled automatically from the |authtoken|.
562 562 :type apiuser: AuthUser
563 563 :param repogroupid: Set the name or id of repository group
564 564 :type repogroupid: str or int
565 565 :param usergroupid: id of usergroup
566 566 :type usergroupid: str or int
567 567 :param perm: (group.(none|read|write|admin))
568 568 :type perm: str
569 569 :param apply_to_children: 'none', 'repos', 'groups', 'all'
570 570 :type apply_to_children: str
571 571
572 572 Example output:
573 573
574 574 .. code-block:: bash
575 575
576 576 id : <id_given_in_input>
577 577 result : {
578 578 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
579 579 "success": true
580 580
581 581 }
582 582 error : null
583 583
584 584 Example error output:
585 585
586 586 .. code-block:: bash
587 587
588 588 id : <id_given_in_input>
589 589 result : null
590 590 error : {
591 591 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
592 592 }
593 593
594 594 """
595 595
596 596 repo_group = get_repo_group_or_error(repogroupid)
597 597 perm = get_perm_or_error(perm, prefix='group.')
598 598 user_group = get_user_group_or_error(usergroupid)
599 599 if not has_superadmin_permission(apiuser):
600 600 validate_repo_group_permissions(
601 601 apiuser, repogroupid, repo_group, ('group.admin',))
602 602
603 603 # check if we have at least read permission for this user group !
604 604 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
605 605 if not HasUserGroupPermissionAnyApi(*_perms)(
606 606 user=apiuser, user_group_name=user_group.users_group_name):
607 607 raise JSONRPCError(
608 608 'user group `%s` does not exist' % (usergroupid,))
609 609
610 610 apply_to_children = Optional.extract(apply_to_children)
611 611
612 612 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
613 613 try:
614 614 RepoGroupModel().update_permissions(repo_group=repo_group,
615 615 perm_additions=perm_additions,
616 616 recursive=apply_to_children,
617 617 cur_user=apiuser)
618 618 Session().commit()
619 619 return {
620 620 'msg': 'Granted perm: `%s` (recursive:%s) '
621 621 'for user group: `%s` in repo group: `%s`' % (
622 622 perm.permission_name, apply_to_children,
623 623 user_group.users_group_name, repo_group.name
624 624 ),
625 625 'success': True
626 626 }
627 627 except Exception:
628 628 log.exception("Exception occurred while trying to grant user "
629 629 "group permissions to repo group")
630 630 raise JSONRPCError(
631 631 'failed to edit permission for user group: `%s` in '
632 632 'repo group: `%s`' % (
633 633 usergroupid, repo_group.name
634 634 )
635 635 )
636 636
637 637
638 638 @jsonrpc_method()
639 639 def revoke_user_group_permission_from_repo_group(
640 640 request, apiuser, repogroupid, usergroupid,
641 641 apply_to_children=Optional('none')):
642 642 """
643 643 Revoke permission for user group on given repository.
644 644
645 645 This command can only be run using an |authtoken| with admin
646 646 permissions on the |repo| group.
647 647
648 648 :param apiuser: This is filled automatically from the |authtoken|.
649 649 :type apiuser: AuthUser
650 650 :param repogroupid: name or id of repository group
651 651 :type repogroupid: str or int
652 652 :param usergroupid:
653 653 :param apply_to_children: 'none', 'repos', 'groups', 'all'
654 654 :type apply_to_children: str
655 655
656 656 Example output:
657 657
658 658 .. code-block:: bash
659 659
660 660 id : <id_given_in_input>
661 661 result: {
662 662 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
663 663 "success": true
664 664 }
665 665 error: null
666 666
667 667 Example error output:
668 668
669 669 .. code-block:: bash
670 670
671 671 id : <id_given_in_input>
672 672 result : null
673 673 error : {
674 674 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
675 675 }
676 676
677 677
678 678 """
679 679
680 680 repo_group = get_repo_group_or_error(repogroupid)
681 681 user_group = get_user_group_or_error(usergroupid)
682 682 if not has_superadmin_permission(apiuser):
683 683 validate_repo_group_permissions(
684 684 apiuser, repogroupid, repo_group, ('group.admin',))
685 685
686 686 # check if we have at least read permission for this user group !
687 687 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
688 688 if not HasUserGroupPermissionAnyApi(*_perms)(
689 689 user=apiuser, user_group_name=user_group.users_group_name):
690 690 raise JSONRPCError(
691 691 'user group `%s` does not exist' % (usergroupid,))
692 692
693 693 apply_to_children = Optional.extract(apply_to_children)
694 694
695 695 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
696 696 try:
697 697 RepoGroupModel().update_permissions(repo_group=repo_group,
698 698 perm_deletions=perm_deletions,
699 699 recursive=apply_to_children,
700 700 cur_user=apiuser)
701 701 Session().commit()
702 702 return {
703 703 'msg': 'Revoked perm (recursive:%s) for user group: '
704 704 '`%s` in repo group: `%s`' % (
705 705 apply_to_children, user_group.users_group_name,
706 706 repo_group.name
707 707 ),
708 708 'success': True
709 709 }
710 710 except Exception:
711 711 log.exception("Exception occurred while trying revoke user group "
712 712 "permissions from repo group")
713 713 raise JSONRPCError(
714 714 'failed to edit permission for user group: '
715 715 '`%s` in repo group: `%s`' % (
716 716 user_group.users_group_name, repo_group.name
717 717 )
718 718 )
719 719
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now