##// END OF EJS Templates
license: updated copyright year to 2017
marcink -
r1271:47a44c03 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 63 # defines current db version for migrations
54 __dbversion__ = 63 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,43 +1,43 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.admin.navigation import NavigationRegistry
22 from rhodecode.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def includeme(config):
27 def includeme(config):
28 settings = config.get_settings()
28 settings = config.get_settings()
29
29
30 # Create admin navigation registry and add it to the pyramid registry.
30 # Create admin navigation registry and add it to the pyramid registry.
31 labs_active = str2bool(settings.get('labs_settings_active', False))
31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 config.registry.registerUtility(navigation_registry)
33 config.registry.registerUtility(navigation_registry)
34
34
35 config.add_route(
35 config.add_route(
36 name='admin_settings_open_source',
36 name='admin_settings_open_source',
37 pattern=ADMIN_PREFIX + '/settings/open_source')
37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 config.add_route(
38 config.add_route(
39 name='admin_settings_vcs_svn_generate_cfg',
39 name='admin_settings_vcs_svn_generate_cfg',
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41
41
42 # Scan module for configuration decorators.
42 # Scan module for configuration decorators.
43 config.scan()
43 config.scan()
@@ -1,29 +1,29 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from zope.interface import Interface
21 from zope.interface import Interface
22
22
23
23
24 class IAdminNavigationRegistry(Interface):
24 class IAdminNavigationRegistry(Interface):
25 """
25 """
26 Interface for the admin navigation registry. Currently this is only
26 Interface for the admin navigation registry. Currently this is only
27 used to register and retrieve it via pyramids registry.
27 used to register and retrieve it via pyramids registry.
28 """
28 """
29 pass
29 pass
@@ -1,126 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pylons import url
25 from pylons import url
26 from zope.interface import implementer
26 from zope.interface import implementer
27
27
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
29 from rhodecode.lib.utils import get_registry
29 from rhodecode.lib.utils import get_registry
30 from rhodecode.translation import _
30 from rhodecode.translation import _
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36
36
37
37
38 class NavEntry(object):
38 class NavEntry(object):
39 """
39 """
40 Represents an entry in the admin navigation.
40 Represents an entry in the admin navigation.
41
41
42 :param key: Unique identifier used to store reference in an OrderedDict.
42 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
43 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
44 :param view_name: Name of the view, used generate the URL.
45 :param pyramid: Indicator to use pyramid for URL generation. This should
45 :param pyramid: Indicator to use pyramid for URL generation. This should
46 be removed as soon as we are fully migrated to pyramid.
46 be removed as soon as we are fully migrated to pyramid.
47 """
47 """
48
48
49 def __init__(self, key, name, view_name, pyramid=False):
49 def __init__(self, key, name, view_name, pyramid=False):
50 self.key = key
50 self.key = key
51 self.name = name
51 self.name = name
52 self.view_name = view_name
52 self.view_name = view_name
53 self.pyramid = pyramid
53 self.pyramid = pyramid
54
54
55 def generate_url(self, request):
55 def generate_url(self, request):
56 if self.pyramid:
56 if self.pyramid:
57 if hasattr(request, 'route_path'):
57 if hasattr(request, 'route_path'):
58 return request.route_path(self.view_name)
58 return request.route_path(self.view_name)
59 else:
59 else:
60 # TODO: johbo: Remove this after migrating to pyramid.
60 # TODO: johbo: Remove this after migrating to pyramid.
61 # We need the pyramid request here to generate URLs to pyramid
61 # We need the pyramid request here to generate URLs to pyramid
62 # views from within pylons views.
62 # views from within pylons views.
63 from pyramid.threadlocal import get_current_request
63 from pyramid.threadlocal import get_current_request
64 pyramid_request = get_current_request()
64 pyramid_request = get_current_request()
65 return pyramid_request.route_path(self.view_name)
65 return pyramid_request.route_path(self.view_name)
66 else:
66 else:
67 return url(self.view_name)
67 return url(self.view_name)
68
68
69
69
70 @implementer(IAdminNavigationRegistry)
70 @implementer(IAdminNavigationRegistry)
71 class NavigationRegistry(object):
71 class NavigationRegistry(object):
72
72
73 _base_entries = [
73 _base_entries = [
74 NavEntry('global', _('Global'), 'admin_settings_global'),
74 NavEntry('global', _('Global'), 'admin_settings_global'),
75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
78 NavEntry('issuetracker', _('Issue Tracker'),
78 NavEntry('issuetracker', _('Issue Tracker'),
79 'admin_settings_issuetracker'),
79 'admin_settings_issuetracker'),
80 NavEntry('email', _('Email'), 'admin_settings_email'),
80 NavEntry('email', _('Email'), 'admin_settings_email'),
81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
83 NavEntry('integrations', _('Integrations'),
83 NavEntry('integrations', _('Integrations'),
84 'global_integrations_home', pyramid=True),
84 'global_integrations_home', pyramid=True),
85 NavEntry('system', _('System Info'), 'admin_settings_system'),
85 NavEntry('system', _('System Info'), 'admin_settings_system'),
86 NavEntry('open_source', _('Open Source Licenses'),
86 NavEntry('open_source', _('Open Source Licenses'),
87 'admin_settings_open_source', pyramid=True),
87 'admin_settings_open_source', pyramid=True),
88 # TODO: marcink: we disable supervisor now until the supervisor stats
88 # TODO: marcink: we disable supervisor now until the supervisor stats
89 # page is fixed in the nix configuration
89 # page is fixed in the nix configuration
90 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
90 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
91 ]
91 ]
92
92
93 _labs_entry = NavEntry('labs', _('Labs'),
93 _labs_entry = NavEntry('labs', _('Labs'),
94 'admin_settings_labs')
94 'admin_settings_labs')
95
95
96 def __init__(self, labs_active=False):
96 def __init__(self, labs_active=False):
97 self._registered_entries = collections.OrderedDict([
97 self._registered_entries = collections.OrderedDict([
98 (item.key, item) for item in self.__class__._base_entries
98 (item.key, item) for item in self.__class__._base_entries
99 ])
99 ])
100
100
101 if labs_active:
101 if labs_active:
102 self.add_entry(self._labs_entry)
102 self.add_entry(self._labs_entry)
103
103
104 def add_entry(self, entry):
104 def add_entry(self, entry):
105 self._registered_entries[entry.key] = entry
105 self._registered_entries[entry.key] = entry
106
106
107 def get_navlist(self, request):
107 def get_navlist(self, request):
108 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
108 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
109 for i in self._registered_entries.values()]
109 for i in self._registered_entries.values()]
110 return navlist
110 return navlist
111
111
112
112
113 def navigation_registry(request):
113 def navigation_registry(request):
114 """
114 """
115 Helper that returns the admin navigation registry.
115 Helper that returns the admin navigation registry.
116 """
116 """
117 pyramid_registry = get_registry(request)
117 pyramid_registry = get_registry(request)
118 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
118 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
119 return nav_registry
119 return nav_registry
120
120
121
121
122 def navigation_list(request):
122 def navigation_list(request):
123 """
123 """
124 Helper that returns the admin navigation as list of NavListEntry objects.
124 Helper that returns the admin navigation as list of NavListEntry objects.
125 """
125 """
126 return navigation_registry(request).get_navlist(request)
126 return navigation_registry(request).get_navlist(request)
@@ -1,82 +1,82 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22 import logging
22 import logging
23
23
24 from pylons import tmpl_context as c
24 from pylons import tmpl_context as c
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib.utils import read_opensource_licenses
29 from rhodecode.lib.utils import read_opensource_licenses
30 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
30 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
31 from rhodecode.translation import _
31 from rhodecode.translation import _
32
32
33 from .navigation import navigation_list
33 from .navigation import navigation_list
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class AdminSettingsView(object):
39 class AdminSettingsView(object):
40
40
41 def __init__(self, context, request):
41 def __init__(self, context, request):
42 self.request = request
42 self.request = request
43 self.context = context
43 self.context = context
44 self.session = request.session
44 self.session = request.session
45 self._rhodecode_user = request.user
45 self._rhodecode_user = request.user
46
46
47 @LoginRequired()
47 @LoginRequired()
48 @HasPermissionAllDecorator('hg.admin')
48 @HasPermissionAllDecorator('hg.admin')
49 @view_config(
49 @view_config(
50 route_name='admin_settings_open_source', request_method='GET',
50 route_name='admin_settings_open_source', request_method='GET',
51 renderer='rhodecode:templates/admin/settings/settings.html')
51 renderer='rhodecode:templates/admin/settings/settings.html')
52 def open_source_licenses(self):
52 def open_source_licenses(self):
53 c.active = 'open_source'
53 c.active = 'open_source'
54 c.navlist = navigation_list(self.request)
54 c.navlist = navigation_list(self.request)
55 c.opensource_licenses = collections.OrderedDict(
55 c.opensource_licenses = collections.OrderedDict(
56 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
56 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
57
57
58 return {}
58 return {}
59
59
60 @LoginRequired()
60 @LoginRequired()
61 @CSRFRequired()
61 @CSRFRequired()
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 @view_config(
63 @view_config(
64 route_name='admin_settings_vcs_svn_generate_cfg',
64 route_name='admin_settings_vcs_svn_generate_cfg',
65 request_method='POST', renderer='json')
65 request_method='POST', renderer='json')
66 def vcs_svn_generate_config(self):
66 def vcs_svn_generate_config(self):
67 try:
67 try:
68 generate_mod_dav_svn_config(self.request.registry)
68 generate_mod_dav_svn_config(self.request.registry)
69 msg = {
69 msg = {
70 'message': _('Apache configuration for Subversion generated.'),
70 'message': _('Apache configuration for Subversion generated.'),
71 'level': 'success',
71 'level': 'success',
72 }
72 }
73 except Exception:
73 except Exception:
74 log.exception(
74 log.exception(
75 'Exception while generating the Apache configuration for Subversion.')
75 'Exception while generating the Apache configuration for Subversion.')
76 msg = {
76 msg = {
77 'message': _('Failed to generate the Apache configuration for Subversion.'),
77 'message': _('Failed to generate the Apache configuration for Subversion.'),
78 'level': 'error',
78 'level': 'error',
79 }
79 }
80
80
81 data = {'message': msg}
81 data = {'message': msg}
82 return data
82 return data
@@ -1,505 +1,505 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import inspect
21 import inspect
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25
25
26 import decorator
26 import decorator
27 import venusian
27 import venusian
28 from collections import OrderedDict
28 from collections import OrderedDict
29
29
30 from pyramid.exceptions import ConfigurationError
30 from pyramid.exceptions import ConfigurationError
31 from pyramid.renderers import render
31 from pyramid.renderers import render
32 from pyramid.response import Response
32 from pyramid.response import Response
33 from pyramid.httpexceptions import HTTPNotFound
33 from pyramid.httpexceptions import HTTPNotFound
34
34
35 from rhodecode.api.exc import (
35 from rhodecode.api.exc import (
36 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
36 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 from rhodecode.lib.auth import AuthUser
37 from rhodecode.lib.auth import AuthUser
38 from rhodecode.lib.base import get_ip_addr
38 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.plugins.utils import get_plugin_settings
41 from rhodecode.lib.plugins.utils import get_plugin_settings
42 from rhodecode.model.db import User, UserApiKeys
42 from rhodecode.model.db import User, UserApiKeys
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 DEFAULT_RENDERER = 'jsonrpc_renderer'
46 DEFAULT_RENDERER = 'jsonrpc_renderer'
47 DEFAULT_URL = '/_admin/apiv2'
47 DEFAULT_URL = '/_admin/apiv2'
48
48
49
49
50 class ExtJsonRenderer(object):
50 class ExtJsonRenderer(object):
51 """
51 """
52 Custom renderer that mkaes use of our ext_json lib
52 Custom renderer that mkaes use of our ext_json lib
53
53
54 """
54 """
55
55
56 def __init__(self, serializer=json.dumps, **kw):
56 def __init__(self, serializer=json.dumps, **kw):
57 """ Any keyword arguments will be passed to the ``serializer``
57 """ Any keyword arguments will be passed to the ``serializer``
58 function."""
58 function."""
59 self.serializer = serializer
59 self.serializer = serializer
60 self.kw = kw
60 self.kw = kw
61
61
62 def __call__(self, info):
62 def __call__(self, info):
63 """ Returns a plain JSON-encoded string with content-type
63 """ Returns a plain JSON-encoded string with content-type
64 ``application/json``. The content-type may be overridden by
64 ``application/json``. The content-type may be overridden by
65 setting ``request.response.content_type``."""
65 setting ``request.response.content_type``."""
66
66
67 def _render(value, system):
67 def _render(value, system):
68 request = system.get('request')
68 request = system.get('request')
69 if request is not None:
69 if request is not None:
70 response = request.response
70 response = request.response
71 ct = response.content_type
71 ct = response.content_type
72 if ct == response.default_content_type:
72 if ct == response.default_content_type:
73 response.content_type = 'application/json'
73 response.content_type = 'application/json'
74
74
75 return self.serializer(value, **self.kw)
75 return self.serializer(value, **self.kw)
76
76
77 return _render
77 return _render
78
78
79
79
80 def jsonrpc_response(request, result):
80 def jsonrpc_response(request, result):
81 rpc_id = getattr(request, 'rpc_id', None)
81 rpc_id = getattr(request, 'rpc_id', None)
82 response = request.response
82 response = request.response
83
83
84 # store content_type before render is called
84 # store content_type before render is called
85 ct = response.content_type
85 ct = response.content_type
86
86
87 ret_value = ''
87 ret_value = ''
88 if rpc_id:
88 if rpc_id:
89 ret_value = {
89 ret_value = {
90 'id': rpc_id,
90 'id': rpc_id,
91 'result': result,
91 'result': result,
92 'error': None,
92 'error': None,
93 }
93 }
94
94
95 # fetch deprecation warnings, and store it inside results
95 # fetch deprecation warnings, and store it inside results
96 deprecation = getattr(request, 'rpc_deprecation', None)
96 deprecation = getattr(request, 'rpc_deprecation', None)
97 if deprecation:
97 if deprecation:
98 ret_value['DEPRECATION_WARNING'] = deprecation
98 ret_value['DEPRECATION_WARNING'] = deprecation
99
99
100 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
100 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
101 response.body = safe_str(raw_body, response.charset)
101 response.body = safe_str(raw_body, response.charset)
102
102
103 if ct == response.default_content_type:
103 if ct == response.default_content_type:
104 response.content_type = 'application/json'
104 response.content_type = 'application/json'
105
105
106 return response
106 return response
107
107
108
108
109 def jsonrpc_error(request, message, retid=None, code=None):
109 def jsonrpc_error(request, message, retid=None, code=None):
110 """
110 """
111 Generate a Response object with a JSON-RPC error body
111 Generate a Response object with a JSON-RPC error body
112
112
113 :param code:
113 :param code:
114 :param retid:
114 :param retid:
115 :param message:
115 :param message:
116 """
116 """
117 err_dict = {'id': retid, 'result': None, 'error': message}
117 err_dict = {'id': retid, 'result': None, 'error': message}
118 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
118 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
119 return Response(
119 return Response(
120 body=body,
120 body=body,
121 status=code,
121 status=code,
122 content_type='application/json'
122 content_type='application/json'
123 )
123 )
124
124
125
125
126 def exception_view(exc, request):
126 def exception_view(exc, request):
127 rpc_id = getattr(request, 'rpc_id', None)
127 rpc_id = getattr(request, 'rpc_id', None)
128
128
129 fault_message = 'undefined error'
129 fault_message = 'undefined error'
130 if isinstance(exc, JSONRPCError):
130 if isinstance(exc, JSONRPCError):
131 fault_message = exc.message
131 fault_message = exc.message
132 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
132 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
133 elif isinstance(exc, JSONRPCValidationError):
133 elif isinstance(exc, JSONRPCValidationError):
134 colander_exc = exc.colander_exception
134 colander_exc = exc.colander_exception
135 #TODO: think maybe of nicer way to serialize errors ?
135 #TODO: think maybe of nicer way to serialize errors ?
136 fault_message = colander_exc.asdict()
136 fault_message = colander_exc.asdict()
137 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
137 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
138 elif isinstance(exc, JSONRPCForbidden):
138 elif isinstance(exc, JSONRPCForbidden):
139 fault_message = 'Access was denied to this resource.'
139 fault_message = 'Access was denied to this resource.'
140 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
140 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
141 elif isinstance(exc, HTTPNotFound):
141 elif isinstance(exc, HTTPNotFound):
142 method = request.rpc_method
142 method = request.rpc_method
143 log.debug('json-rpc method `%s` not found in list of '
143 log.debug('json-rpc method `%s` not found in list of '
144 'api calls: %s, rpc_id:%s',
144 'api calls: %s, rpc_id:%s',
145 method, request.registry.jsonrpc_methods.keys(), rpc_id)
145 method, request.registry.jsonrpc_methods.keys(), rpc_id)
146 fault_message = "No such method: {}".format(method)
146 fault_message = "No such method: {}".format(method)
147
147
148 return jsonrpc_error(request, fault_message, rpc_id)
148 return jsonrpc_error(request, fault_message, rpc_id)
149
149
150
150
151 def request_view(request):
151 def request_view(request):
152 """
152 """
153 Main request handling method. It handles all logic to call a specific
153 Main request handling method. It handles all logic to call a specific
154 exposed method
154 exposed method
155 """
155 """
156
156
157 # check if we can find this session using api_key, get_by_auth_token
157 # check if we can find this session using api_key, get_by_auth_token
158 # search not expired tokens only
158 # search not expired tokens only
159
159
160 try:
160 try:
161 u = User.get_by_auth_token(request.rpc_api_key)
161 u = User.get_by_auth_token(request.rpc_api_key)
162
162
163 if u is None:
163 if u is None:
164 return jsonrpc_error(
164 return jsonrpc_error(
165 request, retid=request.rpc_id, message='Invalid API KEY')
165 request, retid=request.rpc_id, message='Invalid API KEY')
166
166
167 if not u.active:
167 if not u.active:
168 return jsonrpc_error(
168 return jsonrpc_error(
169 request, retid=request.rpc_id,
169 request, retid=request.rpc_id,
170 message='Request from this user not allowed')
170 message='Request from this user not allowed')
171
171
172 # check if we are allowed to use this IP
172 # check if we are allowed to use this IP
173 auth_u = AuthUser(
173 auth_u = AuthUser(
174 u.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
174 u.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
175 if not auth_u.ip_allowed:
175 if not auth_u.ip_allowed:
176 return jsonrpc_error(
176 return jsonrpc_error(
177 request, retid=request.rpc_id,
177 request, retid=request.rpc_id,
178 message='Request from IP:%s not allowed' % (
178 message='Request from IP:%s not allowed' % (
179 request.rpc_ip_addr,))
179 request.rpc_ip_addr,))
180 else:
180 else:
181 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
181 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
182
182
183 # now check if token is valid for API
183 # now check if token is valid for API
184 role = UserApiKeys.ROLE_API
184 role = UserApiKeys.ROLE_API
185 extra_auth_tokens = [
185 extra_auth_tokens = [
186 x.api_key for x in User.extra_valid_auth_tokens(u, role=role)]
186 x.api_key for x in User.extra_valid_auth_tokens(u, role=role)]
187 active_tokens = [u.api_key] + extra_auth_tokens
187 active_tokens = [u.api_key] + extra_auth_tokens
188
188
189 log.debug('Checking if API key has proper role')
189 log.debug('Checking if API key has proper role')
190 if request.rpc_api_key not in active_tokens:
190 if request.rpc_api_key not in active_tokens:
191 return jsonrpc_error(
191 return jsonrpc_error(
192 request, retid=request.rpc_id,
192 request, retid=request.rpc_id,
193 message='API KEY has bad role for an API call')
193 message='API KEY has bad role for an API call')
194
194
195 except Exception as e:
195 except Exception as e:
196 log.exception('Error on API AUTH')
196 log.exception('Error on API AUTH')
197 return jsonrpc_error(
197 return jsonrpc_error(
198 request, retid=request.rpc_id, message='Invalid API KEY')
198 request, retid=request.rpc_id, message='Invalid API KEY')
199
199
200 method = request.rpc_method
200 method = request.rpc_method
201 func = request.registry.jsonrpc_methods[method]
201 func = request.registry.jsonrpc_methods[method]
202
202
203 # now that we have a method, add request._req_params to
203 # now that we have a method, add request._req_params to
204 # self.kargs and dispatch control to WGIController
204 # self.kargs and dispatch control to WGIController
205 argspec = inspect.getargspec(func)
205 argspec = inspect.getargspec(func)
206 arglist = argspec[0]
206 arglist = argspec[0]
207 defaults = map(type, argspec[3] or [])
207 defaults = map(type, argspec[3] or [])
208 default_empty = types.NotImplementedType
208 default_empty = types.NotImplementedType
209
209
210 # kw arguments required by this method
210 # kw arguments required by this method
211 func_kwargs = dict(itertools.izip_longest(
211 func_kwargs = dict(itertools.izip_longest(
212 reversed(arglist), reversed(defaults), fillvalue=default_empty))
212 reversed(arglist), reversed(defaults), fillvalue=default_empty))
213
213
214 # This attribute will need to be first param of a method that uses
214 # This attribute will need to be first param of a method that uses
215 # api_key, which is translated to instance of user at that name
215 # api_key, which is translated to instance of user at that name
216 user_var = 'apiuser'
216 user_var = 'apiuser'
217 request_var = 'request'
217 request_var = 'request'
218
218
219 for arg in [user_var, request_var]:
219 for arg in [user_var, request_var]:
220 if arg not in arglist:
220 if arg not in arglist:
221 return jsonrpc_error(
221 return jsonrpc_error(
222 request,
222 request,
223 retid=request.rpc_id,
223 retid=request.rpc_id,
224 message='This method [%s] does not support '
224 message='This method [%s] does not support '
225 'required parameter `%s`' % (func.__name__, arg))
225 'required parameter `%s`' % (func.__name__, arg))
226
226
227 # get our arglist and check if we provided them as args
227 # get our arglist and check if we provided them as args
228 for arg, default in func_kwargs.items():
228 for arg, default in func_kwargs.items():
229 if arg in [user_var, request_var]:
229 if arg in [user_var, request_var]:
230 # user_var and request_var are pre-hardcoded parameters and we
230 # user_var and request_var are pre-hardcoded parameters and we
231 # don't need to do any translation
231 # don't need to do any translation
232 continue
232 continue
233
233
234 # skip the required param check if it's default value is
234 # skip the required param check if it's default value is
235 # NotImplementedType (default_empty)
235 # NotImplementedType (default_empty)
236 if default == default_empty and arg not in request.rpc_params:
236 if default == default_empty and arg not in request.rpc_params:
237 return jsonrpc_error(
237 return jsonrpc_error(
238 request,
238 request,
239 retid=request.rpc_id,
239 retid=request.rpc_id,
240 message=('Missing non optional `%s` arg in JSON DATA' % arg)
240 message=('Missing non optional `%s` arg in JSON DATA' % arg)
241 )
241 )
242
242
243 # sanitze extra passed arguments
243 # sanitze extra passed arguments
244 for k in request.rpc_params.keys()[:]:
244 for k in request.rpc_params.keys()[:]:
245 if k not in func_kwargs:
245 if k not in func_kwargs:
246 del request.rpc_params[k]
246 del request.rpc_params[k]
247
247
248 call_params = request.rpc_params
248 call_params = request.rpc_params
249 call_params.update({
249 call_params.update({
250 'request': request,
250 'request': request,
251 'apiuser': auth_u
251 'apiuser': auth_u
252 })
252 })
253 try:
253 try:
254 ret_value = func(**call_params)
254 ret_value = func(**call_params)
255 return jsonrpc_response(request, ret_value)
255 return jsonrpc_response(request, ret_value)
256 except JSONRPCBaseError:
256 except JSONRPCBaseError:
257 raise
257 raise
258 except Exception:
258 except Exception:
259 log.exception('Unhandled exception occured on api call: %s', func)
259 log.exception('Unhandled exception occured on api call: %s', func)
260 return jsonrpc_error(request, retid=request.rpc_id,
260 return jsonrpc_error(request, retid=request.rpc_id,
261 message='Internal server error')
261 message='Internal server error')
262
262
263
263
264 def setup_request(request):
264 def setup_request(request):
265 """
265 """
266 Parse a JSON-RPC request body. It's used inside the predicates method
266 Parse a JSON-RPC request body. It's used inside the predicates method
267 to validate and bootstrap requests for usage in rpc calls.
267 to validate and bootstrap requests for usage in rpc calls.
268
268
269 We need to raise JSONRPCError here if we want to return some errors back to
269 We need to raise JSONRPCError here if we want to return some errors back to
270 user.
270 user.
271 """
271 """
272 log.debug('Executing setup request: %r', request)
272 log.debug('Executing setup request: %r', request)
273 request.rpc_ip_addr = get_ip_addr(request.environ)
273 request.rpc_ip_addr = get_ip_addr(request.environ)
274 # TODO: marcink, deprecate GET at some point
274 # TODO: marcink, deprecate GET at some point
275 if request.method not in ['POST', 'GET']:
275 if request.method not in ['POST', 'GET']:
276 log.debug('unsupported request method "%s"', request.method)
276 log.debug('unsupported request method "%s"', request.method)
277 raise JSONRPCError(
277 raise JSONRPCError(
278 'unsupported request method "%s". Please use POST' % request.method)
278 'unsupported request method "%s". Please use POST' % request.method)
279
279
280 if 'CONTENT_LENGTH' not in request.environ:
280 if 'CONTENT_LENGTH' not in request.environ:
281 log.debug("No Content-Length")
281 log.debug("No Content-Length")
282 raise JSONRPCError("Empty body, No Content-Length in request")
282 raise JSONRPCError("Empty body, No Content-Length in request")
283
283
284 else:
284 else:
285 length = request.environ['CONTENT_LENGTH']
285 length = request.environ['CONTENT_LENGTH']
286 log.debug('Content-Length: %s', length)
286 log.debug('Content-Length: %s', length)
287
287
288 if length == 0:
288 if length == 0:
289 log.debug("Content-Length is 0")
289 log.debug("Content-Length is 0")
290 raise JSONRPCError("Content-Length is 0")
290 raise JSONRPCError("Content-Length is 0")
291
291
292 raw_body = request.body
292 raw_body = request.body
293 try:
293 try:
294 json_body = json.loads(raw_body)
294 json_body = json.loads(raw_body)
295 except ValueError as e:
295 except ValueError as e:
296 # catch JSON errors Here
296 # catch JSON errors Here
297 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
297 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
298
298
299 request.rpc_id = json_body.get('id')
299 request.rpc_id = json_body.get('id')
300 request.rpc_method = json_body.get('method')
300 request.rpc_method = json_body.get('method')
301
301
302 # check required base parameters
302 # check required base parameters
303 try:
303 try:
304 api_key = json_body.get('api_key')
304 api_key = json_body.get('api_key')
305 if not api_key:
305 if not api_key:
306 api_key = json_body.get('auth_token')
306 api_key = json_body.get('auth_token')
307
307
308 if not api_key:
308 if not api_key:
309 raise KeyError('api_key or auth_token')
309 raise KeyError('api_key or auth_token')
310
310
311 request.rpc_api_key = api_key
311 request.rpc_api_key = api_key
312 request.rpc_id = json_body['id']
312 request.rpc_id = json_body['id']
313 request.rpc_method = json_body['method']
313 request.rpc_method = json_body['method']
314 request.rpc_params = json_body['args'] \
314 request.rpc_params = json_body['args'] \
315 if isinstance(json_body['args'], dict) else {}
315 if isinstance(json_body['args'], dict) else {}
316
316
317 log.debug(
317 log.debug(
318 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
318 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
319 except KeyError as e:
319 except KeyError as e:
320 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
320 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
321
321
322 log.debug('setup complete, now handling method:%s rpcid:%s',
322 log.debug('setup complete, now handling method:%s rpcid:%s',
323 request.rpc_method, request.rpc_id, )
323 request.rpc_method, request.rpc_id, )
324
324
325
325
326 class RoutePredicate(object):
326 class RoutePredicate(object):
327 def __init__(self, val, config):
327 def __init__(self, val, config):
328 self.val = val
328 self.val = val
329
329
330 def text(self):
330 def text(self):
331 return 'jsonrpc route = %s' % self.val
331 return 'jsonrpc route = %s' % self.val
332
332
333 phash = text
333 phash = text
334
334
335 def __call__(self, info, request):
335 def __call__(self, info, request):
336 if self.val:
336 if self.val:
337 # potentially setup and bootstrap our call
337 # potentially setup and bootstrap our call
338 setup_request(request)
338 setup_request(request)
339
339
340 # Always return True so that even if it isn't a valid RPC it
340 # Always return True so that even if it isn't a valid RPC it
341 # will fall through to the underlaying handlers like notfound_view
341 # will fall through to the underlaying handlers like notfound_view
342 return True
342 return True
343
343
344
344
345 class NotFoundPredicate(object):
345 class NotFoundPredicate(object):
346 def __init__(self, val, config):
346 def __init__(self, val, config):
347 self.val = val
347 self.val = val
348
348
349 def text(self):
349 def text(self):
350 return 'jsonrpc method not found = %s' % self.val
350 return 'jsonrpc method not found = %s' % self.val
351
351
352 phash = text
352 phash = text
353
353
354 def __call__(self, info, request):
354 def __call__(self, info, request):
355 return hasattr(request, 'rpc_method')
355 return hasattr(request, 'rpc_method')
356
356
357
357
358 class MethodPredicate(object):
358 class MethodPredicate(object):
359 def __init__(self, val, config):
359 def __init__(self, val, config):
360 self.method = val
360 self.method = val
361
361
362 def text(self):
362 def text(self):
363 return 'jsonrpc method = %s' % self.method
363 return 'jsonrpc method = %s' % self.method
364
364
365 phash = text
365 phash = text
366
366
367 def __call__(self, context, request):
367 def __call__(self, context, request):
368 # we need to explicitly return False here, so pyramid doesn't try to
368 # we need to explicitly return False here, so pyramid doesn't try to
369 # execute our view directly. We need our main handler to execute things
369 # execute our view directly. We need our main handler to execute things
370 return getattr(request, 'rpc_method') == self.method
370 return getattr(request, 'rpc_method') == self.method
371
371
372
372
373 def add_jsonrpc_method(config, view, **kwargs):
373 def add_jsonrpc_method(config, view, **kwargs):
374 # pop the method name
374 # pop the method name
375 method = kwargs.pop('method', None)
375 method = kwargs.pop('method', None)
376
376
377 if method is None:
377 if method is None:
378 raise ConfigurationError(
378 raise ConfigurationError(
379 'Cannot register a JSON-RPC method without specifying the '
379 'Cannot register a JSON-RPC method without specifying the '
380 '"method"')
380 '"method"')
381
381
382 # we define custom predicate, to enable to detect conflicting methods,
382 # we define custom predicate, to enable to detect conflicting methods,
383 # those predicates are kind of "translation" from the decorator variables
383 # those predicates are kind of "translation" from the decorator variables
384 # to internal predicates names
384 # to internal predicates names
385
385
386 kwargs['jsonrpc_method'] = method
386 kwargs['jsonrpc_method'] = method
387
387
388 # register our view into global view store for validation
388 # register our view into global view store for validation
389 config.registry.jsonrpc_methods[method] = view
389 config.registry.jsonrpc_methods[method] = view
390
390
391 # we're using our main request_view handler, here, so each method
391 # we're using our main request_view handler, here, so each method
392 # has a unified handler for itself
392 # has a unified handler for itself
393 config.add_view(request_view, route_name='apiv2', **kwargs)
393 config.add_view(request_view, route_name='apiv2', **kwargs)
394
394
395
395
396 class jsonrpc_method(object):
396 class jsonrpc_method(object):
397 """
397 """
398 decorator that works similar to @add_view_config decorator,
398 decorator that works similar to @add_view_config decorator,
399 but tailored for our JSON RPC
399 but tailored for our JSON RPC
400 """
400 """
401
401
402 venusian = venusian # for testing injection
402 venusian = venusian # for testing injection
403
403
404 def __init__(self, method=None, **kwargs):
404 def __init__(self, method=None, **kwargs):
405 self.method = method
405 self.method = method
406 self.kwargs = kwargs
406 self.kwargs = kwargs
407
407
408 def __call__(self, wrapped):
408 def __call__(self, wrapped):
409 kwargs = self.kwargs.copy()
409 kwargs = self.kwargs.copy()
410 kwargs['method'] = self.method or wrapped.__name__
410 kwargs['method'] = self.method or wrapped.__name__
411 depth = kwargs.pop('_depth', 0)
411 depth = kwargs.pop('_depth', 0)
412
412
413 def callback(context, name, ob):
413 def callback(context, name, ob):
414 config = context.config.with_package(info.module)
414 config = context.config.with_package(info.module)
415 config.add_jsonrpc_method(view=ob, **kwargs)
415 config.add_jsonrpc_method(view=ob, **kwargs)
416
416
417 info = venusian.attach(wrapped, callback, category='pyramid',
417 info = venusian.attach(wrapped, callback, category='pyramid',
418 depth=depth + 1)
418 depth=depth + 1)
419 if info.scope == 'class':
419 if info.scope == 'class':
420 # ensure that attr is set if decorating a class method
420 # ensure that attr is set if decorating a class method
421 kwargs.setdefault('attr', wrapped.__name__)
421 kwargs.setdefault('attr', wrapped.__name__)
422
422
423 kwargs['_info'] = info.codeinfo # fbo action_method
423 kwargs['_info'] = info.codeinfo # fbo action_method
424 return wrapped
424 return wrapped
425
425
426
426
427 class jsonrpc_deprecated_method(object):
427 class jsonrpc_deprecated_method(object):
428 """
428 """
429 Marks method as deprecated, adds log.warning, and inject special key to
429 Marks method as deprecated, adds log.warning, and inject special key to
430 the request variable to mark method as deprecated.
430 the request variable to mark method as deprecated.
431 Also injects special docstring that extract_docs will catch to mark
431 Also injects special docstring that extract_docs will catch to mark
432 method as deprecated.
432 method as deprecated.
433
433
434 :param use_method: specify which method should be used instead of
434 :param use_method: specify which method should be used instead of
435 the decorated one
435 the decorated one
436
436
437 Use like::
437 Use like::
438
438
439 @jsonrpc_method()
439 @jsonrpc_method()
440 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
440 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
441 def old_func(request, apiuser, arg1, arg2):
441 def old_func(request, apiuser, arg1, arg2):
442 ...
442 ...
443 """
443 """
444
444
445 def __init__(self, use_method, deprecated_at_version):
445 def __init__(self, use_method, deprecated_at_version):
446 self.use_method = use_method
446 self.use_method = use_method
447 self.deprecated_at_version = deprecated_at_version
447 self.deprecated_at_version = deprecated_at_version
448 self.deprecated_msg = ''
448 self.deprecated_msg = ''
449
449
450 def __call__(self, func):
450 def __call__(self, func):
451 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
451 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
452 method=self.use_method)
452 method=self.use_method)
453
453
454 docstring = """\n
454 docstring = """\n
455 .. deprecated:: {version}
455 .. deprecated:: {version}
456
456
457 {deprecation_message}
457 {deprecation_message}
458
458
459 {original_docstring}
459 {original_docstring}
460 """
460 """
461 func.__doc__ = docstring.format(
461 func.__doc__ = docstring.format(
462 version=self.deprecated_at_version,
462 version=self.deprecated_at_version,
463 deprecation_message=self.deprecated_msg,
463 deprecation_message=self.deprecated_msg,
464 original_docstring=func.__doc__)
464 original_docstring=func.__doc__)
465 return decorator.decorator(self.__wrapper, func)
465 return decorator.decorator(self.__wrapper, func)
466
466
467 def __wrapper(self, func, *fargs, **fkwargs):
467 def __wrapper(self, func, *fargs, **fkwargs):
468 log.warning('DEPRECATED API CALL on function %s, please '
468 log.warning('DEPRECATED API CALL on function %s, please '
469 'use `%s` instead', func, self.use_method)
469 'use `%s` instead', func, self.use_method)
470 # alter function docstring to mark as deprecated, this is picked up
470 # alter function docstring to mark as deprecated, this is picked up
471 # via fabric file that generates API DOC.
471 # via fabric file that generates API DOC.
472 result = func(*fargs, **fkwargs)
472 result = func(*fargs, **fkwargs)
473
473
474 request = fargs[0]
474 request = fargs[0]
475 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
475 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
476 return result
476 return result
477
477
478
478
479 def includeme(config):
479 def includeme(config):
480 plugin_module = 'rhodecode.api'
480 plugin_module = 'rhodecode.api'
481 plugin_settings = get_plugin_settings(
481 plugin_settings = get_plugin_settings(
482 plugin_module, config.registry.settings)
482 plugin_module, config.registry.settings)
483
483
484 if not hasattr(config.registry, 'jsonrpc_methods'):
484 if not hasattr(config.registry, 'jsonrpc_methods'):
485 config.registry.jsonrpc_methods = OrderedDict()
485 config.registry.jsonrpc_methods = OrderedDict()
486
486
487 # match filter by given method only
487 # match filter by given method only
488 config.add_view_predicate(
488 config.add_view_predicate(
489 'jsonrpc_method', MethodPredicate)
489 'jsonrpc_method', MethodPredicate)
490
490
491 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
491 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
492 serializer=json.dumps, indent=4))
492 serializer=json.dumps, indent=4))
493 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
493 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
494
494
495 config.add_route_predicate(
495 config.add_route_predicate(
496 'jsonrpc_call', RoutePredicate)
496 'jsonrpc_call', RoutePredicate)
497
497
498 config.add_route(
498 config.add_route(
499 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
499 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
500
500
501 config.scan(plugin_module, ignore='rhodecode.api.tests')
501 config.scan(plugin_module, ignore='rhodecode.api.tests')
502 # register some exception handling view
502 # register some exception handling view
503 config.add_view(exception_view, context=JSONRPCBaseError)
503 config.add_view(exception_view, context=JSONRPCBaseError)
504 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
504 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
505 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
505 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,39 +1,39 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 class JSONRPCBaseError(Exception):
22 class JSONRPCBaseError(Exception):
23 pass
23 pass
24
24
25
25
26 class JSONRPCError(JSONRPCBaseError):
26 class JSONRPCError(JSONRPCBaseError):
27 pass
27 pass
28
28
29
29
30 class JSONRPCValidationError(JSONRPCBaseError):
30 class JSONRPCValidationError(JSONRPCBaseError):
31
31
32 def __init__(self, *args, **kwargs):
32 def __init__(self, *args, **kwargs):
33 self.colander_exception = kwargs.pop('colander_exc')
33 self.colander_exception = kwargs.pop('colander_exc')
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
35
35
36
36
37 class JSONRPCForbidden(JSONRPCBaseError):
37 class JSONRPCForbidden(JSONRPCBaseError):
38 pass
38 pass
39
39
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,42 +1,42 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26
26
27
27
28 @pytest.fixture(scope="class")
28 @pytest.fixture(scope="class")
29 def testuser_api(request, pylonsapp):
29 def testuser_api(request, pylonsapp):
30 cls = request.cls
30 cls = request.cls
31 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
31 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
32 cls.apikey = cls.usr.api_key
32 cls.apikey = cls.usr.api_key
33 cls.test_user = UserModel().create_or_update(
33 cls.test_user = UserModel().create_or_update(
34 username='test-api',
34 username='test-api',
35 password='test',
35 password='test',
36 email='test@api.rhodecode.org',
36 email='test@api.rhodecode.org',
37 firstname='first',
37 firstname='first',
38 lastname='last'
38 lastname='last'
39 )
39 )
40 Session().commit()
40 Session().commit()
41 cls.TEST_USER_LOGIN = cls.test_user.username
41 cls.TEST_USER_LOGIN = cls.test_user.username
42 cls.apikey_regular = cls.test_user.api_key
42 cls.apikey_regular = cls.test_user.api_key
@@ -1,62 +1,62 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.model.db import Repository, RepositoryField
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error)
25 build_data, api_call, assert_ok, assert_error)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestAddFieldToRepo(object):
29 class TestAddFieldToRepo(object):
30 def test_api_add_field_to_repo(self, backend):
30 def test_api_add_field_to_repo(self, backend):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 repo_name = repo.repo_name
32 repo_name = repo.repo_name
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'add_field_to_repo',
34 self.apikey, 'add_field_to_repo',
35 repoid=repo_name,
35 repoid=repo_name,
36 key='extra_field',
36 key='extra_field',
37 label='extra_field_label',
37 label='extra_field_label',
38 description='extra_field_desc')
38 description='extra_field_desc')
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'Added new repository field `extra_field`',
41 'msg': 'Added new repository field `extra_field`',
42 'success': True,
42 'success': True,
43 }
43 }
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 repo = Repository.get_by_repo_name(repo_name)
46 repo = Repository.get_by_repo_name(repo_name)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 _data = repo_field.get_dict()
48 _data = repo_field.get_dict()
49 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_desc'] == 'extra_field_desc'
50 assert _data['field_key'] == 'extra_field'
50 assert _data['field_key'] == 'extra_field'
51 assert _data['field_label'] == 'extra_field_label'
51 assert _data['field_label'] == 'extra_field_label'
52
52
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'add_field_to_repo',
54 self.apikey, 'add_field_to_repo',
55 repoid=repo_name,
55 repoid=repo_name,
56 key='extra_field',
56 key='extra_field',
57 label='extra_field_label',
57 label='extra_field_label',
58 description='extra_field_desc')
58 description='extra_field_desc')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
61 repo_name)
61 repo_name)
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
@@ -1,72 +1,72 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestAddUserToUserGroup(object):
30 class TestAddUserToUserGroup(object):
31 def test_api_add_user_to_user_group(self, user_util):
31 def test_api_add_user_to_user_group(self, user_util):
32 group = user_util.create_user_group()
32 group = user_util.create_user_group()
33 user = user_util.create_user()
33 user = user_util.create_user()
34 group_name = group.users_group_name
34 group_name = group.users_group_name
35 user_name = user.username
35 user_name = user.username
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'add_user_to_user_group',
37 self.apikey, 'add_user_to_user_group',
38 usergroupid=group_name, userid=user_name)
38 usergroupid=group_name, userid=user_name)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'added member `%s` to user group `%s`' % (
41 'msg': 'added member `%s` to user group `%s`' % (
42 user_name, group_name
42 user_name, group_name
43 ),
43 ),
44 'success': True
44 'success': True
45 }
45 }
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
49 user = user_util.create_user()
49 user = user_util.create_user()
50 user_name = user.username
50 user_name = user.username
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'add_user_to_user_group',
52 self.apikey, 'add_user_to_user_group',
53 usergroupid='false-group',
53 usergroupid='false-group',
54 userid=user_name)
54 userid=user_name)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56
56
57 expected = 'user group `%s` does not exist' % 'false-group'
57 expected = 'user group `%s` does not exist' % 'false-group'
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
62 group = user_util.create_user_group()
62 group = user_util.create_user_group()
63 user = user_util.create_user()
63 user = user_util.create_user()
64 group_name = group.users_group_name
64 group_name = group.users_group_name
65 user_name = user.username
65 user_name = user.username
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'add_user_to_user_group',
67 self.apikey, 'add_user_to_user_group',
68 usergroupid=group_name, userid=user_name)
68 usergroupid=group_name, userid=user_name)
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 expected = 'failed to add member to user group `%s`' % (group_name,)
71 expected = 'failed to add member to user group `%s`' % (group_name,)
72 assert_error(id_, expected, given=response.body)
72 assert_error(id_, expected, given=response.body)
@@ -1,125 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.utils import Optional, OAttr
23 from rhodecode.api.utils import Optional, OAttr
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApi(object):
29 class TestApi(object):
30 maxDiff = None
30 maxDiff = None
31
31
32 def test_Optional_object(self):
32 def test_Optional_object(self):
33
33
34 option1 = Optional(None)
34 option1 = Optional(None)
35 assert '<Optional:%s>' % (None,) == repr(option1)
35 assert '<Optional:%s>' % (None,) == repr(option1)
36 assert option1() is None
36 assert option1() is None
37
37
38 assert 1 == Optional.extract(Optional(1))
38 assert 1 == Optional.extract(Optional(1))
39 assert 'example' == Optional.extract('example')
39 assert 'example' == Optional.extract('example')
40
40
41 def test_Optional_OAttr(self):
41 def test_Optional_OAttr(self):
42 option1 = Optional(OAttr('apiuser'))
42 option1 = Optional(OAttr('apiuser'))
43 assert 'apiuser' == Optional.extract(option1)
43 assert 'apiuser' == Optional.extract(option1)
44
44
45 def test_OAttr_object(self):
45 def test_OAttr_object(self):
46 oattr1 = OAttr('apiuser')
46 oattr1 = OAttr('apiuser')
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 assert oattr1() == oattr1
48 assert oattr1() == oattr1
49
49
50 def test_api_wrong_key(self):
50 def test_api_wrong_key(self):
51 id_, params = build_data('trololo', 'get_user')
51 id_, params = build_data('trololo', 'get_user')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Invalid API KEY'
54 expected = 'Invalid API KEY'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 def test_api_missing_non_optional_param(self):
57 def test_api_missing_non_optional_param(self):
58 id_, params = build_data(self.apikey, 'get_repo')
58 id_, params = build_data(self.apikey, 'get_repo')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_missing_non_optional_param_args_null(self):
64 def test_api_missing_non_optional_param_args_null(self):
65 id_, params = build_data(self.apikey, 'get_repo')
65 id_, params = build_data(self.apikey, 'get_repo')
66 params = params.replace('"args": {}', '"args": null')
66 params = params.replace('"args": {}', '"args": null')
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 def test_api_missing_non_optional_param_args_bad(self):
72 def test_api_missing_non_optional_param_args_bad(self):
73 id_, params = build_data(self.apikey, 'get_repo')
73 id_, params = build_data(self.apikey, 'get_repo')
74 params = params.replace('"args": {}', '"args": 1')
74 params = params.replace('"args": {}', '"args": 1')
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
79
79
80 def test_api_non_existing_method(self, request):
80 def test_api_non_existing_method(self, request):
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'No such method: not_existing'
83 expected = 'No such method: not_existing'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_disabled_user(self, request):
86 def test_api_disabled_user(self, request):
87
87
88 def set_active(active):
88 def set_active(active):
89 from rhodecode.model.db import Session, User
89 from rhodecode.model.db import Session, User
90 user = User.get_by_auth_token(self.apikey)
90 user = User.get_by_auth_token(self.apikey)
91 user.active = active
91 user.active = active
92 Session().add(user)
92 Session().add(user)
93 Session().commit()
93 Session().commit()
94
94
95 request.addfinalizer(lambda: set_active(True))
95 request.addfinalizer(lambda: set_active(True))
96
96
97 set_active(False)
97 set_active(False)
98 id_, params = build_data(self.apikey, 'test', args='xx')
98 id_, params = build_data(self.apikey, 'test', args='xx')
99 response = api_call(self.app, params)
99 response = api_call(self.app, params)
100 expected = 'Request from this user not allowed'
100 expected = 'Request from this user not allowed'
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
102
102
103 def test_api_args_is_null(self):
103 def test_api_args_is_null(self):
104 __, params = build_data(self.apikey, 'get_users', )
104 __, params = build_data(self.apikey, 'get_users', )
105 params = params.replace('"args": {}', '"args": null')
105 params = params.replace('"args": {}', '"args": null')
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107 assert response.status == '200 OK'
107 assert response.status == '200 OK'
108
108
109 def test_api_args_is_bad(self):
109 def test_api_args_is_bad(self):
110 __, params = build_data(self.apikey, 'get_users', )
110 __, params = build_data(self.apikey, 'get_users', )
111 params = params.replace('"args": {}', '"args": 1')
111 params = params.replace('"args": {}', '"args": 1')
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113 assert response.status == '200 OK'
113 assert response.status == '200 OK'
114
114
115 def test_api_args_different_args(self):
115 def test_api_args_different_args(self):
116 import string
116 import string
117 expected = {
117 expected = {
118 'ascii_letters': string.ascii_letters,
118 'ascii_letters': string.ascii_letters,
119 'ws': string.whitespace,
119 'ws': string.whitespace,
120 'printables': string.printable
120 'printables': string.printable
121 }
121 }
122 id_, params = build_data(self.apikey, 'test', args=expected)
122 id_, params = build_data(self.apikey, 'test', args=expected)
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124 assert response.status == '200 OK'
124 assert response.status == '200 OK'
125 assert_ok(id_, expected, response.body)
125 assert_ok(id_, expected, response.body)
@@ -1,111 +1,111 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_close_pull_request(self, pr_util):
33 def test_api_close_pull_request(self, pr_util):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35 pull_request_id = pull_request.pull_request_id
35 pull_request_id = pull_request.pull_request_id
36 author = pull_request.user_id
36 author = pull_request.user_id
37 repo = pull_request.target_repo.repo_id
37 repo = pull_request.target_repo.repo_id
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'close_pull_request',
39 self.apikey, 'close_pull_request',
40 repoid=pull_request.target_repo.repo_name,
40 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id)
41 pullrequestid=pull_request.pull_request_id)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 expected = {
43 expected = {
44 'pull_request_id': pull_request_id,
44 'pull_request_id': pull_request_id,
45 'closed': True,
45 'closed': True,
46 }
46 }
47 assert_ok(id_, expected, response.body)
47 assert_ok(id_, expected, response.body)
48 action = 'user_closed_pull_request:%d' % pull_request_id
48 action = 'user_closed_pull_request:%d' % pull_request_id
49 journal = UserLog.query()\
49 journal = UserLog.query()\
50 .filter(UserLog.user_id == author)\
50 .filter(UserLog.user_id == author)\
51 .filter(UserLog.repository_id == repo)\
51 .filter(UserLog.repository_id == repo)\
52 .filter(UserLog.action == action)\
52 .filter(UserLog.action == action)\
53 .all()
53 .all()
54 assert len(journal) == 1
54 assert len(journal) == 1
55
55
56 @pytest.mark.backends("git", "hg")
56 @pytest.mark.backends("git", "hg")
57 def test_api_close_pull_request_already_closed_error(self, pr_util):
57 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 pull_request = pr_util.create_pull_request()
58 pull_request = pr_util.create_pull_request()
59 pull_request_id = pull_request.pull_request_id
59 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
60 pull_request_repo = pull_request.target_repo.repo_name
61 PullRequestModel().close_pull_request(
61 PullRequestModel().close_pull_request(
62 pull_request, pull_request.author)
62 pull_request, pull_request.author)
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey, 'close_pull_request',
64 self.apikey, 'close_pull_request',
65 repoid=pull_request_repo, pullrequestid=pull_request_id)
65 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'pull request `%s` is already closed' % pull_request_id
68 expected = 'pull request `%s` is already closed' % pull_request_id
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 @pytest.mark.backends("git", "hg")
71 @pytest.mark.backends("git", "hg")
72 def test_api_close_pull_request_repo_error(self):
72 def test_api_close_pull_request_repo_error(self):
73 id_, params = build_data(
73 id_, params = build_data(
74 self.apikey, 'close_pull_request',
74 self.apikey, 'close_pull_request',
75 repoid=666, pullrequestid=1)
75 repoid=666, pullrequestid=1)
76 response = api_call(self.app, params)
76 response = api_call(self.app, params)
77
77
78 expected = 'repository `666` does not exist'
78 expected = 'repository `666` does not exist'
79 assert_error(id_, expected, given=response.body)
79 assert_error(id_, expected, given=response.body)
80
80
81 @pytest.mark.backends("git", "hg")
81 @pytest.mark.backends("git", "hg")
82 def test_api_close_pull_request_non_admin_with_userid_error(self,
82 def test_api_close_pull_request_non_admin_with_userid_error(self,
83 pr_util):
83 pr_util):
84 pull_request = pr_util.create_pull_request()
84 pull_request = pr_util.create_pull_request()
85 id_, params = build_data(
85 id_, params = build_data(
86 self.apikey_regular, 'close_pull_request',
86 self.apikey_regular, 'close_pull_request',
87 repoid=pull_request.target_repo.repo_name,
87 repoid=pull_request.target_repo.repo_name,
88 pullrequestid=pull_request.pull_request_id,
88 pullrequestid=pull_request.pull_request_id,
89 userid=TEST_USER_ADMIN_LOGIN)
89 userid=TEST_USER_ADMIN_LOGIN)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 expected = 'userid is not the same as your user'
92 expected = 'userid is not the same as your user'
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 @pytest.mark.backends("git", "hg")
95 @pytest.mark.backends("git", "hg")
96 def test_api_close_pull_request_no_perms_to_close(
96 def test_api_close_pull_request_no_perms_to_close(
97 self, user_util, pr_util):
97 self, user_util, pr_util):
98 user = user_util.create_user()
98 user = user_util.create_user()
99 pull_request = pr_util.create_pull_request()
99 pull_request = pr_util.create_pull_request()
100
100
101 id_, params = build_data(
101 id_, params = build_data(
102 user.api_key, 'close_pull_request',
102 user.api_key, 'close_pull_request',
103 repoid=pull_request.target_repo.repo_name,
103 repoid=pull_request.target_repo.repo_name,
104 pullrequestid=pull_request.pull_request_id,)
104 pullrequestid=pull_request.pull_request_id,)
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106
106
107 expected = ('pull request `%s` close failed, '
107 expected = ('pull request `%s` close failed, '
108 'no permission to close.') % pull_request.pull_request_id
108 'no permission to close.') % pull_request.pull_request_id
109
109
110 response_json = response.json['error']
110 response_json = response.json['error']
111 assert response_json == expected
111 assert response_json == expected
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import ChangesetStatus
23 from rhodecode.model.db import ChangesetStatus
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestCommentCommit(object):
29 class TestCommentCommit(object):
30 def test_api_comment_commit_on_empty_repo(self, backend):
30 def test_api_comment_commit_on_empty_repo(self, backend):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'comment_commit', repoid=repo.repo_name,
33 self.apikey, 'comment_commit', repoid=repo.repo_name,
34 commit_id='tip', message='message', status_change=None)
34 commit_id='tip', message='message', status_change=None)
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36 expected = 'failed to set comment on repository `%s`' % repo.repo_name
36 expected = 'failed to set comment on repository `%s`' % repo.repo_name
37 assert_error(id_, expected, given=response.body)
37 assert_error(id_, expected, given=response.body)
38
38
39 @pytest.mark.parametrize("status_change, message, commit_id", [
39 @pytest.mark.parametrize("status_change, message, commit_id", [
40 (None, 'Hallo', 'tip'),
40 (None, 'Hallo', 'tip'),
41 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
41 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
42 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
42 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
43 ])
43 ])
44 def test_api_comment_commit(
44 def test_api_comment_commit(
45 self, backend, status_change, message, commit_id,
45 self, backend, status_change, message, commit_id,
46 no_notifications):
46 no_notifications):
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_commit', repoid=backend.repo_name,
48 self.apikey, 'comment_commit', repoid=backend.repo_name,
49 commit_id=commit_id, message=message, status=status_change)
49 commit_id=commit_id, message=message, status=status_change)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 repo = backend.repo.scm_instance()
51 repo = backend.repo.scm_instance()
52 expected = {
52 expected = {
53 'msg': 'Commented on commit `%s` for repository `%s`' % (
53 'msg': 'Commented on commit `%s` for repository `%s`' % (
54 repo.get_changeset().raw_id, backend.repo_name),
54 repo.get_changeset().raw_id, backend.repo_name),
55 'status_change': status_change,
55 'status_change': status_change,
56 'success': True
56 'success': True
57 }
57 }
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
@@ -1,209 +1,209 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import ChangesetCommentsModel
23 from rhodecode.model.comment import ChangesetCommentsModel
24 from rhodecode.model.db import UserLog
24 from rhodecode.model.db import UserLog
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCommentPullRequest(object):
32 class TestCommentPullRequest(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 @pytest.mark.backends("git", "hg")
41 @pytest.mark.backends("git", "hg")
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 pull_request = pr_util.create_pull_request()
43 pull_request = pr_util.create_pull_request()
44 pull_request_id = pull_request.pull_request_id
44 pull_request_id = pull_request.pull_request_id
45 author = pull_request.user_id
45 author = pull_request.user_id
46 repo = pull_request.target_repo.repo_id
46 repo = pull_request.target_repo.repo_id
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_pull_request',
48 self.apikey, 'comment_pull_request',
49 repoid=pull_request.target_repo.repo_name,
49 repoid=pull_request.target_repo.repo_name,
50 pullrequestid=pull_request.pull_request_id,
50 pullrequestid=pull_request.pull_request_id,
51 message='test message')
51 message='test message')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54
54
55 comments = ChangesetCommentsModel().get_comments(
55 comments = ChangesetCommentsModel().get_comments(
56 pull_request.target_repo.repo_id, pull_request=pull_request)
56 pull_request.target_repo.repo_id, pull_request=pull_request)
57
57
58 expected = {
58 expected = {
59 'pull_request_id': pull_request.pull_request_id,
59 'pull_request_id': pull_request.pull_request_id,
60 'comment_id': comments[-1].comment_id,
60 'comment_id': comments[-1].comment_id,
61 'status': {'given': None, 'was_changed': None}
61 'status': {'given': None, 'was_changed': None}
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 action = 'user_commented_pull_request:%d' % pull_request_id
65 action = 'user_commented_pull_request:%d' % pull_request_id
66 journal = UserLog.query()\
66 journal = UserLog.query()\
67 .filter(UserLog.user_id == author)\
67 .filter(UserLog.user_id == author)\
68 .filter(UserLog.repository_id == repo)\
68 .filter(UserLog.repository_id == repo)\
69 .filter(UserLog.action == action)\
69 .filter(UserLog.action == action)\
70 .all()
70 .all()
71 assert len(journal) == 2
71 assert len(journal) == 2
72
72
73 @pytest.mark.backends("git", "hg")
73 @pytest.mark.backends("git", "hg")
74 def test_api_comment_pull_request_change_status(
74 def test_api_comment_pull_request_change_status(
75 self, pr_util, no_notifications):
75 self, pr_util, no_notifications):
76 pull_request = pr_util.create_pull_request()
76 pull_request = pr_util.create_pull_request()
77 pull_request_id = pull_request.pull_request_id
77 pull_request_id = pull_request.pull_request_id
78 id_, params = build_data(
78 id_, params = build_data(
79 self.apikey, 'comment_pull_request',
79 self.apikey, 'comment_pull_request',
80 repoid=pull_request.target_repo.repo_name,
80 repoid=pull_request.target_repo.repo_name,
81 pullrequestid=pull_request.pull_request_id,
81 pullrequestid=pull_request.pull_request_id,
82 status='rejected')
82 status='rejected')
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84 pull_request = PullRequestModel().get(pull_request_id)
84 pull_request = PullRequestModel().get(pull_request_id)
85
85
86 comments = ChangesetCommentsModel().get_comments(
86 comments = ChangesetCommentsModel().get_comments(
87 pull_request.target_repo.repo_id, pull_request=pull_request)
87 pull_request.target_repo.repo_id, pull_request=pull_request)
88 expected = {
88 expected = {
89 'pull_request_id': pull_request.pull_request_id,
89 'pull_request_id': pull_request.pull_request_id,
90 'comment_id': comments[-1].comment_id,
90 'comment_id': comments[-1].comment_id,
91 'status': {'given': 'rejected', 'was_changed': True}
91 'status': {'given': 'rejected', 'was_changed': True}
92 }
92 }
93 assert_ok(id_, expected, response.body)
93 assert_ok(id_, expected, response.body)
94
94
95 @pytest.mark.backends("git", "hg")
95 @pytest.mark.backends("git", "hg")
96 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 def test_api_comment_pull_request_change_status_with_specific_commit_id(
97 self, pr_util, no_notifications):
97 self, pr_util, no_notifications):
98 pull_request = pr_util.create_pull_request()
98 pull_request = pr_util.create_pull_request()
99 pull_request_id = pull_request.pull_request_id
99 pull_request_id = pull_request.pull_request_id
100 latest_commit_id = 'test_commit'
100 latest_commit_id = 'test_commit'
101 # inject additional revision, to fail test the status change on
101 # inject additional revision, to fail test the status change on
102 # non-latest commit
102 # non-latest commit
103 pull_request.revisions = pull_request.revisions + ['test_commit']
103 pull_request.revisions = pull_request.revisions + ['test_commit']
104
104
105 id_, params = build_data(
105 id_, params = build_data(
106 self.apikey, 'comment_pull_request',
106 self.apikey, 'comment_pull_request',
107 repoid=pull_request.target_repo.repo_name,
107 repoid=pull_request.target_repo.repo_name,
108 pullrequestid=pull_request.pull_request_id,
108 pullrequestid=pull_request.pull_request_id,
109 status='approved', commit_id=latest_commit_id)
109 status='approved', commit_id=latest_commit_id)
110 response = api_call(self.app, params)
110 response = api_call(self.app, params)
111 pull_request = PullRequestModel().get(pull_request_id)
111 pull_request = PullRequestModel().get(pull_request_id)
112
112
113 expected = {
113 expected = {
114 'pull_request_id': pull_request.pull_request_id,
114 'pull_request_id': pull_request.pull_request_id,
115 'comment_id': None,
115 'comment_id': None,
116 'status': {'given': 'approved', 'was_changed': False}
116 'status': {'given': 'approved', 'was_changed': False}
117 }
117 }
118 assert_ok(id_, expected, response.body)
118 assert_ok(id_, expected, response.body)
119
119
120 @pytest.mark.backends("git", "hg")
120 @pytest.mark.backends("git", "hg")
121 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 def test_api_comment_pull_request_change_status_with_specific_commit_id(
122 self, pr_util, no_notifications):
122 self, pr_util, no_notifications):
123 pull_request = pr_util.create_pull_request()
123 pull_request = pr_util.create_pull_request()
124 pull_request_id = pull_request.pull_request_id
124 pull_request_id = pull_request.pull_request_id
125 latest_commit_id = pull_request.revisions[0]
125 latest_commit_id = pull_request.revisions[0]
126
126
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey, 'comment_pull_request',
128 self.apikey, 'comment_pull_request',
129 repoid=pull_request.target_repo.repo_name,
129 repoid=pull_request.target_repo.repo_name,
130 pullrequestid=pull_request.pull_request_id,
130 pullrequestid=pull_request.pull_request_id,
131 status='approved', commit_id=latest_commit_id)
131 status='approved', commit_id=latest_commit_id)
132 response = api_call(self.app, params)
132 response = api_call(self.app, params)
133 pull_request = PullRequestModel().get(pull_request_id)
133 pull_request = PullRequestModel().get(pull_request_id)
134
134
135 comments = ChangesetCommentsModel().get_comments(
135 comments = ChangesetCommentsModel().get_comments(
136 pull_request.target_repo.repo_id, pull_request=pull_request)
136 pull_request.target_repo.repo_id, pull_request=pull_request)
137 expected = {
137 expected = {
138 'pull_request_id': pull_request.pull_request_id,
138 'pull_request_id': pull_request.pull_request_id,
139 'comment_id': comments[-1].comment_id,
139 'comment_id': comments[-1].comment_id,
140 'status': {'given': 'approved', 'was_changed': True}
140 'status': {'given': 'approved', 'was_changed': True}
141 }
141 }
142 assert_ok(id_, expected, response.body)
142 assert_ok(id_, expected, response.body)
143
143
144 @pytest.mark.backends("git", "hg")
144 @pytest.mark.backends("git", "hg")
145 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 def test_api_comment_pull_request_missing_params_error(self, pr_util):
146 pull_request = pr_util.create_pull_request()
146 pull_request = pr_util.create_pull_request()
147 pull_request_id = pull_request.pull_request_id
147 pull_request_id = pull_request.pull_request_id
148 pull_request_repo = pull_request.target_repo.repo_name
148 pull_request_repo = pull_request.target_repo.repo_name
149 id_, params = build_data(
149 id_, params = build_data(
150 self.apikey, 'comment_pull_request',
150 self.apikey, 'comment_pull_request',
151 repoid=pull_request_repo,
151 repoid=pull_request_repo,
152 pullrequestid=pull_request_id)
152 pullrequestid=pull_request_id)
153 response = api_call(self.app, params)
153 response = api_call(self.app, params)
154
154
155 expected = 'Both message and status parameters are missing. At least one is required.'
155 expected = 'Both message and status parameters are missing. At least one is required.'
156 assert_error(id_, expected, given=response.body)
156 assert_error(id_, expected, given=response.body)
157
157
158 @pytest.mark.backends("git", "hg")
158 @pytest.mark.backends("git", "hg")
159 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
160 pull_request = pr_util.create_pull_request()
160 pull_request = pr_util.create_pull_request()
161 pull_request_id = pull_request.pull_request_id
161 pull_request_id = pull_request.pull_request_id
162 pull_request_repo = pull_request.target_repo.repo_name
162 pull_request_repo = pull_request.target_repo.repo_name
163 id_, params = build_data(
163 id_, params = build_data(
164 self.apikey, 'comment_pull_request',
164 self.apikey, 'comment_pull_request',
165 repoid=pull_request_repo,
165 repoid=pull_request_repo,
166 pullrequestid=pull_request_id,
166 pullrequestid=pull_request_id,
167 status='42')
167 status='42')
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169
169
170 expected = 'Unknown comment status: `42`'
170 expected = 'Unknown comment status: `42`'
171 assert_error(id_, expected, given=response.body)
171 assert_error(id_, expected, given=response.body)
172
172
173 @pytest.mark.backends("git", "hg")
173 @pytest.mark.backends("git", "hg")
174 def test_api_comment_pull_request_repo_error(self):
174 def test_api_comment_pull_request_repo_error(self):
175 id_, params = build_data(
175 id_, params = build_data(
176 self.apikey, 'comment_pull_request',
176 self.apikey, 'comment_pull_request',
177 repoid=666, pullrequestid=1)
177 repoid=666, pullrequestid=1)
178 response = api_call(self.app, params)
178 response = api_call(self.app, params)
179
179
180 expected = 'repository `666` does not exist'
180 expected = 'repository `666` does not exist'
181 assert_error(id_, expected, given=response.body)
181 assert_error(id_, expected, given=response.body)
182
182
183 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
184 def test_api_comment_pull_request_non_admin_with_userid_error(
184 def test_api_comment_pull_request_non_admin_with_userid_error(
185 self, pr_util):
185 self, pr_util):
186 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
187 id_, params = build_data(
187 id_, params = build_data(
188 self.apikey_regular, 'comment_pull_request',
188 self.apikey_regular, 'comment_pull_request',
189 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=pull_request.pull_request_id,
190 pullrequestid=pull_request.pull_request_id,
191 userid=TEST_USER_ADMIN_LOGIN)
191 userid=TEST_USER_ADMIN_LOGIN)
192 response = api_call(self.app, params)
192 response = api_call(self.app, params)
193
193
194 expected = 'userid is not the same as your user'
194 expected = 'userid is not the same as your user'
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
199 pull_request = pr_util.create_pull_request()
199 pull_request = pr_util.create_pull_request()
200 id_, params = build_data(
200 id_, params = build_data(
201 self.apikey_regular, 'comment_pull_request',
201 self.apikey_regular, 'comment_pull_request',
202 repoid=pull_request.target_repo.repo_name,
202 repoid=pull_request.target_repo.repo_name,
203 status='approved',
203 status='approved',
204 pullrequestid=pull_request.pull_request_id,
204 pullrequestid=pull_request.pull_request_id,
205 commit_id='XXX')
205 commit_id='XXX')
206 response = api_call(self.app, params)
206 response = api_call(self.app, params)
207
207
208 expected = 'Invalid commit_id `XXX` for this pull request.'
208 expected = 'Invalid commit_id `XXX` for this pull request.'
209 assert_error(id_, expected, given=response.body)
209 assert_error(id_, expected, given=response.body)
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.model.gist import GistModel
25 from rhodecode.model.gist import GistModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestApiCreateGist(object):
32 class TestApiCreateGist(object):
33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
38 ])
38 ])
39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey_regular, 'create_gist',
41 self.apikey_regular, 'create_gist',
42 lifetime=lifetime,
42 lifetime=lifetime,
43 description='foobar-gist',
43 description='foobar-gist',
44 gist_type=gist_type,
44 gist_type=gist_type,
45 acl_level=gist_acl_level,
45 acl_level=gist_acl_level,
46 files={'foobar_ąć': {'content': 'foo'}})
46 files={'foobar_ąć': {'content': 'foo'}})
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48 response_json = response.json
48 response_json = response.json
49 gist = response_json['result']['gist']
49 gist = response_json['result']['gist']
50 expected = {
50 expected = {
51 'gist': {
51 'gist': {
52 'access_id': gist['access_id'],
52 'access_id': gist['access_id'],
53 'created_on': gist['created_on'],
53 'created_on': gist['created_on'],
54 'modified_at': gist['modified_at'],
54 'modified_at': gist['modified_at'],
55 'description': 'foobar-gist',
55 'description': 'foobar-gist',
56 'expires': gist['expires'],
56 'expires': gist['expires'],
57 'gist_id': gist['gist_id'],
57 'gist_id': gist['gist_id'],
58 'type': gist_type,
58 'type': gist_type,
59 'url': gist['url'],
59 'url': gist['url'],
60 # content is empty since we don't show it here
60 # content is empty since we don't show it here
61 'content': None,
61 'content': None,
62 'acl_level': gist_acl_level,
62 'acl_level': gist_acl_level,
63 },
63 },
64 'msg': 'created new gist'
64 'msg': 'created new gist'
65 }
65 }
66 try:
66 try:
67 assert_ok(id_, expected, given=response.body)
67 assert_ok(id_, expected, given=response.body)
68 finally:
68 finally:
69 Fixture().destroy_gists()
69 Fixture().destroy_gists()
70
70
71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
72 ({'gist_type': '"ups" is not one of private, public'},
72 ({'gist_type': '"ups" is not one of private, public'},
73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
74
74
75 ({'lifetime': '-120 is less than minimum value -1'},
75 ({'lifetime': '-120 is less than minimum value -1'},
76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
77
77
78 ({'0.content': 'Required'},
78 ({'0.content': 'Required'},
79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
80 ])
80 ])
81 def test_api_try_create_gist(
81 def test_api_try_create_gist(
82 self, expected, lifetime, gist_type, gist_acl_level, files):
82 self, expected, lifetime, gist_type, gist_acl_level, files):
83 id_, params = build_data(
83 id_, params = build_data(
84 self.apikey_regular, 'create_gist',
84 self.apikey_regular, 'create_gist',
85 lifetime=lifetime,
85 lifetime=lifetime,
86 description='foobar-gist',
86 description='foobar-gist',
87 gist_type=gist_type,
87 gist_type=gist_type,
88 acl_level=gist_acl_level,
88 acl_level=gist_acl_level,
89 files=files)
89 files=files)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 try:
92 try:
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94 finally:
94 finally:
95 Fixture().destroy_gists()
95 Fixture().destroy_gists()
96
96
97 @mock.patch.object(GistModel, 'create', crash)
97 @mock.patch.object(GistModel, 'create', crash)
98 def test_api_create_gist_exception_occurred(self):
98 def test_api_create_gist_exception_occurred(self):
99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
100 response = api_call(self.app, params)
100 response = api_call(self.app, params)
101 expected = 'failed to create gist'
101 expected = 'failed to create gist'
102 assert_error(id_, expected, given=response.body)
102 assert_error(id_, expected, given=response.body)
@@ -1,279 +1,279 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCreatePullRequestApi(object):
32 class TestCreatePullRequestApi(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 def test_create_with_wrong_data(self):
41 def test_create_with_wrong_data(self):
42 required_data = {
42 required_data = {
43 'source_repo': 'tests/source_repo',
43 'source_repo': 'tests/source_repo',
44 'target_repo': 'tests/target_repo',
44 'target_repo': 'tests/target_repo',
45 'source_ref': 'branch:default:initial',
45 'source_ref': 'branch:default:initial',
46 'target_ref': 'branch:default:new-feature',
46 'target_ref': 'branch:default:new-feature',
47 'title': 'Test PR 1'
47 'title': 'Test PR 1'
48 }
48 }
49 for key in required_data:
49 for key in required_data:
50 data = required_data.copy()
50 data = required_data.copy()
51 data.pop(key)
51 data.pop(key)
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_pull_request', **data)
53 self.apikey, 'create_pull_request', **data)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.backends("git", "hg")
59 @pytest.mark.backends("git", "hg")
60 def test_create_with_correct_data(self, backend):
60 def test_create_with_correct_data(self, backend):
61 data = self._prepare_data(backend)
61 data = self._prepare_data(backend)
62 RepoModel().revoke_user_permission(
62 RepoModel().revoke_user_permission(
63 self.source.repo_name, User.DEFAULT_USER)
63 self.source.repo_name, User.DEFAULT_USER)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_pull_request', **data)
65 self.apikey_regular, 'create_pull_request', **data)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67 expected_message = "Created new pull request `{title}`".format(
67 expected_message = "Created new pull request `{title}`".format(
68 title=data['title'])
68 title=data['title'])
69 result = response.json
69 result = response.json
70 assert result['result']['msg'] == expected_message
70 assert result['result']['msg'] == expected_message
71 pull_request_id = result['result']['pull_request_id']
71 pull_request_id = result['result']['pull_request_id']
72 pull_request = PullRequestModel().get(pull_request_id)
72 pull_request = PullRequestModel().get(pull_request_id)
73 assert pull_request.title == data['title']
73 assert pull_request.title == data['title']
74 assert pull_request.description == data['description']
74 assert pull_request.description == data['description']
75 assert pull_request.source_ref == data['source_ref']
75 assert pull_request.source_ref == data['source_ref']
76 assert pull_request.target_ref == data['target_ref']
76 assert pull_request.target_ref == data['target_ref']
77 assert pull_request.source_repo.repo_name == data['source_repo']
77 assert pull_request.source_repo.repo_name == data['source_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
79 assert pull_request.revisions == [self.commit_ids['change']]
79 assert pull_request.revisions == [self.commit_ids['change']]
80 assert pull_request.reviewers == []
80 assert pull_request.reviewers == []
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_create_with_empty_description(self, backend):
83 def test_create_with_empty_description(self, backend):
84 data = self._prepare_data(backend)
84 data = self._prepare_data(backend)
85 data.pop('description')
85 data.pop('description')
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'create_pull_request', **data)
87 self.apikey_regular, 'create_pull_request', **data)
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected_message = "Created new pull request `{title}`".format(
89 expected_message = "Created new pull request `{title}`".format(
90 title=data['title'])
90 title=data['title'])
91 result = response.json
91 result = response.json
92 assert result['result']['msg'] == expected_message
92 assert result['result']['msg'] == expected_message
93 pull_request_id = result['result']['pull_request_id']
93 pull_request_id = result['result']['pull_request_id']
94 pull_request = PullRequestModel().get(pull_request_id)
94 pull_request = PullRequestModel().get(pull_request_id)
95 assert pull_request.description == ''
95 assert pull_request.description == ''
96
96
97 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
98 def test_create_with_reviewers_specified_by_names(
98 def test_create_with_reviewers_specified_by_names(
99 self, backend, no_notifications):
99 self, backend, no_notifications):
100 data = self._prepare_data(backend)
100 data = self._prepare_data(backend)
101 reviewers = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
101 reviewers = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
102 data['reviewers'] = reviewers
102 data['reviewers'] = reviewers
103 id_, params = build_data(
103 id_, params = build_data(
104 self.apikey_regular, 'create_pull_request', **data)
104 self.apikey_regular, 'create_pull_request', **data)
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106
106
107 expected_message = "Created new pull request `{title}`".format(
107 expected_message = "Created new pull request `{title}`".format(
108 title=data['title'])
108 title=data['title'])
109 result = response.json
109 result = response.json
110 assert result['result']['msg'] == expected_message
110 assert result['result']['msg'] == expected_message
111 pull_request_id = result['result']['pull_request_id']
111 pull_request_id = result['result']['pull_request_id']
112 pull_request = PullRequestModel().get(pull_request_id)
112 pull_request = PullRequestModel().get(pull_request_id)
113 actual_reviewers = [r.user.username for r in pull_request.reviewers]
113 actual_reviewers = [r.user.username for r in pull_request.reviewers]
114 assert sorted(actual_reviewers) == sorted(reviewers)
114 assert sorted(actual_reviewers) == sorted(reviewers)
115
115
116 @pytest.mark.backends("git", "hg")
116 @pytest.mark.backends("git", "hg")
117 def test_create_with_reviewers_specified_by_ids(
117 def test_create_with_reviewers_specified_by_ids(
118 self, backend, no_notifications):
118 self, backend, no_notifications):
119 data = self._prepare_data(backend)
119 data = self._prepare_data(backend)
120 reviewer_names = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
120 reviewer_names = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
121 reviewers = [
121 reviewers = [
122 UserModel().get_by_username(n).user_id for n in reviewer_names]
122 UserModel().get_by_username(n).user_id for n in reviewer_names]
123 data['reviewers'] = reviewers
123 data['reviewers'] = reviewers
124 id_, params = build_data(
124 id_, params = build_data(
125 self.apikey_regular, 'create_pull_request', **data)
125 self.apikey_regular, 'create_pull_request', **data)
126 response = api_call(self.app, params)
126 response = api_call(self.app, params)
127
127
128 expected_message = "Created new pull request `{title}`".format(
128 expected_message = "Created new pull request `{title}`".format(
129 title=data['title'])
129 title=data['title'])
130 result = response.json
130 result = response.json
131 assert result['result']['msg'] == expected_message
131 assert result['result']['msg'] == expected_message
132 pull_request_id = result['result']['pull_request_id']
132 pull_request_id = result['result']['pull_request_id']
133 pull_request = PullRequestModel().get(pull_request_id)
133 pull_request = PullRequestModel().get(pull_request_id)
134 actual_reviewers = [r.user.username for r in pull_request.reviewers]
134 actual_reviewers = [r.user.username for r in pull_request.reviewers]
135 assert sorted(actual_reviewers) == sorted(reviewer_names)
135 assert sorted(actual_reviewers) == sorted(reviewer_names)
136
136
137 @pytest.mark.backends("git", "hg")
137 @pytest.mark.backends("git", "hg")
138 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
138 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
139 data = self._prepare_data(backend)
139 data = self._prepare_data(backend)
140 reviewers = ['somebody']
140 reviewers = ['somebody']
141 data['reviewers'] = reviewers
141 data['reviewers'] = reviewers
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey_regular, 'create_pull_request', **data)
143 self.apikey_regular, 'create_pull_request', **data)
144 response = api_call(self.app, params)
144 response = api_call(self.app, params)
145 expected_message = 'user `somebody` does not exist'
145 expected_message = 'user `somebody` does not exist'
146 assert_error(id_, expected_message, given=response.body)
146 assert_error(id_, expected_message, given=response.body)
147
147
148 @pytest.mark.backends("git", "hg")
148 @pytest.mark.backends("git", "hg")
149 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
149 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
150 data = self._prepare_data(backend)
150 data = self._prepare_data(backend)
151 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
151 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
152 data['reviewers'] = reviewers
152 data['reviewers'] = reviewers
153 id_, params = build_data(
153 id_, params = build_data(
154 self.apikey_regular, 'create_pull_request', **data)
154 self.apikey_regular, 'create_pull_request', **data)
155 response = api_call(self.app, params)
155 response = api_call(self.app, params)
156 expected_message = 'reviewers should be specified as a list'
156 expected_message = 'reviewers should be specified as a list'
157 assert_error(id_, expected_message, given=response.body)
157 assert_error(id_, expected_message, given=response.body)
158
158
159 @pytest.mark.backends("git", "hg")
159 @pytest.mark.backends("git", "hg")
160 def test_create_with_no_commit_hashes(self, backend):
160 def test_create_with_no_commit_hashes(self, backend):
161 data = self._prepare_data(backend)
161 data = self._prepare_data(backend)
162 expected_source_ref = data['source_ref']
162 expected_source_ref = data['source_ref']
163 expected_target_ref = data['target_ref']
163 expected_target_ref = data['target_ref']
164 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
164 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
165 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
165 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
166 id_, params = build_data(
166 id_, params = build_data(
167 self.apikey_regular, 'create_pull_request', **data)
167 self.apikey_regular, 'create_pull_request', **data)
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169 expected_message = "Created new pull request `{title}`".format(
169 expected_message = "Created new pull request `{title}`".format(
170 title=data['title'])
170 title=data['title'])
171 result = response.json
171 result = response.json
172 assert result['result']['msg'] == expected_message
172 assert result['result']['msg'] == expected_message
173 pull_request_id = result['result']['pull_request_id']
173 pull_request_id = result['result']['pull_request_id']
174 pull_request = PullRequestModel().get(pull_request_id)
174 pull_request = PullRequestModel().get(pull_request_id)
175 assert pull_request.source_ref == expected_source_ref
175 assert pull_request.source_ref == expected_source_ref
176 assert pull_request.target_ref == expected_target_ref
176 assert pull_request.target_ref == expected_target_ref
177
177
178 @pytest.mark.backends("git", "hg")
178 @pytest.mark.backends("git", "hg")
179 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
179 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
180 def test_create_fails_with_wrong_repo(self, backend, data_key):
180 def test_create_fails_with_wrong_repo(self, backend, data_key):
181 repo_name = 'fake-repo'
181 repo_name = 'fake-repo'
182 data = self._prepare_data(backend)
182 data = self._prepare_data(backend)
183 data[data_key] = repo_name
183 data[data_key] = repo_name
184 id_, params = build_data(
184 id_, params = build_data(
185 self.apikey_regular, 'create_pull_request', **data)
185 self.apikey_regular, 'create_pull_request', **data)
186 response = api_call(self.app, params)
186 response = api_call(self.app, params)
187 expected_message = 'repository `{}` does not exist'.format(repo_name)
187 expected_message = 'repository `{}` does not exist'.format(repo_name)
188 assert_error(id_, expected_message, given=response.body)
188 assert_error(id_, expected_message, given=response.body)
189
189
190 @pytest.mark.backends("git", "hg")
190 @pytest.mark.backends("git", "hg")
191 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
191 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
192 def test_create_fails_with_non_existing_branch(self, backend, data_key):
192 def test_create_fails_with_non_existing_branch(self, backend, data_key):
193 branch_name = 'test-branch'
193 branch_name = 'test-branch'
194 data = self._prepare_data(backend)
194 data = self._prepare_data(backend)
195 data[data_key] = "branch:{}".format(branch_name)
195 data[data_key] = "branch:{}".format(branch_name)
196 id_, params = build_data(
196 id_, params = build_data(
197 self.apikey_regular, 'create_pull_request', **data)
197 self.apikey_regular, 'create_pull_request', **data)
198 response = api_call(self.app, params)
198 response = api_call(self.app, params)
199 expected_message = 'The specified branch `{}` does not exist'.format(
199 expected_message = 'The specified branch `{}` does not exist'.format(
200 branch_name)
200 branch_name)
201 assert_error(id_, expected_message, given=response.body)
201 assert_error(id_, expected_message, given=response.body)
202
202
203 @pytest.mark.backends("git", "hg")
203 @pytest.mark.backends("git", "hg")
204 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
204 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
205 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
205 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
206 data = self._prepare_data(backend)
206 data = self._prepare_data(backend)
207 ref = 'stange-ref'
207 ref = 'stange-ref'
208 data[data_key] = ref
208 data[data_key] = ref
209 id_, params = build_data(
209 id_, params = build_data(
210 self.apikey_regular, 'create_pull_request', **data)
210 self.apikey_regular, 'create_pull_request', **data)
211 response = api_call(self.app, params)
211 response = api_call(self.app, params)
212 expected_message = (
212 expected_message = (
213 'Ref `{ref}` given in a wrong format. Please check the API'
213 'Ref `{ref}` given in a wrong format. Please check the API'
214 ' documentation for more details'.format(ref=ref))
214 ' documentation for more details'.format(ref=ref))
215 assert_error(id_, expected_message, given=response.body)
215 assert_error(id_, expected_message, given=response.body)
216
216
217 @pytest.mark.backends("git", "hg")
217 @pytest.mark.backends("git", "hg")
218 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
218 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
219 def test_create_fails_with_non_existing_ref(self, backend, data_key):
219 def test_create_fails_with_non_existing_ref(self, backend, data_key):
220 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
220 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
221 ref = self._get_full_ref(backend, commit_id)
221 ref = self._get_full_ref(backend, commit_id)
222 data = self._prepare_data(backend)
222 data = self._prepare_data(backend)
223 data[data_key] = ref
223 data[data_key] = ref
224 id_, params = build_data(
224 id_, params = build_data(
225 self.apikey_regular, 'create_pull_request', **data)
225 self.apikey_regular, 'create_pull_request', **data)
226 response = api_call(self.app, params)
226 response = api_call(self.app, params)
227 expected_message = 'Ref `{}` does not exist'.format(ref)
227 expected_message = 'Ref `{}` does not exist'.format(ref)
228 assert_error(id_, expected_message, given=response.body)
228 assert_error(id_, expected_message, given=response.body)
229
229
230 @pytest.mark.backends("git", "hg")
230 @pytest.mark.backends("git", "hg")
231 def test_create_fails_when_no_revisions(self, backend):
231 def test_create_fails_when_no_revisions(self, backend):
232 data = self._prepare_data(backend, source_head='initial')
232 data = self._prepare_data(backend, source_head='initial')
233 id_, params = build_data(
233 id_, params = build_data(
234 self.apikey_regular, 'create_pull_request', **data)
234 self.apikey_regular, 'create_pull_request', **data)
235 response = api_call(self.app, params)
235 response = api_call(self.app, params)
236 expected_message = 'no commits found'
236 expected_message = 'no commits found'
237 assert_error(id_, expected_message, given=response.body)
237 assert_error(id_, expected_message, given=response.body)
238
238
239 @pytest.mark.backends("git", "hg")
239 @pytest.mark.backends("git", "hg")
240 def test_create_fails_when_no_permissions(self, backend):
240 def test_create_fails_when_no_permissions(self, backend):
241 data = self._prepare_data(backend)
241 data = self._prepare_data(backend)
242 RepoModel().revoke_user_permission(
242 RepoModel().revoke_user_permission(
243 self.source.repo_name, User.DEFAULT_USER)
243 self.source.repo_name, User.DEFAULT_USER)
244 RepoModel().revoke_user_permission(
244 RepoModel().revoke_user_permission(
245 self.source.repo_name, self.test_user)
245 self.source.repo_name, self.test_user)
246 id_, params = build_data(
246 id_, params = build_data(
247 self.apikey_regular, 'create_pull_request', **data)
247 self.apikey_regular, 'create_pull_request', **data)
248 response = api_call(self.app, params)
248 response = api_call(self.app, params)
249 expected_message = 'repository `{}` does not exist'.format(
249 expected_message = 'repository `{}` does not exist'.format(
250 self.source.repo_name)
250 self.source.repo_name)
251 assert_error(id_, expected_message, given=response.body)
251 assert_error(id_, expected_message, given=response.body)
252
252
253 def _prepare_data(
253 def _prepare_data(
254 self, backend, source_head='change', target_head='initial'):
254 self, backend, source_head='change', target_head='initial'):
255 commits = [
255 commits = [
256 {'message': 'initial'},
256 {'message': 'initial'},
257 {'message': 'change'},
257 {'message': 'change'},
258 {'message': 'new-feature', 'parents': ['initial']},
258 {'message': 'new-feature', 'parents': ['initial']},
259 ]
259 ]
260 self.commit_ids = backend.create_master_repo(commits)
260 self.commit_ids = backend.create_master_repo(commits)
261 self.source = backend.create_repo(heads=[source_head])
261 self.source = backend.create_repo(heads=[source_head])
262 self.target = backend.create_repo(heads=[target_head])
262 self.target = backend.create_repo(heads=[target_head])
263 data = {
263 data = {
264 'source_repo': self.source.repo_name,
264 'source_repo': self.source.repo_name,
265 'target_repo': self.target.repo_name,
265 'target_repo': self.target.repo_name,
266 'source_ref': self._get_full_ref(
266 'source_ref': self._get_full_ref(
267 backend, self.commit_ids[source_head]),
267 backend, self.commit_ids[source_head]),
268 'target_ref': self._get_full_ref(
268 'target_ref': self._get_full_ref(
269 backend, self.commit_ids[target_head]),
269 backend, self.commit_ids[target_head]),
270 'title': 'Test PR 1',
270 'title': 'Test PR 1',
271 'description': 'Test'
271 'description': 'Test'
272 }
272 }
273 RepoModel().grant_user_permission(
273 RepoModel().grant_user_permission(
274 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
274 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
275 return data
275 return data
276
276
277 def _get_full_ref(self, backend, commit_id):
277 def _get_full_ref(self, backend, commit_id):
278 return 'branch:{branch}:{commit_id}'.format(
278 return 'branch:{branch}:{commit_id}'.format(
279 branch=backend.default_branch_name, commit_id=commit_id)
279 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,350 +1,350 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.utils2 import safe_unicode
26 from rhodecode.lib.utils2 import safe_unicode
27 from rhodecode.lib.vcs import settings
27 from rhodecode.lib.vcs import settings
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 from rhodecode.api.tests.utils import (
32 from rhodecode.api.tests.utils import (
33 build_data, api_call, assert_ok, assert_error, crash)
33 build_data, api_call, assert_ok, assert_error, crash)
34 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.fixture import Fixture
35
35
36
36
37 fixture = Fixture()
37 fixture = Fixture()
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestCreateRepo(object):
41 class TestCreateRepo(object):
42
42
43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
44 ('api repo-1', 'api-repo-1', False),
44 ('api repo-1', 'api-repo-1', False),
45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
46 (u'unicode-ąć', u'unicode-ąć', False),
46 (u'unicode-ąć', u'unicode-ąć', False),
47 ('some repo v1.2', 'some-repo-v1.2', False),
47 ('some repo v1.2', 'some-repo-v1.2', False),
48 ('v2.0', 'v2.0', False),
48 ('v2.0', 'v2.0', False),
49 ])
49 ])
50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
51
51
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey,
53 self.apikey,
54 'create_repo',
54 'create_repo',
55 repo_name=given,
55 repo_name=given,
56 owner=TEST_USER_ADMIN_LOGIN,
56 owner=TEST_USER_ADMIN_LOGIN,
57 repo_type=backend.alias,
57 repo_type=backend.alias,
58 )
58 )
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 ret = {
61 ret = {
62 'msg': 'Created new repository `%s`' % (expected_name,),
62 'msg': 'Created new repository `%s`' % (expected_name,),
63 'success': True,
63 'success': True,
64 'task': None,
64 'task': None,
65 }
65 }
66 expected = ret
66 expected = ret
67 assert_ok(id_, expected, given=response.body)
67 assert_ok(id_, expected, given=response.body)
68
68
69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
70 assert repo is not None
70 assert repo is not None
71
71
72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74 body = json.loads(response.body)
74 body = json.loads(response.body)
75
75
76 assert body['result']['enable_downloads'] is False
76 assert body['result']['enable_downloads'] is False
77 assert body['result']['enable_locking'] is False
77 assert body['result']['enable_locking'] is False
78 assert body['result']['enable_statistics'] is False
78 assert body['result']['enable_statistics'] is False
79
79
80 fixture.destroy_repo(safe_unicode(expected_name))
80 fixture.destroy_repo(safe_unicode(expected_name))
81
81
82 def test_api_create_restricted_repo_type(self, backend):
82 def test_api_create_restricted_repo_type(self, backend):
83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey,
85 self.apikey,
86 'create_repo',
86 'create_repo',
87 repo_name=repo_name,
87 repo_name=repo_name,
88 owner=TEST_USER_ADMIN_LOGIN,
88 owner=TEST_USER_ADMIN_LOGIN,
89 repo_type=backend.alias,
89 repo_type=backend.alias,
90 )
90 )
91 git_backend = settings.BACKENDS['git']
91 git_backend = settings.BACKENDS['git']
92 with mock.patch(
92 with mock.patch(
93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
94 response = api_call(self.app, params)
94 response = api_call(self.app, params)
95
95
96 repo = RepoModel().get_by_repo_name(repo_name)
96 repo = RepoModel().get_by_repo_name(repo_name)
97
97
98 if backend.alias == 'git':
98 if backend.alias == 'git':
99 assert repo is not None
99 assert repo is not None
100 expected = {
100 expected = {
101 'msg': 'Created new repository `{0}`'.format(repo_name,),
101 'msg': 'Created new repository `{0}`'.format(repo_name,),
102 'success': True,
102 'success': True,
103 'task': None,
103 'task': None,
104 }
104 }
105 assert_ok(id_, expected, given=response.body)
105 assert_ok(id_, expected, given=response.body)
106 else:
106 else:
107 assert repo is None
107 assert repo is None
108
108
109 fixture.destroy_repo(repo_name)
109 fixture.destroy_repo(repo_name)
110
110
111 def test_api_create_repo_with_booleans(self, backend):
111 def test_api_create_repo_with_booleans(self, backend):
112 repo_name = 'api-repo-2'
112 repo_name = 'api-repo-2'
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey,
114 self.apikey,
115 'create_repo',
115 'create_repo',
116 repo_name=repo_name,
116 repo_name=repo_name,
117 owner=TEST_USER_ADMIN_LOGIN,
117 owner=TEST_USER_ADMIN_LOGIN,
118 repo_type=backend.alias,
118 repo_type=backend.alias,
119 enable_statistics=True,
119 enable_statistics=True,
120 enable_locking=True,
120 enable_locking=True,
121 enable_downloads=True
121 enable_downloads=True
122 )
122 )
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124
124
125 repo = RepoModel().get_by_repo_name(repo_name)
125 repo = RepoModel().get_by_repo_name(repo_name)
126
126
127 assert repo is not None
127 assert repo is not None
128 ret = {
128 ret = {
129 'msg': 'Created new repository `%s`' % (repo_name,),
129 'msg': 'Created new repository `%s`' % (repo_name,),
130 'success': True,
130 'success': True,
131 'task': None,
131 'task': None,
132 }
132 }
133 expected = ret
133 expected = ret
134 assert_ok(id_, expected, given=response.body)
134 assert_ok(id_, expected, given=response.body)
135
135
136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
137 response = api_call(self.app, params)
137 response = api_call(self.app, params)
138 body = json.loads(response.body)
138 body = json.loads(response.body)
139
139
140 assert body['result']['enable_downloads'] is True
140 assert body['result']['enable_downloads'] is True
141 assert body['result']['enable_locking'] is True
141 assert body['result']['enable_locking'] is True
142 assert body['result']['enable_statistics'] is True
142 assert body['result']['enable_statistics'] is True
143
143
144 fixture.destroy_repo(repo_name)
144 fixture.destroy_repo(repo_name)
145
145
146 def test_api_create_repo_in_group(self, backend):
146 def test_api_create_repo_in_group(self, backend):
147 repo_group_name = 'my_gr'
147 repo_group_name = 'my_gr'
148 # create the parent
148 # create the parent
149 fixture.create_repo_group(repo_group_name)
149 fixture.create_repo_group(repo_group_name)
150
150
151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
152 id_, params = build_data(
152 id_, params = build_data(
153 self.apikey, 'create_repo',
153 self.apikey, 'create_repo',
154 repo_name=repo_name,
154 repo_name=repo_name,
155 owner=TEST_USER_ADMIN_LOGIN,
155 owner=TEST_USER_ADMIN_LOGIN,
156 repo_type=backend.alias,)
156 repo_type=backend.alias,)
157 response = api_call(self.app, params)
157 response = api_call(self.app, params)
158 repo = RepoModel().get_by_repo_name(repo_name)
158 repo = RepoModel().get_by_repo_name(repo_name)
159 assert repo is not None
159 assert repo is not None
160 assert repo.group is not None
160 assert repo.group is not None
161
161
162 ret = {
162 ret = {
163 'msg': 'Created new repository `%s`' % (repo_name,),
163 'msg': 'Created new repository `%s`' % (repo_name,),
164 'success': True,
164 'success': True,
165 'task': None,
165 'task': None,
166 }
166 }
167 expected = ret
167 expected = ret
168 assert_ok(id_, expected, given=response.body)
168 assert_ok(id_, expected, given=response.body)
169 fixture.destroy_repo(repo_name)
169 fixture.destroy_repo(repo_name)
170 fixture.destroy_repo_group(repo_group_name)
170 fixture.destroy_repo_group(repo_group_name)
171
171
172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
173 repo_group_name = 'fake_group'
173 repo_group_name = 'fake_group'
174
174
175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
176 id_, params = build_data(
176 id_, params = build_data(
177 self.apikey, 'create_repo',
177 self.apikey, 'create_repo',
178 repo_name=repo_name,
178 repo_name=repo_name,
179 owner=TEST_USER_ADMIN_LOGIN,
179 owner=TEST_USER_ADMIN_LOGIN,
180 repo_type=backend.alias,)
180 repo_type=backend.alias,)
181 response = api_call(self.app, params)
181 response = api_call(self.app, params)
182
182
183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
184 repo_group_name)}
184 repo_group_name)}
185 assert_error(id_, expected, given=response.body)
185 assert_error(id_, expected, given=response.body)
186
186
187 def test_api_create_repo_unknown_owner(self, backend):
187 def test_api_create_repo_unknown_owner(self, backend):
188 repo_name = 'api-repo-2'
188 repo_name = 'api-repo-2'
189 owner = 'i-dont-exist'
189 owner = 'i-dont-exist'
190 id_, params = build_data(
190 id_, params = build_data(
191 self.apikey, 'create_repo',
191 self.apikey, 'create_repo',
192 repo_name=repo_name,
192 repo_name=repo_name,
193 owner=owner,
193 owner=owner,
194 repo_type=backend.alias)
194 repo_type=backend.alias)
195 response = api_call(self.app, params)
195 response = api_call(self.app, params)
196 expected = 'user `%s` does not exist' % (owner,)
196 expected = 'user `%s` does not exist' % (owner,)
197 assert_error(id_, expected, given=response.body)
197 assert_error(id_, expected, given=response.body)
198
198
199 def test_api_create_repo_dont_specify_owner(self, backend):
199 def test_api_create_repo_dont_specify_owner(self, backend):
200 repo_name = 'api-repo-3'
200 repo_name = 'api-repo-3'
201 id_, params = build_data(
201 id_, params = build_data(
202 self.apikey, 'create_repo',
202 self.apikey, 'create_repo',
203 repo_name=repo_name,
203 repo_name=repo_name,
204 repo_type=backend.alias)
204 repo_type=backend.alias)
205 response = api_call(self.app, params)
205 response = api_call(self.app, params)
206
206
207 repo = RepoModel().get_by_repo_name(repo_name)
207 repo = RepoModel().get_by_repo_name(repo_name)
208 assert repo is not None
208 assert repo is not None
209 ret = {
209 ret = {
210 'msg': 'Created new repository `%s`' % (repo_name,),
210 'msg': 'Created new repository `%s`' % (repo_name,),
211 'success': True,
211 'success': True,
212 'task': None,
212 'task': None,
213 }
213 }
214 expected = ret
214 expected = ret
215 assert_ok(id_, expected, given=response.body)
215 assert_ok(id_, expected, given=response.body)
216 fixture.destroy_repo(repo_name)
216 fixture.destroy_repo(repo_name)
217
217
218 def test_api_create_repo_by_non_admin(self, backend):
218 def test_api_create_repo_by_non_admin(self, backend):
219 repo_name = 'api-repo-4'
219 repo_name = 'api-repo-4'
220 id_, params = build_data(
220 id_, params = build_data(
221 self.apikey_regular, 'create_repo',
221 self.apikey_regular, 'create_repo',
222 repo_name=repo_name,
222 repo_name=repo_name,
223 repo_type=backend.alias)
223 repo_type=backend.alias)
224 response = api_call(self.app, params)
224 response = api_call(self.app, params)
225
225
226 repo = RepoModel().get_by_repo_name(repo_name)
226 repo = RepoModel().get_by_repo_name(repo_name)
227 assert repo is not None
227 assert repo is not None
228 ret = {
228 ret = {
229 'msg': 'Created new repository `%s`' % (repo_name,),
229 'msg': 'Created new repository `%s`' % (repo_name,),
230 'success': True,
230 'success': True,
231 'task': None,
231 'task': None,
232 }
232 }
233 expected = ret
233 expected = ret
234 assert_ok(id_, expected, given=response.body)
234 assert_ok(id_, expected, given=response.body)
235 fixture.destroy_repo(repo_name)
235 fixture.destroy_repo(repo_name)
236
236
237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
238 repo_name = 'api-repo-5'
238 repo_name = 'api-repo-5'
239 owner = 'i-dont-exist'
239 owner = 'i-dont-exist'
240 id_, params = build_data(
240 id_, params = build_data(
241 self.apikey_regular, 'create_repo',
241 self.apikey_regular, 'create_repo',
242 repo_name=repo_name,
242 repo_name=repo_name,
243 repo_type=backend.alias,
243 repo_type=backend.alias,
244 owner=owner)
244 owner=owner)
245 response = api_call(self.app, params)
245 response = api_call(self.app, params)
246
246
247 expected = 'Only RhodeCode super-admin can specify `owner` param'
247 expected = 'Only RhodeCode super-admin can specify `owner` param'
248 assert_error(id_, expected, given=response.body)
248 assert_error(id_, expected, given=response.body)
249 fixture.destroy_repo(repo_name)
249 fixture.destroy_repo(repo_name)
250
250
251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
252 repo_group_name = 'no-access'
252 repo_group_name = 'no-access'
253 fixture.create_repo_group(repo_group_name)
253 fixture.create_repo_group(repo_group_name)
254 repo_name = 'no-access/api-repo'
254 repo_name = 'no-access/api-repo'
255
255
256 id_, params = build_data(
256 id_, params = build_data(
257 self.apikey_regular, 'create_repo',
257 self.apikey_regular, 'create_repo',
258 repo_name=repo_name,
258 repo_name=repo_name,
259 repo_type=backend.alias)
259 repo_type=backend.alias)
260 response = api_call(self.app, params)
260 response = api_call(self.app, params)
261
261
262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
263 repo_group_name)}
263 repo_group_name)}
264 assert_error(id_, expected, given=response.body)
264 assert_error(id_, expected, given=response.body)
265 fixture.destroy_repo_group(repo_group_name)
265 fixture.destroy_repo_group(repo_group_name)
266 fixture.destroy_repo(repo_name)
266 fixture.destroy_repo(repo_name)
267
267
268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
269 self, backend, user_util):
269 self, backend, user_util):
270
270
271 regular_user = user_util.create_user()
271 regular_user = user_util.create_user()
272 regular_user_api_key = regular_user.api_key
272 regular_user_api_key = regular_user.api_key
273
273
274 usr = UserModel().get_by_username(regular_user.username)
274 usr = UserModel().get_by_username(regular_user.username)
275 usr.inherit_default_permissions = False
275 usr.inherit_default_permissions = False
276 Session().add(usr)
276 Session().add(usr)
277
277
278 repo_name = backend.new_repo_name()
278 repo_name = backend.new_repo_name()
279 id_, params = build_data(
279 id_, params = build_data(
280 regular_user_api_key, 'create_repo',
280 regular_user_api_key, 'create_repo',
281 repo_name=repo_name,
281 repo_name=repo_name,
282 repo_type=backend.alias)
282 repo_type=backend.alias)
283 response = api_call(self.app, params)
283 response = api_call(self.app, params)
284 expected = {
284 expected = {
285 "repo_name": "You do not have the permission to "
285 "repo_name": "You do not have the permission to "
286 "store repositories in the root location."}
286 "store repositories in the root location."}
287 assert_error(id_, expected, given=response.body)
287 assert_error(id_, expected, given=response.body)
288
288
289 def test_api_create_repo_exists(self, backend):
289 def test_api_create_repo_exists(self, backend):
290 repo_name = backend.repo_name
290 repo_name = backend.repo_name
291 id_, params = build_data(
291 id_, params = build_data(
292 self.apikey, 'create_repo',
292 self.apikey, 'create_repo',
293 repo_name=repo_name,
293 repo_name=repo_name,
294 owner=TEST_USER_ADMIN_LOGIN,
294 owner=TEST_USER_ADMIN_LOGIN,
295 repo_type=backend.alias,)
295 repo_type=backend.alias,)
296 response = api_call(self.app, params)
296 response = api_call(self.app, params)
297 expected = {
297 expected = {
298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
299 repo_name)}
299 repo_name)}
300 assert_error(id_, expected, given=response.body)
300 assert_error(id_, expected, given=response.body)
301
301
302 @mock.patch.object(RepoModel, 'create', crash)
302 @mock.patch.object(RepoModel, 'create', crash)
303 def test_api_create_repo_exception_occurred(self, backend):
303 def test_api_create_repo_exception_occurred(self, backend):
304 repo_name = 'api-repo-6'
304 repo_name = 'api-repo-6'
305 id_, params = build_data(
305 id_, params = build_data(
306 self.apikey, 'create_repo',
306 self.apikey, 'create_repo',
307 repo_name=repo_name,
307 repo_name=repo_name,
308 owner=TEST_USER_ADMIN_LOGIN,
308 owner=TEST_USER_ADMIN_LOGIN,
309 repo_type=backend.alias,)
309 repo_type=backend.alias,)
310 response = api_call(self.app, params)
310 response = api_call(self.app, params)
311 expected = 'failed to create repository `%s`' % (repo_name,)
311 expected = 'failed to create repository `%s`' % (repo_name,)
312 assert_error(id_, expected, given=response.body)
312 assert_error(id_, expected, given=response.body)
313
313
314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
315 (None, 'foo bar x', 'foo-bar-x'),
315 (None, 'foo bar x', 'foo-bar-x'),
316 ('foo', '/foo//bar x', 'foo/bar-x'),
316 ('foo', '/foo//bar x', 'foo/bar-x'),
317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
318 ])
318 ])
319 def test_create_repo_with_extra_slashes_in_name(
319 def test_create_repo_with_extra_slashes_in_name(
320 self, backend, parent_group, dirty_name, expected_name):
320 self, backend, parent_group, dirty_name, expected_name):
321
321
322 if parent_group:
322 if parent_group:
323 gr = fixture.create_repo_group(parent_group)
323 gr = fixture.create_repo_group(parent_group)
324 assert gr.group_name == parent_group
324 assert gr.group_name == parent_group
325
325
326 id_, params = build_data(
326 id_, params = build_data(
327 self.apikey, 'create_repo',
327 self.apikey, 'create_repo',
328 repo_name=dirty_name,
328 repo_name=dirty_name,
329 repo_type=backend.alias,
329 repo_type=backend.alias,
330 owner=TEST_USER_ADMIN_LOGIN,)
330 owner=TEST_USER_ADMIN_LOGIN,)
331 response = api_call(self.app, params)
331 response = api_call(self.app, params)
332 expected ={
332 expected ={
333 "msg": "Created new repository `{}`".format(expected_name),
333 "msg": "Created new repository `{}`".format(expected_name),
334 "task": None,
334 "task": None,
335 "success": True
335 "success": True
336 }
336 }
337 assert_ok(id_, expected, response.body)
337 assert_ok(id_, expected, response.body)
338
338
339 repo = RepoModel().get_by_repo_name(expected_name)
339 repo = RepoModel().get_by_repo_name(expected_name)
340 assert repo is not None
340 assert repo is not None
341
341
342 expected = {
342 expected = {
343 'msg': 'Created new repository `%s`' % (expected_name,),
343 'msg': 'Created new repository `%s`' % (expected_name,),
344 'success': True,
344 'success': True,
345 'task': None,
345 'task': None,
346 }
346 }
347 assert_ok(id_, expected, given=response.body)
347 assert_ok(id_, expected, given=response.body)
348 fixture.destroy_repo(expected_name)
348 fixture.destroy_repo(expected_name)
349 if parent_group:
349 if parent_group:
350 fixture.destroy_repo_group(parent_group)
350 fixture.destroy_repo_group(parent_group)
@@ -1,289 +1,289 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31
31
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 @pytest.mark.usefixtures("testuser_api", "app")
36 @pytest.mark.usefixtures("testuser_api", "app")
37 class TestCreateRepoGroup(object):
37 class TestCreateRepoGroup(object):
38 def test_api_create_repo_group(self):
38 def test_api_create_repo_group(self):
39 repo_group_name = 'api-repo-group'
39 repo_group_name = 'api-repo-group'
40
40
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 assert repo_group is None
42 assert repo_group is None
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'create_repo_group',
45 self.apikey, 'create_repo_group',
46 group_name=repo_group_name,
46 group_name=repo_group_name,
47 owner=TEST_USER_ADMIN_LOGIN,)
47 owner=TEST_USER_ADMIN_LOGIN,)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 assert repo_group is not None
51 assert repo_group is not None
52 ret = {
52 ret = {
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 'repo_group': repo_group.get_api_data()
54 'repo_group': repo_group.get_api_data()
55 }
55 }
56 expected = ret
56 expected = ret
57 try:
57 try:
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
59 finally:
59 finally:
60 fixture.destroy_repo_group(repo_group_name)
60 fixture.destroy_repo_group(repo_group_name)
61
61
62 def test_api_create_repo_group_in_another_group(self):
62 def test_api_create_repo_group_in_another_group(self):
63 repo_group_name = 'api-repo-group'
63 repo_group_name = 'api-repo-group'
64
64
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 assert repo_group is None
66 assert repo_group is None
67 # create the parent
67 # create the parent
68 fixture.create_repo_group(repo_group_name)
68 fixture.create_repo_group(repo_group_name)
69
69
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 id_, params = build_data(
71 id_, params = build_data(
72 self.apikey, 'create_repo_group',
72 self.apikey, 'create_repo_group',
73 group_name=full_repo_group_name,
73 group_name=full_repo_group_name,
74 owner=TEST_USER_ADMIN_LOGIN,
74 owner=TEST_USER_ADMIN_LOGIN,
75 copy_permissions=True)
75 copy_permissions=True)
76 response = api_call(self.app, params)
76 response = api_call(self.app, params)
77
77
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 assert repo_group is not None
79 assert repo_group is not None
80 ret = {
80 ret = {
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 'repo_group': repo_group.get_api_data()
82 'repo_group': repo_group.get_api_data()
83 }
83 }
84 expected = ret
84 expected = ret
85 try:
85 try:
86 assert_ok(id_, expected, given=response.body)
86 assert_ok(id_, expected, given=response.body)
87 finally:
87 finally:
88 fixture.destroy_repo_group(full_repo_group_name)
88 fixture.destroy_repo_group(full_repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
90
90
91 def test_api_create_repo_group_in_another_group_not_existing(self):
91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 repo_group_name = 'api-repo-group-no'
92 repo_group_name = 'api-repo-group-no'
93
93
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 assert repo_group is None
95 assert repo_group is None
96
96
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'create_repo_group',
99 self.apikey, 'create_repo_group',
100 group_name=full_repo_group_name,
100 group_name=full_repo_group_name,
101 owner=TEST_USER_ADMIN_LOGIN,
101 owner=TEST_USER_ADMIN_LOGIN,
102 copy_permissions=True)
102 copy_permissions=True)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 expected = {
104 expected = {
105 'repo_group':
105 'repo_group':
106 'Parent repository group `{}` does not exist'.format(
106 'Parent repository group `{}` does not exist'.format(
107 repo_group_name)}
107 repo_group_name)}
108 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
109
109
110 def test_api_create_repo_group_that_exists(self):
110 def test_api_create_repo_group_that_exists(self):
111 repo_group_name = 'api-repo-group'
111 repo_group_name = 'api-repo-group'
112
112
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 assert repo_group is None
114 assert repo_group is None
115
115
116 fixture.create_repo_group(repo_group_name)
116 fixture.create_repo_group(repo_group_name)
117 id_, params = build_data(
117 id_, params = build_data(
118 self.apikey, 'create_repo_group',
118 self.apikey, 'create_repo_group',
119 group_name=repo_group_name,
119 group_name=repo_group_name,
120 owner=TEST_USER_ADMIN_LOGIN,)
120 owner=TEST_USER_ADMIN_LOGIN,)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122 expected = {
122 expected = {
123 'unique_repo_group_name':
123 'unique_repo_group_name':
124 'Repository group with name `{}` already exists'.format(
124 'Repository group with name `{}` already exists'.format(
125 repo_group_name)}
125 repo_group_name)}
126 try:
126 try:
127 assert_error(id_, expected, given=response.body)
127 assert_error(id_, expected, given=response.body)
128 finally:
128 finally:
129 fixture.destroy_repo_group(repo_group_name)
129 fixture.destroy_repo_group(repo_group_name)
130
130
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 self, user_util):
132 self, user_util):
133 regular_user = user_util.create_user()
133 regular_user = user_util.create_user()
134 regular_user_api_key = regular_user.api_key
134 regular_user_api_key = regular_user.api_key
135
135
136 repo_group_name = 'api-repo-group-by-regular-user'
136 repo_group_name = 'api-repo-group-by-regular-user'
137
137
138 usr = UserModel().get_by_username(regular_user.username)
138 usr = UserModel().get_by_username(regular_user.username)
139 usr.inherit_default_permissions = False
139 usr.inherit_default_permissions = False
140 Session().add(usr)
140 Session().add(usr)
141
141
142 UserModel().grant_perm(
142 UserModel().grant_perm(
143 regular_user.username, 'hg.repogroup.create.true')
143 regular_user.username, 'hg.repogroup.create.true')
144 Session().commit()
144 Session().commit()
145
145
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 assert repo_group is None
147 assert repo_group is None
148
148
149 id_, params = build_data(
149 id_, params = build_data(
150 regular_user_api_key, 'create_repo_group',
150 regular_user_api_key, 'create_repo_group',
151 group_name=repo_group_name)
151 group_name=repo_group_name)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 assert repo_group is not None
155 assert repo_group is not None
156 expected = {
156 expected = {
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 'repo_group': repo_group.get_api_data()
158 'repo_group': repo_group.get_api_data()
159 }
159 }
160 try:
160 try:
161 assert_ok(id_, expected, given=response.body)
161 assert_ok(id_, expected, given=response.body)
162 finally:
162 finally:
163 fixture.destroy_repo_group(repo_group_name)
163 fixture.destroy_repo_group(repo_group_name)
164
164
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 self, user_util):
166 self, user_util):
167
167
168 repo_group_name = 'api-repo-group-parent'
168 repo_group_name = 'api-repo-group-parent'
169
169
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 assert repo_group is None
171 assert repo_group is None
172 # create the parent
172 # create the parent
173 fixture.create_repo_group(repo_group_name)
173 fixture.create_repo_group(repo_group_name)
174
174
175 # user perms
175 # user perms
176 regular_user = user_util.create_user()
176 regular_user = user_util.create_user()
177 regular_user_api_key = regular_user.api_key
177 regular_user_api_key = regular_user.api_key
178
178
179 usr = UserModel().get_by_username(regular_user.username)
179 usr = UserModel().get_by_username(regular_user.username)
180 usr.inherit_default_permissions = False
180 usr.inherit_default_permissions = False
181 Session().add(usr)
181 Session().add(usr)
182
182
183 RepoGroupModel().grant_user_permission(
183 RepoGroupModel().grant_user_permission(
184 repo_group_name, regular_user.username, 'group.admin')
184 repo_group_name, regular_user.username, 'group.admin')
185 Session().commit()
185 Session().commit()
186
186
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 id_, params = build_data(
188 id_, params = build_data(
189 regular_user_api_key, 'create_repo_group',
189 regular_user_api_key, 'create_repo_group',
190 group_name=full_repo_group_name)
190 group_name=full_repo_group_name)
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192
192
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 assert repo_group is not None
194 assert repo_group is not None
195 expected = {
195 expected = {
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 'repo_group': repo_group.get_api_data()
197 'repo_group': repo_group.get_api_data()
198 }
198 }
199 try:
199 try:
200 assert_ok(id_, expected, given=response.body)
200 assert_ok(id_, expected, given=response.body)
201 finally:
201 finally:
202 fixture.destroy_repo_group(full_repo_group_name)
202 fixture.destroy_repo_group(full_repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
204
204
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 repo_group_name = 'api-repo-group'
206 repo_group_name = 'api-repo-group'
207
207
208 id_, params = build_data(
208 id_, params = build_data(
209 self.apikey_regular, 'create_repo_group',
209 self.apikey_regular, 'create_repo_group',
210 group_name=repo_group_name)
210 group_name=repo_group_name)
211 response = api_call(self.app, params)
211 response = api_call(self.app, params)
212
212
213 expected = {
213 expected = {
214 'repo_group':
214 'repo_group':
215 u'You do not have the permission to store '
215 u'You do not have the permission to store '
216 u'repository groups in the root location.'}
216 u'repository groups in the root location.'}
217 assert_error(id_, expected, given=response.body)
217 assert_error(id_, expected, given=response.body)
218
218
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 repo_group_name = 'api-repo-group-regular-user'
220 repo_group_name = 'api-repo-group-regular-user'
221
221
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 assert repo_group is None
223 assert repo_group is None
224 # create the parent
224 # create the parent
225 fixture.create_repo_group(repo_group_name)
225 fixture.create_repo_group(repo_group_name)
226
226
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228
228
229 id_, params = build_data(
229 id_, params = build_data(
230 self.apikey_regular, 'create_repo_group',
230 self.apikey_regular, 'create_repo_group',
231 group_name=full_repo_group_name)
231 group_name=full_repo_group_name)
232 response = api_call(self.app, params)
232 response = api_call(self.app, params)
233
233
234 expected = {
234 expected = {
235 'repo_group':
235 'repo_group':
236 'Parent repository group `{}` does not exist'.format(
236 'Parent repository group `{}` does not exist'.format(
237 repo_group_name)}
237 repo_group_name)}
238 try:
238 try:
239 assert_error(id_, expected, given=response.body)
239 assert_error(id_, expected, given=response.body)
240 finally:
240 finally:
241 fixture.destroy_repo_group(repo_group_name)
241 fixture.destroy_repo_group(repo_group_name)
242
242
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 self):
244 self):
245 repo_group_name = 'api-repo-group'
245 repo_group_name = 'api-repo-group'
246
246
247 id_, params = build_data(
247 id_, params = build_data(
248 self.apikey_regular, 'create_repo_group',
248 self.apikey_regular, 'create_repo_group',
249 group_name=repo_group_name,
249 group_name=repo_group_name,
250 owner=TEST_USER_ADMIN_LOGIN,)
250 owner=TEST_USER_ADMIN_LOGIN,)
251 response = api_call(self.app, params)
251 response = api_call(self.app, params)
252
252
253 expected = "Only RhodeCode super-admin can specify `owner` param"
253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 assert_error(id_, expected, given=response.body)
254 assert_error(id_, expected, given=response.body)
255
255
256 @mock.patch.object(RepoGroupModel, 'create', crash)
256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 def test_api_create_repo_group_exception_occurred(self):
257 def test_api_create_repo_group_exception_occurred(self):
258 repo_group_name = 'api-repo-group'
258 repo_group_name = 'api-repo-group'
259
259
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 assert repo_group is None
261 assert repo_group is None
262
262
263 id_, params = build_data(
263 id_, params = build_data(
264 self.apikey, 'create_repo_group',
264 self.apikey, 'create_repo_group',
265 group_name=repo_group_name,
265 group_name=repo_group_name,
266 owner=TEST_USER_ADMIN_LOGIN,)
266 owner=TEST_USER_ADMIN_LOGIN,)
267 response = api_call(self.app, params)
267 response = api_call(self.app, params)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 assert_error(id_, expected, given=response.body)
269 assert_error(id_, expected, given=response.body)
270
270
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 existing_repo_group = user_util.create_repo_group()
272 existing_repo_group = user_util.create_repo_group()
273 dirty_group_name = '//{}//group2//'.format(
273 dirty_group_name = '//{}//group2//'.format(
274 existing_repo_group.group_name)
274 existing_repo_group.group_name)
275 cleaned_group_name = '{}/group2'.format(
275 cleaned_group_name = '{}/group2'.format(
276 existing_repo_group.group_name)
276 existing_repo_group.group_name)
277
277
278 id_, params = build_data(
278 id_, params = build_data(
279 self.apikey, 'create_repo_group',
279 self.apikey, 'create_repo_group',
280 group_name=dirty_group_name,
280 group_name=dirty_group_name,
281 owner=TEST_USER_ADMIN_LOGIN,)
281 owner=TEST_USER_ADMIN_LOGIN,)
282 response = api_call(self.app, params)
282 response = api_call(self.app, params)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 expected = {
284 expected = {
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 'repo_group': repo_group.get_api_data()
286 'repo_group': repo_group.get_api_data()
287 }
287 }
288 assert_ok(id_, expected, given=response.body)
288 assert_ok(id_, expected, given=response.body)
289 fixture.destroy_repo_group(cleaned_group_name)
289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.model.db import RepoGroup
31 from rhodecode.model.db import RepoGroup
32
32
33
33
34 # TODO: mikhail: remove fixture from here
34 # TODO: mikhail: remove fixture from here
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateUser(object):
39 class TestCreateUser(object):
40 def test_api_create_existing_user(self):
40 def test_api_create_existing_user(self):
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey, 'create_user',
42 self.apikey, 'create_user',
43 username=TEST_USER_ADMIN_LOGIN,
43 username=TEST_USER_ADMIN_LOGIN,
44 email='test@foo.com',
44 email='test@foo.com',
45 password='trololo')
45 password='trololo')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 assert_error(id_, expected, given=response.body)
49 assert_error(id_, expected, given=response.body)
50
50
51 def test_api_create_user_with_existing_email(self):
51 def test_api_create_user_with_existing_email(self):
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_user',
53 self.apikey, 'create_user',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 email=TEST_USER_REGULAR_EMAIL,
55 email=TEST_USER_REGULAR_EMAIL,
56 password='trololo')
56 password='trololo')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
61
61
62 def test_api_create_user(self):
62 def test_api_create_user(self):
63 username = 'test_new_api_user'
63 username = 'test_new_api_user'
64 email = username + "@foo.com"
64 email = username + "@foo.com"
65
65
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'create_user',
67 self.apikey, 'create_user',
68 username=username,
68 username=username,
69 email=email,
69 email=email,
70 password='example')
70 password='example')
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 usr = UserModel().get_by_username(username)
73 usr = UserModel().get_by_username(username)
74 ret = {
74 ret = {
75 'msg': 'created new user `%s`' % (username,),
75 'msg': 'created new user `%s`' % (username,),
76 'user': jsonify(usr.get_api_data(include_secrets=True)),
76 'user': jsonify(usr.get_api_data(include_secrets=True)),
77 }
77 }
78 try:
78 try:
79 expected = ret
79 expected = ret
80 assert check_password('example', usr.password)
80 assert check_password('example', usr.password)
81 assert_ok(id_, expected, given=response.body)
81 assert_ok(id_, expected, given=response.body)
82 finally:
82 finally:
83 fixture.destroy_user(usr.user_id)
83 fixture.destroy_user(usr.user_id)
84
84
85 def test_api_create_user_without_password(self):
85 def test_api_create_user_without_password(self):
86 username = 'test_new_api_user_passwordless'
86 username = 'test_new_api_user_passwordless'
87 email = username + "@foo.com"
87 email = username + "@foo.com"
88
88
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey, 'create_user',
90 self.apikey, 'create_user',
91 username=username,
91 username=username,
92 email=email)
92 email=email)
93 response = api_call(self.app, params)
93 response = api_call(self.app, params)
94
94
95 usr = UserModel().get_by_username(username)
95 usr = UserModel().get_by_username(username)
96 ret = {
96 ret = {
97 'msg': 'created new user `%s`' % (username,),
97 'msg': 'created new user `%s`' % (username,),
98 'user': jsonify(usr.get_api_data(include_secrets=True)),
98 'user': jsonify(usr.get_api_data(include_secrets=True)),
99 }
99 }
100 try:
100 try:
101 expected = ret
101 expected = ret
102 assert_ok(id_, expected, given=response.body)
102 assert_ok(id_, expected, given=response.body)
103 finally:
103 finally:
104 fixture.destroy_user(usr.user_id)
104 fixture.destroy_user(usr.user_id)
105
105
106 def test_api_create_user_with_extern_name(self):
106 def test_api_create_user_with_extern_name(self):
107 username = 'test_new_api_user_passwordless'
107 username = 'test_new_api_user_passwordless'
108 email = username + "@foo.com"
108 email = username + "@foo.com"
109
109
110 id_, params = build_data(
110 id_, params = build_data(
111 self.apikey, 'create_user',
111 self.apikey, 'create_user',
112 username=username,
112 username=username,
113 email=email, extern_name='rhodecode')
113 email=email, extern_name='rhodecode')
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115
115
116 usr = UserModel().get_by_username(username)
116 usr = UserModel().get_by_username(username)
117 ret = {
117 ret = {
118 'msg': 'created new user `%s`' % (username,),
118 'msg': 'created new user `%s`' % (username,),
119 'user': jsonify(usr.get_api_data(include_secrets=True)),
119 'user': jsonify(usr.get_api_data(include_secrets=True)),
120 }
120 }
121 try:
121 try:
122 expected = ret
122 expected = ret
123 assert_ok(id_, expected, given=response.body)
123 assert_ok(id_, expected, given=response.body)
124 finally:
124 finally:
125 fixture.destroy_user(usr.user_id)
125 fixture.destroy_user(usr.user_id)
126
126
127 def test_api_create_user_with_password_change(self):
127 def test_api_create_user_with_password_change(self):
128 username = 'test_new_api_user_password_change'
128 username = 'test_new_api_user_password_change'
129 email = username + "@foo.com"
129 email = username + "@foo.com"
130
130
131 id_, params = build_data(
131 id_, params = build_data(
132 self.apikey, 'create_user',
132 self.apikey, 'create_user',
133 username=username,
133 username=username,
134 email=email, extern_name='rhodecode',
134 email=email, extern_name='rhodecode',
135 force_password_change=True)
135 force_password_change=True)
136 response = api_call(self.app, params)
136 response = api_call(self.app, params)
137
137
138 usr = UserModel().get_by_username(username)
138 usr = UserModel().get_by_username(username)
139 ret = {
139 ret = {
140 'msg': 'created new user `%s`' % (username,),
140 'msg': 'created new user `%s`' % (username,),
141 'user': jsonify(usr.get_api_data(include_secrets=True)),
141 'user': jsonify(usr.get_api_data(include_secrets=True)),
142 }
142 }
143 try:
143 try:
144 expected = ret
144 expected = ret
145 assert_ok(id_, expected, given=response.body)
145 assert_ok(id_, expected, given=response.body)
146 finally:
146 finally:
147 fixture.destroy_user(usr.user_id)
147 fixture.destroy_user(usr.user_id)
148
148
149 def test_api_create_user_with_personal_repo_group(self):
149 def test_api_create_user_with_personal_repo_group(self):
150 username = 'test_new_api_user_personal_group'
150 username = 'test_new_api_user_personal_group'
151 email = username + "@foo.com"
151 email = username + "@foo.com"
152
152
153 id_, params = build_data(
153 id_, params = build_data(
154 self.apikey, 'create_user',
154 self.apikey, 'create_user',
155 username=username,
155 username=username,
156 email=email, extern_name='rhodecode',
156 email=email, extern_name='rhodecode',
157 create_personal_repo_group=True)
157 create_personal_repo_group=True)
158 response = api_call(self.app, params)
158 response = api_call(self.app, params)
159
159
160 usr = UserModel().get_by_username(username)
160 usr = UserModel().get_by_username(username)
161 ret = {
161 ret = {
162 'msg': 'created new user `%s`' % (username,),
162 'msg': 'created new user `%s`' % (username,),
163 'user': jsonify(usr.get_api_data(include_secrets=True)),
163 'user': jsonify(usr.get_api_data(include_secrets=True)),
164 }
164 }
165
165
166 personal_group = RepoGroup.get_by_group_name(username)
166 personal_group = RepoGroup.get_by_group_name(username)
167 assert personal_group
167 assert personal_group
168 assert personal_group.personal == True
168 assert personal_group.personal == True
169 assert personal_group.user.username == username
169 assert personal_group.user.username == username
170
170
171 try:
171 try:
172 expected = ret
172 expected = ret
173 assert_ok(id_, expected, given=response.body)
173 assert_ok(id_, expected, given=response.body)
174 finally:
174 finally:
175 fixture.destroy_repo_group(username)
175 fixture.destroy_repo_group(username)
176 fixture.destroy_user(usr.user_id)
176 fixture.destroy_user(usr.user_id)
177
177
178
178
179 @mock.patch.object(UserModel, 'create_or_update', crash)
179 @mock.patch.object(UserModel, 'create_or_update', crash)
180 def test_api_create_user_when_exception_happened(self):
180 def test_api_create_user_when_exception_happened(self):
181
181
182 username = 'test_new_api_user'
182 username = 'test_new_api_user'
183 email = username + "@foo.com"
183 email = username + "@foo.com"
184
184
185 id_, params = build_data(
185 id_, params = build_data(
186 self.apikey, 'create_user',
186 self.apikey, 'create_user',
187 username=username,
187 username=username,
188 email=email,
188 email=email,
189 password='trololo')
189 password='trololo')
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191 expected = 'failed to create user `%s`' % (username,)
191 expected = 'failed to create user `%s`' % (username,)
192 assert_error(id_, expected, given=response.body)
192 assert_error(id_, expected, given=response.body)
@@ -1,114 +1,114 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestCreateUserGroup(object):
33 class TestCreateUserGroup(object):
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36 def test_api_create_user_group(self):
36 def test_api_create_user_group(self):
37 group_name = 'some_new_group'
37 group_name = 'some_new_group'
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'create_user_group', group_name=group_name)
39 self.apikey, 'create_user_group', group_name=group_name)
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 ret = {
42 ret = {
43 'msg': 'created new user group `%s`' % (group_name,),
43 'msg': 'created new user group `%s`' % (group_name,),
44 'user_group': jsonify(
44 'user_group': jsonify(
45 UserGroupModel()
45 UserGroupModel()
46 .get_by_name(group_name)
46 .get_by_name(group_name)
47 .get_api_data()
47 .get_api_data()
48 )
48 )
49 }
49 }
50 expected = ret
50 expected = ret
51 assert_ok(id_, expected, given=response.body)
51 assert_ok(id_, expected, given=response.body)
52 self.fixture.destroy_user_group(group_name)
52 self.fixture.destroy_user_group(group_name)
53
53
54 def test_api_create_user_group_regular_user(self):
54 def test_api_create_user_group_regular_user(self):
55 group_name = 'some_new_group'
55 group_name = 'some_new_group'
56
56
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 usr.inherit_default_permissions = False
58 usr.inherit_default_permissions = False
59 Session().add(usr)
59 Session().add(usr)
60 UserModel().grant_perm(
60 UserModel().grant_perm(
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
62 Session().commit()
62 Session().commit()
63
63
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_user_group', group_name=group_name)
65 self.apikey_regular, 'create_user_group', group_name=group_name)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'msg': 'created new user group `%s`' % (group_name,),
69 'msg': 'created new user group `%s`' % (group_name,),
70 'user_group': jsonify(
70 'user_group': jsonify(
71 UserGroupModel()
71 UserGroupModel()
72 .get_by_name(group_name)
72 .get_by_name(group_name)
73 .get_api_data()
73 .get_api_data()
74 )
74 )
75 }
75 }
76 try:
76 try:
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78 finally:
78 finally:
79 self.fixture.destroy_user_group(group_name)
79 self.fixture.destroy_user_group(group_name)
80 UserModel().revoke_perm(
80 UserModel().revoke_perm(
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
83 usr.inherit_default_permissions = True
83 usr.inherit_default_permissions = True
84 Session().add(usr)
84 Session().add(usr)
85 Session().commit()
85 Session().commit()
86
86
87 def test_api_create_user_group_regular_user_no_permission(self):
87 def test_api_create_user_group_regular_user_no_permission(self):
88 group_name = 'some_new_group'
88 group_name = 'some_new_group'
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular, 'create_user_group', group_name=group_name)
90 self.apikey_regular, 'create_user_group', group_name=group_name)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = "Access was denied to this resource."
92 expected = "Access was denied to this resource."
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 def test_api_create_user_group_that_exist(self, user_util):
95 def test_api_create_user_group_that_exist(self, user_util):
96 group = user_util.create_user_group()
96 group = user_util.create_user_group()
97 group_name = group.users_group_name
97 group_name = group.users_group_name
98
98
99 id_, params = build_data(
99 id_, params = build_data(
100 self.apikey, 'create_user_group', group_name=group_name)
100 self.apikey, 'create_user_group', group_name=group_name)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102
102
103 expected = "user group `%s` already exist" % (group_name,)
103 expected = "user group `%s` already exist" % (group_name,)
104 assert_error(id_, expected, given=response.body)
104 assert_error(id_, expected, given=response.body)
105
105
106 @mock.patch.object(UserGroupModel, 'create', crash)
106 @mock.patch.object(UserGroupModel, 'create', crash)
107 def test_api_create_user_group_exception_occurred(self):
107 def test_api_create_user_group_exception_occurred(self):
108 group_name = 'exception_happens'
108 group_name = 'exception_happens'
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey, 'create_user_group', group_name=group_name)
110 self.apikey, 'create_user_group', group_name=group_name)
111 response = api_call(self.app, params)
111 response = api_call(self.app, params)
112
112
113 expected = 'failed to create group `%s`' % (group_name,)
113 expected = 'failed to create group `%s`' % (group_name,)
114 assert_error(id_, expected, given=response.body)
114 assert_error(id_, expected, given=response.body)
@@ -1,61 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.gist import GistModel
24 from rhodecode.model.gist import GistModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteGist(object):
30 class TestApiDeleteGist(object):
31 def test_api_delete_gist(self, gist_util):
31 def test_api_delete_gist(self, gist_util):
32 gist_id = gist_util.create_gist().gist_access_id
32 gist_id = gist_util.create_gist().gist_access_id
33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 def test_api_delete_gist_regular_user(self, gist_util):
38 def test_api_delete_gist_regular_user(self, gist_util):
39 gist_id = gist_util.create_gist(
39 gist_id = gist_util.create_gist(
40 owner=self.TEST_USER_LOGIN).gist_access_id
40 owner=self.TEST_USER_LOGIN).gist_access_id
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey_regular, 'delete_gist', gistid=gist_id)
42 self.apikey_regular, 'delete_gist', gistid=gist_id)
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
48 gist_id = gist_util.create_gist().gist_access_id
48 gist_id = gist_util.create_gist().gist_access_id
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey_regular, 'delete_gist', gistid=gist_id)
50 self.apikey_regular, 'delete_gist', gistid=gist_id)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52 expected = 'gist `%s` does not exist' % (gist_id,)
52 expected = 'gist `%s` does not exist' % (gist_id,)
53 assert_error(id_, expected, given=response.body)
53 assert_error(id_, expected, given=response.body)
54
54
55 @mock.patch.object(GistModel, 'delete', crash)
55 @mock.patch.object(GistModel, 'delete', crash)
56 def test_api_delete_gist_exception_occurred(self, gist_util):
56 def test_api_delete_gist_exception_occurred(self, gist_util):
57 gist_id = gist_util.create_gist().gist_access_id
57 gist_id = gist_util.create_gist().gist_access_id
58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60 expected = 'failed to delete gist ID:%s' % (gist_id,)
60 expected = 'failed to delete gist ID:%s' % (gist_id,)
61 assert_error(id_, expected, given=response.body)
61 assert_error(id_, expected, given=response.body)
@@ -1,73 +1,73 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteRepo(object):
30 class TestApiDeleteRepo(object):
31 def test_api_delete_repo(self, backend):
31 def test_api_delete_repo(self, backend):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33
33
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 expected = {
38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
40 'success': True
40 'success': True
41 }
41 }
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 repo = backend.create_repo(cur_user=user_regular.username)
45 repo = backend.create_repo(cur_user=user_regular.username)
46 id_, params = build_data(
46 id_, params = build_data(
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
52 'success': True
52 'success': True
53 }
53 }
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_delete_repo_by_non_admin_no_permission(
56 def test_api_delete_repo_by_non_admin_no_permission(
57 self, backend, user_regular):
57 self, backend, user_regular):
58 repo = backend.create_repo()
58 repo = backend.create_repo()
59 id_, params = build_data(
59 id_, params = build_data(
60 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
60 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62 expected = 'repository `%s` does not exist' % (repo.repo_name)
62 expected = 'repository `%s` does not exist' % (repo.repo_name)
63 assert_error(id_, expected, given=response.body)
63 assert_error(id_, expected, given=response.body)
64
64
65 def test_api_delete_repo_exception_occurred(self, backend):
65 def test_api_delete_repo_exception_occurred(self, backend):
66 repo = backend.create_repo()
66 repo = backend.create_repo()
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'delete_repo', repoid=repo.repo_name, )
68 self.apikey, 'delete_repo', repoid=repo.repo_name, )
69 with mock.patch.object(RepoModel, 'delete', crash):
69 with mock.patch.object(RepoModel, 'delete', crash):
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71 expected = 'failed to delete repository `%s`' % (
71 expected = 'failed to delete repository `%s`' % (
72 repo.repo_name,)
72 repo.repo_name,)
73 assert_error(id_, expected, given=response.body)
73 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestApiDeleteRepoGroup(object):
31 class TestApiDeleteRepoGroup(object):
32 def test_api_delete_repo_group(self, user_util):
32 def test_api_delete_repo_group(self, user_util):
33 repo_group = user_util.create_repo_group(auto_cleanup=False)
33 repo_group = user_util.create_repo_group(auto_cleanup=False)
34 repo_group_name = repo_group.group_name
34 repo_group_name = repo_group.group_name
35 repo_group_id = repo_group.group_id
35 repo_group_id = repo_group.group_id
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 ret = {
40 ret = {
41 'msg': 'deleted repo group ID:%s %s' % (
41 'msg': 'deleted repo group ID:%s %s' % (
42 repo_group_id, repo_group_name
42 repo_group_id, repo_group_name
43 ),
43 ),
44 'repo_group': None
44 'repo_group': None
45 }
45 }
46 expected = ret
46 expected = ret
47 assert_ok(id_, expected, given=response.body)
47 assert_ok(id_, expected, given=response.body)
48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
49 assert gr is None
49 assert gr is None
50
50
51 def test_api_delete_repo_group_regular_user(self, user_util):
51 def test_api_delete_repo_group_regular_user(self, user_util):
52 repo_group = user_util.create_repo_group(auto_cleanup=False)
52 repo_group = user_util.create_repo_group(auto_cleanup=False)
53 repo_group_name = repo_group.group_name
53 repo_group_name = repo_group.group_name
54 repo_group_id = repo_group.group_id
54 repo_group_id = repo_group.group_id
55
55
56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 user_util.grant_user_permission_to_repo_group(
57 user_util.grant_user_permission_to_repo_group(
58 repo_group, user, 'group.admin')
58 repo_group, user, 'group.admin')
59
59
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63
63
64 ret = {
64 ret = {
65 'msg': 'deleted repo group ID:%s %s' % (
65 'msg': 'deleted repo group ID:%s %s' % (
66 repo_group_id, repo_group_name
66 repo_group_id, repo_group_name
67 ),
67 ),
68 'repo_group': None
68 'repo_group': None
69 }
69 }
70 expected = ret
70 expected = ret
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
73 assert gr is None
73 assert gr is None
74
74
75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
76 repo_group = user_util.create_repo_group()
76 repo_group = user_util.create_repo_group()
77 repo_group_name = repo_group.group_name
77 repo_group_name = repo_group.group_name
78
78
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey_regular, 'delete_repo_group',
80 self.apikey_regular, 'delete_repo_group',
81 repogroupid=repo_group_name, )
81 repogroupid=repo_group_name, )
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = 'repository group `%s` does not exist' % (
84 expected = 'repository group `%s` does not exist' % (
85 repo_group_name,)
85 repo_group_name,)
86 assert_error(id_, expected, given=response.body)
86 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error, crash)
27 build_data, api_call, assert_ok, assert_error, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestDeleteUser(object):
31 class TestDeleteUser(object):
32 def test_api_delete_user(self, user_util):
32 def test_api_delete_user(self, user_util):
33 usr = user_util.create_user(auto_cleanup=False)
33 usr = user_util.create_user(auto_cleanup=False)
34
34
35 username = usr.username
35 username = usr.username
36 usr_id = usr.user_id
36 usr_id = usr.user_id
37
37
38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
42 'user': None}
42 'user': None}
43 expected = ret
43 expected = ret
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 @mock.patch.object(UserModel, 'delete', crash)
46 @mock.patch.object(UserModel, 'delete', crash)
47 def test_api_delete_user_when_exception_happened(self, user_util):
47 def test_api_delete_user_when_exception_happened(self, user_util):
48 usr = user_util.create_user()
48 usr = user_util.create_user()
49 username = usr.username
49 username = usr.username
50
50
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'delete_user', userid=username, )
52 self.apikey, 'delete_user', userid=username, )
53 response = api_call(self.app, params)
53 response = api_call(self.app, params)
54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
55 usr.username)
55 usr.username)
56 expected = ret
56 expected = ret
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash)
28 build_data, api_call, assert_error, assert_ok, crash)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestDeleteUserGroup(object):
32 class TestDeleteUserGroup(object):
33 def test_api_delete_user_group(self, user_util):
33 def test_api_delete_user_group(self, user_util):
34 user_group = user_util.create_user_group(auto_cleanup=False)
34 user_group = user_util.create_user_group(auto_cleanup=False)
35 group_name = user_group.users_group_name
35 group_name = user_group.users_group_name
36 group_id = user_group.users_group_id
36 group_id = user_group.users_group_id
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey, 'delete_user_group', usergroupid=group_name)
38 self.apikey, 'delete_user_group', usergroupid=group_name)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {
41 expected = {
42 'user_group': None,
42 'user_group': None,
43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
44 }
44 }
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_delete_user_group_regular_user(self, user_util):
47 def test_api_delete_user_group_regular_user(self, user_util):
48 ugroup = user_util.create_user_group(auto_cleanup=False)
48 ugroup = user_util.create_user_group(auto_cleanup=False)
49 group_name = ugroup.users_group_name
49 group_name = ugroup.users_group_name
50 group_id = ugroup.users_group_id
50 group_id = ugroup.users_group_id
51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
52
52
53 user_util.grant_user_permission_to_user_group(
53 user_util.grant_user_permission_to_user_group(
54 ugroup, user, 'usergroup.admin')
54 ugroup, user, 'usergroup.admin')
55
55
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59
59
60 expected = {
60 expected = {
61 'user_group': None,
61 'user_group': None,
62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
63 }
63 }
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
67 user_group = user_util.create_user_group()
67 user_group = user_util.create_user_group()
68 group_name = user_group.users_group_name
68 group_name = user_group.users_group_name
69
69
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
71 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73
73
74 expected = 'user group `%s` does not exist' % (group_name)
74 expected = 'user group `%s` does not exist' % (group_name)
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
78 ugroup = user_util.create_user_group()
78 ugroup = user_util.create_user_group()
79 group_name = ugroup.users_group_name
79 group_name = ugroup.users_group_name
80 repo = backend.create_repo()
80 repo = backend.create_repo()
81
81
82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
83 repo, ugroup, 'repository.write')
83 repo, ugroup, 'repository.write')
84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
85
85
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'delete_user_group',
87 self.apikey, 'delete_user_group',
88 usergroupid=group_name)
88 usergroupid=group_name)
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90
90
91 expected = msg
91 expected = msg
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_delete_user_group_exception_occurred(self, user_util):
94 def test_api_delete_user_group_exception_occurred(self, user_util):
95 ugroup = user_util.create_user_group()
95 ugroup = user_util.create_user_group()
96 group_name = ugroup.users_group_name
96 group_name = ugroup.users_group_name
97 group_id = ugroup.users_group_id
97 group_id = ugroup.users_group_id
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'delete_user_group',
99 self.apikey, 'delete_user_group',
100 usergroupid=group_name)
100 usergroupid=group_name)
101
101
102 with mock.patch.object(UserGroupModel, 'delete', crash):
102 with mock.patch.object(UserGroupModel, 'delete', crash):
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 expected = 'failed to delete user group ID:%s %s' % (
104 expected = 'failed to delete user group ID:%s %s' % (
105 group_id, group_name)
105 group_id, group_name)
106 assert_error(id_, expected, given=response.body)
106 assert_error(id_, expected, given=response.body)
@@ -1,79 +1,79 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.views import deprecated_api
24 from rhodecode.api.views import deprecated_api
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call)
27 build_data, api_call)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestCommitComment(object):
31 class TestCommitComment(object):
32 def test_deprecated_message_in_docstring(self):
32 def test_deprecated_message_in_docstring(self):
33 docstring = deprecated_api.changeset_comment.__doc__
33 docstring = deprecated_api.changeset_comment.__doc__
34 assert '.. deprecated:: 3.4.0' in docstring
34 assert '.. deprecated:: 3.4.0' in docstring
35 assert 'Please use method `comment_commit` instead.' in docstring
35 assert 'Please use method `comment_commit` instead.' in docstring
36
36
37 def test_deprecated_message_in_retvalue(self):
37 def test_deprecated_message_in_retvalue(self):
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'show_ip')
40 self.apikey, 'show_ip')
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'id': id_,
44 'id': id_,
45 'error': None,
45 'error': None,
46 'result': json.loads(response.body)['result'],
46 'result': json.loads(response.body)['result'],
47 'DEPRECATION_WARNING':
47 'DEPRECATION_WARNING':
48 'DEPRECATED METHOD Please use method `get_ip` instead.'
48 'DEPRECATED METHOD Please use method `get_ip` instead.'
49 }
49 }
50 assert expected == json.loads(response.body)
50 assert expected == json.loads(response.body)
51
51
52 # def test_calls_comment_commit(self, backend, no_notifications):
52 # def test_calls_comment_commit(self, backend, no_notifications):
53 # data = {
53 # data = {
54 # 'repoid': backend.repo_name,
54 # 'repoid': backend.repo_name,
55 # 'status': ChangesetStatus.STATUS_APPROVED,
55 # 'status': ChangesetStatus.STATUS_APPROVED,
56 # 'message': 'Approved',
56 # 'message': 'Approved',
57 # 'revision': 'tip'
57 # 'revision': 'tip'
58 # }
58 # }
59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
61 # api_call(self.app, params)
61 # api_call(self.app, params)
62 #
62 #
63 # _, call_args = comment_mock.call_args
63 # _, call_args = comment_mock.call_args
64 # data['commit_id'] = data.pop('revision')
64 # data['commit_id'] = data.pop('revision')
65 # for key in data:
65 # for key in data:
66 # assert call_args[key] == data[key]
66 # assert call_args[key] == data[key]
67
67
68 # def test_warning_log_contains_deprecation_message(self):
68 # def test_warning_log_contains_deprecation_message(self):
69 # api = self.SampleApi()
69 # api = self.SampleApi()
70 # with patch.object(utils, 'log') as log_mock:
70 # with patch.object(utils, 'log') as log_mock:
71 # api.api_method()
71 # api.api_method()
72 #
72 #
73 # assert log_mock.warning.call_count == 1
73 # assert log_mock.warning.call_count == 1
74 # call_args = log_mock.warning.call_args[0]
74 # call_args = log_mock.warning.call_args[0]
75 # assert (
75 # assert (
76 # call_args[0] ==
76 # call_args[0] ==
77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
78 # assert call_args[1].__name__ == 'api_method'
78 # assert call_args[1].__name__ == 'api_method'
79 # assert call_args[2] == 'new_method' No newline at end of file
79 # assert call_args[2] == 'new_method'
@@ -1,279 +1,279 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo_group import RepoGroupModel
27 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30 from rhodecode.api.tests.utils import (
30 from rhodecode.api.tests.utils import (
31 build_data, api_call, assert_error, assert_ok, crash)
31 build_data, api_call, assert_error, assert_ok, crash)
32 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestApiForkRepo(object):
39 class TestApiForkRepo(object):
40 def test_api_fork_repo(self, backend):
40 def test_api_fork_repo(self, backend):
41 source_name = backend['minimal'].repo_name
41 source_name = backend['minimal'].repo_name
42 fork_name = backend.new_repo_name()
42 fork_name = backend.new_repo_name()
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'fork_repo',
45 self.apikey, 'fork_repo',
46 repoid=source_name,
46 repoid=source_name,
47 fork_name=fork_name,
47 fork_name=fork_name,
48 owner=TEST_USER_ADMIN_LOGIN)
48 owner=TEST_USER_ADMIN_LOGIN)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
53 'success': True,
53 'success': True,
54 'task': None,
54 'task': None,
55 }
55 }
56 try:
56 try:
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
58 finally:
58 finally:
59 fixture.destroy_repo(fork_name)
59 fixture.destroy_repo(fork_name)
60
60
61 def test_api_fork_repo_into_group(self, backend, user_util):
61 def test_api_fork_repo_into_group(self, backend, user_util):
62 source_name = backend['minimal'].repo_name
62 source_name = backend['minimal'].repo_name
63 repo_group = user_util.create_repo_group()
63 repo_group = user_util.create_repo_group()
64 fork_name = '%s/api-repo-fork' % repo_group.group_name
64 fork_name = '%s/api-repo-fork' % repo_group.group_name
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'fork_repo',
66 self.apikey, 'fork_repo',
67 repoid=source_name,
67 repoid=source_name,
68 fork_name=fork_name,
68 fork_name=fork_name,
69 owner=TEST_USER_ADMIN_LOGIN)
69 owner=TEST_USER_ADMIN_LOGIN)
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71
71
72 ret = {
72 ret = {
73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
74 'success': True,
74 'success': True,
75 'task': None,
75 'task': None,
76 }
76 }
77 expected = ret
77 expected = ret
78 try:
78 try:
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80 finally:
80 finally:
81 fixture.destroy_repo(fork_name)
81 fixture.destroy_repo(fork_name)
82
82
83 def test_api_fork_repo_non_admin(self, backend):
83 def test_api_fork_repo_non_admin(self, backend):
84 source_name = backend['minimal'].repo_name
84 source_name = backend['minimal'].repo_name
85 fork_name = backend.new_repo_name()
85 fork_name = backend.new_repo_name()
86
86
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'fork_repo',
88 self.apikey_regular, 'fork_repo',
89 repoid=source_name,
89 repoid=source_name,
90 fork_name=fork_name)
90 fork_name=fork_name)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92
92
93 expected = {
93 expected = {
94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
95 'success': True,
95 'success': True,
96 'task': None,
96 'task': None,
97 }
97 }
98 try:
98 try:
99 assert_ok(id_, expected, given=response.body)
99 assert_ok(id_, expected, given=response.body)
100 finally:
100 finally:
101 fixture.destroy_repo(fork_name)
101 fixture.destroy_repo(fork_name)
102
102
103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
104 source_name = backend['minimal'].repo_name
104 source_name = backend['minimal'].repo_name
105 repo_group = user_util.create_repo_group()
105 repo_group = user_util.create_repo_group()
106 repo_group_name = repo_group.group_name
106 repo_group_name = repo_group.group_name
107 fork_name = '%s/api-repo-fork' % repo_group_name
107 fork_name = '%s/api-repo-fork' % repo_group_name
108
108
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey_regular, 'fork_repo',
110 self.apikey_regular, 'fork_repo',
111 repoid=source_name,
111 repoid=source_name,
112 fork_name=fork_name)
112 fork_name=fork_name)
113 response = api_call(self.app, params)
113 response = api_call(self.app, params)
114
114
115 expected = {
115 expected = {
116 'repo_group': 'Repository group `{}` does not exist'.format(
116 'repo_group': 'Repository group `{}` does not exist'.format(
117 repo_group_name)}
117 repo_group_name)}
118 try:
118 try:
119 assert_error(id_, expected, given=response.body)
119 assert_error(id_, expected, given=response.body)
120 finally:
120 finally:
121 fixture.destroy_repo(fork_name)
121 fixture.destroy_repo(fork_name)
122
122
123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
124 source_name = backend['minimal'].repo_name
124 source_name = backend['minimal'].repo_name
125 repo_group = user_util.create_repo_group()
125 repo_group = user_util.create_repo_group()
126 fork_name = '%s/api-repo-fork' % repo_group.group_name
126 fork_name = '%s/api-repo-fork' % repo_group.group_name
127
127
128 RepoGroupModel().grant_user_permission(
128 RepoGroupModel().grant_user_permission(
129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
130 Session().commit()
130 Session().commit()
131
131
132 id_, params = build_data(
132 id_, params = build_data(
133 self.apikey_regular, 'fork_repo',
133 self.apikey_regular, 'fork_repo',
134 repoid=source_name,
134 repoid=source_name,
135 fork_name=fork_name)
135 fork_name=fork_name)
136 response = api_call(self.app, params)
136 response = api_call(self.app, params)
137
137
138 expected = {
138 expected = {
139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
140 'success': True,
140 'success': True,
141 'task': None,
141 'task': None,
142 }
142 }
143 try:
143 try:
144 assert_ok(id_, expected, given=response.body)
144 assert_ok(id_, expected, given=response.body)
145 finally:
145 finally:
146 fixture.destroy_repo(fork_name)
146 fixture.destroy_repo(fork_name)
147
147
148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
149 source_name = backend['minimal'].repo_name
149 source_name = backend['minimal'].repo_name
150 fork_name = backend.new_repo_name()
150 fork_name = backend.new_repo_name()
151 id_, params = build_data(
151 id_, params = build_data(
152 self.apikey_regular, 'fork_repo',
152 self.apikey_regular, 'fork_repo',
153 repoid=source_name,
153 repoid=source_name,
154 fork_name=fork_name,
154 fork_name=fork_name,
155 owner=TEST_USER_ADMIN_LOGIN)
155 owner=TEST_USER_ADMIN_LOGIN)
156 response = api_call(self.app, params)
156 response = api_call(self.app, params)
157 expected = 'Only RhodeCode super-admin can specify `owner` param'
157 expected = 'Only RhodeCode super-admin can specify `owner` param'
158 assert_error(id_, expected, given=response.body)
158 assert_error(id_, expected, given=response.body)
159
159
160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
161 self, backend):
161 self, backend):
162 source_name = backend['minimal'].repo_name
162 source_name = backend['minimal'].repo_name
163 RepoModel().grant_user_permission(repo=source_name,
163 RepoModel().grant_user_permission(repo=source_name,
164 user=self.TEST_USER_LOGIN,
164 user=self.TEST_USER_LOGIN,
165 perm='repository.none')
165 perm='repository.none')
166 fork_name = backend.new_repo_name()
166 fork_name = backend.new_repo_name()
167 id_, params = build_data(
167 id_, params = build_data(
168 self.apikey_regular, 'fork_repo',
168 self.apikey_regular, 'fork_repo',
169 repoid=backend.repo_name,
169 repoid=backend.repo_name,
170 fork_name=fork_name)
170 fork_name=fork_name)
171 response = api_call(self.app, params)
171 response = api_call(self.app, params)
172 expected = 'repository `%s` does not exist' % (backend.repo_name)
172 expected = 'repository `%s` does not exist' % (backend.repo_name)
173 assert_error(id_, expected, given=response.body)
173 assert_error(id_, expected, given=response.body)
174
174
175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
176 self, backend, user_util):
176 self, backend, user_util):
177
177
178 regular_user = user_util.create_user()
178 regular_user = user_util.create_user()
179 regular_user_api_key = regular_user.api_key
179 regular_user_api_key = regular_user.api_key
180 usr = UserModel().get_by_username(regular_user.username)
180 usr = UserModel().get_by_username(regular_user.username)
181 usr.inherit_default_permissions = False
181 usr.inherit_default_permissions = False
182 Session().add(usr)
182 Session().add(usr)
183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
184
184
185 source_name = backend['minimal'].repo_name
185 source_name = backend['minimal'].repo_name
186 fork_name = backend.new_repo_name()
186 fork_name = backend.new_repo_name()
187 id_, params = build_data(
187 id_, params = build_data(
188 regular_user_api_key, 'fork_repo',
188 regular_user_api_key, 'fork_repo',
189 repoid=source_name,
189 repoid=source_name,
190 fork_name=fork_name)
190 fork_name=fork_name)
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192 expected = {
192 expected = {
193 "repo_name": "You do not have the permission to "
193 "repo_name": "You do not have the permission to "
194 "store repositories in the root location."}
194 "store repositories in the root location."}
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 def test_api_fork_repo_non_admin_no_permission_to_fork(
197 def test_api_fork_repo_non_admin_no_permission_to_fork(
198 self, backend, user_util):
198 self, backend, user_util):
199
199
200 regular_user = user_util.create_user()
200 regular_user = user_util.create_user()
201 regular_user_api_key = regular_user.api_key
201 regular_user_api_key = regular_user.api_key
202 usr = UserModel().get_by_username(regular_user.username)
202 usr = UserModel().get_by_username(regular_user.username)
203 usr.inherit_default_permissions = False
203 usr.inherit_default_permissions = False
204 Session().add(usr)
204 Session().add(usr)
205
205
206 source_name = backend['minimal'].repo_name
206 source_name = backend['minimal'].repo_name
207 fork_name = backend.new_repo_name()
207 fork_name = backend.new_repo_name()
208 id_, params = build_data(
208 id_, params = build_data(
209 regular_user_api_key, 'fork_repo',
209 regular_user_api_key, 'fork_repo',
210 repoid=source_name,
210 repoid=source_name,
211 fork_name=fork_name)
211 fork_name=fork_name)
212 response = api_call(self.app, params)
212 response = api_call(self.app, params)
213
213
214 expected = "Access was denied to this resource."
214 expected = "Access was denied to this resource."
215 assert_error(id_, expected, given=response.body)
215 assert_error(id_, expected, given=response.body)
216
216
217 def test_api_fork_repo_unknown_owner(self, backend):
217 def test_api_fork_repo_unknown_owner(self, backend):
218 source_name = backend['minimal'].repo_name
218 source_name = backend['minimal'].repo_name
219 fork_name = backend.new_repo_name()
219 fork_name = backend.new_repo_name()
220 owner = 'i-dont-exist'
220 owner = 'i-dont-exist'
221 id_, params = build_data(
221 id_, params = build_data(
222 self.apikey, 'fork_repo',
222 self.apikey, 'fork_repo',
223 repoid=source_name,
223 repoid=source_name,
224 fork_name=fork_name,
224 fork_name=fork_name,
225 owner=owner)
225 owner=owner)
226 response = api_call(self.app, params)
226 response = api_call(self.app, params)
227 expected = 'user `%s` does not exist' % (owner,)
227 expected = 'user `%s` does not exist' % (owner,)
228 assert_error(id_, expected, given=response.body)
228 assert_error(id_, expected, given=response.body)
229
229
230 def test_api_fork_repo_fork_exists(self, backend):
230 def test_api_fork_repo_fork_exists(self, backend):
231 source_name = backend['minimal'].repo_name
231 source_name = backend['minimal'].repo_name
232 fork_name = backend.new_repo_name()
232 fork_name = backend.new_repo_name()
233 fork_repo = fixture.create_fork(source_name, fork_name)
233 fork_repo = fixture.create_fork(source_name, fork_name)
234
234
235 id_, params = build_data(
235 id_, params = build_data(
236 self.apikey, 'fork_repo',
236 self.apikey, 'fork_repo',
237 repoid=source_name,
237 repoid=source_name,
238 fork_name=fork_name,
238 fork_name=fork_name,
239 owner=TEST_USER_ADMIN_LOGIN)
239 owner=TEST_USER_ADMIN_LOGIN)
240 response = api_call(self.app, params)
240 response = api_call(self.app, params)
241
241
242 try:
242 try:
243 expected = {
243 expected = {
244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
245 fork_name)}
245 fork_name)}
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247 finally:
247 finally:
248 fixture.destroy_repo(fork_repo.repo_name)
248 fixture.destroy_repo(fork_repo.repo_name)
249
249
250 def test_api_fork_repo_repo_exists(self, backend):
250 def test_api_fork_repo_repo_exists(self, backend):
251 source_name = backend['minimal'].repo_name
251 source_name = backend['minimal'].repo_name
252 fork_name = source_name
252 fork_name = source_name
253
253
254 id_, params = build_data(
254 id_, params = build_data(
255 self.apikey, 'fork_repo',
255 self.apikey, 'fork_repo',
256 repoid=source_name,
256 repoid=source_name,
257 fork_name=fork_name,
257 fork_name=fork_name,
258 owner=TEST_USER_ADMIN_LOGIN)
258 owner=TEST_USER_ADMIN_LOGIN)
259 response = api_call(self.app, params)
259 response = api_call(self.app, params)
260
260
261 expected = {
261 expected = {
262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
263 fork_name)}
263 fork_name)}
264 assert_error(id_, expected, given=response.body)
264 assert_error(id_, expected, given=response.body)
265
265
266 @mock.patch.object(RepoModel, 'create_fork', crash)
266 @mock.patch.object(RepoModel, 'create_fork', crash)
267 def test_api_fork_repo_exception_occurred(self, backend):
267 def test_api_fork_repo_exception_occurred(self, backend):
268 source_name = backend['minimal'].repo_name
268 source_name = backend['minimal'].repo_name
269 fork_name = backend.new_repo_name()
269 fork_name = backend.new_repo_name()
270 id_, params = build_data(
270 id_, params = build_data(
271 self.apikey, 'fork_repo',
271 self.apikey, 'fork_repo',
272 repoid=source_name,
272 repoid=source_name,
273 fork_name=fork_name,
273 fork_name=fork_name,
274 owner=TEST_USER_ADMIN_LOGIN)
274 owner=TEST_USER_ADMIN_LOGIN)
275 response = api_call(self.app, params)
275 response = api_call(self.app, params)
276
276
277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
278 fork_name)
278 fork_name)
279 assert_error(id_, expected, given=response.body)
279 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util):
31 def test_api_get_gist(self, gist_util):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
35 gist_modified_at = gist.modified_at
35 gist_modified_at = gist.modified_at
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_gist', gistid=gist_id, )
37 self.apikey, 'get_gist', gistid=gist_id, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'access_id': gist_id,
41 'access_id': gist_id,
42 'created_on': gist_created_on,
42 'created_on': gist_created_on,
43 'modified_at': gist_modified_at,
43 'modified_at': gist_modified_at,
44 'description': 'new-gist',
44 'description': 'new-gist',
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
48 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util):
55 def test_api_get_gist_with_content(self, gist_util):
56 mapping = {
56 mapping = {
57 u'filename1.txt': {'content': u'hello world'},
57 u'filename1.txt': {'content': u'hello world'},
58 u'filename1ą.txt': {'content': u'hello worldę'}
58 u'filename1ą.txt': {'content': u'hello worldę'}
59 }
59 }
60 gist = gist_util.create_gist(gist_mapping=mapping)
60 gist = gist_util.create_gist(gist_mapping=mapping)
61 gist_id = gist.gist_access_id
61 gist_id = gist.gist_access_id
62 gist_created_on = gist.created_on
62 gist_created_on = gist.created_on
63 gist_modified_at = gist.modified_at
63 gist_modified_at = gist.modified_at
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'access_id': gist_id,
69 'access_id': gist_id,
70 'created_on': gist_created_on,
70 'created_on': gist_created_on,
71 'modified_at': gist_modified_at,
71 'modified_at': gist_modified_at,
72 'description': 'new-gist',
72 'description': 'new-gist',
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
76 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
80 u'filename1ą.txt': u'hello worldę'
80 u'filename1ą.txt': u'hello worldę'
81 },
81 },
82 }
82 }
83
83
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_gist_not_existing(self):
86 def test_api_get_gist_not_existing(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'get_gist', gistid='12345', )
88 self.apikey_regular, 'get_gist', gistid='12345', )
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = 'gist `%s` does not exist' % ('12345',)
90 expected = 'gist `%s` does not exist' % ('12345',)
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 gist = gist_util.create_gist()
94 gist = gist_util.create_gist()
95 gist_id = gist.gist_access_id
95 gist_id = gist.gist_access_id
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 expected = 'gist `%s` does not exist' % (gist_id,)
100 expected = 'gist `%s` does not exist' % (gist_id,)
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error)
26 build_data, api_call, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gists(self, gist_util):
31 def test_api_get_gists(self, gist_util):
32 gist_util.create_gist()
32 gist_util.create_gist()
33 gist_util.create_gist()
33 gist_util.create_gist()
34
34
35 id_, params = build_data(self.apikey, 'get_gists')
35 id_, params = build_data(self.apikey, 'get_gists')
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37 assert len(response.json['result']) == 2
37 assert len(response.json['result']) == 2
38
38
39 def test_api_get_gists_regular_user(self, gist_util):
39 def test_api_get_gists_regular_user(self, gist_util):
40 # by admin
40 # by admin
41 gist_util.create_gist()
41 gist_util.create_gist()
42 gist_util.create_gist()
42 gist_util.create_gist()
43
43
44 # by reg user
44 # by reg user
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
48
48
49 id_, params = build_data(self.apikey_regular, 'get_gists')
49 id_, params = build_data(self.apikey_regular, 'get_gists')
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 assert len(response.json['result']) == 3
51 assert len(response.json['result']) == 3
52
52
53 def test_api_get_gists_only_for_regular_user(self, gist_util):
53 def test_api_get_gists_only_for_regular_user(self, gist_util):
54 # by admin
54 # by admin
55 gist_util.create_gist()
55 gist_util.create_gist()
56 gist_util.create_gist()
56 gist_util.create_gist()
57
57
58 # by reg user
58 # by reg user
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
62
62
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66 assert len(response.json['result']) == 3
66 assert len(response.json['result']) == 3
67
67
68 def test_api_get_gists_regular_user_with_different_userid(self):
68 def test_api_get_gists_regular_user_with_different_userid(self):
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey_regular, 'get_gists',
70 self.apikey_regular, 'get_gists',
71 userid=TEST_USER_ADMIN_LOGIN)
71 userid=TEST_USER_ADMIN_LOGIN)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 expected = 'userid is not the same as your user'
73 expected = 'userid is not the same as your user'
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -1,36 +1,36 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetIp(object):
28 class TestGetIp(object):
29 def test_api_get_ip(self):
29 def test_api_get_ip(self):
30 id_, params = build_data(self.apikey, 'get_ip')
30 id_, params = build_data(self.apikey, 'get_ip')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32 expected = {
32 expected = {
33 'server_ip_addr': '0.0.0.0',
33 'server_ip_addr': '0.0.0.0',
34 'user_ips': []
34 'user_ips': []
35 }
35 }
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
@@ -1,91 +1,91 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Repository, User
24 from rhodecode.model.db import Repository, User
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetLocks(object):
31 class TestGetLocks(object):
32 def test_api_get_user_locks_regular_user(self):
32 def test_api_get_user_locks_regular_user(self):
33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35 expected = []
35 expected = []
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 def test_api_get_user_locks_with_userid_regular_user(self):
38 def test_api_get_user_locks_with_userid_regular_user(self):
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 expected = 'userid is not the same as your user'
42 expected = 'userid is not the same as your user'
43 assert_error(id_, expected, given=response.body)
43 assert_error(id_, expected, given=response.body)
44
44
45 def test_api_get_user_locks(self):
45 def test_api_get_user_locks(self):
46 id_, params = build_data(self.apikey, 'get_user_locks')
46 id_, params = build_data(self.apikey, 'get_user_locks')
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48 expected = []
48 expected = []
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
52 ('apikey', True),
52 ('apikey', True),
53 ('apikey_regular', False),
53 ('apikey_regular', False),
54 ])
54 ])
55 def test_api_get_user_locks_with_one_locked_repo(
55 def test_api_get_user_locks_with_one_locked_repo(
56 self, apikey_attr, expect_secrets, backend):
56 self, apikey_attr, expect_secrets, backend):
57
57
58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
59 Repository.lock(
59 Repository.lock(
60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
61
61
62 apikey = getattr(self, apikey_attr)
62 apikey = getattr(self, apikey_attr)
63
63
64 id_, params = build_data(apikey, 'get_user_locks')
64 id_, params = build_data(apikey, 'get_user_locks')
65 if apikey_attr == 'apikey':
65 if apikey_attr == 'apikey':
66 # super-admin should call in specific user
66 # super-admin should call in specific user
67 id_, params = build_data(apikey, 'get_user_locks',
67 id_, params = build_data(apikey, 'get_user_locks',
68 userid=self.TEST_USER_LOGIN)
68 userid=self.TEST_USER_LOGIN)
69
69
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
75 self, backend):
75 self, backend):
76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
77
77
78 Repository.lock(repo, User.get_by_username(
78 Repository.lock(repo, User.get_by_username(
79 self.TEST_USER_LOGIN).user_id)
79 self.TEST_USER_LOGIN).user_id)
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = [repo.get_api_data(include_secrets=True)]
83 expected = [repo.get_api_data(include_secrets=True)]
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_user_locks_with_userid(self):
86 def test_api_get_user_locks_with_userid(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = []
90 expected = []
91 assert_ok(id_, expected, given=response.body)
91 assert_ok(id_, expected, given=response.body)
@@ -1,133 +1,133 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24 import urlobject
24 import urlobject
25 from pylons import url
25 from pylons import url
26
26
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util):
36 def test_api_get_pull_request(self, pr_util):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_pull_request',
40 self.apikey, 'get_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 assert response.status == '200 OK'
46 assert response.status == '200 OK'
47
47
48 url_obj = urlobject.URLObject(
48 url_obj = urlobject.URLObject(
49 url(
49 url(
50 'pullrequest_show',
50 'pullrequest_show',
51 repo_name=pull_request.target_repo.repo_name,
51 repo_name=pull_request.target_repo.repo_name,
52 pull_request_id=pull_request.pull_request_id, qualified=True))
52 pull_request_id=pull_request.pull_request_id, qualified=True))
53 pr_url = unicode(
53 pr_url = unicode(
54 url_obj.with_netloc('test.example.com:80'))
54 url_obj.with_netloc('test.example.com:80'))
55 source_url = unicode(
55 source_url = unicode(
56 pull_request.source_repo.clone_url()
56 pull_request.source_repo.clone_url()
57 .with_netloc('test.example.com:80'))
57 .with_netloc('test.example.com:80'))
58 target_url = unicode(
58 target_url = unicode(
59 pull_request.target_repo.clone_url()
59 pull_request.target_repo.clone_url()
60 .with_netloc('test.example.com:80'))
60 .with_netloc('test.example.com:80'))
61 shadow_url = unicode(
61 shadow_url = unicode(
62 PullRequestModel().get_shadow_clone_url(pull_request))
62 PullRequestModel().get_shadow_clone_url(pull_request))
63 expected = {
63 expected = {
64 'pull_request_id': pull_request.pull_request_id,
64 'pull_request_id': pull_request.pull_request_id,
65 'url': pr_url,
65 'url': pr_url,
66 'title': pull_request.title,
66 'title': pull_request.title,
67 'description': pull_request.description,
67 'description': pull_request.description,
68 'status': pull_request.status,
68 'status': pull_request.status,
69 'created_on': pull_request.created_on,
69 'created_on': pull_request.created_on,
70 'updated_on': pull_request.updated_on,
70 'updated_on': pull_request.updated_on,
71 'commit_ids': pull_request.revisions,
71 'commit_ids': pull_request.revisions,
72 'review_status': pull_request.calculated_review_status(),
72 'review_status': pull_request.calculated_review_status(),
73 'mergeable': {
73 'mergeable': {
74 'status': True,
74 'status': True,
75 'message': 'This pull request can be automatically merged.',
75 'message': 'This pull request can be automatically merged.',
76 },
76 },
77 'source': {
77 'source': {
78 'clone_url': source_url,
78 'clone_url': source_url,
79 'repository': pull_request.source_repo.repo_name,
79 'repository': pull_request.source_repo.repo_name,
80 'reference': {
80 'reference': {
81 'name': pull_request.source_ref_parts.name,
81 'name': pull_request.source_ref_parts.name,
82 'type': pull_request.source_ref_parts.type,
82 'type': pull_request.source_ref_parts.type,
83 'commit_id': pull_request.source_ref_parts.commit_id,
83 'commit_id': pull_request.source_ref_parts.commit_id,
84 },
84 },
85 },
85 },
86 'target': {
86 'target': {
87 'clone_url': target_url,
87 'clone_url': target_url,
88 'repository': pull_request.target_repo.repo_name,
88 'repository': pull_request.target_repo.repo_name,
89 'reference': {
89 'reference': {
90 'name': pull_request.target_ref_parts.name,
90 'name': pull_request.target_ref_parts.name,
91 'type': pull_request.target_ref_parts.type,
91 'type': pull_request.target_ref_parts.type,
92 'commit_id': pull_request.target_ref_parts.commit_id,
92 'commit_id': pull_request.target_ref_parts.commit_id,
93 },
93 },
94 },
94 },
95 'merge': {
95 'merge': {
96 'clone_url': shadow_url,
96 'clone_url': shadow_url,
97 'reference': {
97 'reference': {
98 'name': pull_request.shadow_merge_ref.name,
98 'name': pull_request.shadow_merge_ref.name,
99 'type': pull_request.shadow_merge_ref.type,
99 'type': pull_request.shadow_merge_ref.type,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 },
101 },
102 },
102 },
103 'author': pull_request.author.get_api_data(include_secrets=False,
103 'author': pull_request.author.get_api_data(include_secrets=False,
104 details='basic'),
104 details='basic'),
105 'reviewers': [
105 'reviewers': [
106 {
106 {
107 'user': reviewer.get_api_data(include_secrets=False,
107 'user': reviewer.get_api_data(include_secrets=False,
108 details='basic'),
108 details='basic'),
109 'reasons': reasons,
109 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
110 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
111 }
112 for reviewer, reasons, st in pull_request.reviewers_statuses()
112 for reviewer, reasons, st in pull_request.reviewers_statuses()
113 ]
113 ]
114 }
114 }
115 assert_ok(id_, expected, response.body)
115 assert_ok(id_, expected, response.body)
116
116
117 def test_api_get_pull_request_repo_error(self):
117 def test_api_get_pull_request_repo_error(self):
118 id_, params = build_data(
118 id_, params = build_data(
119 self.apikey, 'get_pull_request',
119 self.apikey, 'get_pull_request',
120 repoid=666, pullrequestid=1)
120 repoid=666, pullrequestid=1)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122
122
123 expected = 'repository `666` does not exist'
123 expected = 'repository `666` does not exist'
124 assert_error(id_, expected, given=response.body)
124 assert_error(id_, expected, given=response.body)
125
125
126 def test_api_get_pull_request_pull_request_error(self):
126 def test_api_get_pull_request_pull_request_error(self):
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey, 'get_pull_request',
128 self.apikey, 'get_pull_request',
129 repoid=1, pullrequestid=666)
129 repoid=1, pullrequestid=666)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 expected = 'pull request `666` does not exist'
132 expected = 'pull request `666` does not exist'
133 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
@@ -1,80 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error)
27 build_data, api_call, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetPullRequest(object):
31 class TestGetPullRequest(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_get_pull_requests(self, pr_util):
33 def test_api_get_pull_requests(self, pr_util):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35 pull_request_2 = PullRequestModel().create(
35 pull_request_2 = PullRequestModel().create(
36 created_by=pull_request.author,
36 created_by=pull_request.author,
37 source_repo=pull_request.source_repo,
37 source_repo=pull_request.source_repo,
38 source_ref=pull_request.source_ref,
38 source_ref=pull_request.source_ref,
39 target_repo=pull_request.target_repo,
39 target_repo=pull_request.target_repo,
40 target_ref=pull_request.target_ref,
40 target_ref=pull_request.target_ref,
41 revisions=pull_request.revisions,
41 revisions=pull_request.revisions,
42 reviewers=(),
42 reviewers=(),
43 title=pull_request.title,
43 title=pull_request.title,
44 description=pull_request.description,
44 description=pull_request.description,
45 )
45 )
46 Session().commit()
46 Session().commit()
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'get_pull_requests',
48 self.apikey, 'get_pull_requests',
49 repoid=pull_request.target_repo.repo_name)
49 repoid=pull_request.target_repo.repo_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 assert response.status == '200 OK'
51 assert response.status == '200 OK'
52 assert len(response.json['result']) == 2
52 assert len(response.json['result']) == 2
53
53
54 PullRequestModel().close_pull_request(
54 PullRequestModel().close_pull_request(
55 pull_request_2, pull_request_2.author)
55 pull_request_2, pull_request_2.author)
56 Session().commit()
56 Session().commit()
57
57
58 id_, params = build_data(
58 id_, params = build_data(
59 self.apikey, 'get_pull_requests',
59 self.apikey, 'get_pull_requests',
60 repoid=pull_request.target_repo.repo_name,
60 repoid=pull_request.target_repo.repo_name,
61 status='new')
61 status='new')
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63 assert response.status == '200 OK'
63 assert response.status == '200 OK'
64 assert len(response.json['result']) == 1
64 assert len(response.json['result']) == 1
65
65
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'get_pull_requests',
67 self.apikey, 'get_pull_requests',
68 repoid=pull_request.target_repo.repo_name,
68 repoid=pull_request.target_repo.repo_name,
69 status='closed')
69 status='closed')
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71 assert response.status == '200 OK'
71 assert response.status == '200 OK'
72 assert len(response.json['result']) == 1
72 assert len(response.json['result']) == 1
73
73
74 @pytest.mark.backends("git", "hg")
74 @pytest.mark.backends("git", "hg")
75 def test_api_get_pull_requests_repo_error(self):
75 def test_api_get_pull_requests_repo_error(self):
76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
77 response = api_call(self.app, params)
77 response = api_call(self.app, params)
78
78
79 expected = 'repository `666` does not exist'
79 expected = 'repository `666` does not exist'
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
@@ -1,139 +1,139 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestGetRepo(object):
33 class TestGetRepo(object):
34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
35 ('apikey', True),
35 ('apikey', True),
36 ('apikey_regular', False),
36 ('apikey_regular', False),
37 ])
37 ])
38 @pytest.mark.parametrize("cache_param", [
38 @pytest.mark.parametrize("cache_param", [
39 True,
39 True,
40 False,
40 False,
41 None,
41 None,
42 ])
42 ])
43 def test_api_get_repo(
43 def test_api_get_repo(
44 self, apikey_attr, expect_secrets, cache_param, backend,
44 self, apikey_attr, expect_secrets, cache_param, backend,
45 user_util):
45 user_util):
46 repo = backend.create_repo()
46 repo = backend.create_repo()
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 group = user_util.create_user_group(members=[usr])
48 group = user_util.create_user_group(members=[usr])
49 user_util.grant_user_group_permission_to_repo(
49 user_util.grant_user_group_permission_to_repo(
50 repo=repo, user_group=group, permission_name='repository.read')
50 repo=repo, user_group=group, permission_name='repository.read')
51 Session().commit()
51 Session().commit()
52 kwargs = {
52 kwargs = {
53 'repoid': repo.repo_name,
53 'repoid': repo.repo_name,
54 }
54 }
55 if cache_param is not None:
55 if cache_param is not None:
56 kwargs['cache'] = cache_param
56 kwargs['cache'] = cache_param
57
57
58 apikey = getattr(self, apikey_attr)
58 apikey = getattr(self, apikey_attr)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 ret = repo.get_api_data()
62 ret = repo.get_api_data()
63
63
64 permissions = expected_permissions(repo)
64 permissions = expected_permissions(repo)
65
65
66 followers = []
66 followers = []
67 for user in repo.followers:
67 for user in repo.followers:
68 followers.append(user.user.get_api_data(
68 followers.append(user.user.get_api_data(
69 include_secrets=expect_secrets))
69 include_secrets=expect_secrets))
70
70
71 ret['members'] = permissions
71 ret['members'] = permissions
72 ret['permissions'] = permissions
72 ret['permissions'] = permissions
73 ret['followers'] = followers
73 ret['followers'] = followers
74
74
75 expected = ret
75 expected = ret
76
76
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78
78
79 @pytest.mark.parametrize("grant_perm", [
79 @pytest.mark.parametrize("grant_perm", [
80 'repository.admin',
80 'repository.admin',
81 'repository.write',
81 'repository.write',
82 'repository.read',
82 'repository.read',
83 ])
83 ])
84 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
84 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
85 # TODO: Depending on which tests are running before this one, we
85 # TODO: Depending on which tests are running before this one, we
86 # start with a different number of permissions in the database.
86 # start with a different number of permissions in the database.
87 repo = RepoModel().get_by_repo_name(backend.repo_name)
87 repo = RepoModel().get_by_repo_name(backend.repo_name)
88 permission_count = len(repo.repo_to_perm)
88 permission_count = len(repo.repo_to_perm)
89
89
90 RepoModel().grant_user_permission(repo=backend.repo_name,
90 RepoModel().grant_user_permission(repo=backend.repo_name,
91 user=self.TEST_USER_LOGIN,
91 user=self.TEST_USER_LOGIN,
92 perm=grant_perm)
92 perm=grant_perm)
93 Session().commit()
93 Session().commit()
94 id_, params = build_data(
94 id_, params = build_data(
95 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
95 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
96 response = api_call(self.app, params)
96 response = api_call(self.app, params)
97
97
98 repo = RepoModel().get_by_repo_name(backend.repo_name)
98 repo = RepoModel().get_by_repo_name(backend.repo_name)
99 ret = repo.get_api_data()
99 ret = repo.get_api_data()
100
100
101 assert permission_count + 1, len(repo.repo_to_perm)
101 assert permission_count + 1, len(repo.repo_to_perm)
102
102
103 permissions = expected_permissions(repo)
103 permissions = expected_permissions(repo)
104
104
105 followers = []
105 followers = []
106 for user in repo.followers:
106 for user in repo.followers:
107 followers.append(user.user.get_api_data())
107 followers.append(user.user.get_api_data())
108
108
109 ret['members'] = permissions
109 ret['members'] = permissions
110 ret['permissions'] = permissions
110 ret['permissions'] = permissions
111 ret['followers'] = followers
111 ret['followers'] = followers
112
112
113 expected = ret
113 expected = ret
114 try:
114 try:
115 assert_ok(id_, expected, given=response.body)
115 assert_ok(id_, expected, given=response.body)
116 finally:
116 finally:
117 RepoModel().revoke_user_permission(
117 RepoModel().revoke_user_permission(
118 backend.repo_name, self.TEST_USER_LOGIN)
118 backend.repo_name, self.TEST_USER_LOGIN)
119
119
120 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
120 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
121 RepoModel().grant_user_permission(repo=backend.repo_name,
121 RepoModel().grant_user_permission(repo=backend.repo_name,
122 user=self.TEST_USER_LOGIN,
122 user=self.TEST_USER_LOGIN,
123 perm='repository.none')
123 perm='repository.none')
124
124
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
126 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
127 response = api_call(self.app, params)
127 response = api_call(self.app, params)
128
128
129 expected = 'repository `%s` does not exist' % (backend.repo_name)
129 expected = 'repository `%s` does not exist' % (backend.repo_name)
130 assert_error(id_, expected, given=response.body)
130 assert_error(id_, expected, given=response.body)
131
131
132 def test_api_get_repo_not_existing(self):
132 def test_api_get_repo_not_existing(self):
133 id_, params = build_data(
133 id_, params = build_data(
134 self.apikey, 'get_repo', repoid='no-such-repo')
134 self.apikey, 'get_repo', repoid='no-such-repo')
135 response = api_call(self.app, params)
135 response = api_call(self.app, params)
136
136
137 ret = 'repository `%s` does not exist' % 'no-such-repo'
137 ret = 'repository `%s` does not exist' % 'no-such-repo'
138 expected = ret
138 expected = ret
139 assert_error(id_, expected, given=response.body)
139 assert_error(id_, expected, given=response.body)
@@ -1,140 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetRepoChangeset(object):
28 class TestGetRepoChangeset(object):
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
30 def test_get_repo_changeset(self, details, backend):
30 def test_get_repo_changeset(self, details, backend):
31 commit = backend.repo.get_commit(commit_idx=0)
31 commit = backend.repo.get_commit(commit_idx=0)
32 __, params = build_data(
32 __, params = build_data(
33 self.apikey, 'get_repo_changeset',
33 self.apikey, 'get_repo_changeset',
34 repoid=backend.repo_name, revision=commit.raw_id,
34 repoid=backend.repo_name, revision=commit.raw_id,
35 details=details,
35 details=details,
36 )
36 )
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38 result = response.json['result']
38 result = response.json['result']
39 assert result['revision'] == 0
39 assert result['revision'] == 0
40 assert result['raw_id'] == commit.raw_id
40 assert result['raw_id'] == commit.raw_id
41
41
42 if details == 'full':
42 if details == 'full':
43 assert result['refs']['bookmarks'] == getattr(
43 assert result['refs']['bookmarks'] == getattr(
44 commit, 'bookmarks', [])
44 commit, 'bookmarks', [])
45 assert result['refs']['branches'] == [commit.branch]
45 assert result['refs']['branches'] == [commit.branch]
46 assert result['refs']['tags'] == commit.tags
46 assert result['refs']['tags'] == commit.tags
47
47
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 def test_get_repo_changeset_bad_type(self, details, backend):
49 def test_get_repo_changeset_bad_type(self, details, backend):
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'get_repo_changeset',
51 self.apikey, 'get_repo_changeset',
52 repoid=backend.repo_name, revision=0,
52 repoid=backend.repo_name, revision=0,
53 details=details,
53 details=details,
54 )
54 )
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56 expected = 'commit_id must be a string value'
56 expected = 'commit_id must be a string value'
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 def test_get_repo_changesets(self, details, backend):
60 def test_get_repo_changesets(self, details, backend):
61 limit = 2
61 limit = 2
62 commit = backend.repo.get_commit(commit_idx=0)
62 commit = backend.repo.get_commit(commit_idx=0)
63 __, params = build_data(
63 __, params = build_data(
64 self.apikey, 'get_repo_changesets',
64 self.apikey, 'get_repo_changesets',
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 details=details,
66 details=details,
67 )
67 )
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69 result = response.json['result']
69 result = response.json['result']
70 assert result
70 assert result
71 assert len(result) == limit
71 assert len(result) == limit
72 for x in xrange(limit):
72 for x in xrange(limit):
73 assert result[x]['revision'] == x
73 assert result[x]['revision'] == x
74
74
75 if details == 'full':
75 if details == 'full':
76 for x in xrange(limit):
76 for x in xrange(limit):
77 assert 'bookmarks' in result[x]['refs']
77 assert 'bookmarks' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
80
80
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 @pytest.mark.parametrize("start_rev, expected_revision", [
82 @pytest.mark.parametrize("start_rev, expected_revision", [
83 ("0", 0),
83 ("0", 0),
84 ("10", 10),
84 ("10", 10),
85 ("20", 20),
85 ("20", 20),
86 ])
86 ])
87 @pytest.mark.backends("hg", "git")
87 @pytest.mark.backends("hg", "git")
88 def test_get_repo_changesets_commit_range(
88 def test_get_repo_changesets_commit_range(
89 self, details, backend, start_rev, expected_revision):
89 self, details, backend, start_rev, expected_revision):
90 limit = 10
90 limit = 10
91 __, params = build_data(
91 __, params = build_data(
92 self.apikey, 'get_repo_changesets',
92 self.apikey, 'get_repo_changesets',
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 details=details,
94 details=details,
95 )
95 )
96 response = api_call(self.app, params)
96 response = api_call(self.app, params)
97 result = response.json['result']
97 result = response.json['result']
98 assert result
98 assert result
99 assert len(result) == limit
99 assert len(result) == limit
100 for i in xrange(limit):
100 for i in xrange(limit):
101 assert result[i]['revision'] == int(expected_revision) + i
101 assert result[i]['revision'] == int(expected_revision) + i
102
102
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 @pytest.mark.parametrize("start_rev, expected_revision", [
104 @pytest.mark.parametrize("start_rev, expected_revision", [
105 ("0", 0),
105 ("0", 0),
106 ("10", 9),
106 ("10", 9),
107 ("20", 19),
107 ("20", 19),
108 ])
108 ])
109 def test_get_repo_changesets_commit_range_svn(
109 def test_get_repo_changesets_commit_range_svn(
110 self, details, backend_svn, start_rev, expected_revision):
110 self, details, backend_svn, start_rev, expected_revision):
111
111
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 # in our API allows to pass in a "Commit ID" as well as a
113 # in our API allows to pass in a "Commit ID" as well as a
114 # "Commit Index". In the case of Subversion it is not possible to
114 # "Commit Index". In the case of Subversion it is not possible to
115 # distinguish these cases. As a workaround we implemented this
115 # distinguish these cases. As a workaround we implemented this
116 # behavior which gives a preference to see it as a "Commit ID".
116 # behavior which gives a preference to see it as a "Commit ID".
117
117
118 limit = 10
118 limit = 10
119 __, params = build_data(
119 __, params = build_data(
120 self.apikey, 'get_repo_changesets',
120 self.apikey, 'get_repo_changesets',
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 details=details,
122 details=details,
123 )
123 )
124 response = api_call(self.app, params)
124 response = api_call(self.app, params)
125 result = response.json['result']
125 result = response.json['result']
126 assert result
126 assert result
127 assert len(result) == limit
127 assert len(result) == limit
128 for i in xrange(limit):
128 for i in xrange(limit):
129 assert result[i]['revision'] == int(expected_revision) + i
129 assert result[i]['revision'] == int(expected_revision) + i
130
130
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 def test_get_repo_changesets_bad_type(self, details, backend):
132 def test_get_repo_changesets_bad_type(self, details, backend):
133 id_, params = build_data(
133 id_, params = build_data(
134 self.apikey, 'get_repo_changesets',
134 self.apikey, 'get_repo_changesets',
135 repoid=backend.repo_name, start_rev=0, limit=2,
135 repoid=backend.repo_name, start_rev=0, limit=2,
136 details=details,
136 details=details,
137 )
137 )
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139 expected = 'commit_id must be a string value'
139 expected = 'commit_id must be a string value'
140 assert_error(id_, expected, given=response.body)
140 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, expected_permissions)
26 build_data, api_call, assert_ok, assert_error, expected_permissions)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetRepoGroup(object):
30 class TestApiGetRepoGroup(object):
31 def test_api_get_repo_group(self, user_util):
31 def test_api_get_repo_group(self, user_util):
32 repo_group = user_util.create_repo_group()
32 repo_group = user_util.create_repo_group()
33 repo_group_name = repo_group.group_name
33 repo_group_name = repo_group.group_name
34
34
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38
38
39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
40 ret = repo_group.get_api_data()
40 ret = repo_group.get_api_data()
41
41
42 permissions = expected_permissions(repo_group)
42 permissions = expected_permissions(repo_group)
43
43
44 ret['members'] = permissions
44 ret['members'] = permissions
45 expected = ret
45 expected = ret
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 def test_api_get_repo_group_not_existing(self):
48 def test_api_get_repo_group_not_existing(self):
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
54 expected = ret
54 expected = ret
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiGetRepoGroups(object):
29 class TestApiGetRepoGroups(object):
30 def test_api_get_repo_groups(self):
30 def test_api_get_repo_groups(self):
31 id_, params = build_data(self.apikey, 'get_repo_groups')
31 id_, params = build_data(self.apikey, 'get_repo_groups')
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33
33
34 result = []
34 result = []
35 for repo in RepoGroupModel().get_all():
35 for repo in RepoGroupModel().get_all():
36 result.append(repo.get_api_data())
36 result.append(repo.get_api_data())
37 ret = jsonify(result)
37 ret = jsonify(result)
38
38
39 expected = ret
39 expected = ret
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetRepoNodes(object):
31 class TestGetRepoNodes(object):
32 @pytest.mark.parametrize("name, ret_type", [
32 @pytest.mark.parametrize("name, ret_type", [
33 ('all', 'all'),
33 ('all', 'all'),
34 ('dirs', 'dirs'),
34 ('dirs', 'dirs'),
35 ('files', 'files'),
35 ('files', 'files'),
36 ])
36 ])
37 def test_api_get_repo_nodes(self, name, ret_type, backend):
37 def test_api_get_repo_nodes(self, name, ret_type, backend):
38 commit_id = 'tip'
38 commit_id = 'tip'
39 path = '/'
39 path = '/'
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'get_repo_nodes',
41 self.apikey, 'get_repo_nodes',
42 repoid=backend.repo_name, revision=commit_id,
42 repoid=backend.repo_name, revision=commit_id,
43 root_path=path,
43 root_path=path,
44 ret_type=ret_type)
44 ret_type=ret_type)
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 # we don't the actual return types here since it's tested somewhere
47 # we don't the actual return types here since it's tested somewhere
48 # else
48 # else
49 expected = response.json['result']
49 expected = response.json['result']
50 assert_ok(id_, expected, given=response.body)
50 assert_ok(id_, expected, given=response.body)
51
51
52 def test_api_get_repo_nodes_bad_commits(self, backend):
52 def test_api_get_repo_nodes_bad_commits(self, backend):
53 commit_id = 'i-dont-exist'
53 commit_id = 'i-dont-exist'
54 path = '/'
54 path = '/'
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey, 'get_repo_nodes',
56 self.apikey, 'get_repo_nodes',
57 repoid=backend.repo_name, revision=commit_id,
57 repoid=backend.repo_name, revision=commit_id,
58 root_path=path, )
58 root_path=path, )
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_get_repo_nodes_bad_path(self, backend):
64 def test_api_get_repo_nodes_bad_path(self, backend):
65 commit_id = 'tip'
65 commit_id = 'tip'
66 path = '/idontexits'
66 path = '/idontexits'
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'get_repo_nodes',
68 self.apikey, 'get_repo_nodes',
69 repoid=backend.repo_name, revision=commit_id,
69 repoid=backend.repo_name, revision=commit_id,
70 root_path=path, )
70 root_path=path, )
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 commit_id = 'tip'
77 commit_id = 'tip'
78 path = '/'
78 path = '/'
79 max_file_bytes = 500
79 max_file_bytes = 500
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'get_repo_nodes',
82 self.apikey, 'get_repo_nodes',
83 repoid=backend.repo_name, revision=commit_id, details='full',
83 repoid=backend.repo_name, revision=commit_id, details='full',
84 root_path=path)
84 root_path=path)
85 response = api_call(self.app, params)
85 response = api_call(self.app, params)
86 assert any(file['content'] and len(file['content']) > max_file_bytes
86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 for file in response.json['result'])
87 for file in response.json['result'])
88
88
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey, 'get_repo_nodes',
90 self.apikey, 'get_repo_nodes',
91 repoid=backend.repo_name, revision=commit_id,
91 repoid=backend.repo_name, revision=commit_id,
92 root_path=path, details='full',
92 root_path=path, details='full',
93 max_file_bytes=max_file_bytes)
93 max_file_bytes=max_file_bytes)
94 response = api_call(self.app, params)
94 response = api_call(self.app, params)
95 assert all(
95 assert all(
96 file['content'] is None if file['size'] > max_file_bytes else True
96 file['content'] is None if file['size'] > max_file_bytes else True
97 for file in response.json['result'])
97 for file in response.json['result'])
98
98
99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
100 commit_id = 'tip'
100 commit_id = 'tip'
101 path = '/'
101 path = '/'
102 ret_type = 'error'
102 ret_type = 'error'
103 id_, params = build_data(
103 id_, params = build_data(
104 self.apikey, 'get_repo_nodes',
104 self.apikey, 'get_repo_nodes',
105 repoid=backend.repo_name, revision=commit_id,
105 repoid=backend.repo_name, revision=commit_id,
106 root_path=path,
106 root_path=path,
107 ret_type=ret_type)
107 ret_type=ret_type)
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109
109
110 expected = ('ret_type must be one of %s'
110 expected = ('ret_type must be one of %s'
111 % (','.join(['all', 'dirs', 'files'])))
111 % (','.join(['all', 'dirs', 'files'])))
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
113
113
114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
115 ('all', 'all', 'repository.write'),
115 ('all', 'all', 'repository.write'),
116 ('dirs', 'dirs', 'repository.admin'),
116 ('dirs', 'dirs', 'repository.admin'),
117 ('files', 'files', 'repository.read'),
117 ('files', 'files', 'repository.read'),
118 ])
118 ])
119 def test_api_get_repo_nodes_by_regular_user(
119 def test_api_get_repo_nodes_by_regular_user(
120 self, name, ret_type, grant_perm, backend):
120 self, name, ret_type, grant_perm, backend):
121 RepoModel().grant_user_permission(repo=backend.repo_name,
121 RepoModel().grant_user_permission(repo=backend.repo_name,
122 user=self.TEST_USER_LOGIN,
122 user=self.TEST_USER_LOGIN,
123 perm=grant_perm)
123 perm=grant_perm)
124 Session().commit()
124 Session().commit()
125
125
126 commit_id = 'tip'
126 commit_id = 'tip'
127 path = '/'
127 path = '/'
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey_regular, 'get_repo_nodes',
129 self.apikey_regular, 'get_repo_nodes',
130 repoid=backend.repo_name, revision=commit_id,
130 repoid=backend.repo_name, revision=commit_id,
131 root_path=path,
131 root_path=path,
132 ret_type=ret_type)
132 ret_type=ret_type)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 # we don't the actual return types here since it's tested somewhere
135 # we don't the actual return types here since it's tested somewhere
136 # else
136 # else
137 expected = response.json['result']
137 expected = response.json['result']
138 try:
138 try:
139 assert_ok(id_, expected, given=response.body)
139 assert_ok(id_, expected, given=response.body)
140 finally:
140 finally:
141 RepoModel().revoke_user_permission(
141 RepoModel().revoke_user_permission(
142 backend.repo_name, self.TEST_USER_LOGIN)
142 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestGetRepo(object):
33 class TestGetRepo(object):
34 def test_api_get_repo_refs(self, backend, user_util):
34 def test_api_get_repo_refs(self, backend, user_util):
35 repo = backend.create_repo()
35 repo = backend.create_repo()
36 id_, params = build_data(self.apikey, 'get_repo_refs',
36 id_, params = build_data(self.apikey, 'get_repo_refs',
37 **{'repoid': repo.repo_name,})
37 **{'repoid': repo.repo_name,})
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39 expected = repo.scm_instance().refs()
39 expected = repo.scm_instance().refs()
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, jsonify)
26 build_data, api_call, assert_ok, assert_error, jsonify)
27 from rhodecode.model.db import User
27 from rhodecode.model.db import User
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetRepos(object):
31 class TestGetRepos(object):
32 def test_api_get_repos(self):
32 def test_api_get_repos(self):
33 id_, params = build_data(self.apikey, 'get_repos')
33 id_, params = build_data(self.apikey, 'get_repos')
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35
35
36 result = []
36 result = []
37 for repo in RepoModel().get_all():
37 for repo in RepoModel().get_all():
38 result.append(repo.get_api_data(include_secrets=True))
38 result.append(repo.get_api_data(include_secrets=True))
39 ret = jsonify(result)
39 ret = jsonify(result)
40
40
41 expected = ret
41 expected = ret
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_get_repos_only_toplevel(self, user_util):
44 def test_api_get_repos_only_toplevel(self, user_util):
45 repo_group = user_util.create_repo_group(auto_cleanup=True)
45 repo_group = user_util.create_repo_group(auto_cleanup=True)
46 user_util.create_repo(parent=repo_group)
46 user_util.create_repo(parent=repo_group)
47
47
48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 result = []
51 result = []
52 for repo in RepoModel().get_repos_for_root(root=None):
52 for repo in RepoModel().get_repos_for_root(root=None):
53 result.append(repo.get_api_data(include_secrets=True))
53 result.append(repo.get_api_data(include_secrets=True))
54 expected = jsonify(result)
54 expected = jsonify(result)
55
55
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 def test_api_get_repos_with_wrong_root(self):
58 def test_api_get_repos_with_wrong_root(self):
59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 expected = 'Root repository group `abracadabra` does not exist'
62 expected = 'Root repository group `abracadabra` does not exist'
63 assert_error(id_, expected, given=response.body)
63 assert_error(id_, expected, given=response.body)
64
64
65 def test_api_get_repos_with_root(self, user_util):
65 def test_api_get_repos_with_root(self, user_util):
66 repo_group = user_util.create_repo_group(auto_cleanup=True)
66 repo_group = user_util.create_repo_group(auto_cleanup=True)
67 repo_group_name = repo_group.group_name
67 repo_group_name = repo_group.group_name
68
68
69 user_util.create_repo(parent=repo_group)
69 user_util.create_repo(parent=repo_group)
70 user_util.create_repo(parent=repo_group)
70 user_util.create_repo(parent=repo_group)
71
71
72 # nested, should not show up
72 # nested, should not show up
73 user_util._test_name = '{}/'.format(repo_group_name)
73 user_util._test_name = '{}/'.format(repo_group_name)
74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
75 user_util.create_repo(parent=sub_repo_group)
75 user_util.create_repo(parent=sub_repo_group)
76
76
77 id_, params = build_data(self.apikey, 'get_repos',
77 id_, params = build_data(self.apikey, 'get_repos',
78 root=repo_group_name, traverse=0)
78 root=repo_group_name, traverse=0)
79 response = api_call(self.app, params)
79 response = api_call(self.app, params)
80
80
81 result = []
81 result = []
82 for repo in RepoModel().get_repos_for_root(repo_group):
82 for repo in RepoModel().get_repos_for_root(repo_group):
83 result.append(repo.get_api_data(include_secrets=True))
83 result.append(repo.get_api_data(include_secrets=True))
84
84
85 assert len(result) == 2
85 assert len(result) == 2
86 expected = jsonify(result)
86 expected = jsonify(result)
87 assert_ok(id_, expected, given=response.body)
87 assert_ok(id_, expected, given=response.body)
88
88
89 def test_api_get_repos_with_root_and_traverse(self, user_util):
89 def test_api_get_repos_with_root_and_traverse(self, user_util):
90 repo_group = user_util.create_repo_group(auto_cleanup=True)
90 repo_group = user_util.create_repo_group(auto_cleanup=True)
91 repo_group_name = repo_group.group_name
91 repo_group_name = repo_group.group_name
92
92
93 user_util.create_repo(parent=repo_group)
93 user_util.create_repo(parent=repo_group)
94 user_util.create_repo(parent=repo_group)
94 user_util.create_repo(parent=repo_group)
95
95
96 # nested, should not show up
96 # nested, should not show up
97 user_util._test_name = '{}/'.format(repo_group_name)
97 user_util._test_name = '{}/'.format(repo_group_name)
98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
99 user_util.create_repo(parent=sub_repo_group)
99 user_util.create_repo(parent=sub_repo_group)
100
100
101 id_, params = build_data(self.apikey, 'get_repos',
101 id_, params = build_data(self.apikey, 'get_repos',
102 root=repo_group_name, traverse=1)
102 root=repo_group_name, traverse=1)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104
104
105 result = []
105 result = []
106 for repo in RepoModel().get_repos_for_root(
106 for repo in RepoModel().get_repos_for_root(
107 repo_group_name, traverse=True):
107 repo_group_name, traverse=True):
108 result.append(repo.get_api_data(include_secrets=True))
108 result.append(repo.get_api_data(include_secrets=True))
109
109
110 assert len(result) == 3
110 assert len(result) == 3
111 expected = jsonify(result)
111 expected = jsonify(result)
112 assert_ok(id_, expected, given=response.body)
112 assert_ok(id_, expected, given=response.body)
113
113
114 def test_api_get_repos_non_admin(self):
114 def test_api_get_repos_non_admin(self):
115 id_, params = build_data(self.apikey_regular, 'get_repos')
115 id_, params = build_data(self.apikey_regular, 'get_repos')
116 response = api_call(self.app, params)
116 response = api_call(self.app, params)
117
117
118 user = User.get_by_username(self.TEST_USER_LOGIN)
118 user = User.get_by_username(self.TEST_USER_LOGIN)
119 allowed_repos = user.AuthUser.permissions['repositories']
119 allowed_repos = user.AuthUser.permissions['repositories']
120
120
121 result = []
121 result = []
122 for repo in RepoModel().get_all():
122 for repo in RepoModel().get_all():
123 perm = allowed_repos[repo.repo_name]
123 perm = allowed_repos[repo.repo_name]
124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
125 result.append(repo.get_api_data())
125 result.append(repo.get_api_data())
126 ret = jsonify(result)
126 ret = jsonify(result)
127
127
128 expected = ret
128 expected = ret
129 assert_ok(id_, expected, given=response.body)
129 assert_ok(id_, expected, given=response.body)
@@ -1,84 +1,84 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
26
26
27
27
28 @pytest.fixture
28 @pytest.fixture
29 def http_host_stub():
29 def http_host_stub():
30 """
30 """
31 To ensure that we can get an IP address, this test shall run with a
31 To ensure that we can get an IP address, this test shall run with a
32 hostname set to "localhost".
32 hostname set to "localhost".
33 """
33 """
34 return 'localhost:80'
34 return 'localhost:80'
35
35
36
36
37 @pytest.mark.usefixtures("testuser_api", "app")
37 @pytest.mark.usefixtures("testuser_api", "app")
38 class TestGetServerInfo(object):
38 class TestGetServerInfo(object):
39 def test_api_get_server_info(self):
39 def test_api_get_server_info(self):
40 id_, params = build_data(self.apikey, 'get_server_info')
40 id_, params = build_data(self.apikey, 'get_server_info')
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 resp = response.json
42 resp = response.json
43 expected = ScmModel().get_server_info()
43 expected = ScmModel().get_server_info()
44 expected['memory'] = resp['result']['memory']
44 expected['memory'] = resp['result']['memory']
45 expected['uptime'] = resp['result']['uptime']
45 expected['uptime'] = resp['result']['uptime']
46 expected['load'] = resp['result']['load']
46 expected['load'] = resp['result']['load']
47 expected['cpu'] = resp['result']['cpu']
47 expected['cpu'] = resp['result']['cpu']
48 expected['storage'] = resp['result']['storage']
48 expected['storage'] = resp['result']['storage']
49 expected['storage_temp'] = resp['result']['storage_temp']
49 expected['storage_temp'] = resp['result']['storage_temp']
50 expected['storage_inodes'] = resp['result']['storage_inodes']
50 expected['storage_inodes'] = resp['result']['storage_inodes']
51 expected['server'] = resp['result']['server']
51 expected['server'] = resp['result']['server']
52
52
53 expected['index_storage'] = resp['result']['index_storage']
53 expected['index_storage'] = resp['result']['index_storage']
54 expected['storage'] = resp['result']['storage']
54 expected['storage'] = resp['result']['storage']
55
55
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 def test_api_get_server_info_ip(self):
58 def test_api_get_server_info_ip(self):
59 id_, params = build_data(self.apikey, 'get_server_info')
59 id_, params = build_data(self.apikey, 'get_server_info')
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61 resp = response.json
61 resp = response.json
62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
63 expected['memory'] = resp['result']['memory']
63 expected['memory'] = resp['result']['memory']
64 expected['uptime'] = resp['result']['uptime']
64 expected['uptime'] = resp['result']['uptime']
65 expected['load'] = resp['result']['load']
65 expected['load'] = resp['result']['load']
66 expected['cpu'] = resp['result']['cpu']
66 expected['cpu'] = resp['result']['cpu']
67 expected['storage'] = resp['result']['storage']
67 expected['storage'] = resp['result']['storage']
68 expected['storage_temp'] = resp['result']['storage_temp']
68 expected['storage_temp'] = resp['result']['storage_temp']
69 expected['storage_inodes'] = resp['result']['storage_inodes']
69 expected['storage_inodes'] = resp['result']['storage_inodes']
70 expected['server'] = resp['result']['server']
70 expected['server'] = resp['result']['server']
71
71
72 expected['index_storage'] = resp['result']['index_storage']
72 expected['index_storage'] = resp['result']['index_storage']
73 expected['storage'] = resp['result']['storage']
73 expected['storage'] = resp['result']['storage']
74
74
75 assert_ok(id_, expected, given=response.body)
75 assert_ok(id_, expected, given=response.body)
76
76
77 def test_api_get_server_info_data_for_search_index_build(self):
77 def test_api_get_server_info_data_for_search_index_build(self):
78 id_, params = build_data(self.apikey, 'get_server_info')
78 id_, params = build_data(self.apikey, 'get_server_info')
79 response = api_call(self.app, params)
79 response = api_call(self.app, params)
80 resp = response.json
80 resp = response.json
81
81
82 # required by indexer
82 # required by indexer
83 assert resp['result']['index_storage']
83 assert resp['result']['index_storage']
84 assert resp['result']['storage']
84 assert resp['result']['storage']
@@ -1,80 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.auth import AuthUser
23 from rhodecode.lib.auth import AuthUser
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetUser(object):
31 class TestGetUser(object):
32 def test_api_get_user(self):
32 def test_api_get_user(self):
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36
36
37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
38 ret = usr.get_api_data(include_secrets=True)
38 ret = usr.get_api_data(include_secrets=True)
39 ret['permissions'] = AuthUser(usr.user_id).permissions
39 ret['permissions'] = AuthUser(usr.user_id).permissions
40
40
41 expected = ret
41 expected = ret
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_get_user_not_existing(self):
44 def test_api_get_user_not_existing(self):
45 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
45 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = "user `%s` does not exist" % 'trololo'
48 expected = "user `%s` does not exist" % 'trololo'
49 assert_error(id_, expected, given=response.body)
49 assert_error(id_, expected, given=response.body)
50
50
51 def test_api_get_user_without_giving_userid(self):
51 def test_api_get_user_without_giving_userid(self):
52 id_, params = build_data(self.apikey, 'get_user')
52 id_, params = build_data(self.apikey, 'get_user')
53 response = api_call(self.app, params)
53 response = api_call(self.app, params)
54
54
55 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
55 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
56 ret = usr.get_api_data(include_secrets=True)
56 ret = usr.get_api_data(include_secrets=True)
57 ret['permissions'] = AuthUser(usr.user_id).permissions
57 ret['permissions'] = AuthUser(usr.user_id).permissions
58
58
59 expected = ret
59 expected = ret
60 assert_ok(id_, expected, given=response.body)
60 assert_ok(id_, expected, given=response.body)
61
61
62 def test_api_get_user_without_giving_userid_non_admin(self):
62 def test_api_get_user_without_giving_userid_non_admin(self):
63 id_, params = build_data(self.apikey_regular, 'get_user')
63 id_, params = build_data(self.apikey_regular, 'get_user')
64 response = api_call(self.app, params)
64 response = api_call(self.app, params)
65
65
66 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
66 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
67 ret = usr.get_api_data(include_secrets=True)
67 ret = usr.get_api_data(include_secrets=True)
68 ret['permissions'] = AuthUser(usr.user_id).permissions
68 ret['permissions'] = AuthUser(usr.user_id).permissions
69
69
70 expected = ret
70 expected = ret
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72
72
73 def test_api_get_user_with_giving_userid_non_admin(self):
73 def test_api_get_user_with_giving_userid_non_admin(self):
74 id_, params = build_data(
74 id_, params = build_data(
75 self.apikey_regular, 'get_user',
75 self.apikey_regular, 'get_user',
76 userid=self.TEST_USER_LOGIN)
76 userid=self.TEST_USER_LOGIN)
77 response = api_call(self.app, params)
77 response = api_call(self.app, params)
78
78
79 expected = 'userid is not the same as your user'
79 expected = 'userid is not the same as your user'
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGetUserGroups(object):
29 class TestGetUserGroups(object):
30 def test_api_get_user_group(self, user_util):
30 def test_api_get_user_group(self, user_util):
31 user, group = user_util.create_user_with_group()
31 user, group = user_util.create_user_with_group()
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35
35
36 ret = group.get_api_data()
36 ret = group.get_api_data()
37 ret['users'] = [user.get_api_data()]
37 ret['users'] = [user.get_api_data()]
38
38
39 permissions = expected_permissions(group)
39 permissions = expected_permissions(group)
40
40
41 ret['members'] = permissions
41 ret['members'] = permissions
42 expected = ret
42 expected = ret
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_api_get_user_group_regular_user(self, user_util):
45 def test_api_get_user_group_regular_user(self, user_util):
46 user, group = user_util.create_user_with_group()
46 user, group = user_util.create_user_with_group()
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey_regular, 'get_user_group',
48 self.apikey_regular, 'get_user_group',
49 usergroupid=group.users_group_name)
49 usergroupid=group.users_group_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 ret = group.get_api_data()
52 ret = group.get_api_data()
53 ret['users'] = [user.get_api_data()]
53 ret['users'] = [user.get_api_data()]
54
54
55 permissions = expected_permissions(group)
55 permissions = expected_permissions(group)
56
56
57 ret['members'] = permissions
57 ret['members'] = permissions
58 expected = ret
58 expected = ret
59 assert_ok(id_, expected, given=response.body)
59 assert_ok(id_, expected, given=response.body)
60
60
61 def test_api_get_user_group_regular_user_permission_denied(
61 def test_api_get_user_group_regular_user_permission_denied(
62 self, user_util):
62 self, user_util):
63 group = user_util.create_user_group()
63 group = user_util.create_user_group()
64 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
64 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
65 group_name = group.users_group_name
65 group_name = group.users_group_name
66 user_util.grant_user_permission_to_user_group(
66 user_util.grant_user_permission_to_user_group(
67 group, user, 'usergroup.none')
67 group, user, 'usergroup.none')
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey_regular, 'get_user_group', usergroupid=group_name)
70 self.apikey_regular, 'get_user_group', usergroupid=group_name)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 expected = 'user group `%s` does not exist' % (group_name,)
73 expected = 'user group `%s` does not exist' % (group_name,)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import json
22 import json
23
23
24 import pytest
24 import pytest
25
25
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.api.tests.utils import build_data, api_call
27 from rhodecode.api.tests.utils import build_data, api_call
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetUserGroups(object):
31 class TestGetUserGroups(object):
32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
33 ('apikey', True),
33 ('apikey', True),
34 ('apikey_regular', False),
34 ('apikey_regular', False),
35 ])
35 ])
36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
37 first_group = user_util.create_user_group()
37 first_group = user_util.create_user_group()
38 second_group = user_util.create_user_group()
38 second_group = user_util.create_user_group()
39 expected = [
39 expected = [
40 g.get_api_data(include_secrets=expect_secrets)
40 g.get_api_data(include_secrets=expect_secrets)
41 for g in (first_group, second_group)]
41 for g in (first_group, second_group)]
42
42
43 apikey = getattr(self, apikey_attr)
43 apikey = getattr(self, apikey_attr)
44 id_, params = build_data(apikey, 'get_user_groups', )
44 id_, params = build_data(apikey, 'get_user_groups', )
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46 self._assert_ok(id_, expected, response)
46 self._assert_ok(id_, expected, response)
47
47
48 def test_api_get_user_groups_regular_user(self, user_util):
48 def test_api_get_user_groups_regular_user(self, user_util):
49 first_group = user_util.create_user_group()
49 first_group = user_util.create_user_group()
50 second_group = user_util.create_user_group()
50 second_group = user_util.create_user_group()
51 expected = [g.get_api_data() for g in (first_group, second_group)]
51 expected = [g.get_api_data() for g in (first_group, second_group)]
52
52
53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55 self._assert_ok(id_, expected, response)
55 self._assert_ok(id_, expected, response)
56
56
57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
58 group = user_util.create_user_group()
58 group = user_util.create_user_group()
59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
60 user_util.grant_user_permission_to_user_group(
60 user_util.grant_user_permission_to_user_group(
61 group, user, 'usergroup.none')
61 group, user, 'usergroup.none')
62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
63 response = api_call(self.app, params)
63 response = api_call(self.app, params)
64 expected = []
64 expected = []
65 self._assert_ok(id_, expected, response)
65 self._assert_ok(id_, expected, response)
66
66
67 def _assert_ok(self, id_, expected_list, response):
67 def _assert_ok(self, id_, expected_list, response):
68 result = json.loads(response.body)
68 result = json.loads(response.body)
69 assert result['id'] == id_
69 assert result['id'] == id_
70 assert result['error'] is None
70 assert result['error'] is None
71 assert sorted(result['result']) == sorted(expected_list)
71 assert sorted(result['result']) == sorted(expected_list)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, jsonify)
25 build_data, api_call, assert_ok, jsonify)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGetUsers(object):
29 class TestGetUsers(object):
30 def test_api_get_users(self):
30 def test_api_get_users(self):
31 id_, params = build_data(self.apikey, 'get_users', )
31 id_, params = build_data(self.apikey, 'get_users', )
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33 ret_all = []
33 ret_all = []
34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
35 .order_by(User.username).all()
35 .order_by(User.username).all()
36 for usr in _users:
36 for usr in _users:
37 ret = usr.get_api_data(include_secrets=True)
37 ret = usr.get_api_data(include_secrets=True)
38 ret_all.append(jsonify(ret))
38 ret_all.append(jsonify(ret))
39 expected = ret_all
39 expected = ret_all
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserGroupPermission(object):
30 class TestGrantUserGroupPermission(object):
31 @pytest.mark.parametrize("name, perm", [
31 @pytest.mark.parametrize("name, perm", [
32 ('none', 'repository.none'),
32 ('none', 'repository.none'),
33 ('read', 'repository.read'),
33 ('read', 'repository.read'),
34 ('write', 'repository.write'),
34 ('write', 'repository.write'),
35 ('admin', 'repository.admin')
35 ('admin', 'repository.admin')
36 ])
36 ])
37 def test_api_grant_user_group_permission(
37 def test_api_grant_user_group_permission(
38 self, name, perm, backend, user_util):
38 self, name, perm, backend, user_util):
39 user_group = user_util.create_user_group()
39 user_group = user_util.create_user_group()
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey,
41 self.apikey,
42 'grant_user_group_permission',
42 'grant_user_group_permission',
43 repoid=backend.repo_name,
43 repoid=backend.repo_name,
44 usergroupid=user_group.users_group_name,
44 usergroupid=user_group.users_group_name,
45 perm=perm)
45 perm=perm)
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 ret = {
48 ret = {
49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
50 perm, user_group.users_group_name, backend.repo_name
50 perm, user_group.users_group_name, backend.repo_name
51 ),
51 ),
52 'success': True
52 'success': True
53 }
53 }
54 expected = ret
54 expected = ret
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 def test_api_grant_user_group_permission_wrong_permission(
57 def test_api_grant_user_group_permission_wrong_permission(
58 self, backend, user_util):
58 self, backend, user_util):
59 perm = 'haha.no.permission'
59 perm = 'haha.no.permission'
60 user_group = user_util.create_user_group()
60 user_group = user_util.create_user_group()
61 id_, params = build_data(
61 id_, params = build_data(
62 self.apikey,
62 self.apikey,
63 'grant_user_group_permission',
63 'grant_user_group_permission',
64 repoid=backend.repo_name,
64 repoid=backend.repo_name,
65 usergroupid=user_group.users_group_name,
65 usergroupid=user_group.users_group_name,
66 perm=perm)
66 perm=perm)
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'permission `%s` does not exist' % (perm,)
69 expected = 'permission `%s` does not exist' % (perm,)
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
73 def test_api_grant_user_group_permission_exception_when_adding(
73 def test_api_grant_user_group_permission_exception_when_adding(
74 self, backend, user_util):
74 self, backend, user_util):
75 perm = 'repository.read'
75 perm = 'repository.read'
76 user_group = user_util.create_user_group()
76 user_group = user_util.create_user_group()
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey,
78 self.apikey,
79 'grant_user_group_permission',
79 'grant_user_group_permission',
80 repoid=backend.repo_name,
80 repoid=backend.repo_name,
81 usergroupid=user_group.users_group_name,
81 usergroupid=user_group.users_group_name,
82 perm=perm)
82 perm=perm)
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84
84
85 expected = (
85 expected = (
86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
87 user_group.users_group_name, backend.repo_name
87 user_group.users_group_name, backend.repo_name
88 )
88 )
89 )
89 )
90 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
@@ -1,172 +1,172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserGroupPermissionFromRepoGroup(object):
31 class TestGrantUserGroupPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 ('none', 'group.none', 'none'),
33 ('none', 'group.none', 'none'),
34 ('read', 'group.read', 'none'),
34 ('read', 'group.read', 'none'),
35 ('write', 'group.write', 'none'),
35 ('write', 'group.write', 'none'),
36 ('admin', 'group.admin', 'none'),
36 ('admin', 'group.admin', 'none'),
37
37
38 ('none', 'group.none', 'all'),
38 ('none', 'group.none', 'all'),
39 ('read', 'group.read', 'all'),
39 ('read', 'group.read', 'all'),
40 ('write', 'group.write', 'all'),
40 ('write', 'group.write', 'all'),
41 ('admin', 'group.admin', 'all'),
41 ('admin', 'group.admin', 'all'),
42
42
43 ('none', 'group.none', 'repos'),
43 ('none', 'group.none', 'repos'),
44 ('read', 'group.read', 'repos'),
44 ('read', 'group.read', 'repos'),
45 ('write', 'group.write', 'repos'),
45 ('write', 'group.write', 'repos'),
46 ('admin', 'group.admin', 'repos'),
46 ('admin', 'group.admin', 'repos'),
47
47
48 ('none', 'group.none', 'groups'),
48 ('none', 'group.none', 'groups'),
49 ('read', 'group.read', 'groups'),
49 ('read', 'group.read', 'groups'),
50 ('write', 'group.write', 'groups'),
50 ('write', 'group.write', 'groups'),
51 ('admin', 'group.admin', 'groups'),
51 ('admin', 'group.admin', 'groups'),
52 ])
52 ])
53 def test_api_grant_user_group_permission_to_repo_group(
53 def test_api_grant_user_group_permission_to_repo_group(
54 self, name, perm, apply_to_children, user_util):
54 self, name, perm, apply_to_children, user_util):
55 user_group = user_util.create_user_group()
55 user_group = user_util.create_user_group()
56 repo_group = user_util.create_repo_group()
56 repo_group = user_util.create_repo_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey,
58 self.apikey,
59 'grant_user_group_permission_to_repo_group',
59 'grant_user_group_permission_to_repo_group',
60 repogroupid=repo_group.name,
60 repogroupid=repo_group.name,
61 usergroupid=user_group.users_group_name,
61 usergroupid=user_group.users_group_name,
62 perm=perm,
62 perm=perm,
63 apply_to_children=apply_to_children,)
63 apply_to_children=apply_to_children,)
64 response = api_call(self.app, params)
64 response = api_call(self.app, params)
65
65
66 ret = {
66 ret = {
67 'msg': (
67 'msg': (
68 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
68 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
69 ' in repo group: `%s`' % (
69 ' in repo group: `%s`' % (
70 perm, apply_to_children, user_group.users_group_name,
70 perm, apply_to_children, user_group.users_group_name,
71 repo_group.name
71 repo_group.name
72 )
72 )
73 ),
73 ),
74 'success': True
74 'success': True
75 }
75 }
76 expected = ret
76 expected = ret
77 try:
77 try:
78 assert_ok(id_, expected, given=response.body)
78 assert_ok(id_, expected, given=response.body)
79 finally:
79 finally:
80 RepoGroupModel().revoke_user_group_permission(
80 RepoGroupModel().revoke_user_group_permission(
81 repo_group.group_id, user_group.users_group_id)
81 repo_group.group_id, user_group.users_group_id)
82
82
83 @pytest.mark.parametrize(
83 @pytest.mark.parametrize(
84 "name, perm, apply_to_children, grant_admin, access_ok", [
84 "name, perm, apply_to_children, grant_admin, access_ok", [
85 ('none_fails', 'group.none', 'none', False, False),
85 ('none_fails', 'group.none', 'none', False, False),
86 ('read_fails', 'group.read', 'none', False, False),
86 ('read_fails', 'group.read', 'none', False, False),
87 ('write_fails', 'group.write', 'none', False, False),
87 ('write_fails', 'group.write', 'none', False, False),
88 ('admin_fails', 'group.admin', 'none', False, False),
88 ('admin_fails', 'group.admin', 'none', False, False),
89
89
90 # with granted perms
90 # with granted perms
91 ('none_ok', 'group.none', 'none', True, True),
91 ('none_ok', 'group.none', 'none', True, True),
92 ('read_ok', 'group.read', 'none', True, True),
92 ('read_ok', 'group.read', 'none', True, True),
93 ('write_ok', 'group.write', 'none', True, True),
93 ('write_ok', 'group.write', 'none', True, True),
94 ('admin_ok', 'group.admin', 'none', True, True),
94 ('admin_ok', 'group.admin', 'none', True, True),
95 ]
95 ]
96 )
96 )
97 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
97 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
98 self, name, perm, apply_to_children, grant_admin, access_ok,
98 self, name, perm, apply_to_children, grant_admin, access_ok,
99 user_util):
99 user_util):
100 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
100 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
101 user_group = user_util.create_user_group()
101 user_group = user_util.create_user_group()
102 repo_group = user_util.create_repo_group()
102 repo_group = user_util.create_repo_group()
103 if grant_admin:
103 if grant_admin:
104 user_util.grant_user_permission_to_repo_group(
104 user_util.grant_user_permission_to_repo_group(
105 repo_group, user, 'group.admin')
105 repo_group, user, 'group.admin')
106
106
107 id_, params = build_data(
107 id_, params = build_data(
108 self.apikey_regular,
108 self.apikey_regular,
109 'grant_user_group_permission_to_repo_group',
109 'grant_user_group_permission_to_repo_group',
110 repogroupid=repo_group.name,
110 repogroupid=repo_group.name,
111 usergroupid=user_group.users_group_name,
111 usergroupid=user_group.users_group_name,
112 perm=perm,
112 perm=perm,
113 apply_to_children=apply_to_children,)
113 apply_to_children=apply_to_children,)
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115 if access_ok:
115 if access_ok:
116 ret = {
116 ret = {
117 'msg': (
117 'msg': (
118 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
118 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
119 ' in repo group: `%s`' % (
119 ' in repo group: `%s`' % (
120 perm, apply_to_children, user_group.users_group_name,
120 perm, apply_to_children, user_group.users_group_name,
121 repo_group.name
121 repo_group.name
122 )
122 )
123 ),
123 ),
124 'success': True
124 'success': True
125 }
125 }
126 expected = ret
126 expected = ret
127 try:
127 try:
128 assert_ok(id_, expected, given=response.body)
128 assert_ok(id_, expected, given=response.body)
129 finally:
129 finally:
130 RepoGroupModel().revoke_user_group_permission(
130 RepoGroupModel().revoke_user_group_permission(
131 repo_group.group_id, user_group.users_group_id)
131 repo_group.group_id, user_group.users_group_id)
132 else:
132 else:
133 expected = 'repository group `%s` does not exist' % (
133 expected = 'repository group `%s` does not exist' % (
134 repo_group.name,)
134 repo_group.name,)
135 assert_error(id_, expected, given=response.body)
135 assert_error(id_, expected, given=response.body)
136
136
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 self, user_util):
138 self, user_util):
139 user_group = user_util.create_user_group()
139 user_group = user_util.create_user_group()
140 repo_group = user_util.create_repo_group()
140 repo_group = user_util.create_repo_group()
141 perm = 'haha.no.permission'
141 perm = 'haha.no.permission'
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey,
143 self.apikey,
144 'grant_user_group_permission_to_repo_group',
144 'grant_user_group_permission_to_repo_group',
145 repogroupid=repo_group.name,
145 repogroupid=repo_group.name,
146 usergroupid=user_group.users_group_name,
146 usergroupid=user_group.users_group_name,
147 perm=perm)
147 perm=perm)
148 response = api_call(self.app, params)
148 response = api_call(self.app, params)
149
149
150 expected = 'permission `%s` does not exist' % (perm,)
150 expected = 'permission `%s` does not exist' % (perm,)
151 assert_error(id_, expected, given=response.body)
151 assert_error(id_, expected, given=response.body)
152
152
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 def test_api_grant_user_group_permission_exception_when_adding_2(
154 def test_api_grant_user_group_permission_exception_when_adding_2(
155 self, user_util):
155 self, user_util):
156 user_group = user_util.create_user_group()
156 user_group = user_util.create_user_group()
157 repo_group = user_util.create_repo_group()
157 repo_group = user_util.create_repo_group()
158 perm = 'group.read'
158 perm = 'group.read'
159 id_, params = build_data(
159 id_, params = build_data(
160 self.apikey,
160 self.apikey,
161 'grant_user_group_permission_to_repo_group',
161 'grant_user_group_permission_to_repo_group',
162 repogroupid=repo_group.name,
162 repogroupid=repo_group.name,
163 usergroupid=user_group.users_group_name,
163 usergroupid=user_group.users_group_name,
164 perm=perm)
164 perm=perm)
165 response = api_call(self.app, params)
165 response = api_call(self.app, params)
166
166
167 expected = (
167 expected = (
168 'failed to edit permission for user group: `%s`'
168 'failed to edit permission for user group: `%s`'
169 ' in repo group: `%s`' % (
169 ' in repo group: `%s`' % (
170 user_group.users_group_name, repo_group.name)
170 user_group.users_group_name, repo_group.name)
171 )
171 )
172 assert_error(id_, expected, given=response.body)
172 assert_error(id_, expected, given=response.body)
@@ -1,97 +1,97 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error)
25 build_data, api_call, assert_ok, assert_error)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGrantUserGroupPermissionFromUserGroup(object):
29 class TestGrantUserGroupPermissionFromUserGroup(object):
30 @pytest.mark.parametrize("name, perm", [
30 @pytest.mark.parametrize("name, perm", [
31 ('none', 'usergroup.none'),
31 ('none', 'usergroup.none'),
32 ('read', 'usergroup.read'),
32 ('read', 'usergroup.read'),
33 ('write', 'usergroup.write'),
33 ('write', 'usergroup.write'),
34 ('admin', 'usergroup.admin'),
34 ('admin', 'usergroup.admin'),
35
35
36 ('none', 'usergroup.none'),
36 ('none', 'usergroup.none'),
37 ('read', 'usergroup.read'),
37 ('read', 'usergroup.read'),
38 ('write', 'usergroup.write'),
38 ('write', 'usergroup.write'),
39 ('admin', 'usergroup.admin'),
39 ('admin', 'usergroup.admin'),
40
40
41 ('none', 'usergroup.none'),
41 ('none', 'usergroup.none'),
42 ('read', 'usergroup.read'),
42 ('read', 'usergroup.read'),
43 ('write', 'usergroup.write'),
43 ('write', 'usergroup.write'),
44 ('admin', 'usergroup.admin'),
44 ('admin', 'usergroup.admin'),
45
45
46 ('none', 'usergroup.none'),
46 ('none', 'usergroup.none'),
47 ('read', 'usergroup.read'),
47 ('read', 'usergroup.read'),
48 ('write', 'usergroup.write'),
48 ('write', 'usergroup.write'),
49 ('admin', 'usergroup.admin'),
49 ('admin', 'usergroup.admin'),
50 ])
50 ])
51 def test_api_grant_user_group_permission_to_user_group(
51 def test_api_grant_user_group_permission_to_user_group(
52 self, name, perm, user_util):
52 self, name, perm, user_util):
53 group = user_util.create_user_group()
53 group = user_util.create_user_group()
54 target_group = user_util.create_user_group()
54 target_group = user_util.create_user_group()
55
55
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey,
57 self.apikey,
58 'grant_user_group_permission_to_user_group',
58 'grant_user_group_permission_to_user_group',
59 usergroupid=target_group.users_group_name,
59 usergroupid=target_group.users_group_name,
60 sourceusergroupid=group.users_group_name,
60 sourceusergroupid=group.users_group_name,
61 perm=perm)
61 perm=perm)
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63
63
64 expected = {
64 expected = {
65 'msg': (
65 'msg': (
66 'Granted perm: `%s` for user group: `%s`'
66 'Granted perm: `%s` for user group: `%s`'
67 ' in user group: `%s`' % (
67 ' in user group: `%s`' % (
68 perm, group.users_group_name,
68 perm, group.users_group_name,
69 target_group.users_group_name
69 target_group.users_group_name
70 )
70 )
71 ),
71 ),
72 'success': True
72 'success': True
73 }
73 }
74 try:
74 try:
75 assert_ok(id_, expected, given=response.body)
75 assert_ok(id_, expected, given=response.body)
76 finally:
76 finally:
77 UserGroupModel().revoke_user_group_permission(
77 UserGroupModel().revoke_user_group_permission(
78 target_group.users_group_id, group.users_group_id)
78 target_group.users_group_id, group.users_group_id)
79
79
80 def test_api_grant_user_group_permission_to_user_group_same_failure(
80 def test_api_grant_user_group_permission_to_user_group_same_failure(
81 self, user_util):
81 self, user_util):
82 group = user_util.create_user_group()
82 group = user_util.create_user_group()
83
83
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey,
85 self.apikey,
86 'grant_user_group_permission_to_user_group',
86 'grant_user_group_permission_to_user_group',
87 usergroupid=group.users_group_name,
87 usergroupid=group.users_group_name,
88 sourceusergroupid=group.users_group_name,
88 sourceusergroupid=group.users_group_name,
89 perm='usergroup.none')
89 perm='usergroup.none')
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 expected = (
92 expected = (
93 'failed to edit permission for user group: `%s`'
93 'failed to edit permission for user group: `%s`'
94 ' in user group: `%s`' % (
94 ' in user group: `%s`' % (
95 group.users_group_name, group.users_group_name)
95 group.users_group_name, group.users_group_name)
96 )
96 )
97 assert_error(id_, expected, given=response.body)
97 assert_error(id_, expected, given=response.body)
@@ -1,87 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserPermission(object):
30 class TestGrantUserPermission(object):
31 @pytest.mark.parametrize("name, perm", [
31 @pytest.mark.parametrize("name, perm", [
32 ('none', 'repository.none'),
32 ('none', 'repository.none'),
33 ('read', 'repository.read'),
33 ('read', 'repository.read'),
34 ('write', 'repository.write'),
34 ('write', 'repository.write'),
35 ('admin', 'repository.admin')
35 ('admin', 'repository.admin')
36 ])
36 ])
37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
38 user = user_util.create_user()
38 user = user_util.create_user()
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey,
40 self.apikey,
41 'grant_user_permission',
41 'grant_user_permission',
42 repoid=backend.repo_name,
42 repoid=backend.repo_name,
43 userid=user.username,
43 userid=user.username,
44 perm=perm)
44 perm=perm)
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 ret = {
47 ret = {
48 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
48 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
49 perm, user.username, backend.repo_name
49 perm, user.username, backend.repo_name
50 ),
50 ),
51 'success': True
51 'success': True
52 }
52 }
53 expected = ret
53 expected = ret
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_grant_user_permission_wrong_permission(
56 def test_api_grant_user_permission_wrong_permission(
57 self, backend, user_util):
57 self, backend, user_util):
58 user = user_util.create_user()
58 user = user_util.create_user()
59 perm = 'haha.no.permission'
59 perm = 'haha.no.permission'
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey,
61 self.apikey,
62 'grant_user_permission',
62 'grant_user_permission',
63 repoid=backend.repo_name,
63 repoid=backend.repo_name,
64 userid=user.username,
64 userid=user.username,
65 perm=perm)
65 perm=perm)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'permission `%s` does not exist' % (perm,)
68 expected = 'permission `%s` does not exist' % (perm,)
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
71 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
72 def test_api_grant_user_permission_exception_when_adding(
72 def test_api_grant_user_permission_exception_when_adding(
73 self, backend, user_util):
73 self, backend, user_util):
74 user = user_util.create_user()
74 user = user_util.create_user()
75 perm = 'repository.read'
75 perm = 'repository.read'
76 id_, params = build_data(
76 id_, params = build_data(
77 self.apikey,
77 self.apikey,
78 'grant_user_permission',
78 'grant_user_permission',
79 repoid=backend.repo_name,
79 repoid=backend.repo_name,
80 userid=user.username,
80 userid=user.username,
81 perm=perm)
81 perm=perm)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
85 user.username, backend.repo_name
85 user.username, backend.repo_name
86 )
86 )
87 assert_error(id_, expected, given=response.body)
87 assert_error(id_, expected, given=response.body)
@@ -1,157 +1,157 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserPermissionFromRepoGroup(object):
31 class TestGrantUserPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 ('none', 'group.none', 'none'),
33 ('none', 'group.none', 'none'),
34 ('read', 'group.read', 'none'),
34 ('read', 'group.read', 'none'),
35 ('write', 'group.write', 'none'),
35 ('write', 'group.write', 'none'),
36 ('admin', 'group.admin', 'none'),
36 ('admin', 'group.admin', 'none'),
37
37
38 ('none', 'group.none', 'all'),
38 ('none', 'group.none', 'all'),
39 ('read', 'group.read', 'all'),
39 ('read', 'group.read', 'all'),
40 ('write', 'group.write', 'all'),
40 ('write', 'group.write', 'all'),
41 ('admin', 'group.admin', 'all'),
41 ('admin', 'group.admin', 'all'),
42
42
43 ('none', 'group.none', 'repos'),
43 ('none', 'group.none', 'repos'),
44 ('read', 'group.read', 'repos'),
44 ('read', 'group.read', 'repos'),
45 ('write', 'group.write', 'repos'),
45 ('write', 'group.write', 'repos'),
46 ('admin', 'group.admin', 'repos'),
46 ('admin', 'group.admin', 'repos'),
47
47
48 ('none', 'group.none', 'groups'),
48 ('none', 'group.none', 'groups'),
49 ('read', 'group.read', 'groups'),
49 ('read', 'group.read', 'groups'),
50 ('write', 'group.write', 'groups'),
50 ('write', 'group.write', 'groups'),
51 ('admin', 'group.admin', 'groups'),
51 ('admin', 'group.admin', 'groups'),
52 ])
52 ])
53 def test_api_grant_user_permission_to_repo_group(
53 def test_api_grant_user_permission_to_repo_group(
54 self, name, perm, apply_to_children, user_util):
54 self, name, perm, apply_to_children, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 repo_group = user_util.create_repo_group()
56 repo_group = user_util.create_repo_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey, 'grant_user_permission_to_repo_group',
58 self.apikey, 'grant_user_permission_to_repo_group',
59 repogroupid=repo_group.name, userid=user.username,
59 repogroupid=repo_group.name, userid=user.username,
60 perm=perm, apply_to_children=apply_to_children)
60 perm=perm, apply_to_children=apply_to_children)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 ret = {
63 ret = {
64 'msg': (
64 'msg': (
65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
66 ' in repo group: `%s`' % (
66 ' in repo group: `%s`' % (
67 perm, apply_to_children, user.username, repo_group.name
67 perm, apply_to_children, user.username, repo_group.name
68 )
68 )
69 ),
69 ),
70 'success': True
70 'success': True
71 }
71 }
72 expected = ret
72 expected = ret
73 assert_ok(id_, expected, given=response.body)
73 assert_ok(id_, expected, given=response.body)
74
74
75 @pytest.mark.parametrize(
75 @pytest.mark.parametrize(
76 "name, perm, apply_to_children, grant_admin, access_ok", [
76 "name, perm, apply_to_children, grant_admin, access_ok", [
77 ('none_fails', 'group.none', 'none', False, False),
77 ('none_fails', 'group.none', 'none', False, False),
78 ('read_fails', 'group.read', 'none', False, False),
78 ('read_fails', 'group.read', 'none', False, False),
79 ('write_fails', 'group.write', 'none', False, False),
79 ('write_fails', 'group.write', 'none', False, False),
80 ('admin_fails', 'group.admin', 'none', False, False),
80 ('admin_fails', 'group.admin', 'none', False, False),
81
81
82 # with granted perms
82 # with granted perms
83 ('none_ok', 'group.none', 'none', True, True),
83 ('none_ok', 'group.none', 'none', True, True),
84 ('read_ok', 'group.read', 'none', True, True),
84 ('read_ok', 'group.read', 'none', True, True),
85 ('write_ok', 'group.write', 'none', True, True),
85 ('write_ok', 'group.write', 'none', True, True),
86 ('admin_ok', 'group.admin', 'none', True, True),
86 ('admin_ok', 'group.admin', 'none', True, True),
87 ]
87 ]
88 )
88 )
89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
90 self, name, perm, apply_to_children, grant_admin, access_ok,
90 self, name, perm, apply_to_children, grant_admin, access_ok,
91 user_util):
91 user_util):
92 user = user_util.create_user()
92 user = user_util.create_user()
93 repo_group = user_util.create_repo_group()
93 repo_group = user_util.create_repo_group()
94
94
95 if grant_admin:
95 if grant_admin:
96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
97 user_util.grant_user_permission_to_repo_group(
97 user_util.grant_user_permission_to_repo_group(
98 repo_group, test_user, 'group.admin')
98 repo_group, test_user, 'group.admin')
99
99
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey_regular, 'grant_user_permission_to_repo_group',
101 self.apikey_regular, 'grant_user_permission_to_repo_group',
102 repogroupid=repo_group.name, userid=user.username,
102 repogroupid=repo_group.name, userid=user.username,
103 perm=perm, apply_to_children=apply_to_children)
103 perm=perm, apply_to_children=apply_to_children)
104 response = api_call(self.app, params)
104 response = api_call(self.app, params)
105 if access_ok:
105 if access_ok:
106 ret = {
106 ret = {
107 'msg': (
107 'msg': (
108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
109 ' in repo group: `%s`' % (
109 ' in repo group: `%s`' % (
110 perm, apply_to_children, user.username, repo_group.name
110 perm, apply_to_children, user.username, repo_group.name
111 )
111 )
112 ),
112 ),
113 'success': True
113 'success': True
114 }
114 }
115 expected = ret
115 expected = ret
116 assert_ok(id_, expected, given=response.body)
116 assert_ok(id_, expected, given=response.body)
117 else:
117 else:
118 expected = 'repository group `%s` does not exist' % (
118 expected = 'repository group `%s` does not exist' % (
119 repo_group.name, )
119 repo_group.name, )
120 assert_error(id_, expected, given=response.body)
120 assert_error(id_, expected, given=response.body)
121
121
122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
123 self, user_util):
123 self, user_util):
124 user = user_util.create_user()
124 user = user_util.create_user()
125 repo_group = user_util.create_repo_group()
125 repo_group = user_util.create_repo_group()
126 perm = 'haha.no.permission'
126 perm = 'haha.no.permission'
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey,
128 self.apikey,
129 'grant_user_permission_to_repo_group',
129 'grant_user_permission_to_repo_group',
130 repogroupid=repo_group.name,
130 repogroupid=repo_group.name,
131 userid=user.username,
131 userid=user.username,
132 perm=perm)
132 perm=perm)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 expected = 'permission `%s` does not exist' % (perm,)
135 expected = 'permission `%s` does not exist' % (perm,)
136 assert_error(id_, expected, given=response.body)
136 assert_error(id_, expected, given=response.body)
137
137
138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
140 self, user_util):
140 self, user_util):
141 user = user_util.create_user()
141 user = user_util.create_user()
142 repo_group = user_util.create_repo_group()
142 repo_group = user_util.create_repo_group()
143 perm = 'group.read'
143 perm = 'group.read'
144 id_, params = build_data(
144 id_, params = build_data(
145 self.apikey,
145 self.apikey,
146 'grant_user_permission_to_repo_group',
146 'grant_user_permission_to_repo_group',
147 repogroupid=repo_group.name,
147 repogroupid=repo_group.name,
148 userid=user.username,
148 userid=user.username,
149 perm=perm)
149 perm=perm)
150 response = api_call(self.app, params)
150 response = api_call(self.app, params)
151
151
152 expected = (
152 expected = (
153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
154 user.username, repo_group.name
154 user.username, repo_group.name
155 )
155 )
156 )
156 )
157 assert_error(id_, expected, given=response.body)
157 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserPermissionFromUserGroup(object):
31 class TestGrantUserPermissionFromUserGroup(object):
32 @pytest.mark.parametrize("name, perm", [
32 @pytest.mark.parametrize("name, perm", [
33 ('none', 'usergroup.none'),
33 ('none', 'usergroup.none'),
34 ('read', 'usergroup.read'),
34 ('read', 'usergroup.read'),
35 ('write', 'usergroup.write'),
35 ('write', 'usergroup.write'),
36 ('admin', 'usergroup.admin'),
36 ('admin', 'usergroup.admin'),
37
37
38 ('none', 'usergroup.none'),
38 ('none', 'usergroup.none'),
39 ('read', 'usergroup.read'),
39 ('read', 'usergroup.read'),
40 ('write', 'usergroup.write'),
40 ('write', 'usergroup.write'),
41 ('admin', 'usergroup.admin'),
41 ('admin', 'usergroup.admin'),
42
42
43 ('none', 'usergroup.none'),
43 ('none', 'usergroup.none'),
44 ('read', 'usergroup.read'),
44 ('read', 'usergroup.read'),
45 ('write', 'usergroup.write'),
45 ('write', 'usergroup.write'),
46 ('admin', 'usergroup.admin'),
46 ('admin', 'usergroup.admin'),
47
47
48 ('none', 'usergroup.none'),
48 ('none', 'usergroup.none'),
49 ('read', 'usergroup.read'),
49 ('read', 'usergroup.read'),
50 ('write', 'usergroup.write'),
50 ('write', 'usergroup.write'),
51 ('admin', 'usergroup.admin'),
51 ('admin', 'usergroup.admin'),
52 ])
52 ])
53 def test_api_grant_user_permission_to_user_group(
53 def test_api_grant_user_permission_to_user_group(
54 self, name, perm, user_util):
54 self, name, perm, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 group = user_util.create_user_group()
56 group = user_util.create_user_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey,
58 self.apikey,
59 'grant_user_permission_to_user_group',
59 'grant_user_permission_to_user_group',
60 usergroupid=group.users_group_name,
60 usergroupid=group.users_group_name,
61 userid=user.username,
61 userid=user.username,
62 perm=perm)
62 perm=perm)
63 response = api_call(self.app, params)
63 response = api_call(self.app, params)
64
64
65 ret = {
65 ret = {
66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
67 perm, user.username, group.users_group_name
67 perm, user.username, group.users_group_name
68 ),
68 ),
69 'success': True
69 'success': True
70 }
70 }
71 expected = ret
71 expected = ret
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
74 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
75 ('none_fails', 'usergroup.none', False, False),
75 ('none_fails', 'usergroup.none', False, False),
76 ('read_fails', 'usergroup.read', False, False),
76 ('read_fails', 'usergroup.read', False, False),
77 ('write_fails', 'usergroup.write', False, False),
77 ('write_fails', 'usergroup.write', False, False),
78 ('admin_fails', 'usergroup.admin', False, False),
78 ('admin_fails', 'usergroup.admin', False, False),
79
79
80 # with granted perms
80 # with granted perms
81 ('none_ok', 'usergroup.none', True, True),
81 ('none_ok', 'usergroup.none', True, True),
82 ('read_ok', 'usergroup.read', True, True),
82 ('read_ok', 'usergroup.read', True, True),
83 ('write_ok', 'usergroup.write', True, True),
83 ('write_ok', 'usergroup.write', True, True),
84 ('admin_ok', 'usergroup.admin', True, True),
84 ('admin_ok', 'usergroup.admin', True, True),
85 ])
85 ])
86 def test_api_grant_user_permission_to_user_group_by_regular_user(
86 def test_api_grant_user_permission_to_user_group_by_regular_user(
87 self, name, perm, grant_admin, access_ok, user_util):
87 self, name, perm, grant_admin, access_ok, user_util):
88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
89 user = user_util.create_user()
89 user = user_util.create_user()
90 group = user_util.create_user_group()
90 group = user_util.create_user_group()
91 # grant the user ability to at least read the group
91 # grant the user ability to at least read the group
92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
93 user_util.grant_user_permission_to_user_group(
93 user_util.grant_user_permission_to_user_group(
94 group, api_user, permission)
94 group, api_user, permission)
95
95
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular,
97 self.apikey_regular,
98 'grant_user_permission_to_user_group',
98 'grant_user_permission_to_user_group',
99 usergroupid=group.users_group_name,
99 usergroupid=group.users_group_name,
100 userid=user.username,
100 userid=user.username,
101 perm=perm)
101 perm=perm)
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103
103
104 if access_ok:
104 if access_ok:
105 ret = {
105 ret = {
106 'msg': (
106 'msg': (
107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
108 perm, user.username, group.users_group_name
108 perm, user.username, group.users_group_name
109 )
109 )
110 ),
110 ),
111 'success': True
111 'success': True
112 }
112 }
113 expected = ret
113 expected = ret
114 assert_ok(id_, expected, given=response.body)
114 assert_ok(id_, expected, given=response.body)
115 else:
115 else:
116 expected = 'user group `%s` does not exist' % (
116 expected = 'user group `%s` does not exist' % (
117 group.users_group_name)
117 group.users_group_name)
118 assert_error(id_, expected, given=response.body)
118 assert_error(id_, expected, given=response.body)
119
119
120 def test_api_grant_user_permission_to_user_group_wrong_permission(
120 def test_api_grant_user_permission_to_user_group_wrong_permission(
121 self, user_util):
121 self, user_util):
122 user = user_util.create_user()
122 user = user_util.create_user()
123 group = user_util.create_user_group()
123 group = user_util.create_user_group()
124 perm = 'haha.no.permission'
124 perm = 'haha.no.permission'
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey,
126 self.apikey,
127 'grant_user_permission_to_user_group',
127 'grant_user_permission_to_user_group',
128 usergroupid=group.users_group_name,
128 usergroupid=group.users_group_name,
129 userid=user.username,
129 userid=user.username,
130 perm=perm)
130 perm=perm)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'permission `%s` does not exist' % perm
133 expected = 'permission `%s` does not exist' % perm
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
137 self, user_util):
137 self, user_util):
138 user = user_util.create_user()
138 user = user_util.create_user()
139 group = user_util.create_user_group()
139 group = user_util.create_user_group()
140
140
141 perm = 'usergroup.read'
141 perm = 'usergroup.read'
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey,
143 self.apikey,
144 'grant_user_permission_to_user_group',
144 'grant_user_permission_to_user_group',
145 usergroupid=group.users_group_name,
145 usergroupid=group.users_group_name,
146 userid=user.username,
146 userid=user.username,
147 perm=perm)
147 perm=perm)
148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
149 response = api_call(self.app, params)
149 response = api_call(self.app, params)
150
150
151 expected = (
151 expected = (
152 'failed to edit permission for user: `%s` in user group: `%s`' % (
152 'failed to edit permission for user: `%s` in user group: `%s`' % (
153 user.username, group.users_group_name
153 user.username, group.users_group_name
154 )
154 )
155 )
155 )
156 assert_error(id_, expected, given=response.body)
156 assert_error(id_, expected, given=response.body)
@@ -1,68 +1,68 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo import RepoModel
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestInvalidateCache(object):
31 class TestInvalidateCache(object):
32
32
33 def _set_cache(self, repo_name):
33 def _set_cache(self, repo_name):
34 repo = RepoModel().get_by_repo_name(repo_name)
34 repo = RepoModel().get_by_repo_name(repo_name)
35 repo.scm_instance(cache=True)
35 repo.scm_instance(cache=True)
36
36
37 def test_api_invalidate_cache(self, backend):
37 def test_api_invalidate_cache(self, backend):
38 self._set_cache(backend.repo_name)
38 self._set_cache(backend.repo_name)
39
39
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43
43
44 expected = {
44 expected = {
45 'msg': "Cache for repository `%s` was invalidated" % (
45 'msg': "Cache for repository `%s` was invalidated" % (
46 backend.repo_name,),
46 backend.repo_name,),
47 'repository': backend.repo_name,
47 'repository': backend.repo_name,
48 }
48 }
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
52 def test_api_invalidate_cache_error(self, backend):
52 def test_api_invalidate_cache_error(self, backend):
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56
56
57 expected = 'Error occurred during cache invalidation action'
57 expected = 'Error occurred during cache invalidation action'
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
61 self._set_cache(backend.repo_name)
61 self._set_cache(backend.repo_name)
62
62
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66
66
67 expected = "repository `%s` does not exist" % (backend.repo_name,)
67 expected = "repository `%s` does not exist" % (backend.repo_name,)
68 assert_error(id_, expected, given=response.body)
68 assert_error(id_, expected, given=response.body)
@@ -1,104 +1,104 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error)
28 build_data, api_call, assert_error)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestMergePullRequest(object):
32 class TestMergePullRequest(object):
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_merge_pull_request(self, pr_util, no_notifications):
34 def test_api_merge_pull_request(self, pr_util, no_notifications):
35 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request = pr_util.create_pull_request(mergeable=True)
36 author = pull_request.user_id
36 author = pull_request.user_id
37 repo = pull_request.target_repo.repo_id
37 repo = pull_request.target_repo.repo_id
38 pull_request_id = pull_request.pull_request_id
38 pull_request_id = pull_request.pull_request_id
39 pull_request_repo = pull_request.target_repo.repo_name
39 pull_request_repo = pull_request.target_repo.repo_name
40
40
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey, 'merge_pull_request',
42 self.apikey, 'merge_pull_request',
43 repoid=pull_request_repo,
43 repoid=pull_request_repo,
44 pullrequestid=pull_request_id)
44 pullrequestid=pull_request_id)
45
45
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 # The above api call detaches the pull request DB object from the
48 # The above api call detaches the pull request DB object from the
49 # session because of an unconditional transaction rollback in our
49 # session because of an unconditional transaction rollback in our
50 # middleware. Therefore we need to add it back here if we want to use
50 # middleware. Therefore we need to add it back here if we want to use
51 # it.
51 # it.
52 Session().add(pull_request)
52 Session().add(pull_request)
53
53
54 expected = {
54 expected = {
55 'executed': True,
55 'executed': True,
56 'failure_reason': 0,
56 'failure_reason': 0,
57 'possible': True,
57 'possible': True,
58 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
58 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
59 'merge_ref': pull_request.shadow_merge_ref._asdict()
59 'merge_ref': pull_request.shadow_merge_ref._asdict()
60 }
60 }
61
61
62 response_json = response.json['result']
62 response_json = response.json['result']
63 assert response_json == expected
63 assert response_json == expected
64
64
65 action = 'user_merged_pull_request:%d' % (pull_request_id, )
65 action = 'user_merged_pull_request:%d' % (pull_request_id, )
66 journal = UserLog.query()\
66 journal = UserLog.query()\
67 .filter(UserLog.user_id == author)\
67 .filter(UserLog.user_id == author)\
68 .filter(UserLog.repository_id == repo)\
68 .filter(UserLog.repository_id == repo)\
69 .filter(UserLog.action == action)\
69 .filter(UserLog.action == action)\
70 .all()
70 .all()
71 assert len(journal) == 1
71 assert len(journal) == 1
72
72
73 id_, params = build_data(
73 id_, params = build_data(
74 self.apikey, 'merge_pull_request',
74 self.apikey, 'merge_pull_request',
75 repoid=pull_request_repo, pullrequestid=pull_request_id)
75 repoid=pull_request_repo, pullrequestid=pull_request_id)
76 response = api_call(self.app, params)
76 response = api_call(self.app, params)
77
77
78 expected = 'pull request `%s` merge failed, pull request is closed' % (
78 expected = 'pull request `%s` merge failed, pull request is closed' % (
79 pull_request_id)
79 pull_request_id)
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_api_merge_pull_request_repo_error(self):
83 def test_api_merge_pull_request_repo_error(self):
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey, 'merge_pull_request',
85 self.apikey, 'merge_pull_request',
86 repoid=666, pullrequestid=1)
86 repoid=666, pullrequestid=1)
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88
88
89 expected = 'repository `666` does not exist'
89 expected = 'repository `666` does not exist'
90 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
91
91
92 @pytest.mark.backends("git", "hg")
92 @pytest.mark.backends("git", "hg")
93 def test_api_merge_pull_request_non_admin_with_userid_error(self,
93 def test_api_merge_pull_request_non_admin_with_userid_error(self,
94 pr_util):
94 pr_util):
95 pull_request = pr_util.create_pull_request(mergeable=True)
95 pull_request = pr_util.create_pull_request(mergeable=True)
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'merge_pull_request',
97 self.apikey_regular, 'merge_pull_request',
98 repoid=pull_request.target_repo.repo_name,
98 repoid=pull_request.target_repo.repo_name,
99 pullrequestid=pull_request.pull_request_id,
99 pullrequestid=pull_request.pull_request_id,
100 userid=TEST_USER_ADMIN_LOGIN)
100 userid=TEST_USER_ADMIN_LOGIN)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102
102
103 expected = 'userid is not the same as your user'
103 expected = 'userid is not the same as your user'
104 assert_error(id_, expected, given=response.body)
104 assert_error(id_, expected, given=response.body)
@@ -1,51 +1,51 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.tests import TESTS_TMP_PATH
25 from rhodecode.tests import TESTS_TMP_PATH
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestPull(object):
31 class TestPull(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_pull(self, backend):
33 def test_api_pull(self, backend):
34 r = backend.create_repo()
34 r = backend.create_repo()
35 repo_name = r.repo_name
35 repo_name = r.repo_name
36 r.clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
36 r.clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37
37
38 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
38 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {'msg': 'Pulled from `%s`' % (repo_name,),
41 expected = {'msg': 'Pulled from `%s`' % (repo_name,),
42 'repository': repo_name}
42 'repository': repo_name}
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_api_pull_error(self, backend):
45 def test_api_pull_error(self, backend):
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'pull', repoid=backend.repo_name)
47 self.apikey, 'pull', repoid=backend.repo_name)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = 'Unable to pull changes from `%s`' % (backend.repo_name,)
50 expected = 'Unable to pull changes from `%s`' % (backend.repo_name,)
51 assert_error(id_, expected, given=response.body)
51 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.model.db import Repository, RepositoryField
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestRemoveFieldFromRepo(object):
28 class TestRemoveFieldFromRepo(object):
29 def test_api_remove_field_from_repo(self, backend):
29 def test_api_remove_field_from_repo(self, backend):
30 repo = backend.create_repo()
30 repo = backend.create_repo()
31 repo_name = repo.repo_name
31 repo_name = repo.repo_name
32
32
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'add_field_to_repo',
34 self.apikey, 'add_field_to_repo',
35 repoid=repo_name,
35 repoid=repo_name,
36 key='extra_field',
36 key='extra_field',
37 label='extra_field_label',
37 label='extra_field_label',
38 description='extra_field_desc')
38 description='extra_field_desc')
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'Added new repository field `extra_field`',
41 'msg': 'Added new repository field `extra_field`',
42 'success': True,
42 'success': True,
43 }
43 }
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 repo = Repository.get_by_repo_name(repo_name)
46 repo = Repository.get_by_repo_name(repo_name)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 _data = repo_field.get_dict()
48 _data = repo_field.get_dict()
49 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_desc'] == 'extra_field_desc'
50 assert _data['field_key'] == 'extra_field'
50 assert _data['field_key'] == 'extra_field'
51 assert _data['field_label'] == 'extra_field_label'
51 assert _data['field_label'] == 'extra_field_label'
52
52
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'remove_field_from_repo',
54 self.apikey, 'remove_field_from_repo',
55 repoid=repo_name,
55 repoid=repo_name,
56 key='extra_field')
56 key='extra_field')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58 expected = {
58 expected = {
59 'msg': 'Deleted repository field `extra_field`',
59 'msg': 'Deleted repository field `extra_field`',
60 'success': True,
60 'success': True,
61 }
61 }
62 assert_ok(id_, expected, given=response.body)
62 assert_ok(id_, expected, given=response.body)
63 repo = Repository.get_by_repo_name(repo_name)
63 repo = Repository.get_by_repo_name(repo_name)
64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
65
65
66 assert repo_field is None
66 assert repo_field is None
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRemoveUserFromUserGroup(object):
30 class TestRemoveUserFromUserGroup(object):
31 def test_api_remove_user_from_user_group(self, user_util):
31 def test_api_remove_user_from_user_group(self, user_util):
32 user, group = user_util.create_user_with_group()
32 user, group = user_util.create_user_with_group()
33 user_name = user.username
33 user_name = user.username
34 group_name = group.users_group_name
34 group_name = group.users_group_name
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'remove_user_from_user_group',
36 self.apikey, 'remove_user_from_user_group',
37 usergroupid=group_name,
37 usergroupid=group_name,
38 userid=user.username)
38 userid=user.username)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {
41 expected = {
42 'msg': 'removed member `%s` from user group `%s`' % (
42 'msg': 'removed member `%s` from user group `%s`' % (
43 user_name, group_name
43 user_name, group_name
44 ),
44 ),
45 'success': True}
45 'success': True}
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
49 def test_api_remove_user_from_user_group_exception_occurred(
49 def test_api_remove_user_from_user_group_exception_occurred(
50 self, user_util):
50 self, user_util):
51 user, group = user_util.create_user_with_group()
51 user, group = user_util.create_user_with_group()
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'remove_user_from_user_group',
53 self.apikey, 'remove_user_from_user_group',
54 usergroupid=group.users_group_name, userid=user.username)
54 usergroupid=group.users_group_name, userid=user.username)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56 expected = 'failed to remove member from user group `%s`' % (
56 expected = 'failed to remove member from user group `%s`' % (
57 group.users_group_name)
57 group.users_group_name)
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
@@ -1,184 +1,184 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.db import Repository
25 from rhodecode.model.db import Repository
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.lib.ext_json import json
27 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.utils2 import time_to_datetime
28 from rhodecode.lib.utils2 import time_to_datetime
29 from rhodecode.api.tests.utils import (
29 from rhodecode.api.tests.utils import (
30 build_data, api_call, assert_ok, assert_error, crash)
30 build_data, api_call, assert_ok, assert_error, crash)
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32
32
33
33
34 @pytest.mark.usefixtures("testuser_api", "app")
34 @pytest.mark.usefixtures("testuser_api", "app")
35 class TestLock(object):
35 class TestLock(object):
36 def test_api_lock_repo_lock_aquire(self, backend):
36 def test_api_lock_repo_lock_aquire(self, backend):
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey, 'lock',
38 self.apikey, 'lock',
39 userid=TEST_USER_ADMIN_LOGIN,
39 userid=TEST_USER_ADMIN_LOGIN,
40 repoid=backend.repo_name,
40 repoid=backend.repo_name,
41 locked=True)
41 locked=True)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 expected = {
43 expected = {
44 'repo': backend.repo_name, 'locked': True,
44 'repo': backend.repo_name, 'locked': True,
45 'locked_since': response.json['result']['locked_since'],
45 'locked_since': response.json['result']['locked_since'],
46 'locked_by': TEST_USER_ADMIN_LOGIN,
46 'locked_by': TEST_USER_ADMIN_LOGIN,
47 'lock_state_changed': True,
47 'lock_state_changed': True,
48 'lock_reason': Repository.LOCK_API,
48 'lock_reason': Repository.LOCK_API,
49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
51 }
51 }
52 assert_ok(id_, expected, given=response.body)
52 assert_ok(id_, expected, given=response.body)
53
53
54 def test_repo_lock_aquire_by_non_admin(self, backend):
54 def test_repo_lock_aquire_by_non_admin(self, backend):
55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
56 repo_name = repo.repo_name
56 repo_name = repo.repo_name
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey_regular, 'lock',
58 self.apikey_regular, 'lock',
59 repoid=repo_name,
59 repoid=repo_name,
60 locked=True)
60 locked=True)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62 expected = {
62 expected = {
63 'repo': repo_name,
63 'repo': repo_name,
64 'locked': True,
64 'locked': True,
65 'locked_since': response.json['result']['locked_since'],
65 'locked_since': response.json['result']['locked_since'],
66 'locked_by': self.TEST_USER_LOGIN,
66 'locked_by': self.TEST_USER_LOGIN,
67 'lock_state_changed': True,
67 'lock_state_changed': True,
68 'lock_reason': Repository.LOCK_API,
68 'lock_reason': Repository.LOCK_API,
69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
70 % (self.TEST_USER_LOGIN, repo_name, True))
70 % (self.TEST_USER_LOGIN, repo_name, True))
71 }
71 }
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 repo_name = repo.repo_name
76 repo_name = repo.repo_name
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey_regular, 'lock',
78 self.apikey_regular, 'lock',
79 userid=TEST_USER_ADMIN_LOGIN,
79 userid=TEST_USER_ADMIN_LOGIN,
80 repoid=repo_name,
80 repoid=repo_name,
81 locked=True)
81 locked=True)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'userid is not the same as your user'
83 expected = 'userid is not the same as your user'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'lock',
88 self.apikey_regular, 'lock',
89 repoid=backend.repo_name,
89 repoid=backend.repo_name,
90 locked=True)
90 locked=True)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 def test_api_lock_repo_lock_release(self, backend):
95 def test_api_lock_repo_lock_release(self, backend):
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey, 'lock',
97 self.apikey, 'lock',
98 userid=TEST_USER_ADMIN_LOGIN,
98 userid=TEST_USER_ADMIN_LOGIN,
99 repoid=backend.repo_name,
99 repoid=backend.repo_name,
100 locked=False)
100 locked=False)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = {
102 expected = {
103 'repo': backend.repo_name,
103 'repo': backend.repo_name,
104 'locked': False,
104 'locked': False,
105 'locked_since': None,
105 'locked_since': None,
106 'locked_by': TEST_USER_ADMIN_LOGIN,
106 'locked_by': TEST_USER_ADMIN_LOGIN,
107 'lock_state_changed': True,
107 'lock_state_changed': True,
108 'lock_reason': Repository.LOCK_API,
108 'lock_reason': Repository.LOCK_API,
109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
111 }
111 }
112 assert_ok(id_, expected, given=response.body)
112 assert_ok(id_, expected, given=response.body)
113
113
114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
115 id_, params = build_data(
115 id_, params = build_data(
116 self.apikey, 'lock',
116 self.apikey, 'lock',
117 repoid=backend.repo_name,
117 repoid=backend.repo_name,
118 locked=True)
118 locked=True)
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120 time_ = response.json['result']['locked_since']
120 time_ = response.json['result']['locked_since']
121 expected = {
121 expected = {
122 'repo': backend.repo_name,
122 'repo': backend.repo_name,
123 'locked': True,
123 'locked': True,
124 'locked_since': time_,
124 'locked_since': time_,
125 'locked_by': TEST_USER_ADMIN_LOGIN,
125 'locked_by': TEST_USER_ADMIN_LOGIN,
126 'lock_state_changed': True,
126 'lock_state_changed': True,
127 'lock_reason': Repository.LOCK_API,
127 'lock_reason': Repository.LOCK_API,
128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
130 }
130 }
131
131
132 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
133
133
134 def test_api_lock_repo_lock_optional_locked(self, backend):
134 def test_api_lock_repo_lock_optional_locked(self, backend):
135 # TODO: Provide a fixture locked_repository or similar
135 # TODO: Provide a fixture locked_repository or similar
136 repo = Repository.get_by_repo_name(backend.repo_name)
136 repo = Repository.get_by_repo_name(backend.repo_name)
137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
139
139
140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
141 response = api_call(self.app, params)
141 response = api_call(self.app, params)
142 time_ = response.json['result']['locked_since']
142 time_ = response.json['result']['locked_since']
143 expected = {
143 expected = {
144 'repo': backend.repo_name,
144 'repo': backend.repo_name,
145 'locked': True,
145 'locked': True,
146 'locked_since': time_,
146 'locked_since': time_,
147 'locked_by': TEST_USER_ADMIN_LOGIN,
147 'locked_by': TEST_USER_ADMIN_LOGIN,
148 'lock_state_changed': False,
148 'lock_state_changed': False,
149 'lock_reason': Repository.LOCK_API,
149 'lock_reason': Repository.LOCK_API,
150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
152 json.dumps(time_to_datetime(time_))))
152 json.dumps(time_to_datetime(time_))))
153 }
153 }
154 assert_ok(id_, expected, given=response.body)
154 assert_ok(id_, expected, given=response.body)
155
155
156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
158 repo_name = repo.repo_name
158 repo_name = repo.repo_name
159 assert repo.locked == [None, None, None]
159 assert repo.locked == [None, None, None]
160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
161 response = api_call(self.app, params)
161 response = api_call(self.app, params)
162 expected = {
162 expected = {
163 'repo': repo_name,
163 'repo': repo_name,
164 'locked': False,
164 'locked': False,
165 'locked_since': None,
165 'locked_since': None,
166 'locked_by': None,
166 'locked_by': None,
167 'lock_state_changed': False,
167 'lock_state_changed': False,
168 'lock_reason': None,
168 'lock_reason': None,
169 'msg': ('Repo `%s` not locked.' % (repo_name,))
169 'msg': ('Repo `%s` not locked.' % (repo_name,))
170 }
170 }
171 assert_ok(id_, expected, given=response.body)
171 assert_ok(id_, expected, given=response.body)
172
172
173 @mock.patch.object(Repository, 'lock', crash)
173 @mock.patch.object(Repository, 'lock', crash)
174 def test_api_lock_error(self, backend):
174 def test_api_lock_error(self, backend):
175 id_, params = build_data(
175 id_, params = build_data(
176 self.apikey, 'lock',
176 self.apikey, 'lock',
177 userid=TEST_USER_ADMIN_LOGIN,
177 userid=TEST_USER_ADMIN_LOGIN,
178 repoid=backend.repo_name,
178 repoid=backend.repo_name,
179 locked=True)
179 locked=True)
180 response = api_call(self.app, params)
180 response = api_call(self.app, params)
181
181
182 expected = 'Error occurred locking repository `%s`' % (
182 expected = 'Error occurred locking repository `%s`' % (
183 backend.repo_name,)
183 backend.repo_name,)
184 assert_error(id_, expected, given=response.body)
184 assert_error(id_, expected, given=response.body)
@@ -1,44 +1,44 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRescanRepos(object):
30 class TestRescanRepos(object):
31 def test_api_rescan_repos(self):
31 def test_api_rescan_repos(self):
32 id_, params = build_data(self.apikey, 'rescan_repos')
32 id_, params = build_data(self.apikey, 'rescan_repos')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 expected = {'added': [], 'removed': []}
35 expected = {'added': [], 'removed': []}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 @mock.patch.object(ScmModel, 'repo_scan', crash)
38 @mock.patch.object(ScmModel, 'repo_scan', crash)
39 def test_api_rescann_error(self):
39 def test_api_rescann_error(self):
40 id_, params = build_data(self.apikey, 'rescan_repos', )
40 id_, params = build_data(self.apikey, 'rescan_repos', )
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = 'Error occurred during rescan repositories action'
43 expected = 'Error occurred during rescan repositories action'
44 assert_error(id_, expected, given=response.body)
44 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserGroupPermission(object):
30 class TestRevokeUserGroupPermission(object):
31 def test_api_revoke_user_group_permission(self, backend, user_util):
31 def test_api_revoke_user_group_permission(self, backend, user_util):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33 user_group = user_util.create_user_group()
33 user_group = user_util.create_user_group()
34 user_util.grant_user_group_permission_to_repo(
34 user_util.grant_user_group_permission_to_repo(
35 repo, user_group, 'repository.read')
35 repo, user_group, 'repository.read')
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey,
37 self.apikey,
38 'revoke_user_group_permission',
38 'revoke_user_group_permission',
39 repoid=backend.repo_name,
39 repoid=backend.repo_name,
40 usergroupid=user_group.users_group_name)
40 usergroupid=user_group.users_group_name)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
45 user_group.users_group_name, backend.repo_name
45 user_group.users_group_name, backend.repo_name
46 ),
46 ),
47 'success': True
47 'success': True
48 }
48 }
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
51 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
52 def test_api_revoke_user_group_permission_exception_when_adding(
52 def test_api_revoke_user_group_permission_exception_when_adding(
53 self, backend, user_util):
53 self, backend, user_util):
54 user_group = user_util.create_user_group()
54 user_group = user_util.create_user_group()
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey,
56 self.apikey,
57 'revoke_user_group_permission',
57 'revoke_user_group_permission',
58 repoid=backend.repo_name,
58 repoid=backend.repo_name,
59 usergroupid=user_group.users_group_name)
59 usergroupid=user_group.users_group_name)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 expected = (
62 expected = (
63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
64 user_group.users_group_name, backend.repo_name
64 user_group.users_group_name, backend.repo_name
65 )
65 )
66 )
66 )
67 assert_error(id_, expected, given=response.body)
67 assert_error(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, apply_to_children", [
32 @pytest.mark.parametrize("name, apply_to_children", [
33 ('none', 'none'),
33 ('none', 'none'),
34 ('all', 'all'),
34 ('all', 'all'),
35 ('repos', 'repos'),
35 ('repos', 'repos'),
36 ('groups', 'groups'),
36 ('groups', 'groups'),
37 ])
37 ])
38 def test_api_revoke_user_group_permission_from_repo_group(
38 def test_api_revoke_user_group_permission_from_repo_group(
39 self, name, apply_to_children, user_util):
39 self, name, apply_to_children, user_util):
40 user_group = user_util.create_user_group()
40 user_group = user_util.create_user_group()
41 repo_group = user_util.create_repo_group()
41 repo_group = user_util.create_repo_group()
42 user_util.grant_user_group_permission_to_repo_group(
42 user_util.grant_user_group_permission_to_repo_group(
43 repo_group, user_group, 'group.read')
43 repo_group, user_group, 'group.read')
44
44
45 id_, params = build_data(
45 id_, params = build_data(
46 self.apikey, 'revoke_user_group_permission_from_repo_group',
46 self.apikey, 'revoke_user_group_permission_from_repo_group',
47 repogroupid=repo_group.name,
47 repogroupid=repo_group.name,
48 usergroupid=user_group.users_group_name,
48 usergroupid=user_group.users_group_name,
49 apply_to_children=apply_to_children,)
49 apply_to_children=apply_to_children,)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': (
53 'msg': (
54 'Revoked perm (recursive:%s) for user group: `%s`'
54 'Revoked perm (recursive:%s) for user group: `%s`'
55 ' in repo group: `%s`' % (
55 ' in repo group: `%s`' % (
56 apply_to_children, user_group.users_group_name,
56 apply_to_children, user_group.users_group_name,
57 repo_group.name
57 repo_group.name
58 )
58 )
59 ),
59 ),
60 'success': True
60 'success': True
61 }
61 }
62 assert_ok(id_, expected, given=response.body)
62 assert_ok(id_, expected, given=response.body)
63
63
64 @pytest.mark.parametrize(
64 @pytest.mark.parametrize(
65 "name, apply_to_children, grant_admin, access_ok", [
65 "name, apply_to_children, grant_admin, access_ok", [
66 ('none', 'none', False, False),
66 ('none', 'none', False, False),
67 ('all', 'all', False, False),
67 ('all', 'all', False, False),
68 ('repos', 'repos', False, False),
68 ('repos', 'repos', False, False),
69 ('groups', 'groups', False, False),
69 ('groups', 'groups', False, False),
70
70
71 # after granting admin rights
71 # after granting admin rights
72 ('none', 'none', False, False),
72 ('none', 'none', False, False),
73 ('all', 'all', False, False),
73 ('all', 'all', False, False),
74 ('repos', 'repos', False, False),
74 ('repos', 'repos', False, False),
75 ('groups', 'groups', False, False),
75 ('groups', 'groups', False, False),
76 ]
76 ]
77 )
77 )
78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
79 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 self, name, apply_to_children, grant_admin, access_ok, user_util):
80 user_group = user_util.create_user_group()
80 user_group = user_util.create_user_group()
81 repo_group = user_util.create_repo_group()
81 repo_group = user_util.create_repo_group()
82 user_util.grant_user_group_permission_to_repo_group(
82 user_util.grant_user_group_permission_to_repo_group(
83 repo_group, user_group, 'group.read')
83 repo_group, user_group, 'group.read')
84
84
85 if grant_admin:
85 if grant_admin:
86 user_util.grant_user_permission_to_repo_group(
86 user_util.grant_user_permission_to_repo_group(
87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
88
88
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular,
90 self.apikey_regular,
91 'revoke_user_group_permission_from_repo_group',
91 'revoke_user_group_permission_from_repo_group',
92 repogroupid=repo_group.name,
92 repogroupid=repo_group.name,
93 usergroupid=user_group.users_group_name,
93 usergroupid=user_group.users_group_name,
94 apply_to_children=apply_to_children,)
94 apply_to_children=apply_to_children,)
95 response = api_call(self.app, params)
95 response = api_call(self.app, params)
96 if access_ok:
96 if access_ok:
97 expected = {
97 expected = {
98 'msg': (
98 'msg': (
99 'Revoked perm (recursive:%s) for user group: `%s`'
99 'Revoked perm (recursive:%s) for user group: `%s`'
100 ' in repo group: `%s`' % (
100 ' in repo group: `%s`' % (
101 apply_to_children, TEST_USER_ADMIN_LOGIN,
101 apply_to_children, TEST_USER_ADMIN_LOGIN,
102 repo_group.name
102 repo_group.name
103 )
103 )
104 ),
104 ),
105 'success': True
105 'success': True
106 }
106 }
107 assert_ok(id_, expected, given=response.body)
107 assert_ok(id_, expected, given=response.body)
108 else:
108 else:
109 expected = 'repository group `%s` does not exist' % (
109 expected = 'repository group `%s` does not exist' % (
110 repo_group.name,)
110 repo_group.name,)
111 assert_error(id_, expected, given=response.body)
111 assert_error(id_, expected, given=response.body)
112
112
113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
115 self, user_util):
115 self, user_util):
116 user_group = user_util.create_user_group()
116 user_group = user_util.create_user_group()
117 repo_group = user_util.create_repo_group()
117 repo_group = user_util.create_repo_group()
118 id_, params = build_data(
118 id_, params = build_data(
119 self.apikey, 'revoke_user_group_permission_from_repo_group',
119 self.apikey, 'revoke_user_group_permission_from_repo_group',
120 repogroupid=repo_group.name,
120 repogroupid=repo_group.name,
121 usergroupid=user_group.users_group_name)
121 usergroupid=user_group.users_group_name)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123
123
124 expected = (
124 expected = (
125 'failed to edit permission for user group: `%s`'
125 'failed to edit permission for user group: `%s`'
126 ' in repo group: `%s`' % (
126 ' in repo group: `%s`' % (
127 user_group.users_group_name, repo_group.name)
127 user_group.users_group_name, repo_group.name)
128 )
128 )
129 assert_error(id_, expected, given=response.body)
129 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestRevokeUserGroupPermissionFromUserGroup(object):
28 class TestRevokeUserGroupPermissionFromUserGroup(object):
29 @pytest.mark.parametrize("name", [
29 @pytest.mark.parametrize("name", [
30 ('none',),
30 ('none',),
31 ('all',),
31 ('all',),
32 ('repos',),
32 ('repos',),
33 ('groups',),
33 ('groups',),
34 ])
34 ])
35 def test_api_revoke_user_group_permission_from_user_group(
35 def test_api_revoke_user_group_permission_from_user_group(
36 self, name, user_util):
36 self, name, user_util):
37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
38 group = user_util.create_user_group()
38 group = user_util.create_user_group()
39 source_group = user_util.create_user_group()
39 source_group = user_util.create_user_group()
40
40
41 user_util.grant_user_permission_to_user_group(
41 user_util.grant_user_permission_to_user_group(
42 group, user, 'usergroup.read')
42 group, user, 'usergroup.read')
43 user_util.grant_user_group_permission_to_user_group(
43 user_util.grant_user_group_permission_to_user_group(
44 source_group, group, 'usergroup.read')
44 source_group, group, 'usergroup.read')
45
45
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'revoke_user_group_permission_from_user_group',
47 self.apikey, 'revoke_user_group_permission_from_user_group',
48 usergroupid=group.users_group_name,
48 usergroupid=group.users_group_name,
49 sourceusergroupid=source_group.users_group_name)
49 sourceusergroupid=source_group.users_group_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
54 source_group.users_group_name, group.users_group_name
54 source_group.users_group_name, group.users_group_name
55 ),
55 ),
56 'success': True
56 'success': True
57 }
57 }
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermission(object):
30 class TestRevokeUserPermission(object):
31 def test_api_revoke_user_permission(self, backend, user_util):
31 def test_api_revoke_user_permission(self, backend, user_util):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33 user = user_util.create_user()
33 user = user_util.create_user()
34 user_util.grant_user_permission_to_repo(
34 user_util.grant_user_permission_to_repo(
35 repo, user, 'repository.read')
35 repo, user, 'repository.read')
36
36
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey,
38 self.apikey,
39 'revoke_user_permission',
39 'revoke_user_permission',
40 repoid=repo.repo_name,
40 repoid=repo.repo_name,
41 userid=user.username)
41 userid=user.username)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43
43
44 expected = {
44 expected = {
45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
46 user.username, backend.repo_name
46 user.username, backend.repo_name
47 ),
47 ),
48 'success': True
48 'success': True
49 }
49 }
50 assert_ok(id_, expected, given=response.body)
50 assert_ok(id_, expected, given=response.body)
51
51
52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
53 def test_api_revoke_user_permission_exception_when_adding(
53 def test_api_revoke_user_permission_exception_when_adding(
54 self, backend, user_util):
54 self, backend, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey,
57 self.apikey,
58 'revoke_user_permission',
58 'revoke_user_permission',
59 repoid=backend.repo_name,
59 repoid=backend.repo_name,
60 userid=user.username)
60 userid=user.username)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
64 user.username, backend.repo_name
64 user.username, backend.repo_name
65 )
65 )
66 assert_error(id_, expected, given=response.body)
66 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermissionFromRepoGroup(object):
30 class TestRevokeUserPermissionFromRepoGroup(object):
31 @pytest.mark.parametrize("name, apply_to_children", [
31 @pytest.mark.parametrize("name, apply_to_children", [
32 ('none', 'none'),
32 ('none', 'none'),
33 ('all', 'all'),
33 ('all', 'all'),
34 ('repos', 'repos'),
34 ('repos', 'repos'),
35 ('groups', 'groups'),
35 ('groups', 'groups'),
36 ])
36 ])
37 def test_api_revoke_user_permission_from_repo_group(
37 def test_api_revoke_user_permission_from_repo_group(
38 self, name, apply_to_children, user_util):
38 self, name, apply_to_children, user_util):
39 user = user_util.create_user()
39 user = user_util.create_user()
40 repo_group = user_util.create_repo_group()
40 repo_group = user_util.create_repo_group()
41 user_util.grant_user_permission_to_repo_group(
41 user_util.grant_user_permission_to_repo_group(
42 repo_group, user, 'group.read')
42 repo_group, user, 'group.read')
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey,
45 self.apikey,
46 'revoke_user_permission_from_repo_group',
46 'revoke_user_permission_from_repo_group',
47 repogroupid=repo_group.name,
47 repogroupid=repo_group.name,
48 userid=user.username,
48 userid=user.username,
49 apply_to_children=apply_to_children,)
49 apply_to_children=apply_to_children,)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': (
53 'msg': (
54 'Revoked perm (recursive:%s) for user: `%s`'
54 'Revoked perm (recursive:%s) for user: `%s`'
55 ' in repo group: `%s`' % (
55 ' in repo group: `%s`' % (
56 apply_to_children, user.username, repo_group.name
56 apply_to_children, user.username, repo_group.name
57 )
57 )
58 ),
58 ),
59 'success': True
59 'success': True
60 }
60 }
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
62
62
63 @pytest.mark.parametrize(
63 @pytest.mark.parametrize(
64 "name, apply_to_children, grant_admin, access_ok", [
64 "name, apply_to_children, grant_admin, access_ok", [
65 ('none', 'none', False, False),
65 ('none', 'none', False, False),
66 ('all', 'all', False, False),
66 ('all', 'all', False, False),
67 ('repos', 'repos', False, False),
67 ('repos', 'repos', False, False),
68 ('groups', 'groups', False, False),
68 ('groups', 'groups', False, False),
69
69
70 # after granting admin rights
70 # after granting admin rights
71 ('none', 'none', False, False),
71 ('none', 'none', False, False),
72 ('all', 'all', False, False),
72 ('all', 'all', False, False),
73 ('repos', 'repos', False, False),
73 ('repos', 'repos', False, False),
74 ('groups', 'groups', False, False),
74 ('groups', 'groups', False, False),
75 ]
75 ]
76 )
76 )
77 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
77 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 user = user_util.create_user()
79 user = user_util.create_user()
80 repo_group = user_util.create_repo_group()
80 repo_group = user_util.create_repo_group()
81 permission = 'group.admin' if grant_admin else 'group.read'
81 permission = 'group.admin' if grant_admin else 'group.read'
82 user_util.grant_user_permission_to_repo_group(
82 user_util.grant_user_permission_to_repo_group(
83 repo_group, user, permission)
83 repo_group, user, permission)
84
84
85 id_, params = build_data(
85 id_, params = build_data(
86 self.apikey_regular,
86 self.apikey_regular,
87 'revoke_user_permission_from_repo_group',
87 'revoke_user_permission_from_repo_group',
88 repogroupid=repo_group.name,
88 repogroupid=repo_group.name,
89 userid=user.username,
89 userid=user.username,
90 apply_to_children=apply_to_children,)
90 apply_to_children=apply_to_children,)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 if access_ok:
92 if access_ok:
93 expected = {
93 expected = {
94 'msg': (
94 'msg': (
95 'Revoked perm (recursive:%s) for user: `%s`'
95 'Revoked perm (recursive:%s) for user: `%s`'
96 ' in repo group: `%s`' % (
96 ' in repo group: `%s`' % (
97 apply_to_children, user.username, repo_group.name
97 apply_to_children, user.username, repo_group.name
98 )
98 )
99 ),
99 ),
100 'success': True
100 'success': True
101 }
101 }
102 assert_ok(id_, expected, given=response.body)
102 assert_ok(id_, expected, given=response.body)
103 else:
103 else:
104 expected = 'repository group `%s` does not exist' % (
104 expected = 'repository group `%s` does not exist' % (
105 repo_group.name)
105 repo_group.name)
106 assert_error(id_, expected, given=response.body)
106 assert_error(id_, expected, given=response.body)
107
107
108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
110 self, user_util):
110 self, user_util):
111 user = user_util.create_user()
111 user = user_util.create_user()
112 repo_group = user_util.create_repo_group()
112 repo_group = user_util.create_repo_group()
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey,
114 self.apikey,
115 'revoke_user_permission_from_repo_group',
115 'revoke_user_permission_from_repo_group',
116 repogroupid=repo_group.name,
116 repogroupid=repo_group.name,
117 userid=user.username
117 userid=user.username
118 )
118 )
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120
120
121 expected = (
121 expected = (
122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
123 user.username, repo_group.name
123 user.username, repo_group.name
124 )
124 )
125 )
125 )
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermissionFromUserGroup(object):
30 class TestRevokeUserPermissionFromUserGroup(object):
31 @pytest.mark.parametrize("name", [
31 @pytest.mark.parametrize("name", [
32 ('none',),
32 ('none',),
33 ('all',),
33 ('all',),
34 ('repos',),
34 ('repos',),
35 ('groups',),
35 ('groups',),
36 ])
36 ])
37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
38 user = user_util.create_user()
38 user = user_util.create_user()
39 group = user_util.create_user_group()
39 group = user_util.create_user_group()
40 user_util.grant_user_permission_to_user_group(
40 user_util.grant_user_permission_to_user_group(
41 group, user, 'usergroup.admin')
41 group, user, 'usergroup.admin')
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey,
44 self.apikey,
45 'revoke_user_permission_from_user_group',
45 'revoke_user_permission_from_user_group',
46 usergroupid=group.users_group_name,
46 usergroupid=group.users_group_name,
47 userid=user.username)
47 userid=user.username)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
52 user.username, group.users_group_name
52 user.username, group.users_group_name
53 ),
53 ),
54 'success': True
54 'success': True
55 }
55 }
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
59 ('none', False, False),
59 ('none', False, False),
60 ('all', False, False),
60 ('all', False, False),
61 ('repos', False, False),
61 ('repos', False, False),
62 ('groups', False, False),
62 ('groups', False, False),
63
63
64 # after granting admin rights
64 # after granting admin rights
65 ('none', False, False),
65 ('none', False, False),
66 ('all', False, False),
66 ('all', False, False),
67 ('repos', False, False),
67 ('repos', False, False),
68 ('groups', False, False),
68 ('groups', False, False),
69 ])
69 ])
70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
71 self, name, grant_admin, access_ok, user_util):
71 self, name, grant_admin, access_ok, user_util):
72 user = user_util.create_user()
72 user = user_util.create_user()
73 group = user_util.create_user_group()
73 group = user_util.create_user_group()
74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
75 user_util.grant_user_permission_to_user_group(group, user, permission)
75 user_util.grant_user_permission_to_user_group(group, user, permission)
76
76
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey_regular,
78 self.apikey_regular,
79 'revoke_user_permission_from_user_group',
79 'revoke_user_permission_from_user_group',
80 usergroupid=group.users_group_name,
80 usergroupid=group.users_group_name,
81 userid=user.username)
81 userid=user.username)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 if access_ok:
83 if access_ok:
84 expected = {
84 expected = {
85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
86 user.username, group.users_group_name
86 user.username, group.users_group_name
87 ),
87 ),
88 'success': True
88 'success': True
89 }
89 }
90 assert_ok(id_, expected, given=response.body)
90 assert_ok(id_, expected, given=response.body)
91 else:
91 else:
92 expected = 'user group `%s` does not exist' % (
92 expected = 'user group `%s` does not exist' % (
93 group.users_group_name)
93 group.users_group_name)
94 assert_error(id_, expected, given=response.body)
94 assert_error(id_, expected, given=response.body)
95
95
96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
98 self, user_util):
98 self, user_util):
99 user = user_util.create_user()
99 user = user_util.create_user()
100 group = user_util.create_user_group()
100 group = user_util.create_user_group()
101 id_, params = build_data(
101 id_, params = build_data(
102 self.apikey,
102 self.apikey,
103 'revoke_user_permission_from_user_group',
103 'revoke_user_permission_from_user_group',
104 usergroupid=group.users_group_name,
104 usergroupid=group.users_group_name,
105 userid=user.username)
105 userid=user.username)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107
107
108 expected = (
108 expected = (
109 'failed to edit permission for user: `%s` in user group: `%s`' % (
109 'failed to edit permission for user: `%s` in user group: `%s`' % (
110 user.username, group.users_group_name)
110 user.username, group.users_group_name)
111 )
111 )
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
@@ -1,207 +1,207 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.lib.vcs.nodes import FileNode
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (build_data, api_call)
27 from rhodecode.api.tests.utils import (build_data, api_call)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestUpdatePullRequest(object):
31 class TestUpdatePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_update_pull_request_title_or_description(
34 def test_api_update_pull_request_title_or_description(
35 self, pr_util, silence_action_logger, no_notifications):
35 self, pr_util, silence_action_logger, no_notifications):
36 pull_request = pr_util.create_pull_request()
36 pull_request = pr_util.create_pull_request()
37
37
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'update_pull_request',
39 self.apikey, 'update_pull_request',
40 repoid=pull_request.target_repo.repo_name,
40 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id,
41 pullrequestid=pull_request.pull_request_id,
42 title='New TITLE OF A PR',
42 title='New TITLE OF A PR',
43 description='New DESC OF A PR',
43 description='New DESC OF A PR',
44 )
44 )
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 expected = {
47 expected = {
48 "msg": "Updated pull request `{}`".format(
48 "msg": "Updated pull request `{}`".format(
49 pull_request.pull_request_id),
49 pull_request.pull_request_id),
50 "pull_request": response.json['result']['pull_request'],
50 "pull_request": response.json['result']['pull_request'],
51 "updated_commits": {"added": [], "common": [], "removed": []},
51 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_reviewers": {"added": [], "removed": []},
52 "updated_reviewers": {"added": [], "removed": []},
53 }
53 }
54
54
55 response_json = response.json['result']
55 response_json = response.json['result']
56 assert response_json == expected
56 assert response_json == expected
57 pr = response_json['pull_request']
57 pr = response_json['pull_request']
58 assert pr['title'] == 'New TITLE OF A PR'
58 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['description'] == 'New DESC OF A PR'
59 assert pr['description'] == 'New DESC OF A PR'
60
60
61 @pytest.mark.backends("git", "hg")
61 @pytest.mark.backends("git", "hg")
62 def test_api_try_update_closed_pull_request(
62 def test_api_try_update_closed_pull_request(
63 self, pr_util, silence_action_logger, no_notifications):
63 self, pr_util, silence_action_logger, no_notifications):
64 pull_request = pr_util.create_pull_request()
64 pull_request = pr_util.create_pull_request()
65 PullRequestModel().close_pull_request(
65 PullRequestModel().close_pull_request(
66 pull_request, TEST_USER_ADMIN_LOGIN)
66 pull_request, TEST_USER_ADMIN_LOGIN)
67
67
68 id_, params = build_data(
68 id_, params = build_data(
69 self.apikey, 'update_pull_request',
69 self.apikey, 'update_pull_request',
70 repoid=pull_request.target_repo.repo_name,
70 repoid=pull_request.target_repo.repo_name,
71 pullrequestid=pull_request.pull_request_id)
71 pullrequestid=pull_request.pull_request_id)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73
73
74 expected = 'pull request `{}` update failed, pull request ' \
74 expected = 'pull request `{}` update failed, pull request ' \
75 'is closed'.format(pull_request.pull_request_id)
75 'is closed'.format(pull_request.pull_request_id)
76
76
77 response_json = response.json['error']
77 response_json = response.json['error']
78 assert response_json == expected
78 assert response_json == expected
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(
81 def test_api_update_update_commits(
82 self, pr_util, silence_action_logger, no_notifications):
82 self, pr_util, silence_action_logger, no_notifications):
83 commits = [
83 commits = [
84 {'message': 'a'},
84 {'message': 'a'},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
86 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
87 ]
87 ]
88 pull_request = pr_util.create_pull_request(
88 pull_request = pr_util.create_pull_request(
89 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 commits=commits, target_head='a', source_head='b', revisions=['b'])
90 pr_util.update_source_repository(head='c')
90 pr_util.update_source_repository(head='c')
91 repo = pull_request.source_repo.scm_instance()
91 repo = pull_request.source_repo.scm_instance()
92 commits = [x for x in repo.get_commits()]
92 commits = [x for x in repo.get_commits()]
93
93
94 added_commit_id = commits[-1].raw_id # c commit
94 added_commit_id = commits[-1].raw_id # c commit
95 common_commits = commits[1].raw_id # b commit is common ancestor
95 common_commits = commits[1].raw_id # b commit is common ancestor
96
96
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'update_pull_request',
98 self.apikey, 'update_pull_request',
99 repoid=pull_request.target_repo.repo_name,
99 repoid=pull_request.target_repo.repo_name,
100 pullrequestid=pull_request.pull_request_id,
100 pullrequestid=pull_request.pull_request_id,
101 update_commits=True
101 update_commits=True
102 )
102 )
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104
104
105 expected = {
105 expected = {
106 "msg": "Updated pull request `{}`".format(
106 "msg": "Updated pull request `{}`".format(
107 pull_request.pull_request_id),
107 pull_request.pull_request_id),
108 "pull_request": response.json['result']['pull_request'],
108 "pull_request": response.json['result']['pull_request'],
109 "updated_commits": {"added": [added_commit_id],
109 "updated_commits": {"added": [added_commit_id],
110 "common": [common_commits], "removed": []},
110 "common": [common_commits], "removed": []},
111 "updated_reviewers": {"added": [], "removed": []},
111 "updated_reviewers": {"added": [], "removed": []},
112 }
112 }
113
113
114 response_json = response.json['result']
114 response_json = response.json['result']
115 assert response_json == expected
115 assert response_json == expected
116
116
117 @pytest.mark.backends("git", "hg")
117 @pytest.mark.backends("git", "hg")
118 def test_api_update_change_reviewers(
118 def test_api_update_change_reviewers(
119 self, pr_util, silence_action_logger, no_notifications):
119 self, pr_util, silence_action_logger, no_notifications):
120
120
121 users = [x.username for x in User.get_all()]
121 users = [x.username for x in User.get_all()]
122 new = [users.pop(0)]
122 new = [users.pop(0)]
123 removed = sorted(new)
123 removed = sorted(new)
124 added = sorted(users)
124 added = sorted(users)
125
125
126 pull_request = pr_util.create_pull_request(reviewers=new)
126 pull_request = pr_util.create_pull_request(reviewers=new)
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'update_pull_request',
129 self.apikey, 'update_pull_request',
130 repoid=pull_request.target_repo.repo_name,
130 repoid=pull_request.target_repo.repo_name,
131 pullrequestid=pull_request.pull_request_id,
131 pullrequestid=pull_request.pull_request_id,
132 reviewers=added)
132 reviewers=added)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134 expected = {
134 expected = {
135 "msg": "Updated pull request `{}`".format(
135 "msg": "Updated pull request `{}`".format(
136 pull_request.pull_request_id),
136 pull_request.pull_request_id),
137 "pull_request": response.json['result']['pull_request'],
137 "pull_request": response.json['result']['pull_request'],
138 "updated_commits": {"added": [], "common": [], "removed": []},
138 "updated_commits": {"added": [], "common": [], "removed": []},
139 "updated_reviewers": {"added": added, "removed": removed},
139 "updated_reviewers": {"added": added, "removed": removed},
140 }
140 }
141
141
142 response_json = response.json['result']
142 response_json = response.json['result']
143 assert response_json == expected
143 assert response_json == expected
144
144
145 @pytest.mark.backends("git", "hg")
145 @pytest.mark.backends("git", "hg")
146 def test_api_update_bad_user_in_reviewers(self, pr_util):
146 def test_api_update_bad_user_in_reviewers(self, pr_util):
147 pull_request = pr_util.create_pull_request()
147 pull_request = pr_util.create_pull_request()
148
148
149 id_, params = build_data(
149 id_, params = build_data(
150 self.apikey, 'update_pull_request',
150 self.apikey, 'update_pull_request',
151 repoid=pull_request.target_repo.repo_name,
151 repoid=pull_request.target_repo.repo_name,
152 pullrequestid=pull_request.pull_request_id,
152 pullrequestid=pull_request.pull_request_id,
153 reviewers=['bad_name'])
153 reviewers=['bad_name'])
154 response = api_call(self.app, params)
154 response = api_call(self.app, params)
155
155
156 expected = 'user `bad_name` does not exist'
156 expected = 'user `bad_name` does not exist'
157
157
158 response_json = response.json['error']
158 response_json = response.json['error']
159 assert response_json == expected
159 assert response_json == expected
160
160
161 @pytest.mark.backends("git", "hg")
161 @pytest.mark.backends("git", "hg")
162 def test_api_update_repo_error(self, pr_util):
162 def test_api_update_repo_error(self, pr_util):
163 id_, params = build_data(
163 id_, params = build_data(
164 self.apikey, 'update_pull_request',
164 self.apikey, 'update_pull_request',
165 repoid='fake',
165 repoid='fake',
166 pullrequestid='fake',
166 pullrequestid='fake',
167 reviewers=['bad_name'])
167 reviewers=['bad_name'])
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169
169
170 expected = 'repository `fake` does not exist'
170 expected = 'repository `fake` does not exist'
171
171
172 response_json = response.json['error']
172 response_json = response.json['error']
173 assert response_json == expected
173 assert response_json == expected
174
174
175 @pytest.mark.backends("git", "hg")
175 @pytest.mark.backends("git", "hg")
176 def test_api_update_pull_request_error(self, pr_util):
176 def test_api_update_pull_request_error(self, pr_util):
177 pull_request = pr_util.create_pull_request()
177 pull_request = pr_util.create_pull_request()
178
178
179 id_, params = build_data(
179 id_, params = build_data(
180 self.apikey, 'update_pull_request',
180 self.apikey, 'update_pull_request',
181 repoid=pull_request.target_repo.repo_name,
181 repoid=pull_request.target_repo.repo_name,
182 pullrequestid=999999,
182 pullrequestid=999999,
183 reviewers=['bad_name'])
183 reviewers=['bad_name'])
184 response = api_call(self.app, params)
184 response = api_call(self.app, params)
185
185
186 expected = 'pull request `999999` does not exist'
186 expected = 'pull request `999999` does not exist'
187
187
188 response_json = response.json['error']
188 response_json = response.json['error']
189 assert response_json == expected
189 assert response_json == expected
190
190
191 @pytest.mark.backends("git", "hg")
191 @pytest.mark.backends("git", "hg")
192 def test_api_update_pull_request_no_perms_to_update(
192 def test_api_update_pull_request_no_perms_to_update(
193 self, user_util, pr_util):
193 self, user_util, pr_util):
194 user = user_util.create_user()
194 user = user_util.create_user()
195 pull_request = pr_util.create_pull_request()
195 pull_request = pr_util.create_pull_request()
196
196
197 id_, params = build_data(
197 id_, params = build_data(
198 user.api_key, 'update_pull_request',
198 user.api_key, 'update_pull_request',
199 repoid=pull_request.target_repo.repo_name,
199 repoid=pull_request.target_repo.repo_name,
200 pullrequestid=pull_request.pull_request_id,)
200 pullrequestid=pull_request.pull_request_id,)
201 response = api_call(self.app, params)
201 response = api_call(self.app, params)
202
202
203 expected = ('pull request `%s` update failed, '
203 expected = ('pull request `%s` update failed, '
204 'no permission to update.') % pull_request.pull_request_id
204 'no permission to update.') % pull_request.pull_request_id
205
205
206 response_json = response.json['error']
206 response_json = response.json['error']
207 assert response_json == expected
207 assert response_json == expected
@@ -1,189 +1,189 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33 UPDATE_REPO_NAME = 'api_update_me'
33 UPDATE_REPO_NAME = 'api_update_me'
34
34
35
35
36 class SAME_AS_UPDATES(object):
36 class SAME_AS_UPDATES(object):
37 """ Constant used for tests below """
37 """ Constant used for tests below """
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestApiUpdateRepo(object):
41 class TestApiUpdateRepo(object):
42
42
43 @pytest.mark.parametrize("updates, expected", [
43 @pytest.mark.parametrize("updates, expected", [
44 ({'owner': TEST_USER_REGULAR_LOGIN},
44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 SAME_AS_UPDATES),
45 SAME_AS_UPDATES),
46
46
47 ({'description': 'new description'},
47 ({'description': 'new description'},
48 SAME_AS_UPDATES),
48 SAME_AS_UPDATES),
49
49
50 ({'clone_uri': 'http://foo.com/repo'},
50 ({'clone_uri': 'http://foo.com/repo'},
51 SAME_AS_UPDATES),
51 SAME_AS_UPDATES),
52
52
53 ({'clone_uri': None},
53 ({'clone_uri': None},
54 {'clone_uri': ''}),
54 {'clone_uri': ''}),
55
55
56 ({'clone_uri': ''},
56 ({'clone_uri': ''},
57 {'clone_uri': ''}),
57 {'clone_uri': ''}),
58
58
59 ({'landing_rev': 'rev:tip'},
59 ({'landing_rev': 'rev:tip'},
60 {'landing_rev': ['rev', 'tip']}),
60 {'landing_rev': ['rev', 'tip']}),
61
61
62 ({'enable_statistics': True},
62 ({'enable_statistics': True},
63 SAME_AS_UPDATES),
63 SAME_AS_UPDATES),
64
64
65 ({'enable_locking': True},
65 ({'enable_locking': True},
66 SAME_AS_UPDATES),
66 SAME_AS_UPDATES),
67
67
68 ({'enable_downloads': True},
68 ({'enable_downloads': True},
69 SAME_AS_UPDATES),
69 SAME_AS_UPDATES),
70
70
71 ({'repo_name': 'new_repo_name'},
71 ({'repo_name': 'new_repo_name'},
72 {
72 {
73 'repo_name': 'new_repo_name',
73 'repo_name': 'new_repo_name',
74 'url': 'http://test.example.com:80/new_repo_name'
74 'url': 'http://test.example.com:80/new_repo_name'
75 }),
75 }),
76
76
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 '_group': 'test_group_for_update'},
78 '_group': 'test_group_for_update'},
79 {
79 {
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
81 'url': 'http://test.example.com:80/test_group_for_update/{}'.format(UPDATE_REPO_NAME)
81 'url': 'http://test.example.com:80/test_group_for_update/{}'.format(UPDATE_REPO_NAME)
82 }),
82 }),
83 ])
83 ])
84 def test_api_update_repo(self, updates, expected, backend):
84 def test_api_update_repo(self, updates, expected, backend):
85 repo_name = UPDATE_REPO_NAME
85 repo_name = UPDATE_REPO_NAME
86 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
86 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
87 if updates.get('_group'):
87 if updates.get('_group'):
88 fixture.create_repo_group(updates['_group'])
88 fixture.create_repo_group(updates['_group'])
89
89
90 expected_api_data = repo.get_api_data(include_secrets=True)
90 expected_api_data = repo.get_api_data(include_secrets=True)
91 if expected is SAME_AS_UPDATES:
91 if expected is SAME_AS_UPDATES:
92 expected_api_data.update(updates)
92 expected_api_data.update(updates)
93 else:
93 else:
94 expected_api_data.update(expected)
94 expected_api_data.update(expected)
95
95
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey, 'update_repo', repoid=repo_name, **updates)
97 self.apikey, 'update_repo', repoid=repo_name, **updates)
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 if updates.get('repo_name'):
100 if updates.get('repo_name'):
101 repo_name = updates['repo_name']
101 repo_name = updates['repo_name']
102
102
103 try:
103 try:
104 expected = {
104 expected = {
105 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
105 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
106 'repository': jsonify(expected_api_data)
106 'repository': jsonify(expected_api_data)
107 }
107 }
108 assert_ok(id_, expected, given=response.body)
108 assert_ok(id_, expected, given=response.body)
109 finally:
109 finally:
110 fixture.destroy_repo(repo_name)
110 fixture.destroy_repo(repo_name)
111 if updates.get('_group'):
111 if updates.get('_group'):
112 fixture.destroy_repo_group(updates['_group'])
112 fixture.destroy_repo_group(updates['_group'])
113
113
114 def test_api_update_repo_fork_of_field(self, backend):
114 def test_api_update_repo_fork_of_field(self, backend):
115 master_repo = backend.create_repo()
115 master_repo = backend.create_repo()
116 repo = backend.create_repo()
116 repo = backend.create_repo()
117 updates = {
117 updates = {
118 'fork_of': master_repo.repo_name
118 'fork_of': master_repo.repo_name
119 }
119 }
120 expected_api_data = repo.get_api_data(include_secrets=True)
120 expected_api_data = repo.get_api_data(include_secrets=True)
121 expected_api_data.update(updates)
121 expected_api_data.update(updates)
122
122
123 id_, params = build_data(
123 id_, params = build_data(
124 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
124 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
125 response = api_call(self.app, params)
125 response = api_call(self.app, params)
126 expected = {
126 expected = {
127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
128 'repository': jsonify(expected_api_data)
128 'repository': jsonify(expected_api_data)
129 }
129 }
130 assert_ok(id_, expected, given=response.body)
130 assert_ok(id_, expected, given=response.body)
131 result = response.json['result']['repository']
131 result = response.json['result']['repository']
132 assert result['fork_of'] == master_repo.repo_name
132 assert result['fork_of'] == master_repo.repo_name
133
133
134 def test_api_update_repo_fork_of_not_found(self, backend):
134 def test_api_update_repo_fork_of_not_found(self, backend):
135 master_repo_name = 'fake-parent-repo'
135 master_repo_name = 'fake-parent-repo'
136 repo = backend.create_repo()
136 repo = backend.create_repo()
137 updates = {
137 updates = {
138 'fork_of': master_repo_name
138 'fork_of': master_repo_name
139 }
139 }
140 id_, params = build_data(
140 id_, params = build_data(
141 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
141 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
142 response = api_call(self.app, params)
142 response = api_call(self.app, params)
143 expected = {
143 expected = {
144 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
144 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
145 master_repo_name)}
145 master_repo_name)}
146 assert_error(id_, expected, given=response.body)
146 assert_error(id_, expected, given=response.body)
147
147
148 def test_api_update_repo_with_repo_group_not_existing(self):
148 def test_api_update_repo_with_repo_group_not_existing(self):
149 repo_name = 'admin_owned'
149 repo_name = 'admin_owned'
150 fake_repo_group = 'test_group_for_update'
150 fake_repo_group = 'test_group_for_update'
151 fixture.create_repo(repo_name)
151 fixture.create_repo(repo_name)
152 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
152 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
153 id_, params = build_data(
153 id_, params = build_data(
154 self.apikey, 'update_repo', repoid=repo_name, **updates)
154 self.apikey, 'update_repo', repoid=repo_name, **updates)
155 response = api_call(self.app, params)
155 response = api_call(self.app, params)
156 try:
156 try:
157 expected = {
157 expected = {
158 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
158 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
159 }
159 }
160 assert_error(id_, expected, given=response.body)
160 assert_error(id_, expected, given=response.body)
161 finally:
161 finally:
162 fixture.destroy_repo(repo_name)
162 fixture.destroy_repo(repo_name)
163
163
164 def test_api_update_repo_regular_user_not_allowed(self):
164 def test_api_update_repo_regular_user_not_allowed(self):
165 repo_name = 'admin_owned'
165 repo_name = 'admin_owned'
166 fixture.create_repo(repo_name)
166 fixture.create_repo(repo_name)
167 updates = {'active': False}
167 updates = {'active': False}
168 id_, params = build_data(
168 id_, params = build_data(
169 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
169 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
170 response = api_call(self.app, params)
170 response = api_call(self.app, params)
171 try:
171 try:
172 expected = 'repository `%s` does not exist' % (repo_name,)
172 expected = 'repository `%s` does not exist' % (repo_name,)
173 assert_error(id_, expected, given=response.body)
173 assert_error(id_, expected, given=response.body)
174 finally:
174 finally:
175 fixture.destroy_repo(repo_name)
175 fixture.destroy_repo(repo_name)
176
176
177 @mock.patch.object(RepoModel, 'update', crash)
177 @mock.patch.object(RepoModel, 'update', crash)
178 def test_api_update_repo_exception_occurred(self, backend):
178 def test_api_update_repo_exception_occurred(self, backend):
179 repo_name = UPDATE_REPO_NAME
179 repo_name = UPDATE_REPO_NAME
180 fixture.create_repo(repo_name, repo_type=backend.alias)
180 fixture.create_repo(repo_name, repo_type=backend.alias)
181 id_, params = build_data(
181 id_, params = build_data(
182 self.apikey, 'update_repo', repoid=repo_name,
182 self.apikey, 'update_repo', repoid=repo_name,
183 owner=TEST_USER_ADMIN_LOGIN,)
183 owner=TEST_USER_ADMIN_LOGIN,)
184 response = api_call(self.app, params)
184 response = api_call(self.app, params)
185 try:
185 try:
186 expected = 'failed to update repo `%s`' % (repo_name,)
186 expected = 'failed to update repo `%s`' % (repo_name,)
187 assert_error(id_, expected, given=response.body)
187 assert_error(id_, expected, given=response.body)
188 finally:
188 finally:
189 fixture.destroy_repo(repo_name)
189 fixture.destroy_repo(repo_name)
@@ -1,150 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestApiUpdateRepoGroup(object):
32 class TestApiUpdateRepoGroup(object):
33
33
34 def test_update_group_name(self, user_util):
34 def test_update_group_name(self, user_util):
35 new_group_name = 'new-group'
35 new_group_name = 'new-group'
36 initial_name = self._update(user_util, group_name=new_group_name)
36 initial_name = self._update(user_util, group_name=new_group_name)
37 assert RepoGroupModel()._get_repo_group(initial_name) is None
37 assert RepoGroupModel()._get_repo_group(initial_name) is None
38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
39 assert new_group is not None
39 assert new_group is not None
40 assert new_group.full_path == new_group_name
40 assert new_group.full_path == new_group_name
41
41
42 def test_update_group_name_change_parent(self, user_util):
42 def test_update_group_name_change_parent(self, user_util):
43
43
44 parent_group = user_util.create_repo_group()
44 parent_group = user_util.create_repo_group()
45 parent_group_name = parent_group.name
45 parent_group_name = parent_group.name
46
46
47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
48 initial_name = self._update(user_util, group_name=expected_group_name)
48 initial_name = self._update(user_util, group_name=expected_group_name)
49
49
50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
51
51
52 assert repo_group is not None
52 assert repo_group is not None
53 assert repo_group.group_name == expected_group_name
53 assert repo_group.group_name == expected_group_name
54 assert repo_group.full_path == expected_group_name
54 assert repo_group.full_path == expected_group_name
55 assert RepoGroupModel()._get_repo_group(initial_name) is None
55 assert RepoGroupModel()._get_repo_group(initial_name) is None
56
56
57 new_path = os.path.join(
57 new_path = os.path.join(
58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
59 assert os.path.exists(new_path)
59 assert os.path.exists(new_path)
60
60
61 def test_update_enable_locking(self, user_util):
61 def test_update_enable_locking(self, user_util):
62 initial_name = self._update(user_util, enable_locking=True)
62 initial_name = self._update(user_util, enable_locking=True)
63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
64 assert repo_group.enable_locking is True
64 assert repo_group.enable_locking is True
65
65
66 def test_update_description(self, user_util):
66 def test_update_description(self, user_util):
67 description = 'New description'
67 description = 'New description'
68 initial_name = self._update(user_util, description=description)
68 initial_name = self._update(user_util, description=description)
69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
70 assert repo_group.group_description == description
70 assert repo_group.group_description == description
71
71
72 def test_update_owner(self, user_util):
72 def test_update_owner(self, user_util):
73 owner = self.TEST_USER_LOGIN
73 owner = self.TEST_USER_LOGIN
74 initial_name = self._update(user_util, owner=owner)
74 initial_name = self._update(user_util, owner=owner)
75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
76 assert repo_group.user.username == owner
76 assert repo_group.user.username == owner
77
77
78 def test_update_group_name_conflict_with_existing(self, user_util):
78 def test_update_group_name_conflict_with_existing(self, user_util):
79 group_1 = user_util.create_repo_group()
79 group_1 = user_util.create_repo_group()
80 group_2 = user_util.create_repo_group()
80 group_2 = user_util.create_repo_group()
81 repo_group_name_1 = group_1.group_name
81 repo_group_name_1 = group_1.group_name
82 repo_group_name_2 = group_2.group_name
82 repo_group_name_2 = group_2.group_name
83
83
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
86 group_name=repo_group_name_2)
86 group_name=repo_group_name_2)
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 expected = {
88 expected = {
89 'unique_repo_group_name':
89 'unique_repo_group_name':
90 'Repository group with name `{}` already exists'.format(
90 'Repository group with name `{}` already exists'.format(
91 repo_group_name_2)}
91 repo_group_name_2)}
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
95 temp_user = user_util.create_user()
95 temp_user = user_util.create_user()
96 temp_user_api_key = temp_user.api_key
96 temp_user_api_key = temp_user.api_key
97 parent_group = user_util.create_repo_group()
97 parent_group = user_util.create_repo_group()
98 repo_group_name = parent_group.group_name
98 repo_group_name = parent_group.group_name
99 id_, params = build_data(
99 id_, params = build_data(
100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
103 assert_error(id_, expected, given=response.body)
103 assert_error(id_, expected, given=response.body)
104
104
105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
106 self, user_util):
106 self, user_util):
107 temp_user = user_util.create_user()
107 temp_user = user_util.create_user()
108 temp_user_api_key = temp_user.api_key
108 temp_user_api_key = temp_user.api_key
109 parent_group = user_util.create_repo_group(owner=temp_user.username)
109 parent_group = user_util.create_repo_group(owner=temp_user.username)
110 repo_group_name = parent_group.group_name
110 repo_group_name = parent_group.group_name
111
111
112 id_, params = build_data(
112 id_, params = build_data(
113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
114 group_name='at-root-level')
114 group_name='at-root-level')
115 response = api_call(self.app, params)
115 response = api_call(self.app, params)
116 expected = {
116 expected = {
117 'repo_group': 'You do not have the permission to store '
117 'repo_group': 'You do not have the permission to store '
118 'repository groups in the root location.'}
118 'repository groups in the root location.'}
119 assert_error(id_, expected, given=response.body)
119 assert_error(id_, expected, given=response.body)
120
120
121 def _update(self, user_util, **kwargs):
121 def _update(self, user_util, **kwargs):
122 repo_group = user_util.create_repo_group()
122 repo_group = user_util.create_repo_group()
123 initial_name = repo_group.name
123 initial_name = repo_group.name
124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
125 user_util.grant_user_permission_to_repo_group(
125 user_util.grant_user_permission_to_repo_group(
126 repo_group, user, 'group.admin')
126 repo_group, user, 'group.admin')
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'update_repo_group', repogroupid=initial_name,
129 self.apikey, 'update_repo_group', repogroupid=initial_name,
130 **kwargs)
130 **kwargs)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
134
134
135 expected = {
135 expected = {
136 'msg': 'updated repository group ID:{} {}'.format(
136 'msg': 'updated repository group ID:{} {}'.format(
137 repo_group.group_id, repo_group.group_name),
137 repo_group.group_id, repo_group.group_name),
138 'repo_group': {
138 'repo_group': {
139 'repositories': [],
139 'repositories': [],
140 'group_name': repo_group.group_name,
140 'group_name': repo_group.group_name,
141 'group_description': repo_group.group_description,
141 'group_description': repo_group.group_description,
142 'owner': repo_group.user.username,
142 'owner': repo_group.user.username,
143 'group_id': repo_group.group_id,
143 'group_id': repo_group.group_id,
144 'parent_group': (
144 'parent_group': (
145 repo_group.parent_group.name
145 repo_group.parent_group.name
146 if repo_group.parent_group else None)
146 if repo_group.parent_group else None)
147 }
147 }
148 }
148 }
149 assert_ok(id_, expected, given=response.body)
149 assert_ok(id_, expected, given=response.body)
150 return initial_name
150 return initial_name
@@ -1,116 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUser(object):
32 class TestUpdateUser(object):
33 @pytest.mark.parametrize("name, expected", [
33 @pytest.mark.parametrize("name, expected", [
34 ('firstname', 'new_username'),
34 ('firstname', 'new_username'),
35 ('lastname', 'new_username'),
35 ('lastname', 'new_username'),
36 ('email', 'new_username'),
36 ('email', 'new_username'),
37 ('admin', True),
37 ('admin', True),
38 ('admin', False),
38 ('admin', False),
39 ('extern_type', 'ldap'),
39 ('extern_type', 'ldap'),
40 ('extern_type', None),
40 ('extern_type', None),
41 ('extern_name', 'test'),
41 ('extern_name', 'test'),
42 ('extern_name', None),
42 ('extern_name', None),
43 ('active', False),
43 ('active', False),
44 ('active', True),
44 ('active', True),
45 ('password', 'newpass')
45 ('password', 'newpass')
46 ])
46 ])
47 def test_api_update_user(self, name, expected, user_util):
47 def test_api_update_user(self, name, expected, user_util):
48 usr = user_util.create_user()
48 usr = user_util.create_user()
49
49
50 kw = {name: expected, 'userid': usr.user_id}
50 kw = {name: expected, 'userid': usr.user_id}
51 id_, params = build_data(self.apikey, 'update_user', **kw)
51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 ret = {
54 ret = {
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'user': jsonify(
56 'user': jsonify(
57 UserModel()
57 UserModel()
58 .get_by_username(usr.username)
58 .get_by_username(usr.username)
59 .get_api_data(include_secrets=True)
59 .get_api_data(include_secrets=True)
60 )
60 )
61 }
61 }
62
62
63 expected = ret
63 expected = ret
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_update_user_no_changed_params(self):
66 def test_api_update_user_no_changed_params(self):
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 ret = jsonify(usr.get_api_data(include_secrets=True))
68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71
71
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 ret = {
73 ret = {
74 'msg': 'updated user ID:%s %s' % (
74 'msg': 'updated user ID:%s %s' % (
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 'user': ret
76 'user': ret
77 }
77 }
78 expected = ret
78 expected = ret
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80
80
81 def test_api_update_user_by_user_id(self):
81 def test_api_update_user_by_user_id(self):
82 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
82 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
83 ret = jsonify(usr.get_api_data(include_secrets=True))
83 ret = jsonify(usr.get_api_data(include_secrets=True))
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey, 'update_user', userid=usr.user_id)
85 self.apikey, 'update_user', userid=usr.user_id)
86
86
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 ret = {
88 ret = {
89 'msg': 'updated user ID:%s %s' % (
89 'msg': 'updated user ID:%s %s' % (
90 usr.user_id, TEST_USER_ADMIN_LOGIN),
90 usr.user_id, TEST_USER_ADMIN_LOGIN),
91 'user': ret
91 'user': ret
92 }
92 }
93 expected = ret
93 expected = ret
94 assert_ok(id_, expected, given=response.body)
94 assert_ok(id_, expected, given=response.body)
95
95
96 def test_api_update_user_default_user(self):
96 def test_api_update_user_default_user(self):
97 usr = User.get_default_user()
97 usr = User.get_default_user()
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'update_user', userid=usr.user_id)
99 self.apikey, 'update_user', userid=usr.user_id)
100
100
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = 'editing default user is forbidden'
102 expected = 'editing default user is forbidden'
103 assert_error(id_, expected, given=response.body)
103 assert_error(id_, expected, given=response.body)
104
104
105 @mock.patch.object(UserModel, 'update_user', crash)
105 @mock.patch.object(UserModel, 'update_user', crash)
106 def test_api_update_user_when_exception_happens(self):
106 def test_api_update_user_when_exception_happens(self):
107 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
107 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
108 ret = jsonify(usr.get_api_data(include_secrets=True))
108 ret = jsonify(usr.get_api_data(include_secrets=True))
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey, 'update_user', userid=usr.user_id)
110 self.apikey, 'update_user', userid=usr.user_id)
111
111
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113 ret = 'failed to update user `%s`' % (usr.user_id,)
113 ret = 'failed to update user `%s`' % (usr.user_id,)
114
114
115 expected = ret
115 expected = ret
116 assert_error(id_, expected, given=response.body)
116 assert_error(id_, expected, given=response.body)
@@ -1,108 +1,108 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUserGroup(object):
32 class TestUpdateUserGroup(object):
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('active', {'active': False}),
37 ('active', {'active': False}),
38 ('active', {'active': True})
38 ('active', {'active': True})
39 ])
39 ])
40 def test_api_update_user_group(self, changing_attr, updates, user_util):
40 def test_api_update_user_group(self, changing_attr, updates, user_util):
41 user_group = user_util.create_user_group()
41 user_group = user_util.create_user_group()
42 group_name = user_group.users_group_name
42 group_name = user_group.users_group_name
43 expected_api_data = user_group.get_api_data()
43 expected_api_data = user_group.get_api_data()
44 expected_api_data.update(updates)
44 expected_api_data.update(updates)
45
45
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'update_user_group', usergroupid=group_name,
47 self.apikey, 'update_user_group', usergroupid=group_name,
48 **updates)
48 **updates)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': 'updated user group ID:%s %s' % (
52 'msg': 'updated user group ID:%s %s' % (
53 user_group.users_group_id, user_group.users_group_name),
53 user_group.users_group_id, user_group.users_group_name),
54 'user_group': jsonify(expected_api_data)
54 'user_group': jsonify(expected_api_data)
55 }
55 }
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 @pytest.mark.parametrize("changing_attr, updates", [
58 @pytest.mark.parametrize("changing_attr, updates", [
59 # TODO: mikhail: decide if we need to test against the commented params
59 # TODO: mikhail: decide if we need to test against the commented params
60 # ('group_name', {'group_name': 'new_group_name'}),
60 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
63 ('active', {'active': False}),
63 ('active', {'active': False}),
64 ('active', {'active': True})
64 ('active', {'active': True})
65 ])
65 ])
66 def test_api_update_user_group_regular_user(
66 def test_api_update_user_group_regular_user(
67 self, changing_attr, updates, user_util):
67 self, changing_attr, updates, user_util):
68 user_group = user_util.create_user_group()
68 user_group = user_util.create_user_group()
69 group_name = user_group.users_group_name
69 group_name = user_group.users_group_name
70 expected_api_data = user_group.get_api_data()
70 expected_api_data = user_group.get_api_data()
71 expected_api_data.update(updates)
71 expected_api_data.update(updates)
72
72
73
73
74 # grant permission to this user
74 # grant permission to this user
75 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
75 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
76
76
77 user_util.grant_user_permission_to_user_group(
77 user_util.grant_user_permission_to_user_group(
78 user_group, user, 'usergroup.admin')
78 user_group, user, 'usergroup.admin')
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey_regular, 'update_user_group',
80 self.apikey_regular, 'update_user_group',
81 usergroupid=group_name, **updates)
81 usergroupid=group_name, **updates)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = {
83 expected = {
84 'msg': 'updated user group ID:%s %s' % (
84 'msg': 'updated user group ID:%s %s' % (
85 user_group.users_group_id, user_group.users_group_name),
85 user_group.users_group_id, user_group.users_group_name),
86 'user_group': jsonify(expected_api_data)
86 'user_group': jsonify(expected_api_data)
87 }
87 }
88 assert_ok(id_, expected, given=response.body)
88 assert_ok(id_, expected, given=response.body)
89
89
90 def test_api_update_user_group_regular_user_no_permission(self, user_util):
90 def test_api_update_user_group_regular_user_no_permission(self, user_util):
91 user_group = user_util.create_user_group()
91 user_group = user_util.create_user_group()
92 group_name = user_group.users_group_name
92 group_name = user_group.users_group_name
93 id_, params = build_data(
93 id_, params = build_data(
94 self.apikey_regular, 'update_user_group', usergroupid=group_name)
94 self.apikey_regular, 'update_user_group', usergroupid=group_name)
95 response = api_call(self.app, params)
95 response = api_call(self.app, params)
96
96
97 expected = 'user group `%s` does not exist' % (group_name)
97 expected = 'user group `%s` does not exist' % (group_name)
98 assert_error(id_, expected, given=response.body)
98 assert_error(id_, expected, given=response.body)
99
99
100 @mock.patch.object(UserGroupModel, 'update', crash)
100 @mock.patch.object(UserGroupModel, 'update', crash)
101 def test_api_update_user_group_exception_occurred(self, user_util):
101 def test_api_update_user_group_exception_occurred(self, user_util):
102 user_group = user_util.create_user_group()
102 user_group = user_util.create_user_group()
103 group_name = user_group.users_group_name
103 group_name = user_group.users_group_name
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey, 'update_user_group', usergroupid=group_name)
105 self.apikey, 'update_user_group', usergroupid=group_name)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107 expected = 'failed to update user group `%s`' % (group_name,)
107 expected = 'failed to update user group `%s`' % (group_name,)
108 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
@@ -1,268 +1,268 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 from mock import Mock, patch
23 from mock import Mock, patch
24
24
25 from rhodecode.api import utils
25 from rhodecode.api import utils
26 from rhodecode.api import JSONRPCError
26 from rhodecode.api import JSONRPCError
27 from rhodecode.lib.vcs.exceptions import RepositoryError
27 from rhodecode.lib.vcs.exceptions import RepositoryError
28
28
29
29
30 class TestGetCommitOrError(object):
30 class TestGetCommitOrError(object):
31 def setup(self):
31 def setup(self):
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
33
33
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
35 def test_ref_cannot_be_parsed(self, ref):
35 def test_ref_cannot_be_parsed(self, ref):
36 repo = Mock()
36 repo = Mock()
37 with pytest.raises(JSONRPCError) as excinfo:
37 with pytest.raises(JSONRPCError) as excinfo:
38 utils.get_commit_or_error(ref, repo)
38 utils.get_commit_or_error(ref, repo)
39 expected_message = (
39 expected_message = (
40 'Ref `{ref}` given in a wrong format. Please check the API'
40 'Ref `{ref}` given in a wrong format. Please check the API'
41 ' documentation for more details'.format(ref=ref)
41 ' documentation for more details'.format(ref=ref)
42 )
42 )
43 assert excinfo.value.message == expected_message
43 assert excinfo.value.message == expected_message
44
44
45 def test_success_with_hash_specified(self):
45 def test_success_with_hash_specified(self):
46 repo = Mock()
46 repo = Mock()
47 ref_type = 'branch'
47 ref_type = 'branch'
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
49
49
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
51 result = utils.get_commit_or_error(ref, repo)
51 result = utils.get_commit_or_error(ref, repo)
52 get_commit.assert_called_once_with(
52 get_commit.assert_called_once_with(
53 repo, self.commit_hash)
53 repo, self.commit_hash)
54 assert result == get_commit()
54 assert result == get_commit()
55
55
56 def test_raises_an_error_when_commit_not_found(self):
56 def test_raises_an_error_when_commit_not_found(self):
57 repo = Mock()
57 repo = Mock()
58 ref = 'branch:master:{}'.format(self.commit_hash)
58 ref = 'branch:master:{}'.format(self.commit_hash)
59
59
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
61 get_commit.side_effect = RepositoryError('Commit not found')
61 get_commit.side_effect = RepositoryError('Commit not found')
62 with pytest.raises(JSONRPCError) as excinfo:
62 with pytest.raises(JSONRPCError) as excinfo:
63 utils.get_commit_or_error(ref, repo)
63 utils.get_commit_or_error(ref, repo)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
65 assert excinfo.value.message == expected_message
65 assert excinfo.value.message == expected_message
66
66
67
67
68 class TestResolveRefOrError(object):
68 class TestResolveRefOrError(object):
69 def setup(self):
69 def setup(self):
70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
71
71
72 def test_success_with_no_hash_specified(self):
72 def test_success_with_no_hash_specified(self):
73 repo = Mock()
73 repo = Mock()
74 ref_type = 'branch'
74 ref_type = 'branch'
75 ref_name = 'master'
75 ref_name = 'master'
76 ref = '{}:{}'.format(ref_type, ref_name)
76 ref = '{}:{}'.format(ref_type, ref_name)
77
77
78 with patch('rhodecode.api.utils._get_ref_hash') \
78 with patch('rhodecode.api.utils._get_ref_hash') \
79 as _get_ref_hash:
79 as _get_ref_hash:
80 _get_ref_hash.return_value = self.commit_hash
80 _get_ref_hash.return_value = self.commit_hash
81 result = utils.resolve_ref_or_error(ref, repo)
81 result = utils.resolve_ref_or_error(ref, repo)
82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
83 assert result == '{}:{}'.format(ref, self.commit_hash)
83 assert result == '{}:{}'.format(ref, self.commit_hash)
84
84
85 def test_non_supported_refs(self):
85 def test_non_supported_refs(self):
86 repo = Mock()
86 repo = Mock()
87 ref = 'ancestor:ref'
87 ref = 'ancestor:ref'
88 with pytest.raises(JSONRPCError) as excinfo:
88 with pytest.raises(JSONRPCError) as excinfo:
89 utils.resolve_ref_or_error(ref, repo)
89 utils.resolve_ref_or_error(ref, repo)
90 expected_message = 'The specified ancestor `ref` does not exist'
90 expected_message = 'The specified ancestor `ref` does not exist'
91 assert excinfo.value.message == expected_message
91 assert excinfo.value.message == expected_message
92
92
93 def test_branch_is_not_found(self):
93 def test_branch_is_not_found(self):
94 repo = Mock()
94 repo = Mock()
95 ref = 'branch:non-existing-one'
95 ref = 'branch:non-existing-one'
96 with patch('rhodecode.api.utils._get_ref_hash')\
96 with patch('rhodecode.api.utils._get_ref_hash')\
97 as _get_ref_hash:
97 as _get_ref_hash:
98 _get_ref_hash.side_effect = KeyError()
98 _get_ref_hash.side_effect = KeyError()
99 with pytest.raises(JSONRPCError) as excinfo:
99 with pytest.raises(JSONRPCError) as excinfo:
100 utils.resolve_ref_or_error(ref, repo)
100 utils.resolve_ref_or_error(ref, repo)
101 expected_message = (
101 expected_message = (
102 'The specified branch `non-existing-one` does not exist')
102 'The specified branch `non-existing-one` does not exist')
103 assert excinfo.value.message == expected_message
103 assert excinfo.value.message == expected_message
104
104
105 def test_bookmark_is_not_found(self):
105 def test_bookmark_is_not_found(self):
106 repo = Mock()
106 repo = Mock()
107 ref = 'bookmark:non-existing-one'
107 ref = 'bookmark:non-existing-one'
108 with patch('rhodecode.api.utils._get_ref_hash')\
108 with patch('rhodecode.api.utils._get_ref_hash')\
109 as _get_ref_hash:
109 as _get_ref_hash:
110 _get_ref_hash.side_effect = KeyError()
110 _get_ref_hash.side_effect = KeyError()
111 with pytest.raises(JSONRPCError) as excinfo:
111 with pytest.raises(JSONRPCError) as excinfo:
112 utils.resolve_ref_or_error(ref, repo)
112 utils.resolve_ref_or_error(ref, repo)
113 expected_message = (
113 expected_message = (
114 'The specified bookmark `non-existing-one` does not exist')
114 'The specified bookmark `non-existing-one` does not exist')
115 assert excinfo.value.message == expected_message
115 assert excinfo.value.message == expected_message
116
116
117 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
117 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
118 def test_ref_cannot_be_parsed(self, ref):
118 def test_ref_cannot_be_parsed(self, ref):
119 repo = Mock()
119 repo = Mock()
120 with pytest.raises(JSONRPCError) as excinfo:
120 with pytest.raises(JSONRPCError) as excinfo:
121 utils.resolve_ref_or_error(ref, repo)
121 utils.resolve_ref_or_error(ref, repo)
122 expected_message = (
122 expected_message = (
123 'Ref `{ref}` given in a wrong format. Please check the API'
123 'Ref `{ref}` given in a wrong format. Please check the API'
124 ' documentation for more details'.format(ref=ref)
124 ' documentation for more details'.format(ref=ref)
125 )
125 )
126 assert excinfo.value.message == expected_message
126 assert excinfo.value.message == expected_message
127
127
128
128
129 class TestGetRefHash(object):
129 class TestGetRefHash(object):
130 def setup(self):
130 def setup(self):
131 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
131 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
132 self.bookmark_name = 'test-bookmark'
132 self.bookmark_name = 'test-bookmark'
133
133
134 @pytest.mark.parametrize("alias, branch_name", [
134 @pytest.mark.parametrize("alias, branch_name", [
135 ("git", "master"),
135 ("git", "master"),
136 ("hg", "default")
136 ("hg", "default")
137 ])
137 ])
138 def test_returns_hash_by_branch_name(self, alias, branch_name):
138 def test_returns_hash_by_branch_name(self, alias, branch_name):
139 with patch('rhodecode.model.db.Repository') as repo:
139 with patch('rhodecode.model.db.Repository') as repo:
140 repo.scm_instance().alias = alias
140 repo.scm_instance().alias = alias
141 repo.scm_instance().branches = {branch_name: self.commit_hash}
141 repo.scm_instance().branches = {branch_name: self.commit_hash}
142 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
142 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
143 assert result_hash == self.commit_hash
143 assert result_hash == self.commit_hash
144
144
145 @pytest.mark.parametrize("alias, branch_name", [
145 @pytest.mark.parametrize("alias, branch_name", [
146 ("git", "master"),
146 ("git", "master"),
147 ("hg", "default")
147 ("hg", "default")
148 ])
148 ])
149 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
149 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
150 with patch('rhodecode.model.db.Repository') as repo:
150 with patch('rhodecode.model.db.Repository') as repo:
151 repo.scm_instance().alias = alias
151 repo.scm_instance().alias = alias
152 repo.scm_instance().branches = {}
152 repo.scm_instance().branches = {}
153 with pytest.raises(KeyError):
153 with pytest.raises(KeyError):
154 utils._get_ref_hash(repo, 'branch', branch_name)
154 utils._get_ref_hash(repo, 'branch', branch_name)
155
155
156 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
156 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
157 with patch('rhodecode.model.db.Repository') as repo:
157 with patch('rhodecode.model.db.Repository') as repo:
158 repo.scm_instance().alias = 'hg'
158 repo.scm_instance().alias = 'hg'
159 repo.scm_instance().bookmarks = {
159 repo.scm_instance().bookmarks = {
160 self.bookmark_name: self.commit_hash}
160 self.bookmark_name: self.commit_hash}
161 result_hash = utils._get_ref_hash(
161 result_hash = utils._get_ref_hash(
162 repo, 'bookmark', self.bookmark_name)
162 repo, 'bookmark', self.bookmark_name)
163 assert result_hash == self.commit_hash
163 assert result_hash == self.commit_hash
164
164
165 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
165 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
166 with patch('rhodecode.model.db.Repository') as repo:
166 with patch('rhodecode.model.db.Repository') as repo:
167 repo.scm_instance().alias = 'hg'
167 repo.scm_instance().alias = 'hg'
168 repo.scm_instance().bookmarks = {}
168 repo.scm_instance().bookmarks = {}
169 with pytest.raises(KeyError):
169 with pytest.raises(KeyError):
170 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
170 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
171
171
172 def test_raises_error_when_bookmark_is_specified_for_git(self):
172 def test_raises_error_when_bookmark_is_specified_for_git(self):
173 with patch('rhodecode.model.db.Repository') as repo:
173 with patch('rhodecode.model.db.Repository') as repo:
174 repo.scm_instance().alias = 'git'
174 repo.scm_instance().alias = 'git'
175 repo.scm_instance().bookmarks = {
175 repo.scm_instance().bookmarks = {
176 self.bookmark_name: self.commit_hash}
176 self.bookmark_name: self.commit_hash}
177 with pytest.raises(ValueError):
177 with pytest.raises(ValueError):
178 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
178 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
179
179
180
180
181 class TestUserByNameOrError(object):
181 class TestUserByNameOrError(object):
182 def test_user_found_by_id(self):
182 def test_user_found_by_id(self):
183 fake_user = Mock(id=123)
183 fake_user = Mock(id=123)
184 patcher = patch('rhodecode.model.user.UserModel.get_user')
184 patcher = patch('rhodecode.model.user.UserModel.get_user')
185 with patcher as get_user:
185 with patcher as get_user:
186 get_user.return_value = fake_user
186 get_user.return_value = fake_user
187 result = utils.get_user_or_error('123')
187 result = utils.get_user_or_error('123')
188 assert result == fake_user
188 assert result == fake_user
189
189
190 def test_user_found_by_name(self):
190 def test_user_found_by_name(self):
191 fake_user = Mock(id=123)
191 fake_user = Mock(id=123)
192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
193 with patcher as get_by_username:
193 with patcher as get_by_username:
194 get_by_username.return_value = fake_user
194 get_by_username.return_value = fake_user
195 result = utils.get_user_or_error('test')
195 result = utils.get_user_or_error('test')
196 assert result == fake_user
196 assert result == fake_user
197
197
198 def test_user_not_found_by_id(self):
198 def test_user_not_found_by_id(self):
199 patcher = patch('rhodecode.model.user.UserModel.get_user')
199 patcher = patch('rhodecode.model.user.UserModel.get_user')
200 with patcher as get_user:
200 with patcher as get_user:
201 get_user.return_value = None
201 get_user.return_value = None
202 with pytest.raises(JSONRPCError) as excinfo:
202 with pytest.raises(JSONRPCError) as excinfo:
203 utils.get_user_or_error('123')
203 utils.get_user_or_error('123')
204
204
205 expected_message = 'user `123` does not exist'
205 expected_message = 'user `123` does not exist'
206 assert excinfo.value.message == expected_message
206 assert excinfo.value.message == expected_message
207
207
208 def test_user_not_found_by_name(self):
208 def test_user_not_found_by_name(self):
209 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
209 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
210 with patcher as get_by_username:
210 with patcher as get_by_username:
211 get_by_username.return_value = None
211 get_by_username.return_value = None
212 with pytest.raises(JSONRPCError) as excinfo:
212 with pytest.raises(JSONRPCError) as excinfo:
213 utils.get_user_or_error('test')
213 utils.get_user_or_error('test')
214
214
215 expected_message = 'user `test` does not exist'
215 expected_message = 'user `test` does not exist'
216 assert excinfo.value.message == expected_message
216 assert excinfo.value.message == expected_message
217
217
218
218
219 class TestGetCommitDict:
219 class TestGetCommitDict:
220
220
221 @pytest.mark.parametrize('filename, expected', [
221 @pytest.mark.parametrize('filename, expected', [
222 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
222 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
223 (b'sp\xa4cial', u'sp\ufffdcial'),
223 (b'sp\xa4cial', u'sp\ufffdcial'),
224 ])
224 ])
225 def test_decodes_filenames_to_unicode(self, filename, expected):
225 def test_decodes_filenames_to_unicode(self, filename, expected):
226 result = utils._get_commit_dict(filename=filename, op='A')
226 result = utils._get_commit_dict(filename=filename, op='A')
227 assert result['filename'] == expected
227 assert result['filename'] == expected
228
228
229
229
230 class TestRepoAccess(object):
230 class TestRepoAccess(object):
231 def setup_method(self, method):
231 def setup_method(self, method):
232
232
233 self.admin_perm_patch = patch(
233 self.admin_perm_patch = patch(
234 'rhodecode.api.utils.HasPermissionAnyApi')
234 'rhodecode.api.utils.HasPermissionAnyApi')
235 self.repo_perm_patch = patch(
235 self.repo_perm_patch = patch(
236 'rhodecode.api.utils.HasRepoPermissionAnyApi')
236 'rhodecode.api.utils.HasRepoPermissionAnyApi')
237
237
238 def test_has_superadmin_permission_checks_for_admin(self):
238 def test_has_superadmin_permission_checks_for_admin(self):
239 admin_mock = Mock()
239 admin_mock = Mock()
240 with self.admin_perm_patch as amock:
240 with self.admin_perm_patch as amock:
241 amock.return_value = admin_mock
241 amock.return_value = admin_mock
242 assert utils.has_superadmin_permission('fake_user')
242 assert utils.has_superadmin_permission('fake_user')
243 amock.assert_called_once_with('hg.admin')
243 amock.assert_called_once_with('hg.admin')
244
244
245 admin_mock.assert_called_once_with(user='fake_user')
245 admin_mock.assert_called_once_with(user='fake_user')
246
246
247 def test_has_repo_permissions_checks_for_repo_access(self):
247 def test_has_repo_permissions_checks_for_repo_access(self):
248 repo_mock = Mock()
248 repo_mock = Mock()
249 fake_repo = Mock()
249 fake_repo = Mock()
250 with self.repo_perm_patch as rmock:
250 with self.repo_perm_patch as rmock:
251 rmock.return_value = repo_mock
251 rmock.return_value = repo_mock
252 assert utils.validate_repo_permissions(
252 assert utils.validate_repo_permissions(
253 'fake_user', 'fake_repo_id', fake_repo,
253 'fake_user', 'fake_repo_id', fake_repo,
254 ['perm1', 'perm2'])
254 ['perm1', 'perm2'])
255 rmock.assert_called_once_with(*['perm1', 'perm2'])
255 rmock.assert_called_once_with(*['perm1', 'perm2'])
256
256
257 repo_mock.assert_called_once_with(
257 repo_mock.assert_called_once_with(
258 user='fake_user', repo_name=fake_repo.repo_name)
258 user='fake_user', repo_name=fake_repo.repo_name)
259
259
260 def test_has_repo_permissions_raises_not_found(self):
260 def test_has_repo_permissions_raises_not_found(self):
261 repo_mock = Mock(return_value=False)
261 repo_mock = Mock(return_value=False)
262 fake_repo = Mock()
262 fake_repo = Mock()
263 with self.repo_perm_patch as rmock:
263 with self.repo_perm_patch as rmock:
264 rmock.return_value = repo_mock
264 rmock.return_value = repo_mock
265 with pytest.raises(JSONRPCError) as excinfo:
265 with pytest.raises(JSONRPCError) as excinfo:
266 utils.validate_repo_permissions(
266 utils.validate_repo_permissions(
267 'fake_user', 'fake_repo_id', fake_repo, 'perms')
267 'fake_user', 'fake_repo_id', fake_repo, 'perms')
268 assert 'fake_repo_id' in excinfo
268 assert 'fake_repo_id' in excinfo
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import random
22 import random
23
23
24 from rhodecode.api.utils import get_origin
24 from rhodecode.api.utils import get_origin
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26
26
27
27
28 API_URL = '/_admin/api'
28 API_URL = '/_admin/api'
29
29
30
30
31 def assert_ok(id_, expected, given):
31 def assert_ok(id_, expected, given):
32 expected = jsonify({
32 expected = jsonify({
33 'id': id_,
33 'id': id_,
34 'error': None,
34 'error': None,
35 'result': expected
35 'result': expected
36 })
36 })
37 given = json.loads(given)
37 given = json.loads(given)
38 assert expected == given
38 assert expected == given
39
39
40
40
41 def assert_error(id_, expected, given):
41 def assert_error(id_, expected, given):
42 expected = jsonify({
42 expected = jsonify({
43 'id': id_,
43 'id': id_,
44 'error': expected,
44 'error': expected,
45 'result': None
45 'result': None
46 })
46 })
47 given = json.loads(given)
47 given = json.loads(given)
48 assert expected == given
48 assert expected == given
49
49
50
50
51 def jsonify(obj):
51 def jsonify(obj):
52 return json.loads(json.dumps(obj))
52 return json.loads(json.dumps(obj))
53
53
54
54
55 def build_data(apikey, method, **kw):
55 def build_data(apikey, method, **kw):
56 """
56 """
57 Builds API data with given random ID
57 Builds API data with given random ID
58
58
59 :param random_id:
59 :param random_id:
60 """
60 """
61 random_id = random.randrange(1, 9999)
61 random_id = random.randrange(1, 9999)
62 return random_id, json.dumps({
62 return random_id, json.dumps({
63 "id": random_id,
63 "id": random_id,
64 "api_key": apikey,
64 "api_key": apikey,
65 "method": method,
65 "method": method,
66 "args": kw
66 "args": kw
67 })
67 })
68
68
69
69
70 def api_call(app, params, status=None):
70 def api_call(app, params, status=None):
71 response = app.post(
71 response = app.post(
72 API_URL, content_type='application/json', params=params, status=status)
72 API_URL, content_type='application/json', params=params, status=status)
73 return response
73 return response
74
74
75
75
76 def crash(*args, **kwargs):
76 def crash(*args, **kwargs):
77 raise Exception('Total Crash !')
77 raise Exception('Total Crash !')
78
78
79
79
80 def expected_permissions(object_with_permissions):
80 def expected_permissions(object_with_permissions):
81 """
81 """
82 Returns the expected permissions structure for the given object.
82 Returns the expected permissions structure for the given object.
83
83
84 The object is expected to be a `Repository`, `RepositoryGroup`,
84 The object is expected to be a `Repository`, `RepositoryGroup`,
85 or `UserGroup`. They all implement the same permission handling
85 or `UserGroup`. They all implement the same permission handling
86 API.
86 API.
87 """
87 """
88 permissions = []
88 permissions = []
89 for _user in object_with_permissions.permissions():
89 for _user in object_with_permissions.permissions():
90 user_data = {
90 user_data = {
91 'name': _user.username,
91 'name': _user.username,
92 'permission': _user.permission,
92 'permission': _user.permission,
93 'origin': get_origin(_user),
93 'origin': get_origin(_user),
94 'type': "user",
94 'type': "user",
95 }
95 }
96 permissions.append(user_data)
96 permissions.append(user_data)
97
97
98 for _user_group in object_with_permissions.permission_user_groups():
98 for _user_group in object_with_permissions.permission_user_groups():
99 user_group_data = {
99 user_group_data = {
100 'name': _user_group.users_group_name,
100 'name': _user_group.users_group_name,
101 'permission': _user_group.permission,
101 'permission': _user_group.permission,
102 'origin': get_origin(_user_group),
102 'origin': get_origin(_user_group),
103 'type': "user_group",
103 'type': "user_group",
104 }
104 }
105 permissions.append(user_group_data)
105 permissions.append(user_group_data)
106 return permissions
106 return permissions
@@ -1,412 +1,412 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 JSON RPC utils
22 JSON RPC utils
23 """
23 """
24
24
25 import collections
25 import collections
26 import logging
26 import logging
27
27
28 from rhodecode.api.exc import JSONRPCError
28 from rhodecode.api.exc import JSONRPCError
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 from rhodecode.lib.utils import safe_unicode
31 from rhodecode.lib.utils import safe_unicode
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.controllers.utils import get_commit_from_ref_name
33 from rhodecode.controllers.utils import get_commit_from_ref_name
34 from rhodecode.lib.utils2 import str2bool
34 from rhodecode.lib.utils2 import str2bool
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class OAttr(object):
39 class OAttr(object):
40 """
40 """
41 Special Option that defines other attribute, and can default to them
41 Special Option that defines other attribute, and can default to them
42
42
43 Example::
43 Example::
44
44
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 user = Optional.extract(userid, evaluate_locals=local())
46 user = Optional.extract(userid, evaluate_locals=local())
47 #if we pass in userid, we get it, else it will default to apiuser
47 #if we pass in userid, we get it, else it will default to apiuser
48 #attribute
48 #attribute
49 """
49 """
50
50
51 def __init__(self, attr_name):
51 def __init__(self, attr_name):
52 self.attr_name = attr_name
52 self.attr_name = attr_name
53
53
54 def __repr__(self):
54 def __repr__(self):
55 return '<OptionalAttr:%s>' % self.attr_name
55 return '<OptionalAttr:%s>' % self.attr_name
56
56
57 def __call__(self):
57 def __call__(self):
58 return self
58 return self
59
59
60
60
61 class Optional(object):
61 class Optional(object):
62 """
62 """
63 Defines an optional parameter::
63 Defines an optional parameter::
64
64
65 param = param.getval() if isinstance(param, Optional) else param
65 param = param.getval() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
67
67
68 is equivalent of::
68 is equivalent of::
69
69
70 param = Optional.extract(param)
70 param = Optional.extract(param)
71
71
72 """
72 """
73
73
74 def __init__(self, type_):
74 def __init__(self, type_):
75 self.type_ = type_
75 self.type_ = type_
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return '<Optional:%s>' % self.type_.__repr__()
78 return '<Optional:%s>' % self.type_.__repr__()
79
79
80 def __call__(self):
80 def __call__(self):
81 return self.getval()
81 return self.getval()
82
82
83 def getval(self, evaluate_locals=None):
83 def getval(self, evaluate_locals=None):
84 """
84 """
85 returns value from this Optional instance
85 returns value from this Optional instance
86 """
86 """
87 if isinstance(self.type_, OAttr):
87 if isinstance(self.type_, OAttr):
88 param_name = self.type_.attr_name
88 param_name = self.type_.attr_name
89 if evaluate_locals:
89 if evaluate_locals:
90 return evaluate_locals[param_name]
90 return evaluate_locals[param_name]
91 # use params name
91 # use params name
92 return param_name
92 return param_name
93 return self.type_
93 return self.type_
94
94
95 @classmethod
95 @classmethod
96 def extract(cls, val, evaluate_locals=None, binary=None):
96 def extract(cls, val, evaluate_locals=None, binary=None):
97 """
97 """
98 Extracts value from Optional() instance
98 Extracts value from Optional() instance
99
99
100 :param val:
100 :param val:
101 :return: original value if it's not Optional instance else
101 :return: original value if it's not Optional instance else
102 value of instance
102 value of instance
103 """
103 """
104 if isinstance(val, cls):
104 if isinstance(val, cls):
105 val = val.getval(evaluate_locals)
105 val = val.getval(evaluate_locals)
106
106
107 if binary:
107 if binary:
108 val = str2bool(val)
108 val = str2bool(val)
109
109
110 return val
110 return val
111
111
112
112
113 def parse_args(cli_args, key_prefix=''):
113 def parse_args(cli_args, key_prefix=''):
114 from rhodecode.lib.utils2 import (escape_split)
114 from rhodecode.lib.utils2 import (escape_split)
115 kwargs = collections.defaultdict(dict)
115 kwargs = collections.defaultdict(dict)
116 for el in escape_split(cli_args, ','):
116 for el in escape_split(cli_args, ','):
117 kv = escape_split(el, '=', 1)
117 kv = escape_split(el, '=', 1)
118 if len(kv) == 2:
118 if len(kv) == 2:
119 k, v = kv
119 k, v = kv
120 kwargs[key_prefix + k] = v
120 kwargs[key_prefix + k] = v
121 return kwargs
121 return kwargs
122
122
123
123
124 def get_origin(obj):
124 def get_origin(obj):
125 """
125 """
126 Get origin of permission from object.
126 Get origin of permission from object.
127
127
128 :param obj:
128 :param obj:
129 """
129 """
130 origin = 'permission'
130 origin = 'permission'
131
131
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 # admin and owner case, maybe we should use dual string ?
133 # admin and owner case, maybe we should use dual string ?
134 origin = 'owner'
134 origin = 'owner'
135 elif getattr(obj, 'owner_row', ''):
135 elif getattr(obj, 'owner_row', ''):
136 origin = 'owner'
136 origin = 'owner'
137 elif getattr(obj, 'admin_row', ''):
137 elif getattr(obj, 'admin_row', ''):
138 origin = 'super-admin'
138 origin = 'super-admin'
139 return origin
139 return origin
140
140
141
141
142 def store_update(updates, attr, name):
142 def store_update(updates, attr, name):
143 """
143 """
144 Stores param in updates dict if it's not instance of Optional
144 Stores param in updates dict if it's not instance of Optional
145 allows easy updates of passed in params
145 allows easy updates of passed in params
146 """
146 """
147 if not isinstance(attr, Optional):
147 if not isinstance(attr, Optional):
148 updates[name] = attr
148 updates[name] = attr
149
149
150
150
151 def has_superadmin_permission(apiuser):
151 def has_superadmin_permission(apiuser):
152 """
152 """
153 Return True if apiuser is admin or return False
153 Return True if apiuser is admin or return False
154
154
155 :param apiuser:
155 :param apiuser:
156 """
156 """
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 return True
158 return True
159 return False
159 return False
160
160
161
161
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 """
163 """
164 Raise JsonRPCError if apiuser is not authorized or return True
164 Raise JsonRPCError if apiuser is not authorized or return True
165
165
166 :param apiuser:
166 :param apiuser:
167 :param repoid:
167 :param repoid:
168 :param repo:
168 :param repo:
169 :param perms:
169 :param perms:
170 """
170 """
171 if not HasRepoPermissionAnyApi(*perms)(
171 if not HasRepoPermissionAnyApi(*perms)(
172 user=apiuser, repo_name=repo.repo_name):
172 user=apiuser, repo_name=repo.repo_name):
173 raise JSONRPCError(
173 raise JSONRPCError(
174 'repository `%s` does not exist' % repoid)
174 'repository `%s` does not exist' % repoid)
175
175
176 return True
176 return True
177
177
178
178
179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 """
180 """
181 Raise JsonRPCError if apiuser is not authorized or return True
181 Raise JsonRPCError if apiuser is not authorized or return True
182
182
183 :param apiuser:
183 :param apiuser:
184 :param repogroupid: just the id of repository group
184 :param repogroupid: just the id of repository group
185 :param repo_group: instance of repo_group
185 :param repo_group: instance of repo_group
186 :param perms:
186 :param perms:
187 """
187 """
188 if not HasRepoGroupPermissionAnyApi(*perms)(
188 if not HasRepoGroupPermissionAnyApi(*perms)(
189 user=apiuser, group_name=repo_group.group_name):
189 user=apiuser, group_name=repo_group.group_name):
190 raise JSONRPCError(
190 raise JSONRPCError(
191 'repository group `%s` does not exist' % repogroupid)
191 'repository group `%s` does not exist' % repogroupid)
192
192
193 return True
193 return True
194
194
195
195
196 def validate_set_owner_permissions(apiuser, owner):
196 def validate_set_owner_permissions(apiuser, owner):
197 if isinstance(owner, Optional):
197 if isinstance(owner, Optional):
198 owner = get_user_or_error(apiuser.user_id)
198 owner = get_user_or_error(apiuser.user_id)
199 else:
199 else:
200 if has_superadmin_permission(apiuser):
200 if has_superadmin_permission(apiuser):
201 owner = get_user_or_error(owner)
201 owner = get_user_or_error(owner)
202 else:
202 else:
203 # forbid setting owner for non-admins
203 # forbid setting owner for non-admins
204 raise JSONRPCError(
204 raise JSONRPCError(
205 'Only RhodeCode super-admin can specify `owner` param')
205 'Only RhodeCode super-admin can specify `owner` param')
206 return owner
206 return owner
207
207
208
208
209 def get_user_or_error(userid):
209 def get_user_or_error(userid):
210 """
210 """
211 Get user by id or name or return JsonRPCError if not found
211 Get user by id or name or return JsonRPCError if not found
212
212
213 :param userid:
213 :param userid:
214 """
214 """
215 from rhodecode.model.user import UserModel
215 from rhodecode.model.user import UserModel
216
216
217 user_model = UserModel()
217 user_model = UserModel()
218 try:
218 try:
219 user = user_model.get_user(int(userid))
219 user = user_model.get_user(int(userid))
220 except ValueError:
220 except ValueError:
221 user = user_model.get_by_username(userid)
221 user = user_model.get_by_username(userid)
222
222
223 if user is None:
223 if user is None:
224 raise JSONRPCError("user `%s` does not exist" % (userid,))
224 raise JSONRPCError("user `%s` does not exist" % (userid,))
225 return user
225 return user
226
226
227
227
228 def get_repo_or_error(repoid):
228 def get_repo_or_error(repoid):
229 """
229 """
230 Get repo by id or name or return JsonRPCError if not found
230 Get repo by id or name or return JsonRPCError if not found
231
231
232 :param repoid:
232 :param repoid:
233 """
233 """
234 from rhodecode.model.repo import RepoModel
234 from rhodecode.model.repo import RepoModel
235
235
236 repo = RepoModel().get_repo(repoid)
236 repo = RepoModel().get_repo(repoid)
237 if repo is None:
237 if repo is None:
238 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
238 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
239 return repo
239 return repo
240
240
241
241
242 def get_repo_group_or_error(repogroupid):
242 def get_repo_group_or_error(repogroupid):
243 """
243 """
244 Get repo group by id or name or return JsonRPCError if not found
244 Get repo group by id or name or return JsonRPCError if not found
245
245
246 :param repogroupid:
246 :param repogroupid:
247 """
247 """
248 from rhodecode.model.repo_group import RepoGroupModel
248 from rhodecode.model.repo_group import RepoGroupModel
249
249
250 repo_group = RepoGroupModel()._get_repo_group(repogroupid)
250 repo_group = RepoGroupModel()._get_repo_group(repogroupid)
251 if repo_group is None:
251 if repo_group is None:
252 raise JSONRPCError(
252 raise JSONRPCError(
253 'repository group `%s` does not exist' % (repogroupid,))
253 'repository group `%s` does not exist' % (repogroupid,))
254 return repo_group
254 return repo_group
255
255
256
256
257 def get_user_group_or_error(usergroupid):
257 def get_user_group_or_error(usergroupid):
258 """
258 """
259 Get user group by id or name or return JsonRPCError if not found
259 Get user group by id or name or return JsonRPCError if not found
260
260
261 :param usergroupid:
261 :param usergroupid:
262 """
262 """
263 from rhodecode.model.user_group import UserGroupModel
263 from rhodecode.model.user_group import UserGroupModel
264
264
265 user_group = UserGroupModel().get_group(usergroupid)
265 user_group = UserGroupModel().get_group(usergroupid)
266 if user_group is None:
266 if user_group is None:
267 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
267 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
268 return user_group
268 return user_group
269
269
270
270
271 def get_perm_or_error(permid, prefix=None):
271 def get_perm_or_error(permid, prefix=None):
272 """
272 """
273 Get permission by id or name or return JsonRPCError if not found
273 Get permission by id or name or return JsonRPCError if not found
274
274
275 :param permid:
275 :param permid:
276 """
276 """
277 from rhodecode.model.permission import PermissionModel
277 from rhodecode.model.permission import PermissionModel
278
278
279 perm = PermissionModel.cls.get_by_key(permid)
279 perm = PermissionModel.cls.get_by_key(permid)
280 if perm is None:
280 if perm is None:
281 raise JSONRPCError('permission `%s` does not exist' % (permid,))
281 raise JSONRPCError('permission `%s` does not exist' % (permid,))
282 if prefix:
282 if prefix:
283 if not perm.permission_name.startswith(prefix):
283 if not perm.permission_name.startswith(prefix):
284 raise JSONRPCError('permission `%s` is invalid, '
284 raise JSONRPCError('permission `%s` is invalid, '
285 'should start with %s' % (permid, prefix))
285 'should start with %s' % (permid, prefix))
286 return perm
286 return perm
287
287
288
288
289 def get_gist_or_error(gistid):
289 def get_gist_or_error(gistid):
290 """
290 """
291 Get gist by id or gist_access_id or return JsonRPCError if not found
291 Get gist by id or gist_access_id or return JsonRPCError if not found
292
292
293 :param gistid:
293 :param gistid:
294 """
294 """
295 from rhodecode.model.gist import GistModel
295 from rhodecode.model.gist import GistModel
296
296
297 gist = GistModel.cls.get_by_access_id(gistid)
297 gist = GistModel.cls.get_by_access_id(gistid)
298 if gist is None:
298 if gist is None:
299 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
299 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
300 return gist
300 return gist
301
301
302
302
303 def get_pull_request_or_error(pullrequestid):
303 def get_pull_request_or_error(pullrequestid):
304 """
304 """
305 Get pull request by id or return JsonRPCError if not found
305 Get pull request by id or return JsonRPCError if not found
306
306
307 :param pullrequestid:
307 :param pullrequestid:
308 """
308 """
309 from rhodecode.model.pull_request import PullRequestModel
309 from rhodecode.model.pull_request import PullRequestModel
310
310
311 try:
311 try:
312 pull_request = PullRequestModel().get(int(pullrequestid))
312 pull_request = PullRequestModel().get(int(pullrequestid))
313 except ValueError:
313 except ValueError:
314 raise JSONRPCError('pullrequestid must be an integer')
314 raise JSONRPCError('pullrequestid must be an integer')
315 if not pull_request:
315 if not pull_request:
316 raise JSONRPCError('pull request `%s` does not exist' % (
316 raise JSONRPCError('pull request `%s` does not exist' % (
317 pullrequestid,))
317 pullrequestid,))
318 return pull_request
318 return pull_request
319
319
320
320
321 def build_commit_data(commit, detail_level):
321 def build_commit_data(commit, detail_level):
322 parsed_diff = []
322 parsed_diff = []
323 if detail_level == 'extended':
323 if detail_level == 'extended':
324 for f in commit.added:
324 for f in commit.added:
325 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
325 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
326 for f in commit.changed:
326 for f in commit.changed:
327 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
327 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
328 for f in commit.removed:
328 for f in commit.removed:
329 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
329 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
330
330
331 elif detail_level == 'full':
331 elif detail_level == 'full':
332 from rhodecode.lib.diffs import DiffProcessor
332 from rhodecode.lib.diffs import DiffProcessor
333 diff_processor = DiffProcessor(commit.diff())
333 diff_processor = DiffProcessor(commit.diff())
334 for dp in diff_processor.prepare():
334 for dp in diff_processor.prepare():
335 del dp['stats']['ops']
335 del dp['stats']['ops']
336 _stats = dp['stats']
336 _stats = dp['stats']
337 parsed_diff.append(_get_commit_dict(
337 parsed_diff.append(_get_commit_dict(
338 filename=dp['filename'], op=dp['operation'],
338 filename=dp['filename'], op=dp['operation'],
339 new_revision=dp['new_revision'],
339 new_revision=dp['new_revision'],
340 old_revision=dp['old_revision'],
340 old_revision=dp['old_revision'],
341 raw_diff=dp['raw_diff'], stats=_stats))
341 raw_diff=dp['raw_diff'], stats=_stats))
342
342
343 return parsed_diff
343 return parsed_diff
344
344
345
345
346 def get_commit_or_error(ref, repo):
346 def get_commit_or_error(ref, repo):
347 try:
347 try:
348 ref_type, _, ref_hash = ref.split(':')
348 ref_type, _, ref_hash = ref.split(':')
349 except ValueError:
349 except ValueError:
350 raise JSONRPCError(
350 raise JSONRPCError(
351 'Ref `{ref}` given in a wrong format. Please check the API'
351 'Ref `{ref}` given in a wrong format. Please check the API'
352 ' documentation for more details'.format(ref=ref))
352 ' documentation for more details'.format(ref=ref))
353 try:
353 try:
354 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
354 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
355 # once get_commit supports ref_types
355 # once get_commit supports ref_types
356 return get_commit_from_ref_name(repo, ref_hash)
356 return get_commit_from_ref_name(repo, ref_hash)
357 except RepositoryError:
357 except RepositoryError:
358 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
358 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
359
359
360
360
361 def resolve_ref_or_error(ref, repo):
361 def resolve_ref_or_error(ref, repo):
362 def _parse_ref(type_, name, hash_=None):
362 def _parse_ref(type_, name, hash_=None):
363 return type_, name, hash_
363 return type_, name, hash_
364
364
365 try:
365 try:
366 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
366 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
367 except TypeError:
367 except TypeError:
368 raise JSONRPCError(
368 raise JSONRPCError(
369 'Ref `{ref}` given in a wrong format. Please check the API'
369 'Ref `{ref}` given in a wrong format. Please check the API'
370 ' documentation for more details'.format(ref=ref))
370 ' documentation for more details'.format(ref=ref))
371
371
372 try:
372 try:
373 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
373 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
374 except (KeyError, ValueError):
374 except (KeyError, ValueError):
375 raise JSONRPCError(
375 raise JSONRPCError(
376 'The specified {type} `{name}` does not exist'.format(
376 'The specified {type} `{name}` does not exist'.format(
377 type=ref_type, name=ref_name))
377 type=ref_type, name=ref_name))
378
378
379 return ':'.join([ref_type, ref_name, ref_hash])
379 return ':'.join([ref_type, ref_name, ref_hash])
380
380
381
381
382 def _get_commit_dict(
382 def _get_commit_dict(
383 filename, op, new_revision=None, old_revision=None,
383 filename, op, new_revision=None, old_revision=None,
384 raw_diff=None, stats=None):
384 raw_diff=None, stats=None):
385 if stats is None:
385 if stats is None:
386 stats = {
386 stats = {
387 "added": None,
387 "added": None,
388 "binary": None,
388 "binary": None,
389 "deleted": None
389 "deleted": None
390 }
390 }
391 return {
391 return {
392 "filename": safe_unicode(filename),
392 "filename": safe_unicode(filename),
393 "op": op,
393 "op": op,
394
394
395 # extra details
395 # extra details
396 "new_revision": new_revision,
396 "new_revision": new_revision,
397 "old_revision": old_revision,
397 "old_revision": old_revision,
398
398
399 "raw_diff": raw_diff,
399 "raw_diff": raw_diff,
400 "stats": stats
400 "stats": stats
401 }
401 }
402
402
403
403
404 # TODO: mikhail: Think about moving this function to some library
404 # TODO: mikhail: Think about moving this function to some library
405 def _get_ref_hash(repo, type_, name):
405 def _get_ref_hash(repo, type_, name):
406 vcs_repo = repo.scm_instance()
406 vcs_repo = repo.scm_instance()
407 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
407 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
408 return vcs_repo.branches[name]
408 return vcs_repo.branches[name]
409 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
409 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
410 return vcs_repo.bookmarks[name]
410 return vcs_repo.bookmarks[name]
411 else:
411 else:
412 raise ValueError()
412 raise ValueError()
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2016 RhodeCode GmbH
3 # Copyright (C) 2015-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 NOTE:
23 NOTE:
24 Place for deprecated APIs here, if a call needs to be deprecated, please
24 Place for deprecated APIs here, if a call needs to be deprecated, please
25 put it here, and point to a new version
25 put it here, and point to a new version
26 """
26 """
27 import logging
27 import logging
28
28
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
30 from rhodecode.api.utils import Optional, OAttr
30 from rhodecode.api.utils import Optional, OAttr
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 # permission check inside
36 # permission check inside
37 @jsonrpc_method()
37 @jsonrpc_method()
38 @jsonrpc_deprecated_method(
38 @jsonrpc_deprecated_method(
39 use_method='comment_commit', deprecated_at_version='3.4.0')
39 use_method='comment_commit', deprecated_at_version='3.4.0')
40 def changeset_comment(request, apiuser, repoid, revision, message,
40 def changeset_comment(request, apiuser, repoid, revision, message,
41 userid=Optional(OAttr('apiuser')),
41 userid=Optional(OAttr('apiuser')),
42 status=Optional(None)):
42 status=Optional(None)):
43 """
43 """
44 Set a changeset comment, and optionally change the status of the
44 Set a changeset comment, and optionally change the status of the
45 changeset.
45 changeset.
46
46
47 This command can only be run using an |authtoken| with admin
47 This command can only be run using an |authtoken| with admin
48 permissions on the |repo|.
48 permissions on the |repo|.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repoid: Set the repository name or repository ID.
52 :param repoid: Set the repository name or repository ID.
53 :type repoid: str or int
53 :type repoid: str or int
54 :param revision: Specify the revision for which to set a comment.
54 :param revision: Specify the revision for which to set a comment.
55 :type revision: str
55 :type revision: str
56 :param message: The comment text.
56 :param message: The comment text.
57 :type message: str
57 :type message: str
58 :param userid: Set the user name of the comment creator.
58 :param userid: Set the user name of the comment creator.
59 :type userid: Optional(str or int)
59 :type userid: Optional(str or int)
60 :param status: Set the comment status. The following are valid options:
60 :param status: Set the comment status. The following are valid options:
61 * not_reviewed
61 * not_reviewed
62 * approved
62 * approved
63 * rejected
63 * rejected
64 * under_review
64 * under_review
65 :type status: str
65 :type status: str
66
66
67 Example error output:
67 Example error output:
68
68
69 .. code-block:: json
69 .. code-block:: json
70
70
71 {
71 {
72 "id" : <id_given_in_input>,
72 "id" : <id_given_in_input>,
73 "result" : {
73 "result" : {
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
75 "status_change": null or <status>,
75 "status_change": null or <status>,
76 "success": true
76 "success": true
77 },
77 },
78 "error" : null
78 "error" : null
79 }
79 }
80
80
81 """
81 """
82 from .repo_api import comment_commit
82 from .repo_api import comment_commit
83
83
84 return comment_commit(request=request,
84 return comment_commit(request=request,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
86 message=message, userid=userid, status=status)
86 message=message, userid=userid, status=status)
87
87
88
88
89 @jsonrpc_method()
89 @jsonrpc_method()
90 @jsonrpc_deprecated_method(
90 @jsonrpc_deprecated_method(
91 use_method='get_ip', deprecated_at_version='4.0.0')
91 use_method='get_ip', deprecated_at_version='4.0.0')
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
93 from .server_api import get_ip
93 from .server_api import get_ip
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
95
95
96
96
97 @jsonrpc_method()
97 @jsonrpc_method()
98 @jsonrpc_deprecated_method(
98 @jsonrpc_deprecated_method(
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
101 from .user_api import get_user_locks
101 from .user_api import get_user_locks
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid)
@@ -1,255 +1,255 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import time
23 import time
24
24
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.exc import JSONRPCValidationError
26 from rhodecode.api.exc import JSONRPCValidationError
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
29 has_superadmin_permission)
29 has_superadmin_permission)
30 from rhodecode.model.db import Session, or_
30 from rhodecode.model.db import Session, or_
31 from rhodecode.model.gist import Gist, GistModel
31 from rhodecode.model.gist import Gist, GistModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 @jsonrpc_method()
36 @jsonrpc_method()
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
38 """
38 """
39 Get the specified gist, based on the gist ID.
39 Get the specified gist, based on the gist ID.
40
40
41 :param apiuser: This is filled automatically from the |authtoken|.
41 :param apiuser: This is filled automatically from the |authtoken|.
42 :type apiuser: AuthUser
42 :type apiuser: AuthUser
43 :param gistid: Set the id of the private or public gist
43 :param gistid: Set the id of the private or public gist
44 :type gistid: str
44 :type gistid: str
45 :param content: Return the gist content. Default is false.
45 :param content: Return the gist content. Default is false.
46 :type content: Optional(bool)
46 :type content: Optional(bool)
47 """
47 """
48
48
49 gist = get_gist_or_error(gistid)
49 gist = get_gist_or_error(gistid)
50 content = Optional.extract(content)
50 content = Optional.extract(content)
51 if not has_superadmin_permission(apiuser):
51 if not has_superadmin_permission(apiuser):
52 if gist.gist_owner != apiuser.user_id:
52 if gist.gist_owner != apiuser.user_id:
53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
54 data = gist.get_api_data()
54 data = gist.get_api_data()
55 if content:
55 if content:
56 from rhodecode.model.gist import GistModel
56 from rhodecode.model.gist import GistModel
57 rev, gist_files = GistModel().get_gist_files(gistid)
57 rev, gist_files = GistModel().get_gist_files(gistid)
58 data['content'] = dict([(x.path, x.content) for x in gist_files])
58 data['content'] = dict([(x.path, x.content) for x in gist_files])
59 return data
59 return data
60
60
61
61
62 @jsonrpc_method()
62 @jsonrpc_method()
63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
64 """
64 """
65 Get all gists for given user. If userid is empty returned gists
65 Get all gists for given user. If userid is empty returned gists
66 are for user who called the api
66 are for user who called the api
67
67
68 :param apiuser: This is filled automatically from the |authtoken|.
68 :param apiuser: This is filled automatically from the |authtoken|.
69 :type apiuser: AuthUser
69 :type apiuser: AuthUser
70 :param userid: user to get gists for
70 :param userid: user to get gists for
71 :type userid: Optional(str or int)
71 :type userid: Optional(str or int)
72 """
72 """
73
73
74 if not has_superadmin_permission(apiuser):
74 if not has_superadmin_permission(apiuser):
75 # make sure normal user does not pass someone else userid,
75 # make sure normal user does not pass someone else userid,
76 # he is not allowed to do that
76 # he is not allowed to do that
77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
78 raise JSONRPCError(
78 raise JSONRPCError(
79 'userid is not the same as your user'
79 'userid is not the same as your user'
80 )
80 )
81
81
82 if isinstance(userid, Optional):
82 if isinstance(userid, Optional):
83 user_id = apiuser.user_id
83 user_id = apiuser.user_id
84 else:
84 else:
85 user_id = get_user_or_error(userid).user_id
85 user_id = get_user_or_error(userid).user_id
86
86
87 gists = []
87 gists = []
88 _gists = Gist().query() \
88 _gists = Gist().query() \
89 .filter(or_(
89 .filter(or_(
90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
91 .filter(Gist.gist_owner == user_id) \
91 .filter(Gist.gist_owner == user_id) \
92 .order_by(Gist.created_on.desc())
92 .order_by(Gist.created_on.desc())
93 for gist in _gists:
93 for gist in _gists:
94 gists.append(gist.get_api_data())
94 gists.append(gist.get_api_data())
95 return gists
95 return gists
96
96
97
97
98 @jsonrpc_method()
98 @jsonrpc_method()
99 def create_gist(
99 def create_gist(
100 request, apiuser, files, gistid=Optional(None),
100 request, apiuser, files, gistid=Optional(None),
101 owner=Optional(OAttr('apiuser')),
101 owner=Optional(OAttr('apiuser')),
102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
104 description=Optional('')):
104 description=Optional('')):
105 """
105 """
106 Creates a new Gist.
106 Creates a new Gist.
107
107
108 :param apiuser: This is filled automatically from the |authtoken|.
108 :param apiuser: This is filled automatically from the |authtoken|.
109 :type apiuser: AuthUser
109 :type apiuser: AuthUser
110 :param files: files to be added to the gist. The data structure has
110 :param files: files to be added to the gist. The data structure has
111 to match the following example::
111 to match the following example::
112
112
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
114
114
115 :type files: dict
115 :type files: dict
116 :param gistid: Set a custom id for the gist
116 :param gistid: Set a custom id for the gist
117 :type gistid: Optional(str)
117 :type gistid: Optional(str)
118 :param owner: Set the gist owner, defaults to api method caller
118 :param owner: Set the gist owner, defaults to api method caller
119 :type owner: Optional(str or int)
119 :type owner: Optional(str or int)
120 :param gist_type: type of gist ``public`` or ``private``
120 :param gist_type: type of gist ``public`` or ``private``
121 :type gist_type: Optional(str)
121 :type gist_type: Optional(str)
122 :param lifetime: time in minutes of gist lifetime
122 :param lifetime: time in minutes of gist lifetime
123 :type lifetime: Optional(int)
123 :type lifetime: Optional(int)
124 :param acl_level: acl level for this gist, can be
124 :param acl_level: acl level for this gist, can be
125 ``acl_public`` or ``acl_private`` If the value is set to
125 ``acl_public`` or ``acl_private`` If the value is set to
126 ``acl_private`` only logged in users are able to access this gist.
126 ``acl_private`` only logged in users are able to access this gist.
127 If not set it defaults to ``acl_public``.
127 If not set it defaults to ``acl_public``.
128 :type acl_level: Optional(str)
128 :type acl_level: Optional(str)
129 :param description: gist description
129 :param description: gist description
130 :type description: Optional(str)
130 :type description: Optional(str)
131
131
132 Example output:
132 Example output:
133
133
134 .. code-block:: bash
134 .. code-block:: bash
135
135
136 id : <id_given_in_input>
136 id : <id_given_in_input>
137 result : {
137 result : {
138 "msg": "created new gist",
138 "msg": "created new gist",
139 "gist": {}
139 "gist": {}
140 }
140 }
141 error : null
141 error : null
142
142
143 Example error output:
143 Example error output:
144
144
145 .. code-block:: bash
145 .. code-block:: bash
146
146
147 id : <id_given_in_input>
147 id : <id_given_in_input>
148 result : null
148 result : null
149 error : {
149 error : {
150 "failed to create gist"
150 "failed to create gist"
151 }
151 }
152
152
153 """
153 """
154 from rhodecode.model import validation_schema
154 from rhodecode.model import validation_schema
155 from rhodecode.model.validation_schema.schemas import gist_schema
155 from rhodecode.model.validation_schema.schemas import gist_schema
156
156
157 if isinstance(owner, Optional):
157 if isinstance(owner, Optional):
158 owner = apiuser.user_id
158 owner = apiuser.user_id
159
159
160 owner = get_user_or_error(owner)
160 owner = get_user_or_error(owner)
161
161
162 lifetime = Optional.extract(lifetime)
162 lifetime = Optional.extract(lifetime)
163 schema = gist_schema.GistSchema().bind(
163 schema = gist_schema.GistSchema().bind(
164 # bind the given values if it's allowed, however the deferred
164 # bind the given values if it's allowed, however the deferred
165 # validator will still validate it according to other rules
165 # validator will still validate it according to other rules
166 lifetime_options=[lifetime])
166 lifetime_options=[lifetime])
167
167
168 try:
168 try:
169 nodes = gist_schema.nodes_to_sequence(
169 nodes = gist_schema.nodes_to_sequence(
170 files, colander_node=schema.get('nodes'))
170 files, colander_node=schema.get('nodes'))
171
171
172 schema_data = schema.deserialize(dict(
172 schema_data = schema.deserialize(dict(
173 gistid=Optional.extract(gistid),
173 gistid=Optional.extract(gistid),
174 description=Optional.extract(description),
174 description=Optional.extract(description),
175 gist_type=Optional.extract(gist_type),
175 gist_type=Optional.extract(gist_type),
176 lifetime=lifetime,
176 lifetime=lifetime,
177 gist_acl_level=Optional.extract(acl_level),
177 gist_acl_level=Optional.extract(acl_level),
178 nodes=nodes
178 nodes=nodes
179 ))
179 ))
180
180
181 # convert to safer format with just KEYs so we sure no duplicates
181 # convert to safer format with just KEYs so we sure no duplicates
182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
183 schema_data['nodes'], colander_node=schema.get('nodes'))
183 schema_data['nodes'], colander_node=schema.get('nodes'))
184
184
185 except validation_schema.Invalid as err:
185 except validation_schema.Invalid as err:
186 raise JSONRPCValidationError(colander_exc=err)
186 raise JSONRPCValidationError(colander_exc=err)
187
187
188 try:
188 try:
189 gist = GistModel().create(
189 gist = GistModel().create(
190 owner=owner,
190 owner=owner,
191 gist_id=schema_data['gistid'],
191 gist_id=schema_data['gistid'],
192 description=schema_data['description'],
192 description=schema_data['description'],
193 gist_mapping=schema_data['nodes'],
193 gist_mapping=schema_data['nodes'],
194 gist_type=schema_data['gist_type'],
194 gist_type=schema_data['gist_type'],
195 lifetime=schema_data['lifetime'],
195 lifetime=schema_data['lifetime'],
196 gist_acl_level=schema_data['gist_acl_level'])
196 gist_acl_level=schema_data['gist_acl_level'])
197 Session().commit()
197 Session().commit()
198 return {
198 return {
199 'msg': 'created new gist',
199 'msg': 'created new gist',
200 'gist': gist.get_api_data()
200 'gist': gist.get_api_data()
201 }
201 }
202 except Exception:
202 except Exception:
203 log.exception('Error occurred during creation of gist')
203 log.exception('Error occurred during creation of gist')
204 raise JSONRPCError('failed to create gist')
204 raise JSONRPCError('failed to create gist')
205
205
206
206
207 @jsonrpc_method()
207 @jsonrpc_method()
208 def delete_gist(request, apiuser, gistid):
208 def delete_gist(request, apiuser, gistid):
209 """
209 """
210 Deletes existing gist
210 Deletes existing gist
211
211
212 :param apiuser: filled automatically from apikey
212 :param apiuser: filled automatically from apikey
213 :type apiuser: AuthUser
213 :type apiuser: AuthUser
214 :param gistid: id of gist to delete
214 :param gistid: id of gist to delete
215 :type gistid: str
215 :type gistid: str
216
216
217 Example output:
217 Example output:
218
218
219 .. code-block:: bash
219 .. code-block:: bash
220
220
221 id : <id_given_in_input>
221 id : <id_given_in_input>
222 result : {
222 result : {
223 "deleted gist ID: <gist_id>",
223 "deleted gist ID: <gist_id>",
224 "gist": null
224 "gist": null
225 }
225 }
226 error : null
226 error : null
227
227
228 Example error output:
228 Example error output:
229
229
230 .. code-block:: bash
230 .. code-block:: bash
231
231
232 id : <id_given_in_input>
232 id : <id_given_in_input>
233 result : null
233 result : null
234 error : {
234 error : {
235 "failed to delete gist ID:<gist_id>"
235 "failed to delete gist ID:<gist_id>"
236 }
236 }
237
237
238 """
238 """
239
239
240 gist = get_gist_or_error(gistid)
240 gist = get_gist_or_error(gistid)
241 if not has_superadmin_permission(apiuser):
241 if not has_superadmin_permission(apiuser):
242 if gist.gist_owner != apiuser.user_id:
242 if gist.gist_owner != apiuser.user_id:
243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
244
244
245 try:
245 try:
246 GistModel().delete(gist)
246 GistModel().delete(gist)
247 Session().commit()
247 Session().commit()
248 return {
248 return {
249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
250 'gist': None
250 'gist': None
251 }
251 }
252 except Exception:
252 except Exception:
253 log.exception('Error occured during gist deletion')
253 log.exception('Error occured during gist deletion')
254 raise JSONRPCError('failed to delete gist ID:%s'
254 raise JSONRPCError('failed to delete gist ID:%s'
255 % (gist.gist_access_id,)) No newline at end of file
255 % (gist.gist_access_id,))
@@ -1,714 +1,714 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 validate_repo_permissions, resolve_ref_or_error)
28 validate_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import ChangesetCommentsModel
33 from rhodecode.model.comment import ChangesetCommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus
34 from rhodecode.model.db import Session, ChangesetStatus
35 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 @jsonrpc_method()
41 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
43 """
44 Get a pull request based on the given ID.
44 Get a pull request based on the given ID.
45
45
46 :param apiuser: This is filled automatically from the |authtoken|.
46 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
47 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
48 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
49 request was opened.
50 :type repoid: str or int
50 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
51 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
52 :type pullrequestid: int
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 "id": <id_given_in_input>,
58 "id": <id_given_in_input>,
59 "result":
59 "result":
60 {
60 {
61 "pull_request_id": "<pull_request_id>",
61 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
62 "url": "<url>",
63 "title": "<title>",
63 "title": "<title>",
64 "description": "<description>",
64 "description": "<description>",
65 "status" : "<status>",
65 "status" : "<status>",
66 "created_on": "<date_time_created>",
66 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
67 "updated_on": "<date_time_updated>",
68 "commit_ids": [
68 "commit_ids": [
69 ...
69 ...
70 "<commit_id>",
70 "<commit_id>",
71 "<commit_id>",
71 "<commit_id>",
72 ...
72 ...
73 ],
73 ],
74 "review_status": "<review_status>",
74 "review_status": "<review_status>",
75 "mergeable": {
75 "mergeable": {
76 "status": "<bool>",
76 "status": "<bool>",
77 "message": "<message>",
77 "message": "<message>",
78 },
78 },
79 "source": {
79 "source": {
80 "clone_url": "<clone_url>",
80 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
81 "repository": "<repository_name>",
82 "reference":
82 "reference":
83 {
83 {
84 "name": "<name>",
84 "name": "<name>",
85 "type": "<type>",
85 "type": "<type>",
86 "commit_id": "<commit_id>",
86 "commit_id": "<commit_id>",
87 }
87 }
88 },
88 },
89 "target": {
89 "target": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "merge": {
99 "merge": {
100 "clone_url": "<clone_url>",
100 "clone_url": "<clone_url>",
101 "reference":
101 "reference":
102 {
102 {
103 "name": "<name>",
103 "name": "<name>",
104 "type": "<type>",
104 "type": "<type>",
105 "commit_id": "<commit_id>",
105 "commit_id": "<commit_id>",
106 }
106 }
107 },
107 },
108 "author": <user_obj>,
108 "author": <user_obj>,
109 "reviewers": [
109 "reviewers": [
110 ...
110 ...
111 {
111 {
112 "user": "<user_obj>",
112 "user": "<user_obj>",
113 "review_status": "<review_status>",
113 "review_status": "<review_status>",
114 }
114 }
115 ...
115 ...
116 ]
116 ]
117 },
117 },
118 "error": null
118 "error": null
119 """
119 """
120 get_repo_or_error(repoid)
120 get_repo_or_error(repoid)
121 pull_request = get_pull_request_or_error(pullrequestid)
121 pull_request = get_pull_request_or_error(pullrequestid)
122 if not PullRequestModel().check_user_read(
122 if not PullRequestModel().check_user_read(
123 pull_request, apiuser, api=True):
123 pull_request, apiuser, api=True):
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
125 data = pull_request.get_api_data()
125 data = pull_request.get_api_data()
126 return data
126 return data
127
127
128
128
129 @jsonrpc_method()
129 @jsonrpc_method()
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
131 """
131 """
132 Get all pull requests from the repository specified in `repoid`.
132 Get all pull requests from the repository specified in `repoid`.
133
133
134 :param apiuser: This is filled automatically from the |authtoken|.
134 :param apiuser: This is filled automatically from the |authtoken|.
135 :type apiuser: AuthUser
135 :type apiuser: AuthUser
136 :param repoid: Repository name or repository ID.
136 :param repoid: Repository name or repository ID.
137 :type repoid: str or int
137 :type repoid: str or int
138 :param status: Only return pull requests with the specified status.
138 :param status: Only return pull requests with the specified status.
139 Valid options are.
139 Valid options are.
140 * ``new`` (default)
140 * ``new`` (default)
141 * ``open``
141 * ``open``
142 * ``closed``
142 * ``closed``
143 :type status: str
143 :type status: str
144
144
145 Example output:
145 Example output:
146
146
147 .. code-block:: bash
147 .. code-block:: bash
148
148
149 "id": <id_given_in_input>,
149 "id": <id_given_in_input>,
150 "result":
150 "result":
151 [
151 [
152 ...
152 ...
153 {
153 {
154 "pull_request_id": "<pull_request_id>",
154 "pull_request_id": "<pull_request_id>",
155 "url": "<url>",
155 "url": "<url>",
156 "title" : "<title>",
156 "title" : "<title>",
157 "description": "<description>",
157 "description": "<description>",
158 "status": "<status>",
158 "status": "<status>",
159 "created_on": "<date_time_created>",
159 "created_on": "<date_time_created>",
160 "updated_on": "<date_time_updated>",
160 "updated_on": "<date_time_updated>",
161 "commit_ids": [
161 "commit_ids": [
162 ...
162 ...
163 "<commit_id>",
163 "<commit_id>",
164 "<commit_id>",
164 "<commit_id>",
165 ...
165 ...
166 ],
166 ],
167 "review_status": "<review_status>",
167 "review_status": "<review_status>",
168 "mergeable": {
168 "mergeable": {
169 "status": "<bool>",
169 "status": "<bool>",
170 "message: "<message>",
170 "message: "<message>",
171 },
171 },
172 "source": {
172 "source": {
173 "clone_url": "<clone_url>",
173 "clone_url": "<clone_url>",
174 "reference":
174 "reference":
175 {
175 {
176 "name": "<name>",
176 "name": "<name>",
177 "type": "<type>",
177 "type": "<type>",
178 "commit_id": "<commit_id>",
178 "commit_id": "<commit_id>",
179 }
179 }
180 },
180 },
181 "target": {
181 "target": {
182 "clone_url": "<clone_url>",
182 "clone_url": "<clone_url>",
183 "reference":
183 "reference":
184 {
184 {
185 "name": "<name>",
185 "name": "<name>",
186 "type": "<type>",
186 "type": "<type>",
187 "commit_id": "<commit_id>",
187 "commit_id": "<commit_id>",
188 }
188 }
189 },
189 },
190 "merge": {
190 "merge": {
191 "clone_url": "<clone_url>",
191 "clone_url": "<clone_url>",
192 "reference":
192 "reference":
193 {
193 {
194 "name": "<name>",
194 "name": "<name>",
195 "type": "<type>",
195 "type": "<type>",
196 "commit_id": "<commit_id>",
196 "commit_id": "<commit_id>",
197 }
197 }
198 },
198 },
199 "author": <user_obj>,
199 "author": <user_obj>,
200 "reviewers": [
200 "reviewers": [
201 ...
201 ...
202 {
202 {
203 "user": "<user_obj>",
203 "user": "<user_obj>",
204 "review_status": "<review_status>",
204 "review_status": "<review_status>",
205 }
205 }
206 ...
206 ...
207 ]
207 ]
208 }
208 }
209 ...
209 ...
210 ],
210 ],
211 "error": null
211 "error": null
212
212
213 """
213 """
214 repo = get_repo_or_error(repoid)
214 repo = get_repo_or_error(repoid)
215 if not has_superadmin_permission(apiuser):
215 if not has_superadmin_permission(apiuser):
216 _perms = (
216 _perms = (
217 'repository.admin', 'repository.write', 'repository.read',)
217 'repository.admin', 'repository.write', 'repository.read',)
218 validate_repo_permissions(apiuser, repoid, repo, _perms)
218 validate_repo_permissions(apiuser, repoid, repo, _perms)
219
219
220 status = Optional.extract(status)
220 status = Optional.extract(status)
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
222 data = [pr.get_api_data() for pr in pull_requests]
222 data = [pr.get_api_data() for pr in pull_requests]
223 return data
223 return data
224
224
225
225
226 @jsonrpc_method()
226 @jsonrpc_method()
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
228 userid=Optional(OAttr('apiuser'))):
228 userid=Optional(OAttr('apiuser'))):
229 """
229 """
230 Merge the pull request specified by `pullrequestid` into its target
230 Merge the pull request specified by `pullrequestid` into its target
231 repository.
231 repository.
232
232
233 :param apiuser: This is filled automatically from the |authtoken|.
233 :param apiuser: This is filled automatically from the |authtoken|.
234 :type apiuser: AuthUser
234 :type apiuser: AuthUser
235 :param repoid: The Repository name or repository ID of the
235 :param repoid: The Repository name or repository ID of the
236 target repository to which the |pr| is to be merged.
236 target repository to which the |pr| is to be merged.
237 :type repoid: str or int
237 :type repoid: str or int
238 :param pullrequestid: ID of the pull request which shall be merged.
238 :param pullrequestid: ID of the pull request which shall be merged.
239 :type pullrequestid: int
239 :type pullrequestid: int
240 :param userid: Merge the pull request as this user.
240 :param userid: Merge the pull request as this user.
241 :type userid: Optional(str or int)
241 :type userid: Optional(str or int)
242
242
243 Example output:
243 Example output:
244
244
245 .. code-block:: bash
245 .. code-block:: bash
246
246
247 "id": <id_given_in_input>,
247 "id": <id_given_in_input>,
248 "result":
248 "result":
249 {
249 {
250 "executed": "<bool>",
250 "executed": "<bool>",
251 "failure_reason": "<int>",
251 "failure_reason": "<int>",
252 "merge_commit_id": "<merge_commit_id>",
252 "merge_commit_id": "<merge_commit_id>",
253 "possible": "<bool>",
253 "possible": "<bool>",
254 "merge_ref": {
254 "merge_ref": {
255 "commit_id": "<commit_id>",
255 "commit_id": "<commit_id>",
256 "type": "<type>",
256 "type": "<type>",
257 "name": "<name>"
257 "name": "<name>"
258 }
258 }
259 },
259 },
260 "error": null
260 "error": null
261
261
262 """
262 """
263 repo = get_repo_or_error(repoid)
263 repo = get_repo_or_error(repoid)
264 if not isinstance(userid, Optional):
264 if not isinstance(userid, Optional):
265 if (has_superadmin_permission(apiuser) or
265 if (has_superadmin_permission(apiuser) or
266 HasRepoPermissionAnyApi('repository.admin')(
266 HasRepoPermissionAnyApi('repository.admin')(
267 user=apiuser, repo_name=repo.repo_name)):
267 user=apiuser, repo_name=repo.repo_name)):
268 apiuser = get_user_or_error(userid)
268 apiuser = get_user_or_error(userid)
269 else:
269 else:
270 raise JSONRPCError('userid is not the same as your user')
270 raise JSONRPCError('userid is not the same as your user')
271
271
272 pull_request = get_pull_request_or_error(pullrequestid)
272 pull_request = get_pull_request_or_error(pullrequestid)
273 if not PullRequestModel().check_user_merge(
273 if not PullRequestModel().check_user_merge(
274 pull_request, apiuser, api=True):
274 pull_request, apiuser, api=True):
275 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
275 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
276 if pull_request.is_closed():
276 if pull_request.is_closed():
277 raise JSONRPCError(
277 raise JSONRPCError(
278 'pull request `%s` merge failed, pull request is closed' % (
278 'pull request `%s` merge failed, pull request is closed' % (
279 pullrequestid,))
279 pullrequestid,))
280
280
281 target_repo = pull_request.target_repo
281 target_repo = pull_request.target_repo
282 extras = vcs_operation_context(
282 extras = vcs_operation_context(
283 request.environ, repo_name=target_repo.repo_name,
283 request.environ, repo_name=target_repo.repo_name,
284 username=apiuser.username, action='push',
284 username=apiuser.username, action='push',
285 scm=target_repo.repo_type)
285 scm=target_repo.repo_type)
286 merge_response = PullRequestModel().merge(
286 merge_response = PullRequestModel().merge(
287 pull_request, apiuser, extras=extras)
287 pull_request, apiuser, extras=extras)
288 if merge_response.executed:
288 if merge_response.executed:
289 PullRequestModel().close_pull_request(
289 PullRequestModel().close_pull_request(
290 pull_request.pull_request_id, apiuser)
290 pull_request.pull_request_id, apiuser)
291
291
292 Session().commit()
292 Session().commit()
293
293
294 # In previous versions the merge response directly contained the merge
294 # In previous versions the merge response directly contained the merge
295 # commit id. It is now contained in the merge reference object. To be
295 # commit id. It is now contained in the merge reference object. To be
296 # backwards compatible we have to extract it again.
296 # backwards compatible we have to extract it again.
297 merge_response = merge_response._asdict()
297 merge_response = merge_response._asdict()
298 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
298 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
299
299
300 return merge_response
300 return merge_response
301
301
302
302
303 @jsonrpc_method()
303 @jsonrpc_method()
304 def close_pull_request(request, apiuser, repoid, pullrequestid,
304 def close_pull_request(request, apiuser, repoid, pullrequestid,
305 userid=Optional(OAttr('apiuser'))):
305 userid=Optional(OAttr('apiuser'))):
306 """
306 """
307 Close the pull request specified by `pullrequestid`.
307 Close the pull request specified by `pullrequestid`.
308
308
309 :param apiuser: This is filled automatically from the |authtoken|.
309 :param apiuser: This is filled automatically from the |authtoken|.
310 :type apiuser: AuthUser
310 :type apiuser: AuthUser
311 :param repoid: Repository name or repository ID to which the pull
311 :param repoid: Repository name or repository ID to which the pull
312 request belongs.
312 request belongs.
313 :type repoid: str or int
313 :type repoid: str or int
314 :param pullrequestid: ID of the pull request to be closed.
314 :param pullrequestid: ID of the pull request to be closed.
315 :type pullrequestid: int
315 :type pullrequestid: int
316 :param userid: Close the pull request as this user.
316 :param userid: Close the pull request as this user.
317 :type userid: Optional(str or int)
317 :type userid: Optional(str or int)
318
318
319 Example output:
319 Example output:
320
320
321 .. code-block:: bash
321 .. code-block:: bash
322
322
323 "id": <id_given_in_input>,
323 "id": <id_given_in_input>,
324 "result":
324 "result":
325 {
325 {
326 "pull_request_id": "<int>",
326 "pull_request_id": "<int>",
327 "closed": "<bool>"
327 "closed": "<bool>"
328 },
328 },
329 "error": null
329 "error": null
330
330
331 """
331 """
332 repo = get_repo_or_error(repoid)
332 repo = get_repo_or_error(repoid)
333 if not isinstance(userid, Optional):
333 if not isinstance(userid, Optional):
334 if (has_superadmin_permission(apiuser) or
334 if (has_superadmin_permission(apiuser) or
335 HasRepoPermissionAnyApi('repository.admin')(
335 HasRepoPermissionAnyApi('repository.admin')(
336 user=apiuser, repo_name=repo.repo_name)):
336 user=apiuser, repo_name=repo.repo_name)):
337 apiuser = get_user_or_error(userid)
337 apiuser = get_user_or_error(userid)
338 else:
338 else:
339 raise JSONRPCError('userid is not the same as your user')
339 raise JSONRPCError('userid is not the same as your user')
340
340
341 pull_request = get_pull_request_or_error(pullrequestid)
341 pull_request = get_pull_request_or_error(pullrequestid)
342 if not PullRequestModel().check_user_update(
342 if not PullRequestModel().check_user_update(
343 pull_request, apiuser, api=True):
343 pull_request, apiuser, api=True):
344 raise JSONRPCError(
344 raise JSONRPCError(
345 'pull request `%s` close failed, no permission to close.' % (
345 'pull request `%s` close failed, no permission to close.' % (
346 pullrequestid,))
346 pullrequestid,))
347 if pull_request.is_closed():
347 if pull_request.is_closed():
348 raise JSONRPCError(
348 raise JSONRPCError(
349 'pull request `%s` is already closed' % (pullrequestid,))
349 'pull request `%s` is already closed' % (pullrequestid,))
350
350
351 PullRequestModel().close_pull_request(
351 PullRequestModel().close_pull_request(
352 pull_request.pull_request_id, apiuser)
352 pull_request.pull_request_id, apiuser)
353 Session().commit()
353 Session().commit()
354 data = {
354 data = {
355 'pull_request_id': pull_request.pull_request_id,
355 'pull_request_id': pull_request.pull_request_id,
356 'closed': True,
356 'closed': True,
357 }
357 }
358 return data
358 return data
359
359
360
360
361 @jsonrpc_method()
361 @jsonrpc_method()
362 def comment_pull_request(request, apiuser, repoid, pullrequestid,
362 def comment_pull_request(request, apiuser, repoid, pullrequestid,
363 message=Optional(None), status=Optional(None),
363 message=Optional(None), status=Optional(None),
364 commit_id=Optional(None),
364 commit_id=Optional(None),
365 userid=Optional(OAttr('apiuser'))):
365 userid=Optional(OAttr('apiuser'))):
366 """
366 """
367 Comment on the pull request specified with the `pullrequestid`,
367 Comment on the pull request specified with the `pullrequestid`,
368 in the |repo| specified by the `repoid`, and optionally change the
368 in the |repo| specified by the `repoid`, and optionally change the
369 review status.
369 review status.
370
370
371 :param apiuser: This is filled automatically from the |authtoken|.
371 :param apiuser: This is filled automatically from the |authtoken|.
372 :type apiuser: AuthUser
372 :type apiuser: AuthUser
373 :param repoid: The repository name or repository ID.
373 :param repoid: The repository name or repository ID.
374 :type repoid: str or int
374 :type repoid: str or int
375 :param pullrequestid: The pull request ID.
375 :param pullrequestid: The pull request ID.
376 :type pullrequestid: int
376 :type pullrequestid: int
377 :param message: The text content of the comment.
377 :param message: The text content of the comment.
378 :type message: str
378 :type message: str
379 :param status: (**Optional**) Set the approval status of the pull
379 :param status: (**Optional**) Set the approval status of the pull
380 request. Valid options are:
380 request. Valid options are:
381 * not_reviewed
381 * not_reviewed
382 * approved
382 * approved
383 * rejected
383 * rejected
384 * under_review
384 * under_review
385 :type status: str
385 :type status: str
386 :param commit_id: Specify the commit_id for which to set a comment. If
386 :param commit_id: Specify the commit_id for which to set a comment. If
387 given commit_id is different than latest in the PR status
387 given commit_id is different than latest in the PR status
388 change won't be performed.
388 change won't be performed.
389 :type commit_id: str
389 :type commit_id: str
390 :param userid: Comment on the pull request as this user
390 :param userid: Comment on the pull request as this user
391 :type userid: Optional(str or int)
391 :type userid: Optional(str or int)
392
392
393 Example output:
393 Example output:
394
394
395 .. code-block:: bash
395 .. code-block:: bash
396
396
397 id : <id_given_in_input>
397 id : <id_given_in_input>
398 result :
398 result :
399 {
399 {
400 "pull_request_id": "<Integer>",
400 "pull_request_id": "<Integer>",
401 "comment_id": "<Integer>",
401 "comment_id": "<Integer>",
402 "status": {"given": <given_status>,
402 "status": {"given": <given_status>,
403 "was_changed": <bool status_was_actually_changed> },
403 "was_changed": <bool status_was_actually_changed> },
404 }
404 }
405 error : null
405 error : null
406 """
406 """
407 repo = get_repo_or_error(repoid)
407 repo = get_repo_or_error(repoid)
408 if not isinstance(userid, Optional):
408 if not isinstance(userid, Optional):
409 if (has_superadmin_permission(apiuser) or
409 if (has_superadmin_permission(apiuser) or
410 HasRepoPermissionAnyApi('repository.admin')(
410 HasRepoPermissionAnyApi('repository.admin')(
411 user=apiuser, repo_name=repo.repo_name)):
411 user=apiuser, repo_name=repo.repo_name)):
412 apiuser = get_user_or_error(userid)
412 apiuser = get_user_or_error(userid)
413 else:
413 else:
414 raise JSONRPCError('userid is not the same as your user')
414 raise JSONRPCError('userid is not the same as your user')
415
415
416 pull_request = get_pull_request_or_error(pullrequestid)
416 pull_request = get_pull_request_or_error(pullrequestid)
417 if not PullRequestModel().check_user_read(
417 if not PullRequestModel().check_user_read(
418 pull_request, apiuser, api=True):
418 pull_request, apiuser, api=True):
419 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
419 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
420 message = Optional.extract(message)
420 message = Optional.extract(message)
421 status = Optional.extract(status)
421 status = Optional.extract(status)
422 commit_id = Optional.extract(commit_id)
422 commit_id = Optional.extract(commit_id)
423
423
424 if not message and not status:
424 if not message and not status:
425 raise JSONRPCError(
425 raise JSONRPCError(
426 'Both message and status parameters are missing. '
426 'Both message and status parameters are missing. '
427 'At least one is required.')
427 'At least one is required.')
428
428
429 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
429 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
430 status is not None):
430 status is not None):
431 raise JSONRPCError('Unknown comment status: `%s`' % status)
431 raise JSONRPCError('Unknown comment status: `%s`' % status)
432
432
433 if commit_id and commit_id not in pull_request.revisions:
433 if commit_id and commit_id not in pull_request.revisions:
434 raise JSONRPCError(
434 raise JSONRPCError(
435 'Invalid commit_id `%s` for this pull request.' % commit_id)
435 'Invalid commit_id `%s` for this pull request.' % commit_id)
436
436
437 allowed_to_change_status = PullRequestModel().check_user_change_status(
437 allowed_to_change_status = PullRequestModel().check_user_change_status(
438 pull_request, apiuser)
438 pull_request, apiuser)
439
439
440 # if commit_id is passed re-validated if user is allowed to change status
440 # if commit_id is passed re-validated if user is allowed to change status
441 # based on latest commit_id from the PR
441 # based on latest commit_id from the PR
442 if commit_id:
442 if commit_id:
443 commit_idx = pull_request.revisions.index(commit_id)
443 commit_idx = pull_request.revisions.index(commit_id)
444 if commit_idx != 0:
444 if commit_idx != 0:
445 allowed_to_change_status = False
445 allowed_to_change_status = False
446
446
447 text = message
447 text = message
448 status_label = ChangesetStatus.get_status_lbl(status)
448 status_label = ChangesetStatus.get_status_lbl(status)
449 if status and allowed_to_change_status:
449 if status and allowed_to_change_status:
450 st_message = ('Status change %(transition_icon)s %(status)s'
450 st_message = ('Status change %(transition_icon)s %(status)s'
451 % {'transition_icon': '>', 'status': status_label})
451 % {'transition_icon': '>', 'status': status_label})
452 text = message or st_message
452 text = message or st_message
453
453
454 rc_config = SettingsModel().get_all_settings()
454 rc_config = SettingsModel().get_all_settings()
455 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
455 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
456
456
457 status_change = status and allowed_to_change_status
457 status_change = status and allowed_to_change_status
458 comment = ChangesetCommentsModel().create(
458 comment = ChangesetCommentsModel().create(
459 text=text,
459 text=text,
460 repo=pull_request.target_repo.repo_id,
460 repo=pull_request.target_repo.repo_id,
461 user=apiuser.user_id,
461 user=apiuser.user_id,
462 pull_request=pull_request.pull_request_id,
462 pull_request=pull_request.pull_request_id,
463 f_path=None,
463 f_path=None,
464 line_no=None,
464 line_no=None,
465 status_change=(status_label if status_change else None),
465 status_change=(status_label if status_change else None),
466 status_change_type=(status if status_change else None),
466 status_change_type=(status if status_change else None),
467 closing_pr=False,
467 closing_pr=False,
468 renderer=renderer
468 renderer=renderer
469 )
469 )
470
470
471 if allowed_to_change_status and status:
471 if allowed_to_change_status and status:
472 ChangesetStatusModel().set_status(
472 ChangesetStatusModel().set_status(
473 pull_request.target_repo.repo_id,
473 pull_request.target_repo.repo_id,
474 status,
474 status,
475 apiuser.user_id,
475 apiuser.user_id,
476 comment,
476 comment,
477 pull_request=pull_request.pull_request_id
477 pull_request=pull_request.pull_request_id
478 )
478 )
479 Session().flush()
479 Session().flush()
480
480
481 Session().commit()
481 Session().commit()
482 data = {
482 data = {
483 'pull_request_id': pull_request.pull_request_id,
483 'pull_request_id': pull_request.pull_request_id,
484 'comment_id': comment.comment_id if comment else None,
484 'comment_id': comment.comment_id if comment else None,
485 'status': {'given': status, 'was_changed': status_change},
485 'status': {'given': status, 'was_changed': status_change},
486 }
486 }
487 return data
487 return data
488
488
489
489
490 @jsonrpc_method()
490 @jsonrpc_method()
491 def create_pull_request(
491 def create_pull_request(
492 request, apiuser, source_repo, target_repo, source_ref, target_ref,
492 request, apiuser, source_repo, target_repo, source_ref, target_ref,
493 title, description=Optional(''), reviewers=Optional(None)):
493 title, description=Optional(''), reviewers=Optional(None)):
494 """
494 """
495 Creates a new pull request.
495 Creates a new pull request.
496
496
497 Accepts refs in the following formats:
497 Accepts refs in the following formats:
498
498
499 * branch:<branch_name>:<sha>
499 * branch:<branch_name>:<sha>
500 * branch:<branch_name>
500 * branch:<branch_name>
501 * bookmark:<bookmark_name>:<sha> (Mercurial only)
501 * bookmark:<bookmark_name>:<sha> (Mercurial only)
502 * bookmark:<bookmark_name> (Mercurial only)
502 * bookmark:<bookmark_name> (Mercurial only)
503
503
504 :param apiuser: This is filled automatically from the |authtoken|.
504 :param apiuser: This is filled automatically from the |authtoken|.
505 :type apiuser: AuthUser
505 :type apiuser: AuthUser
506 :param source_repo: Set the source repository name.
506 :param source_repo: Set the source repository name.
507 :type source_repo: str
507 :type source_repo: str
508 :param target_repo: Set the target repository name.
508 :param target_repo: Set the target repository name.
509 :type target_repo: str
509 :type target_repo: str
510 :param source_ref: Set the source ref name.
510 :param source_ref: Set the source ref name.
511 :type source_ref: str
511 :type source_ref: str
512 :param target_ref: Set the target ref name.
512 :param target_ref: Set the target ref name.
513 :type target_ref: str
513 :type target_ref: str
514 :param title: Set the pull request title.
514 :param title: Set the pull request title.
515 :type title: str
515 :type title: str
516 :param description: Set the pull request description.
516 :param description: Set the pull request description.
517 :type description: Optional(str)
517 :type description: Optional(str)
518 :param reviewers: Set the new pull request reviewers list.
518 :param reviewers: Set the new pull request reviewers list.
519 :type reviewers: Optional(list)
519 :type reviewers: Optional(list)
520 Accepts username strings or objects of the format:
520 Accepts username strings or objects of the format:
521 {
521 {
522 'username': 'nick', 'reasons': ['original author']
522 'username': 'nick', 'reasons': ['original author']
523 }
523 }
524 """
524 """
525
525
526 source = get_repo_or_error(source_repo)
526 source = get_repo_or_error(source_repo)
527 target = get_repo_or_error(target_repo)
527 target = get_repo_or_error(target_repo)
528 if not has_superadmin_permission(apiuser):
528 if not has_superadmin_permission(apiuser):
529 _perms = ('repository.admin', 'repository.write', 'repository.read',)
529 _perms = ('repository.admin', 'repository.write', 'repository.read',)
530 validate_repo_permissions(apiuser, source_repo, source, _perms)
530 validate_repo_permissions(apiuser, source_repo, source, _perms)
531
531
532 full_source_ref = resolve_ref_or_error(source_ref, source)
532 full_source_ref = resolve_ref_or_error(source_ref, source)
533 full_target_ref = resolve_ref_or_error(target_ref, target)
533 full_target_ref = resolve_ref_or_error(target_ref, target)
534 source_commit = get_commit_or_error(full_source_ref, source)
534 source_commit = get_commit_or_error(full_source_ref, source)
535 target_commit = get_commit_or_error(full_target_ref, target)
535 target_commit = get_commit_or_error(full_target_ref, target)
536 source_scm = source.scm_instance()
536 source_scm = source.scm_instance()
537 target_scm = target.scm_instance()
537 target_scm = target.scm_instance()
538
538
539 commit_ranges = target_scm.compare(
539 commit_ranges = target_scm.compare(
540 target_commit.raw_id, source_commit.raw_id, source_scm,
540 target_commit.raw_id, source_commit.raw_id, source_scm,
541 merge=True, pre_load=[])
541 merge=True, pre_load=[])
542
542
543 ancestor = target_scm.get_common_ancestor(
543 ancestor = target_scm.get_common_ancestor(
544 target_commit.raw_id, source_commit.raw_id, source_scm)
544 target_commit.raw_id, source_commit.raw_id, source_scm)
545
545
546 if not commit_ranges:
546 if not commit_ranges:
547 raise JSONRPCError('no commits found')
547 raise JSONRPCError('no commits found')
548
548
549 if not ancestor:
549 if not ancestor:
550 raise JSONRPCError('no common ancestor found')
550 raise JSONRPCError('no common ancestor found')
551
551
552 reviewer_objects = Optional.extract(reviewers) or []
552 reviewer_objects = Optional.extract(reviewers) or []
553 if not isinstance(reviewer_objects, list):
553 if not isinstance(reviewer_objects, list):
554 raise JSONRPCError('reviewers should be specified as a list')
554 raise JSONRPCError('reviewers should be specified as a list')
555
555
556 reviewers_reasons = []
556 reviewers_reasons = []
557 for reviewer_object in reviewer_objects:
557 for reviewer_object in reviewer_objects:
558 reviewer_reasons = []
558 reviewer_reasons = []
559 if isinstance(reviewer_object, (basestring, int)):
559 if isinstance(reviewer_object, (basestring, int)):
560 reviewer_username = reviewer_object
560 reviewer_username = reviewer_object
561 else:
561 else:
562 reviewer_username = reviewer_object['username']
562 reviewer_username = reviewer_object['username']
563 reviewer_reasons = reviewer_object.get('reasons', [])
563 reviewer_reasons = reviewer_object.get('reasons', [])
564
564
565 user = get_user_or_error(reviewer_username)
565 user = get_user_or_error(reviewer_username)
566 reviewers_reasons.append((user.user_id, reviewer_reasons))
566 reviewers_reasons.append((user.user_id, reviewer_reasons))
567
567
568 pull_request_model = PullRequestModel()
568 pull_request_model = PullRequestModel()
569 pull_request = pull_request_model.create(
569 pull_request = pull_request_model.create(
570 created_by=apiuser.user_id,
570 created_by=apiuser.user_id,
571 source_repo=source_repo,
571 source_repo=source_repo,
572 source_ref=full_source_ref,
572 source_ref=full_source_ref,
573 target_repo=target_repo,
573 target_repo=target_repo,
574 target_ref=full_target_ref,
574 target_ref=full_target_ref,
575 revisions=reversed(
575 revisions=reversed(
576 [commit.raw_id for commit in reversed(commit_ranges)]),
576 [commit.raw_id for commit in reversed(commit_ranges)]),
577 reviewers=reviewers_reasons,
577 reviewers=reviewers_reasons,
578 title=title,
578 title=title,
579 description=Optional.extract(description)
579 description=Optional.extract(description)
580 )
580 )
581
581
582 Session().commit()
582 Session().commit()
583 data = {
583 data = {
584 'msg': 'Created new pull request `{}`'.format(title),
584 'msg': 'Created new pull request `{}`'.format(title),
585 'pull_request_id': pull_request.pull_request_id,
585 'pull_request_id': pull_request.pull_request_id,
586 }
586 }
587 return data
587 return data
588
588
589
589
590 @jsonrpc_method()
590 @jsonrpc_method()
591 def update_pull_request(
591 def update_pull_request(
592 request, apiuser, repoid, pullrequestid, title=Optional(''),
592 request, apiuser, repoid, pullrequestid, title=Optional(''),
593 description=Optional(''), reviewers=Optional(None),
593 description=Optional(''), reviewers=Optional(None),
594 update_commits=Optional(None), close_pull_request=Optional(None)):
594 update_commits=Optional(None), close_pull_request=Optional(None)):
595 """
595 """
596 Updates a pull request.
596 Updates a pull request.
597
597
598 :param apiuser: This is filled automatically from the |authtoken|.
598 :param apiuser: This is filled automatically from the |authtoken|.
599 :type apiuser: AuthUser
599 :type apiuser: AuthUser
600 :param repoid: The repository name or repository ID.
600 :param repoid: The repository name or repository ID.
601 :type repoid: str or int
601 :type repoid: str or int
602 :param pullrequestid: The pull request ID.
602 :param pullrequestid: The pull request ID.
603 :type pullrequestid: int
603 :type pullrequestid: int
604 :param title: Set the pull request title.
604 :param title: Set the pull request title.
605 :type title: str
605 :type title: str
606 :param description: Update pull request description.
606 :param description: Update pull request description.
607 :type description: Optional(str)
607 :type description: Optional(str)
608 :param reviewers: Update pull request reviewers list with new value.
608 :param reviewers: Update pull request reviewers list with new value.
609 :type reviewers: Optional(list)
609 :type reviewers: Optional(list)
610 :param update_commits: Trigger update of commits for this pull request
610 :param update_commits: Trigger update of commits for this pull request
611 :type: update_commits: Optional(bool)
611 :type: update_commits: Optional(bool)
612 :param close_pull_request: Close this pull request with rejected state
612 :param close_pull_request: Close this pull request with rejected state
613 :type: close_pull_request: Optional(bool)
613 :type: close_pull_request: Optional(bool)
614
614
615 Example output:
615 Example output:
616
616
617 .. code-block:: bash
617 .. code-block:: bash
618
618
619 id : <id_given_in_input>
619 id : <id_given_in_input>
620 result :
620 result :
621 {
621 {
622 "msg": "Updated pull request `63`",
622 "msg": "Updated pull request `63`",
623 "pull_request": <pull_request_object>,
623 "pull_request": <pull_request_object>,
624 "updated_reviewers": {
624 "updated_reviewers": {
625 "added": [
625 "added": [
626 "username"
626 "username"
627 ],
627 ],
628 "removed": []
628 "removed": []
629 },
629 },
630 "updated_commits": {
630 "updated_commits": {
631 "added": [
631 "added": [
632 "<sha1_hash>"
632 "<sha1_hash>"
633 ],
633 ],
634 "common": [
634 "common": [
635 "<sha1_hash>",
635 "<sha1_hash>",
636 "<sha1_hash>",
636 "<sha1_hash>",
637 ],
637 ],
638 "removed": []
638 "removed": []
639 }
639 }
640 }
640 }
641 error : null
641 error : null
642 """
642 """
643
643
644 repo = get_repo_or_error(repoid)
644 repo = get_repo_or_error(repoid)
645 pull_request = get_pull_request_or_error(pullrequestid)
645 pull_request = get_pull_request_or_error(pullrequestid)
646 if not PullRequestModel().check_user_update(
646 if not PullRequestModel().check_user_update(
647 pull_request, apiuser, api=True):
647 pull_request, apiuser, api=True):
648 raise JSONRPCError(
648 raise JSONRPCError(
649 'pull request `%s` update failed, no permission to update.' % (
649 'pull request `%s` update failed, no permission to update.' % (
650 pullrequestid,))
650 pullrequestid,))
651 if pull_request.is_closed():
651 if pull_request.is_closed():
652 raise JSONRPCError(
652 raise JSONRPCError(
653 'pull request `%s` update failed, pull request is closed' % (
653 'pull request `%s` update failed, pull request is closed' % (
654 pullrequestid,))
654 pullrequestid,))
655
655
656 reviewer_objects = Optional.extract(reviewers) or []
656 reviewer_objects = Optional.extract(reviewers) or []
657 if not isinstance(reviewer_objects, list):
657 if not isinstance(reviewer_objects, list):
658 raise JSONRPCError('reviewers should be specified as a list')
658 raise JSONRPCError('reviewers should be specified as a list')
659
659
660 reviewers_reasons = []
660 reviewers_reasons = []
661 reviewer_ids = set()
661 reviewer_ids = set()
662 for reviewer_object in reviewer_objects:
662 for reviewer_object in reviewer_objects:
663 reviewer_reasons = []
663 reviewer_reasons = []
664 if isinstance(reviewer_object, (int, basestring)):
664 if isinstance(reviewer_object, (int, basestring)):
665 reviewer_username = reviewer_object
665 reviewer_username = reviewer_object
666 else:
666 else:
667 reviewer_username = reviewer_object['username']
667 reviewer_username = reviewer_object['username']
668 reviewer_reasons = reviewer_object.get('reasons', [])
668 reviewer_reasons = reviewer_object.get('reasons', [])
669
669
670 user = get_user_or_error(reviewer_username)
670 user = get_user_or_error(reviewer_username)
671 reviewer_ids.add(user.user_id)
671 reviewer_ids.add(user.user_id)
672 reviewers_reasons.append((user.user_id, reviewer_reasons))
672 reviewers_reasons.append((user.user_id, reviewer_reasons))
673
673
674 title = Optional.extract(title)
674 title = Optional.extract(title)
675 description = Optional.extract(description)
675 description = Optional.extract(description)
676 if title or description:
676 if title or description:
677 PullRequestModel().edit(
677 PullRequestModel().edit(
678 pull_request, title or pull_request.title,
678 pull_request, title or pull_request.title,
679 description or pull_request.description)
679 description or pull_request.description)
680 Session().commit()
680 Session().commit()
681
681
682 commit_changes = {"added": [], "common": [], "removed": []}
682 commit_changes = {"added": [], "common": [], "removed": []}
683 if str2bool(Optional.extract(update_commits)):
683 if str2bool(Optional.extract(update_commits)):
684 if PullRequestModel().has_valid_update_type(pull_request):
684 if PullRequestModel().has_valid_update_type(pull_request):
685 update_response = PullRequestModel().update_commits(
685 update_response = PullRequestModel().update_commits(
686 pull_request)
686 pull_request)
687 commit_changes = update_response.changes or commit_changes
687 commit_changes = update_response.changes or commit_changes
688 Session().commit()
688 Session().commit()
689
689
690 reviewers_changes = {"added": [], "removed": []}
690 reviewers_changes = {"added": [], "removed": []}
691 if reviewer_ids:
691 if reviewer_ids:
692 added_reviewers, removed_reviewers = \
692 added_reviewers, removed_reviewers = \
693 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
693 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
694
694
695 reviewers_changes['added'] = sorted(
695 reviewers_changes['added'] = sorted(
696 [get_user_or_error(n).username for n in added_reviewers])
696 [get_user_or_error(n).username for n in added_reviewers])
697 reviewers_changes['removed'] = sorted(
697 reviewers_changes['removed'] = sorted(
698 [get_user_or_error(n).username for n in removed_reviewers])
698 [get_user_or_error(n).username for n in removed_reviewers])
699 Session().commit()
699 Session().commit()
700
700
701 if str2bool(Optional.extract(close_pull_request)):
701 if str2bool(Optional.extract(close_pull_request)):
702 PullRequestModel().close_pull_request_with_comment(
702 PullRequestModel().close_pull_request_with_comment(
703 pull_request, apiuser, repo)
703 pull_request, apiuser, repo)
704 Session().commit()
704 Session().commit()
705
705
706 data = {
706 data = {
707 'msg': 'Updated pull request `{}`'.format(
707 'msg': 'Updated pull request `{}`'.format(
708 pull_request.pull_request_id),
708 pull_request.pull_request_id),
709 'pull_request': pull_request.get_api_data(),
709 'pull_request': pull_request.get_api_data(),
710 'updated_commits': commit_changes,
710 'updated_commits': commit_changes,
711 'updated_reviewers': reviewers_changes
711 'updated_reviewers': reviewers_changes
712 }
712 }
713
713
714 return data
714 return data
@@ -1,1960 +1,1960 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import ChangesetCommentsModel
37 from rhodecode.model.comment import ChangesetCommentsModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup)
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup)
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.scm import ScmModel, RepoList
41 from rhodecode.model.scm import ScmModel, RepoList
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
43 from rhodecode.model import validation_schema
43 from rhodecode.model import validation_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 @jsonrpc_method()
49 @jsonrpc_method()
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
51 """
51 """
52 Gets an existing repository by its name or repository_id.
52 Gets an existing repository by its name or repository_id.
53
53
54 The members section so the output returns users groups or users
54 The members section so the output returns users groups or users
55 associated with that repository.
55 associated with that repository.
56
56
57 This command can only be run using an |authtoken| with admin rights,
57 This command can only be run using an |authtoken| with admin rights,
58 or users with at least read rights to the |repo|.
58 or users with at least read rights to the |repo|.
59
59
60 :param apiuser: This is filled automatically from the |authtoken|.
60 :param apiuser: This is filled automatically from the |authtoken|.
61 :type apiuser: AuthUser
61 :type apiuser: AuthUser
62 :param repoid: The repository name or repository id.
62 :param repoid: The repository name or repository id.
63 :type repoid: str or int
63 :type repoid: str or int
64 :param cache: use the cached value for last changeset
64 :param cache: use the cached value for last changeset
65 :type: cache: Optional(bool)
65 :type: cache: Optional(bool)
66
66
67 Example output:
67 Example output:
68
68
69 .. code-block:: bash
69 .. code-block:: bash
70
70
71 {
71 {
72 "error": null,
72 "error": null,
73 "id": <repo_id>,
73 "id": <repo_id>,
74 "result": {
74 "result": {
75 "clone_uri": null,
75 "clone_uri": null,
76 "created_on": "timestamp",
76 "created_on": "timestamp",
77 "description": "repo description",
77 "description": "repo description",
78 "enable_downloads": false,
78 "enable_downloads": false,
79 "enable_locking": false,
79 "enable_locking": false,
80 "enable_statistics": false,
80 "enable_statistics": false,
81 "followers": [
81 "followers": [
82 {
82 {
83 "active": true,
83 "active": true,
84 "admin": false,
84 "admin": false,
85 "api_key": "****************************************",
85 "api_key": "****************************************",
86 "api_keys": [
86 "api_keys": [
87 "****************************************"
87 "****************************************"
88 ],
88 ],
89 "email": "user@example.com",
89 "email": "user@example.com",
90 "emails": [
90 "emails": [
91 "user@example.com"
91 "user@example.com"
92 ],
92 ],
93 "extern_name": "rhodecode",
93 "extern_name": "rhodecode",
94 "extern_type": "rhodecode",
94 "extern_type": "rhodecode",
95 "firstname": "username",
95 "firstname": "username",
96 "ip_addresses": [],
96 "ip_addresses": [],
97 "language": null,
97 "language": null,
98 "last_login": "2015-09-16T17:16:35.854",
98 "last_login": "2015-09-16T17:16:35.854",
99 "lastname": "surname",
99 "lastname": "surname",
100 "user_id": <user_id>,
100 "user_id": <user_id>,
101 "username": "name"
101 "username": "name"
102 }
102 }
103 ],
103 ],
104 "fork_of": "parent-repo",
104 "fork_of": "parent-repo",
105 "landing_rev": [
105 "landing_rev": [
106 "rev",
106 "rev",
107 "tip"
107 "tip"
108 ],
108 ],
109 "last_changeset": {
109 "last_changeset": {
110 "author": "User <user@example.com>",
110 "author": "User <user@example.com>",
111 "branch": "default",
111 "branch": "default",
112 "date": "timestamp",
112 "date": "timestamp",
113 "message": "last commit message",
113 "message": "last commit message",
114 "parents": [
114 "parents": [
115 {
115 {
116 "raw_id": "commit-id"
116 "raw_id": "commit-id"
117 }
117 }
118 ],
118 ],
119 "raw_id": "commit-id",
119 "raw_id": "commit-id",
120 "revision": <revision number>,
120 "revision": <revision number>,
121 "short_id": "short id"
121 "short_id": "short id"
122 },
122 },
123 "lock_reason": null,
123 "lock_reason": null,
124 "locked_by": null,
124 "locked_by": null,
125 "locked_date": null,
125 "locked_date": null,
126 "members": [
126 "members": [
127 {
127 {
128 "name": "super-admin-name",
128 "name": "super-admin-name",
129 "origin": "super-admin",
129 "origin": "super-admin",
130 "permission": "repository.admin",
130 "permission": "repository.admin",
131 "type": "user"
131 "type": "user"
132 },
132 },
133 {
133 {
134 "name": "owner-name",
134 "name": "owner-name",
135 "origin": "owner",
135 "origin": "owner",
136 "permission": "repository.admin",
136 "permission": "repository.admin",
137 "type": "user"
137 "type": "user"
138 },
138 },
139 {
139 {
140 "name": "user-group-name",
140 "name": "user-group-name",
141 "origin": "permission",
141 "origin": "permission",
142 "permission": "repository.write",
142 "permission": "repository.write",
143 "type": "user_group"
143 "type": "user_group"
144 }
144 }
145 ],
145 ],
146 "owner": "owner-name",
146 "owner": "owner-name",
147 "permissions": [
147 "permissions": [
148 {
148 {
149 "name": "super-admin-name",
149 "name": "super-admin-name",
150 "origin": "super-admin",
150 "origin": "super-admin",
151 "permission": "repository.admin",
151 "permission": "repository.admin",
152 "type": "user"
152 "type": "user"
153 },
153 },
154 {
154 {
155 "name": "owner-name",
155 "name": "owner-name",
156 "origin": "owner",
156 "origin": "owner",
157 "permission": "repository.admin",
157 "permission": "repository.admin",
158 "type": "user"
158 "type": "user"
159 },
159 },
160 {
160 {
161 "name": "user-group-name",
161 "name": "user-group-name",
162 "origin": "permission",
162 "origin": "permission",
163 "permission": "repository.write",
163 "permission": "repository.write",
164 "type": "user_group"
164 "type": "user_group"
165 }
165 }
166 ],
166 ],
167 "private": true,
167 "private": true,
168 "repo_id": 676,
168 "repo_id": 676,
169 "repo_name": "user-group/repo-name",
169 "repo_name": "user-group/repo-name",
170 "repo_type": "hg"
170 "repo_type": "hg"
171 }
171 }
172 }
172 }
173 """
173 """
174
174
175 repo = get_repo_or_error(repoid)
175 repo = get_repo_or_error(repoid)
176 cache = Optional.extract(cache)
176 cache = Optional.extract(cache)
177
177
178 include_secrets = False
178 include_secrets = False
179 if has_superadmin_permission(apiuser):
179 if has_superadmin_permission(apiuser):
180 include_secrets = True
180 include_secrets = True
181 else:
181 else:
182 # check if we have at least read permission for this repo !
182 # check if we have at least read permission for this repo !
183 _perms = (
183 _perms = (
184 'repository.admin', 'repository.write', 'repository.read',)
184 'repository.admin', 'repository.write', 'repository.read',)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
186
186
187 permissions = []
187 permissions = []
188 for _user in repo.permissions():
188 for _user in repo.permissions():
189 user_data = {
189 user_data = {
190 'name': _user.username,
190 'name': _user.username,
191 'permission': _user.permission,
191 'permission': _user.permission,
192 'origin': get_origin(_user),
192 'origin': get_origin(_user),
193 'type': "user",
193 'type': "user",
194 }
194 }
195 permissions.append(user_data)
195 permissions.append(user_data)
196
196
197 for _user_group in repo.permission_user_groups():
197 for _user_group in repo.permission_user_groups():
198 user_group_data = {
198 user_group_data = {
199 'name': _user_group.users_group_name,
199 'name': _user_group.users_group_name,
200 'permission': _user_group.permission,
200 'permission': _user_group.permission,
201 'origin': get_origin(_user_group),
201 'origin': get_origin(_user_group),
202 'type': "user_group",
202 'type': "user_group",
203 }
203 }
204 permissions.append(user_group_data)
204 permissions.append(user_group_data)
205
205
206 following_users = [
206 following_users = [
207 user.user.get_api_data(include_secrets=include_secrets)
207 user.user.get_api_data(include_secrets=include_secrets)
208 for user in repo.followers]
208 for user in repo.followers]
209
209
210 if not cache:
210 if not cache:
211 repo.update_commit_cache()
211 repo.update_commit_cache()
212 data = repo.get_api_data(include_secrets=include_secrets)
212 data = repo.get_api_data(include_secrets=include_secrets)
213 data['members'] = permissions # TODO: this should be deprecated soon
213 data['members'] = permissions # TODO: this should be deprecated soon
214 data['permissions'] = permissions
214 data['permissions'] = permissions
215 data['followers'] = following_users
215 data['followers'] = following_users
216 return data
216 return data
217
217
218
218
219 @jsonrpc_method()
219 @jsonrpc_method()
220 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
220 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
221 """
221 """
222 Lists all existing repositories.
222 Lists all existing repositories.
223
223
224 This command can only be run using an |authtoken| with admin rights,
224 This command can only be run using an |authtoken| with admin rights,
225 or users with at least read rights to |repos|.
225 or users with at least read rights to |repos|.
226
226
227 :param apiuser: This is filled automatically from the |authtoken|.
227 :param apiuser: This is filled automatically from the |authtoken|.
228 :type apiuser: AuthUser
228 :type apiuser: AuthUser
229 :param root: specify root repository group to fetch repositories.
229 :param root: specify root repository group to fetch repositories.
230 filters the returned repositories to be members of given root group.
230 filters the returned repositories to be members of given root group.
231 :type root: Optional(None)
231 :type root: Optional(None)
232 :param traverse: traverse given root into subrepositories. With this flag
232 :param traverse: traverse given root into subrepositories. With this flag
233 set to False, it will only return top-level repositories from `root`.
233 set to False, it will only return top-level repositories from `root`.
234 if root is empty it will return just top-level repositories.
234 if root is empty it will return just top-level repositories.
235 :type traverse: Optional(True)
235 :type traverse: Optional(True)
236
236
237
237
238 Example output:
238 Example output:
239
239
240 .. code-block:: bash
240 .. code-block:: bash
241
241
242 id : <id_given_in_input>
242 id : <id_given_in_input>
243 result: [
243 result: [
244 {
244 {
245 "repo_id" : "<repo_id>",
245 "repo_id" : "<repo_id>",
246 "repo_name" : "<reponame>"
246 "repo_name" : "<reponame>"
247 "repo_type" : "<repo_type>",
247 "repo_type" : "<repo_type>",
248 "clone_uri" : "<clone_uri>",
248 "clone_uri" : "<clone_uri>",
249 "private": : "<bool>",
249 "private": : "<bool>",
250 "created_on" : "<datetimecreated>",
250 "created_on" : "<datetimecreated>",
251 "description" : "<description>",
251 "description" : "<description>",
252 "landing_rev": "<landing_rev>",
252 "landing_rev": "<landing_rev>",
253 "owner": "<repo_owner>",
253 "owner": "<repo_owner>",
254 "fork_of": "<name_of_fork_parent>",
254 "fork_of": "<name_of_fork_parent>",
255 "enable_downloads": "<bool>",
255 "enable_downloads": "<bool>",
256 "enable_locking": "<bool>",
256 "enable_locking": "<bool>",
257 "enable_statistics": "<bool>",
257 "enable_statistics": "<bool>",
258 },
258 },
259 ...
259 ...
260 ]
260 ]
261 error: null
261 error: null
262 """
262 """
263
263
264 include_secrets = has_superadmin_permission(apiuser)
264 include_secrets = has_superadmin_permission(apiuser)
265 _perms = ('repository.read', 'repository.write', 'repository.admin',)
265 _perms = ('repository.read', 'repository.write', 'repository.admin',)
266 extras = {'user': apiuser}
266 extras = {'user': apiuser}
267
267
268 root = Optional.extract(root)
268 root = Optional.extract(root)
269 traverse = Optional.extract(traverse, binary=True)
269 traverse = Optional.extract(traverse, binary=True)
270
270
271 if root:
271 if root:
272 # verify parent existance, if it's empty return an error
272 # verify parent existance, if it's empty return an error
273 parent = RepoGroup.get_by_group_name(root)
273 parent = RepoGroup.get_by_group_name(root)
274 if not parent:
274 if not parent:
275 raise JSONRPCError(
275 raise JSONRPCError(
276 'Root repository group `{}` does not exist'.format(root))
276 'Root repository group `{}` does not exist'.format(root))
277
277
278 if traverse:
278 if traverse:
279 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
279 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
280 else:
280 else:
281 repos = RepoModel().get_repos_for_root(root=parent)
281 repos = RepoModel().get_repos_for_root(root=parent)
282 else:
282 else:
283 if traverse:
283 if traverse:
284 repos = RepoModel().get_all()
284 repos = RepoModel().get_all()
285 else:
285 else:
286 # return just top-level
286 # return just top-level
287 repos = RepoModel().get_repos_for_root(root=None)
287 repos = RepoModel().get_repos_for_root(root=None)
288
288
289 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
289 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
290 return [repo.get_api_data(include_secrets=include_secrets)
290 return [repo.get_api_data(include_secrets=include_secrets)
291 for repo in repo_list]
291 for repo in repo_list]
292
292
293
293
294 @jsonrpc_method()
294 @jsonrpc_method()
295 def get_repo_changeset(request, apiuser, repoid, revision,
295 def get_repo_changeset(request, apiuser, repoid, revision,
296 details=Optional('basic')):
296 details=Optional('basic')):
297 """
297 """
298 Returns information about a changeset.
298 Returns information about a changeset.
299
299
300 Additionally parameters define the amount of details returned by
300 Additionally parameters define the amount of details returned by
301 this function.
301 this function.
302
302
303 This command can only be run using an |authtoken| with admin rights,
303 This command can only be run using an |authtoken| with admin rights,
304 or users with at least read rights to the |repo|.
304 or users with at least read rights to the |repo|.
305
305
306 :param apiuser: This is filled automatically from the |authtoken|.
306 :param apiuser: This is filled automatically from the |authtoken|.
307 :type apiuser: AuthUser
307 :type apiuser: AuthUser
308 :param repoid: The repository name or repository id
308 :param repoid: The repository name or repository id
309 :type repoid: str or int
309 :type repoid: str or int
310 :param revision: revision for which listing should be done
310 :param revision: revision for which listing should be done
311 :type revision: str
311 :type revision: str
312 :param details: details can be 'basic|extended|full' full gives diff
312 :param details: details can be 'basic|extended|full' full gives diff
313 info details like the diff itself, and number of changed files etc.
313 info details like the diff itself, and number of changed files etc.
314 :type details: Optional(str)
314 :type details: Optional(str)
315
315
316 """
316 """
317 repo = get_repo_or_error(repoid)
317 repo = get_repo_or_error(repoid)
318 if not has_superadmin_permission(apiuser):
318 if not has_superadmin_permission(apiuser):
319 _perms = (
319 _perms = (
320 'repository.admin', 'repository.write', 'repository.read',)
320 'repository.admin', 'repository.write', 'repository.read',)
321 validate_repo_permissions(apiuser, repoid, repo, _perms)
321 validate_repo_permissions(apiuser, repoid, repo, _perms)
322
322
323 changes_details = Optional.extract(details)
323 changes_details = Optional.extract(details)
324 _changes_details_types = ['basic', 'extended', 'full']
324 _changes_details_types = ['basic', 'extended', 'full']
325 if changes_details not in _changes_details_types:
325 if changes_details not in _changes_details_types:
326 raise JSONRPCError(
326 raise JSONRPCError(
327 'ret_type must be one of %s' % (
327 'ret_type must be one of %s' % (
328 ','.join(_changes_details_types)))
328 ','.join(_changes_details_types)))
329
329
330 pre_load = ['author', 'branch', 'date', 'message', 'parents',
330 pre_load = ['author', 'branch', 'date', 'message', 'parents',
331 'status', '_commit', '_file_paths']
331 'status', '_commit', '_file_paths']
332
332
333 try:
333 try:
334 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
334 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
335 except TypeError as e:
335 except TypeError as e:
336 raise JSONRPCError(e.message)
336 raise JSONRPCError(e.message)
337 _cs_json = cs.__json__()
337 _cs_json = cs.__json__()
338 _cs_json['diff'] = build_commit_data(cs, changes_details)
338 _cs_json['diff'] = build_commit_data(cs, changes_details)
339 if changes_details == 'full':
339 if changes_details == 'full':
340 _cs_json['refs'] = {
340 _cs_json['refs'] = {
341 'branches': [cs.branch],
341 'branches': [cs.branch],
342 'bookmarks': getattr(cs, 'bookmarks', []),
342 'bookmarks': getattr(cs, 'bookmarks', []),
343 'tags': cs.tags
343 'tags': cs.tags
344 }
344 }
345 return _cs_json
345 return _cs_json
346
346
347
347
348 @jsonrpc_method()
348 @jsonrpc_method()
349 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
349 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
350 details=Optional('basic')):
350 details=Optional('basic')):
351 """
351 """
352 Returns a set of commits limited by the number starting
352 Returns a set of commits limited by the number starting
353 from the `start_rev` option.
353 from the `start_rev` option.
354
354
355 Additional parameters define the amount of details returned by this
355 Additional parameters define the amount of details returned by this
356 function.
356 function.
357
357
358 This command can only be run using an |authtoken| with admin rights,
358 This command can only be run using an |authtoken| with admin rights,
359 or users with at least read rights to |repos|.
359 or users with at least read rights to |repos|.
360
360
361 :param apiuser: This is filled automatically from the |authtoken|.
361 :param apiuser: This is filled automatically from the |authtoken|.
362 :type apiuser: AuthUser
362 :type apiuser: AuthUser
363 :param repoid: The repository name or repository ID.
363 :param repoid: The repository name or repository ID.
364 :type repoid: str or int
364 :type repoid: str or int
365 :param start_rev: The starting revision from where to get changesets.
365 :param start_rev: The starting revision from where to get changesets.
366 :type start_rev: str
366 :type start_rev: str
367 :param limit: Limit the number of commits to this amount
367 :param limit: Limit the number of commits to this amount
368 :type limit: str or int
368 :type limit: str or int
369 :param details: Set the level of detail returned. Valid option are:
369 :param details: Set the level of detail returned. Valid option are:
370 ``basic``, ``extended`` and ``full``.
370 ``basic``, ``extended`` and ``full``.
371 :type details: Optional(str)
371 :type details: Optional(str)
372
372
373 .. note::
373 .. note::
374
374
375 Setting the parameter `details` to the value ``full`` is extensive
375 Setting the parameter `details` to the value ``full`` is extensive
376 and returns details like the diff itself, and the number
376 and returns details like the diff itself, and the number
377 of changed files.
377 of changed files.
378
378
379 """
379 """
380 repo = get_repo_or_error(repoid)
380 repo = get_repo_or_error(repoid)
381 if not has_superadmin_permission(apiuser):
381 if not has_superadmin_permission(apiuser):
382 _perms = (
382 _perms = (
383 'repository.admin', 'repository.write', 'repository.read',)
383 'repository.admin', 'repository.write', 'repository.read',)
384 validate_repo_permissions(apiuser, repoid, repo, _perms)
384 validate_repo_permissions(apiuser, repoid, repo, _perms)
385
385
386 changes_details = Optional.extract(details)
386 changes_details = Optional.extract(details)
387 _changes_details_types = ['basic', 'extended', 'full']
387 _changes_details_types = ['basic', 'extended', 'full']
388 if changes_details not in _changes_details_types:
388 if changes_details not in _changes_details_types:
389 raise JSONRPCError(
389 raise JSONRPCError(
390 'ret_type must be one of %s' % (
390 'ret_type must be one of %s' % (
391 ','.join(_changes_details_types)))
391 ','.join(_changes_details_types)))
392
392
393 limit = int(limit)
393 limit = int(limit)
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 'status', '_commit', '_file_paths']
395 'status', '_commit', '_file_paths']
396
396
397 vcs_repo = repo.scm_instance()
397 vcs_repo = repo.scm_instance()
398 # SVN needs a special case to distinguish its index and commit id
398 # SVN needs a special case to distinguish its index and commit id
399 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
399 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
400 start_rev = vcs_repo.commit_ids[0]
400 start_rev = vcs_repo.commit_ids[0]
401
401
402 try:
402 try:
403 commits = vcs_repo.get_commits(
403 commits = vcs_repo.get_commits(
404 start_id=start_rev, pre_load=pre_load)
404 start_id=start_rev, pre_load=pre_load)
405 except TypeError as e:
405 except TypeError as e:
406 raise JSONRPCError(e.message)
406 raise JSONRPCError(e.message)
407 except Exception:
407 except Exception:
408 log.exception('Fetching of commits failed')
408 log.exception('Fetching of commits failed')
409 raise JSONRPCError('Error occurred during commit fetching')
409 raise JSONRPCError('Error occurred during commit fetching')
410
410
411 ret = []
411 ret = []
412 for cnt, commit in enumerate(commits):
412 for cnt, commit in enumerate(commits):
413 if cnt >= limit != -1:
413 if cnt >= limit != -1:
414 break
414 break
415 _cs_json = commit.__json__()
415 _cs_json = commit.__json__()
416 _cs_json['diff'] = build_commit_data(commit, changes_details)
416 _cs_json['diff'] = build_commit_data(commit, changes_details)
417 if changes_details == 'full':
417 if changes_details == 'full':
418 _cs_json['refs'] = {
418 _cs_json['refs'] = {
419 'branches': [commit.branch],
419 'branches': [commit.branch],
420 'bookmarks': getattr(commit, 'bookmarks', []),
420 'bookmarks': getattr(commit, 'bookmarks', []),
421 'tags': commit.tags
421 'tags': commit.tags
422 }
422 }
423 ret.append(_cs_json)
423 ret.append(_cs_json)
424 return ret
424 return ret
425
425
426
426
427 @jsonrpc_method()
427 @jsonrpc_method()
428 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
428 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
429 ret_type=Optional('all'), details=Optional('basic'),
429 ret_type=Optional('all'), details=Optional('basic'),
430 max_file_bytes=Optional(None)):
430 max_file_bytes=Optional(None)):
431 """
431 """
432 Returns a list of nodes and children in a flat list for a given
432 Returns a list of nodes and children in a flat list for a given
433 path at given revision.
433 path at given revision.
434
434
435 It's possible to specify ret_type to show only `files` or `dirs`.
435 It's possible to specify ret_type to show only `files` or `dirs`.
436
436
437 This command can only be run using an |authtoken| with admin rights,
437 This command can only be run using an |authtoken| with admin rights,
438 or users with at least read rights to |repos|.
438 or users with at least read rights to |repos|.
439
439
440 :param apiuser: This is filled automatically from the |authtoken|.
440 :param apiuser: This is filled automatically from the |authtoken|.
441 :type apiuser: AuthUser
441 :type apiuser: AuthUser
442 :param repoid: The repository name or repository ID.
442 :param repoid: The repository name or repository ID.
443 :type repoid: str or int
443 :type repoid: str or int
444 :param revision: The revision for which listing should be done.
444 :param revision: The revision for which listing should be done.
445 :type revision: str
445 :type revision: str
446 :param root_path: The path from which to start displaying.
446 :param root_path: The path from which to start displaying.
447 :type root_path: str
447 :type root_path: str
448 :param ret_type: Set the return type. Valid options are
448 :param ret_type: Set the return type. Valid options are
449 ``all`` (default), ``files`` and ``dirs``.
449 ``all`` (default), ``files`` and ``dirs``.
450 :type ret_type: Optional(str)
450 :type ret_type: Optional(str)
451 :param details: Returns extended information about nodes, such as
451 :param details: Returns extended information about nodes, such as
452 md5, binary, and or content. The valid options are ``basic`` and
452 md5, binary, and or content. The valid options are ``basic`` and
453 ``full``.
453 ``full``.
454 :type details: Optional(str)
454 :type details: Optional(str)
455 :param max_file_bytes: Only return file content under this file size bytes
455 :param max_file_bytes: Only return file content under this file size bytes
456 :type details: Optional(int)
456 :type details: Optional(int)
457
457
458 Example output:
458 Example output:
459
459
460 .. code-block:: bash
460 .. code-block:: bash
461
461
462 id : <id_given_in_input>
462 id : <id_given_in_input>
463 result: [
463 result: [
464 {
464 {
465 "name" : "<name>"
465 "name" : "<name>"
466 "type" : "<type>",
466 "type" : "<type>",
467 "binary": "<true|false>" (only in extended mode)
467 "binary": "<true|false>" (only in extended mode)
468 "md5" : "<md5 of file content>" (only in extended mode)
468 "md5" : "<md5 of file content>" (only in extended mode)
469 },
469 },
470 ...
470 ...
471 ]
471 ]
472 error: null
472 error: null
473 """
473 """
474
474
475 repo = get_repo_or_error(repoid)
475 repo = get_repo_or_error(repoid)
476 if not has_superadmin_permission(apiuser):
476 if not has_superadmin_permission(apiuser):
477 _perms = (
477 _perms = (
478 'repository.admin', 'repository.write', 'repository.read',)
478 'repository.admin', 'repository.write', 'repository.read',)
479 validate_repo_permissions(apiuser, repoid, repo, _perms)
479 validate_repo_permissions(apiuser, repoid, repo, _perms)
480
480
481 ret_type = Optional.extract(ret_type)
481 ret_type = Optional.extract(ret_type)
482 details = Optional.extract(details)
482 details = Optional.extract(details)
483 _extended_types = ['basic', 'full']
483 _extended_types = ['basic', 'full']
484 if details not in _extended_types:
484 if details not in _extended_types:
485 raise JSONRPCError(
485 raise JSONRPCError(
486 'ret_type must be one of %s' % (','.join(_extended_types)))
486 'ret_type must be one of %s' % (','.join(_extended_types)))
487 extended_info = False
487 extended_info = False
488 content = False
488 content = False
489 if details == 'basic':
489 if details == 'basic':
490 extended_info = True
490 extended_info = True
491
491
492 if details == 'full':
492 if details == 'full':
493 extended_info = content = True
493 extended_info = content = True
494
494
495 _map = {}
495 _map = {}
496 try:
496 try:
497 # check if repo is not empty by any chance, skip quicker if it is.
497 # check if repo is not empty by any chance, skip quicker if it is.
498 _scm = repo.scm_instance()
498 _scm = repo.scm_instance()
499 if _scm.is_empty():
499 if _scm.is_empty():
500 return []
500 return []
501
501
502 _d, _f = ScmModel().get_nodes(
502 _d, _f = ScmModel().get_nodes(
503 repo, revision, root_path, flat=False,
503 repo, revision, root_path, flat=False,
504 extended_info=extended_info, content=content,
504 extended_info=extended_info, content=content,
505 max_file_bytes=max_file_bytes)
505 max_file_bytes=max_file_bytes)
506 _map = {
506 _map = {
507 'all': _d + _f,
507 'all': _d + _f,
508 'files': _f,
508 'files': _f,
509 'dirs': _d,
509 'dirs': _d,
510 }
510 }
511 return _map[ret_type]
511 return _map[ret_type]
512 except KeyError:
512 except KeyError:
513 raise JSONRPCError(
513 raise JSONRPCError(
514 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
514 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
515 except Exception:
515 except Exception:
516 log.exception("Exception occurred while trying to get repo nodes")
516 log.exception("Exception occurred while trying to get repo nodes")
517 raise JSONRPCError(
517 raise JSONRPCError(
518 'failed to get repo: `%s` nodes' % repo.repo_name
518 'failed to get repo: `%s` nodes' % repo.repo_name
519 )
519 )
520
520
521
521
522 @jsonrpc_method()
522 @jsonrpc_method()
523 def get_repo_refs(request, apiuser, repoid):
523 def get_repo_refs(request, apiuser, repoid):
524 """
524 """
525 Returns a dictionary of current references. It returns
525 Returns a dictionary of current references. It returns
526 bookmarks, branches, closed_branches, and tags for given repository
526 bookmarks, branches, closed_branches, and tags for given repository
527
527
528 It's possible to specify ret_type to show only `files` or `dirs`.
528 It's possible to specify ret_type to show only `files` or `dirs`.
529
529
530 This command can only be run using an |authtoken| with admin rights,
530 This command can only be run using an |authtoken| with admin rights,
531 or users with at least read rights to |repos|.
531 or users with at least read rights to |repos|.
532
532
533 :param apiuser: This is filled automatically from the |authtoken|.
533 :param apiuser: This is filled automatically from the |authtoken|.
534 :type apiuser: AuthUser
534 :type apiuser: AuthUser
535 :param repoid: The repository name or repository ID.
535 :param repoid: The repository name or repository ID.
536 :type repoid: str or int
536 :type repoid: str or int
537
537
538 Example output:
538 Example output:
539
539
540 .. code-block:: bash
540 .. code-block:: bash
541
541
542 id : <id_given_in_input>
542 id : <id_given_in_input>
543 "result": {
543 "result": {
544 "bookmarks": {
544 "bookmarks": {
545 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
545 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
546 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
546 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
547 },
547 },
548 "branches": {
548 "branches": {
549 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
550 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
551 },
551 },
552 "branches_closed": {},
552 "branches_closed": {},
553 "tags": {
553 "tags": {
554 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
554 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
555 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
555 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
556 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
556 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
557 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
557 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
558 }
558 }
559 }
559 }
560 error: null
560 error: null
561 """
561 """
562
562
563 repo = get_repo_or_error(repoid)
563 repo = get_repo_or_error(repoid)
564 if not has_superadmin_permission(apiuser):
564 if not has_superadmin_permission(apiuser):
565 _perms = ('repository.admin', 'repository.write', 'repository.read',)
565 _perms = ('repository.admin', 'repository.write', 'repository.read',)
566 validate_repo_permissions(apiuser, repoid, repo, _perms)
566 validate_repo_permissions(apiuser, repoid, repo, _perms)
567
567
568 try:
568 try:
569 # check if repo is not empty by any chance, skip quicker if it is.
569 # check if repo is not empty by any chance, skip quicker if it is.
570 vcs_instance = repo.scm_instance()
570 vcs_instance = repo.scm_instance()
571 refs = vcs_instance.refs()
571 refs = vcs_instance.refs()
572 return refs
572 return refs
573 except Exception:
573 except Exception:
574 log.exception("Exception occurred while trying to get repo refs")
574 log.exception("Exception occurred while trying to get repo refs")
575 raise JSONRPCError(
575 raise JSONRPCError(
576 'failed to get repo: `%s` references' % repo.repo_name
576 'failed to get repo: `%s` references' % repo.repo_name
577 )
577 )
578
578
579
579
580 @jsonrpc_method()
580 @jsonrpc_method()
581 def create_repo(
581 def create_repo(
582 request, apiuser, repo_name, repo_type,
582 request, apiuser, repo_name, repo_type,
583 owner=Optional(OAttr('apiuser')),
583 owner=Optional(OAttr('apiuser')),
584 description=Optional(''),
584 description=Optional(''),
585 private=Optional(False),
585 private=Optional(False),
586 clone_uri=Optional(None),
586 clone_uri=Optional(None),
587 landing_rev=Optional('rev:tip'),
587 landing_rev=Optional('rev:tip'),
588 enable_statistics=Optional(False),
588 enable_statistics=Optional(False),
589 enable_locking=Optional(False),
589 enable_locking=Optional(False),
590 enable_downloads=Optional(False),
590 enable_downloads=Optional(False),
591 copy_permissions=Optional(False)):
591 copy_permissions=Optional(False)):
592 """
592 """
593 Creates a repository.
593 Creates a repository.
594
594
595 * If the repository name contains "/", repository will be created inside
595 * If the repository name contains "/", repository will be created inside
596 a repository group or nested repository groups
596 a repository group or nested repository groups
597
597
598 For example "foo/bar/repo1" will create |repo| called "repo1" inside
598 For example "foo/bar/repo1" will create |repo| called "repo1" inside
599 group "foo/bar". You have to have permissions to access and write to
599 group "foo/bar". You have to have permissions to access and write to
600 the last repository group ("bar" in this example)
600 the last repository group ("bar" in this example)
601
601
602 This command can only be run using an |authtoken| with at least
602 This command can only be run using an |authtoken| with at least
603 permissions to create repositories, or write permissions to
603 permissions to create repositories, or write permissions to
604 parent repository groups.
604 parent repository groups.
605
605
606 :param apiuser: This is filled automatically from the |authtoken|.
606 :param apiuser: This is filled automatically from the |authtoken|.
607 :type apiuser: AuthUser
607 :type apiuser: AuthUser
608 :param repo_name: Set the repository name.
608 :param repo_name: Set the repository name.
609 :type repo_name: str
609 :type repo_name: str
610 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
610 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
611 :type repo_type: str
611 :type repo_type: str
612 :param owner: user_id or username
612 :param owner: user_id or username
613 :type owner: Optional(str)
613 :type owner: Optional(str)
614 :param description: Set the repository description.
614 :param description: Set the repository description.
615 :type description: Optional(str)
615 :type description: Optional(str)
616 :param private: set repository as private
616 :param private: set repository as private
617 :type private: bool
617 :type private: bool
618 :param clone_uri: set clone_uri
618 :param clone_uri: set clone_uri
619 :type clone_uri: str
619 :type clone_uri: str
620 :param landing_rev: <rev_type>:<rev>
620 :param landing_rev: <rev_type>:<rev>
621 :type landing_rev: str
621 :type landing_rev: str
622 :param enable_locking:
622 :param enable_locking:
623 :type enable_locking: bool
623 :type enable_locking: bool
624 :param enable_downloads:
624 :param enable_downloads:
625 :type enable_downloads: bool
625 :type enable_downloads: bool
626 :param enable_statistics:
626 :param enable_statistics:
627 :type enable_statistics: bool
627 :type enable_statistics: bool
628 :param copy_permissions: Copy permission from group in which the
628 :param copy_permissions: Copy permission from group in which the
629 repository is being created.
629 repository is being created.
630 :type copy_permissions: bool
630 :type copy_permissions: bool
631
631
632
632
633 Example output:
633 Example output:
634
634
635 .. code-block:: bash
635 .. code-block:: bash
636
636
637 id : <id_given_in_input>
637 id : <id_given_in_input>
638 result: {
638 result: {
639 "msg": "Created new repository `<reponame>`",
639 "msg": "Created new repository `<reponame>`",
640 "success": true,
640 "success": true,
641 "task": "<celery task id or None if done sync>"
641 "task": "<celery task id or None if done sync>"
642 }
642 }
643 error: null
643 error: null
644
644
645
645
646 Example error output:
646 Example error output:
647
647
648 .. code-block:: bash
648 .. code-block:: bash
649
649
650 id : <id_given_in_input>
650 id : <id_given_in_input>
651 result : null
651 result : null
652 error : {
652 error : {
653 'failed to create repository `<repo_name>`'
653 'failed to create repository `<repo_name>`'
654 }
654 }
655
655
656 """
656 """
657
657
658 owner = validate_set_owner_permissions(apiuser, owner)
658 owner = validate_set_owner_permissions(apiuser, owner)
659
659
660 description = Optional.extract(description)
660 description = Optional.extract(description)
661 copy_permissions = Optional.extract(copy_permissions)
661 copy_permissions = Optional.extract(copy_permissions)
662 clone_uri = Optional.extract(clone_uri)
662 clone_uri = Optional.extract(clone_uri)
663 landing_commit_ref = Optional.extract(landing_rev)
663 landing_commit_ref = Optional.extract(landing_rev)
664
664
665 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
665 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
666 if isinstance(private, Optional):
666 if isinstance(private, Optional):
667 private = defs.get('repo_private') or Optional.extract(private)
667 private = defs.get('repo_private') or Optional.extract(private)
668 if isinstance(repo_type, Optional):
668 if isinstance(repo_type, Optional):
669 repo_type = defs.get('repo_type')
669 repo_type = defs.get('repo_type')
670 if isinstance(enable_statistics, Optional):
670 if isinstance(enable_statistics, Optional):
671 enable_statistics = defs.get('repo_enable_statistics')
671 enable_statistics = defs.get('repo_enable_statistics')
672 if isinstance(enable_locking, Optional):
672 if isinstance(enable_locking, Optional):
673 enable_locking = defs.get('repo_enable_locking')
673 enable_locking = defs.get('repo_enable_locking')
674 if isinstance(enable_downloads, Optional):
674 if isinstance(enable_downloads, Optional):
675 enable_downloads = defs.get('repo_enable_downloads')
675 enable_downloads = defs.get('repo_enable_downloads')
676
676
677 schema = repo_schema.RepoSchema().bind(
677 schema = repo_schema.RepoSchema().bind(
678 repo_type_options=rhodecode.BACKENDS.keys(),
678 repo_type_options=rhodecode.BACKENDS.keys(),
679 # user caller
679 # user caller
680 user=apiuser)
680 user=apiuser)
681
681
682 try:
682 try:
683 schema_data = schema.deserialize(dict(
683 schema_data = schema.deserialize(dict(
684 repo_name=repo_name,
684 repo_name=repo_name,
685 repo_type=repo_type,
685 repo_type=repo_type,
686 repo_owner=owner.username,
686 repo_owner=owner.username,
687 repo_description=description,
687 repo_description=description,
688 repo_landing_commit_ref=landing_commit_ref,
688 repo_landing_commit_ref=landing_commit_ref,
689 repo_clone_uri=clone_uri,
689 repo_clone_uri=clone_uri,
690 repo_private=private,
690 repo_private=private,
691 repo_copy_permissions=copy_permissions,
691 repo_copy_permissions=copy_permissions,
692 repo_enable_statistics=enable_statistics,
692 repo_enable_statistics=enable_statistics,
693 repo_enable_downloads=enable_downloads,
693 repo_enable_downloads=enable_downloads,
694 repo_enable_locking=enable_locking))
694 repo_enable_locking=enable_locking))
695 except validation_schema.Invalid as err:
695 except validation_schema.Invalid as err:
696 raise JSONRPCValidationError(colander_exc=err)
696 raise JSONRPCValidationError(colander_exc=err)
697
697
698 try:
698 try:
699 data = {
699 data = {
700 'owner': owner,
700 'owner': owner,
701 'repo_name': schema_data['repo_group']['repo_name_without_group'],
701 'repo_name': schema_data['repo_group']['repo_name_without_group'],
702 'repo_name_full': schema_data['repo_name'],
702 'repo_name_full': schema_data['repo_name'],
703 'repo_group': schema_data['repo_group']['repo_group_id'],
703 'repo_group': schema_data['repo_group']['repo_group_id'],
704 'repo_type': schema_data['repo_type'],
704 'repo_type': schema_data['repo_type'],
705 'repo_description': schema_data['repo_description'],
705 'repo_description': schema_data['repo_description'],
706 'repo_private': schema_data['repo_private'],
706 'repo_private': schema_data['repo_private'],
707 'clone_uri': schema_data['repo_clone_uri'],
707 'clone_uri': schema_data['repo_clone_uri'],
708 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
708 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
709 'enable_statistics': schema_data['repo_enable_statistics'],
709 'enable_statistics': schema_data['repo_enable_statistics'],
710 'enable_locking': schema_data['repo_enable_locking'],
710 'enable_locking': schema_data['repo_enable_locking'],
711 'enable_downloads': schema_data['repo_enable_downloads'],
711 'enable_downloads': schema_data['repo_enable_downloads'],
712 'repo_copy_permissions': schema_data['repo_copy_permissions'],
712 'repo_copy_permissions': schema_data['repo_copy_permissions'],
713 }
713 }
714
714
715 task = RepoModel().create(form_data=data, cur_user=owner)
715 task = RepoModel().create(form_data=data, cur_user=owner)
716 from celery.result import BaseAsyncResult
716 from celery.result import BaseAsyncResult
717 task_id = None
717 task_id = None
718 if isinstance(task, BaseAsyncResult):
718 if isinstance(task, BaseAsyncResult):
719 task_id = task.task_id
719 task_id = task.task_id
720 # no commit, it's done in RepoModel, or async via celery
720 # no commit, it's done in RepoModel, or async via celery
721 return {
721 return {
722 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
722 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
723 'success': True, # cannot return the repo data here since fork
723 'success': True, # cannot return the repo data here since fork
724 # can be done async
724 # can be done async
725 'task': task_id
725 'task': task_id
726 }
726 }
727 except Exception:
727 except Exception:
728 log.exception(
728 log.exception(
729 u"Exception while trying to create the repository %s",
729 u"Exception while trying to create the repository %s",
730 schema_data['repo_name'])
730 schema_data['repo_name'])
731 raise JSONRPCError(
731 raise JSONRPCError(
732 'failed to create repository `%s`' % (schema_data['repo_name'],))
732 'failed to create repository `%s`' % (schema_data['repo_name'],))
733
733
734
734
735 @jsonrpc_method()
735 @jsonrpc_method()
736 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
736 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
737 description=Optional('')):
737 description=Optional('')):
738 """
738 """
739 Adds an extra field to a repository.
739 Adds an extra field to a repository.
740
740
741 This command can only be run using an |authtoken| with at least
741 This command can only be run using an |authtoken| with at least
742 write permissions to the |repo|.
742 write permissions to the |repo|.
743
743
744 :param apiuser: This is filled automatically from the |authtoken|.
744 :param apiuser: This is filled automatically from the |authtoken|.
745 :type apiuser: AuthUser
745 :type apiuser: AuthUser
746 :param repoid: Set the repository name or repository id.
746 :param repoid: Set the repository name or repository id.
747 :type repoid: str or int
747 :type repoid: str or int
748 :param key: Create a unique field key for this repository.
748 :param key: Create a unique field key for this repository.
749 :type key: str
749 :type key: str
750 :param label:
750 :param label:
751 :type label: Optional(str)
751 :type label: Optional(str)
752 :param description:
752 :param description:
753 :type description: Optional(str)
753 :type description: Optional(str)
754 """
754 """
755 repo = get_repo_or_error(repoid)
755 repo = get_repo_or_error(repoid)
756 if not has_superadmin_permission(apiuser):
756 if not has_superadmin_permission(apiuser):
757 _perms = ('repository.admin',)
757 _perms = ('repository.admin',)
758 validate_repo_permissions(apiuser, repoid, repo, _perms)
758 validate_repo_permissions(apiuser, repoid, repo, _perms)
759
759
760 label = Optional.extract(label) or key
760 label = Optional.extract(label) or key
761 description = Optional.extract(description)
761 description = Optional.extract(description)
762
762
763 field = RepositoryField.get_by_key_name(key, repo)
763 field = RepositoryField.get_by_key_name(key, repo)
764 if field:
764 if field:
765 raise JSONRPCError('Field with key '
765 raise JSONRPCError('Field with key '
766 '`%s` exists for repo `%s`' % (key, repoid))
766 '`%s` exists for repo `%s`' % (key, repoid))
767
767
768 try:
768 try:
769 RepoModel().add_repo_field(repo, key, field_label=label,
769 RepoModel().add_repo_field(repo, key, field_label=label,
770 field_desc=description)
770 field_desc=description)
771 Session().commit()
771 Session().commit()
772 return {
772 return {
773 'msg': "Added new repository field `%s`" % (key,),
773 'msg': "Added new repository field `%s`" % (key,),
774 'success': True,
774 'success': True,
775 }
775 }
776 except Exception:
776 except Exception:
777 log.exception("Exception occurred while trying to add field to repo")
777 log.exception("Exception occurred while trying to add field to repo")
778 raise JSONRPCError(
778 raise JSONRPCError(
779 'failed to create new field for repository `%s`' % (repoid,))
779 'failed to create new field for repository `%s`' % (repoid,))
780
780
781
781
782 @jsonrpc_method()
782 @jsonrpc_method()
783 def remove_field_from_repo(request, apiuser, repoid, key):
783 def remove_field_from_repo(request, apiuser, repoid, key):
784 """
784 """
785 Removes an extra field from a repository.
785 Removes an extra field from a repository.
786
786
787 This command can only be run using an |authtoken| with at least
787 This command can only be run using an |authtoken| with at least
788 write permissions to the |repo|.
788 write permissions to the |repo|.
789
789
790 :param apiuser: This is filled automatically from the |authtoken|.
790 :param apiuser: This is filled automatically from the |authtoken|.
791 :type apiuser: AuthUser
791 :type apiuser: AuthUser
792 :param repoid: Set the repository name or repository ID.
792 :param repoid: Set the repository name or repository ID.
793 :type repoid: str or int
793 :type repoid: str or int
794 :param key: Set the unique field key for this repository.
794 :param key: Set the unique field key for this repository.
795 :type key: str
795 :type key: str
796 """
796 """
797
797
798 repo = get_repo_or_error(repoid)
798 repo = get_repo_or_error(repoid)
799 if not has_superadmin_permission(apiuser):
799 if not has_superadmin_permission(apiuser):
800 _perms = ('repository.admin',)
800 _perms = ('repository.admin',)
801 validate_repo_permissions(apiuser, repoid, repo, _perms)
801 validate_repo_permissions(apiuser, repoid, repo, _perms)
802
802
803 field = RepositoryField.get_by_key_name(key, repo)
803 field = RepositoryField.get_by_key_name(key, repo)
804 if not field:
804 if not field:
805 raise JSONRPCError('Field with key `%s` does not '
805 raise JSONRPCError('Field with key `%s` does not '
806 'exists for repo `%s`' % (key, repoid))
806 'exists for repo `%s`' % (key, repoid))
807
807
808 try:
808 try:
809 RepoModel().delete_repo_field(repo, field_key=key)
809 RepoModel().delete_repo_field(repo, field_key=key)
810 Session().commit()
810 Session().commit()
811 return {
811 return {
812 'msg': "Deleted repository field `%s`" % (key,),
812 'msg': "Deleted repository field `%s`" % (key,),
813 'success': True,
813 'success': True,
814 }
814 }
815 except Exception:
815 except Exception:
816 log.exception(
816 log.exception(
817 "Exception occurred while trying to delete field from repo")
817 "Exception occurred while trying to delete field from repo")
818 raise JSONRPCError(
818 raise JSONRPCError(
819 'failed to delete field for repository `%s`' % (repoid,))
819 'failed to delete field for repository `%s`' % (repoid,))
820
820
821
821
822 @jsonrpc_method()
822 @jsonrpc_method()
823 def update_repo(
823 def update_repo(
824 request, apiuser, repoid, repo_name=Optional(None),
824 request, apiuser, repoid, repo_name=Optional(None),
825 owner=Optional(OAttr('apiuser')), description=Optional(''),
825 owner=Optional(OAttr('apiuser')), description=Optional(''),
826 private=Optional(False), clone_uri=Optional(None),
826 private=Optional(False), clone_uri=Optional(None),
827 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
827 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
828 enable_statistics=Optional(False),
828 enable_statistics=Optional(False),
829 enable_locking=Optional(False),
829 enable_locking=Optional(False),
830 enable_downloads=Optional(False), fields=Optional('')):
830 enable_downloads=Optional(False), fields=Optional('')):
831 """
831 """
832 Updates a repository with the given information.
832 Updates a repository with the given information.
833
833
834 This command can only be run using an |authtoken| with at least
834 This command can only be run using an |authtoken| with at least
835 admin permissions to the |repo|.
835 admin permissions to the |repo|.
836
836
837 * If the repository name contains "/", repository will be updated
837 * If the repository name contains "/", repository will be updated
838 accordingly with a repository group or nested repository groups
838 accordingly with a repository group or nested repository groups
839
839
840 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
840 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
841 called "repo-test" and place it inside group "foo/bar".
841 called "repo-test" and place it inside group "foo/bar".
842 You have to have permissions to access and write to the last repository
842 You have to have permissions to access and write to the last repository
843 group ("bar" in this example)
843 group ("bar" in this example)
844
844
845 :param apiuser: This is filled automatically from the |authtoken|.
845 :param apiuser: This is filled automatically from the |authtoken|.
846 :type apiuser: AuthUser
846 :type apiuser: AuthUser
847 :param repoid: repository name or repository ID.
847 :param repoid: repository name or repository ID.
848 :type repoid: str or int
848 :type repoid: str or int
849 :param repo_name: Update the |repo| name, including the
849 :param repo_name: Update the |repo| name, including the
850 repository group it's in.
850 repository group it's in.
851 :type repo_name: str
851 :type repo_name: str
852 :param owner: Set the |repo| owner.
852 :param owner: Set the |repo| owner.
853 :type owner: str
853 :type owner: str
854 :param fork_of: Set the |repo| as fork of another |repo|.
854 :param fork_of: Set the |repo| as fork of another |repo|.
855 :type fork_of: str
855 :type fork_of: str
856 :param description: Update the |repo| description.
856 :param description: Update the |repo| description.
857 :type description: str
857 :type description: str
858 :param private: Set the |repo| as private. (True | False)
858 :param private: Set the |repo| as private. (True | False)
859 :type private: bool
859 :type private: bool
860 :param clone_uri: Update the |repo| clone URI.
860 :param clone_uri: Update the |repo| clone URI.
861 :type clone_uri: str
861 :type clone_uri: str
862 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
862 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
863 :type landing_rev: str
863 :type landing_rev: str
864 :param enable_statistics: Enable statistics on the |repo|, (True | False).
864 :param enable_statistics: Enable statistics on the |repo|, (True | False).
865 :type enable_statistics: bool
865 :type enable_statistics: bool
866 :param enable_locking: Enable |repo| locking.
866 :param enable_locking: Enable |repo| locking.
867 :type enable_locking: bool
867 :type enable_locking: bool
868 :param enable_downloads: Enable downloads from the |repo|, (True | False).
868 :param enable_downloads: Enable downloads from the |repo|, (True | False).
869 :type enable_downloads: bool
869 :type enable_downloads: bool
870 :param fields: Add extra fields to the |repo|. Use the following
870 :param fields: Add extra fields to the |repo|. Use the following
871 example format: ``field_key=field_val,field_key2=fieldval2``.
871 example format: ``field_key=field_val,field_key2=fieldval2``.
872 Escape ', ' with \,
872 Escape ', ' with \,
873 :type fields: str
873 :type fields: str
874 """
874 """
875
875
876 repo = get_repo_or_error(repoid)
876 repo = get_repo_or_error(repoid)
877
877
878 include_secrets = False
878 include_secrets = False
879 if not has_superadmin_permission(apiuser):
879 if not has_superadmin_permission(apiuser):
880 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
880 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
881 else:
881 else:
882 include_secrets = True
882 include_secrets = True
883
883
884 updates = dict(
884 updates = dict(
885 repo_name=repo_name
885 repo_name=repo_name
886 if not isinstance(repo_name, Optional) else repo.repo_name,
886 if not isinstance(repo_name, Optional) else repo.repo_name,
887
887
888 fork_id=fork_of
888 fork_id=fork_of
889 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
889 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
890
890
891 user=owner
891 user=owner
892 if not isinstance(owner, Optional) else repo.user.username,
892 if not isinstance(owner, Optional) else repo.user.username,
893
893
894 repo_description=description
894 repo_description=description
895 if not isinstance(description, Optional) else repo.description,
895 if not isinstance(description, Optional) else repo.description,
896
896
897 repo_private=private
897 repo_private=private
898 if not isinstance(private, Optional) else repo.private,
898 if not isinstance(private, Optional) else repo.private,
899
899
900 clone_uri=clone_uri
900 clone_uri=clone_uri
901 if not isinstance(clone_uri, Optional) else repo.clone_uri,
901 if not isinstance(clone_uri, Optional) else repo.clone_uri,
902
902
903 repo_landing_rev=landing_rev
903 repo_landing_rev=landing_rev
904 if not isinstance(landing_rev, Optional) else repo._landing_revision,
904 if not isinstance(landing_rev, Optional) else repo._landing_revision,
905
905
906 repo_enable_statistics=enable_statistics
906 repo_enable_statistics=enable_statistics
907 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
907 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
908
908
909 repo_enable_locking=enable_locking
909 repo_enable_locking=enable_locking
910 if not isinstance(enable_locking, Optional) else repo.enable_locking,
910 if not isinstance(enable_locking, Optional) else repo.enable_locking,
911
911
912 repo_enable_downloads=enable_downloads
912 repo_enable_downloads=enable_downloads
913 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
913 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
914
914
915 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
915 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
916
916
917 schema = repo_schema.RepoSchema().bind(
917 schema = repo_schema.RepoSchema().bind(
918 repo_type_options=rhodecode.BACKENDS.keys(),
918 repo_type_options=rhodecode.BACKENDS.keys(),
919 repo_ref_options=ref_choices,
919 repo_ref_options=ref_choices,
920 # user caller
920 # user caller
921 user=apiuser,
921 user=apiuser,
922 old_values=repo.get_api_data())
922 old_values=repo.get_api_data())
923 try:
923 try:
924 schema_data = schema.deserialize(dict(
924 schema_data = schema.deserialize(dict(
925 # we save old value, users cannot change type
925 # we save old value, users cannot change type
926 repo_type=repo.repo_type,
926 repo_type=repo.repo_type,
927
927
928 repo_name=updates['repo_name'],
928 repo_name=updates['repo_name'],
929 repo_owner=updates['user'],
929 repo_owner=updates['user'],
930 repo_description=updates['repo_description'],
930 repo_description=updates['repo_description'],
931 repo_clone_uri=updates['clone_uri'],
931 repo_clone_uri=updates['clone_uri'],
932 repo_fork_of=updates['fork_id'],
932 repo_fork_of=updates['fork_id'],
933 repo_private=updates['repo_private'],
933 repo_private=updates['repo_private'],
934 repo_landing_commit_ref=updates['repo_landing_rev'],
934 repo_landing_commit_ref=updates['repo_landing_rev'],
935 repo_enable_statistics=updates['repo_enable_statistics'],
935 repo_enable_statistics=updates['repo_enable_statistics'],
936 repo_enable_downloads=updates['repo_enable_downloads'],
936 repo_enable_downloads=updates['repo_enable_downloads'],
937 repo_enable_locking=updates['repo_enable_locking']))
937 repo_enable_locking=updates['repo_enable_locking']))
938 except validation_schema.Invalid as err:
938 except validation_schema.Invalid as err:
939 raise JSONRPCValidationError(colander_exc=err)
939 raise JSONRPCValidationError(colander_exc=err)
940
940
941 # save validated data back into the updates dict
941 # save validated data back into the updates dict
942 validated_updates = dict(
942 validated_updates = dict(
943 repo_name=schema_data['repo_group']['repo_name_without_group'],
943 repo_name=schema_data['repo_group']['repo_name_without_group'],
944 repo_group=schema_data['repo_group']['repo_group_id'],
944 repo_group=schema_data['repo_group']['repo_group_id'],
945
945
946 user=schema_data['repo_owner'],
946 user=schema_data['repo_owner'],
947 repo_description=schema_data['repo_description'],
947 repo_description=schema_data['repo_description'],
948 repo_private=schema_data['repo_private'],
948 repo_private=schema_data['repo_private'],
949 clone_uri=schema_data['repo_clone_uri'],
949 clone_uri=schema_data['repo_clone_uri'],
950 repo_landing_rev=schema_data['repo_landing_commit_ref'],
950 repo_landing_rev=schema_data['repo_landing_commit_ref'],
951 repo_enable_statistics=schema_data['repo_enable_statistics'],
951 repo_enable_statistics=schema_data['repo_enable_statistics'],
952 repo_enable_locking=schema_data['repo_enable_locking'],
952 repo_enable_locking=schema_data['repo_enable_locking'],
953 repo_enable_downloads=schema_data['repo_enable_downloads'],
953 repo_enable_downloads=schema_data['repo_enable_downloads'],
954 )
954 )
955
955
956 if schema_data['repo_fork_of']:
956 if schema_data['repo_fork_of']:
957 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
957 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
958 validated_updates['fork_id'] = fork_repo.repo_id
958 validated_updates['fork_id'] = fork_repo.repo_id
959
959
960 # extra fields
960 # extra fields
961 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
961 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
962 if fields:
962 if fields:
963 validated_updates.update(fields)
963 validated_updates.update(fields)
964
964
965 try:
965 try:
966 RepoModel().update(repo, **validated_updates)
966 RepoModel().update(repo, **validated_updates)
967 Session().commit()
967 Session().commit()
968 return {
968 return {
969 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
969 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
970 'repository': repo.get_api_data(include_secrets=include_secrets)
970 'repository': repo.get_api_data(include_secrets=include_secrets)
971 }
971 }
972 except Exception:
972 except Exception:
973 log.exception(
973 log.exception(
974 u"Exception while trying to update the repository %s",
974 u"Exception while trying to update the repository %s",
975 repoid)
975 repoid)
976 raise JSONRPCError('failed to update repo `%s`' % repoid)
976 raise JSONRPCError('failed to update repo `%s`' % repoid)
977
977
978
978
979 @jsonrpc_method()
979 @jsonrpc_method()
980 def fork_repo(request, apiuser, repoid, fork_name,
980 def fork_repo(request, apiuser, repoid, fork_name,
981 owner=Optional(OAttr('apiuser')),
981 owner=Optional(OAttr('apiuser')),
982 description=Optional(''),
982 description=Optional(''),
983 private=Optional(False),
983 private=Optional(False),
984 clone_uri=Optional(None),
984 clone_uri=Optional(None),
985 landing_rev=Optional('rev:tip'),
985 landing_rev=Optional('rev:tip'),
986 copy_permissions=Optional(False)):
986 copy_permissions=Optional(False)):
987 """
987 """
988 Creates a fork of the specified |repo|.
988 Creates a fork of the specified |repo|.
989
989
990 * If the fork_name contains "/", fork will be created inside
990 * If the fork_name contains "/", fork will be created inside
991 a repository group or nested repository groups
991 a repository group or nested repository groups
992
992
993 For example "foo/bar/fork-repo" will create fork called "fork-repo"
993 For example "foo/bar/fork-repo" will create fork called "fork-repo"
994 inside group "foo/bar". You have to have permissions to access and
994 inside group "foo/bar". You have to have permissions to access and
995 write to the last repository group ("bar" in this example)
995 write to the last repository group ("bar" in this example)
996
996
997 This command can only be run using an |authtoken| with minimum
997 This command can only be run using an |authtoken| with minimum
998 read permissions of the forked repo, create fork permissions for an user.
998 read permissions of the forked repo, create fork permissions for an user.
999
999
1000 :param apiuser: This is filled automatically from the |authtoken|.
1000 :param apiuser: This is filled automatically from the |authtoken|.
1001 :type apiuser: AuthUser
1001 :type apiuser: AuthUser
1002 :param repoid: Set repository name or repository ID.
1002 :param repoid: Set repository name or repository ID.
1003 :type repoid: str or int
1003 :type repoid: str or int
1004 :param fork_name: Set the fork name, including it's repository group membership.
1004 :param fork_name: Set the fork name, including it's repository group membership.
1005 :type fork_name: str
1005 :type fork_name: str
1006 :param owner: Set the fork owner.
1006 :param owner: Set the fork owner.
1007 :type owner: str
1007 :type owner: str
1008 :param description: Set the fork description.
1008 :param description: Set the fork description.
1009 :type description: str
1009 :type description: str
1010 :param copy_permissions: Copy permissions from parent |repo|. The
1010 :param copy_permissions: Copy permissions from parent |repo|. The
1011 default is False.
1011 default is False.
1012 :type copy_permissions: bool
1012 :type copy_permissions: bool
1013 :param private: Make the fork private. The default is False.
1013 :param private: Make the fork private. The default is False.
1014 :type private: bool
1014 :type private: bool
1015 :param landing_rev: Set the landing revision. The default is tip.
1015 :param landing_rev: Set the landing revision. The default is tip.
1016
1016
1017 Example output:
1017 Example output:
1018
1018
1019 .. code-block:: bash
1019 .. code-block:: bash
1020
1020
1021 id : <id_for_response>
1021 id : <id_for_response>
1022 api_key : "<api_key>"
1022 api_key : "<api_key>"
1023 args: {
1023 args: {
1024 "repoid" : "<reponame or repo_id>",
1024 "repoid" : "<reponame or repo_id>",
1025 "fork_name": "<forkname>",
1025 "fork_name": "<forkname>",
1026 "owner": "<username or user_id = Optional(=apiuser)>",
1026 "owner": "<username or user_id = Optional(=apiuser)>",
1027 "description": "<description>",
1027 "description": "<description>",
1028 "copy_permissions": "<bool>",
1028 "copy_permissions": "<bool>",
1029 "private": "<bool>",
1029 "private": "<bool>",
1030 "landing_rev": "<landing_rev>"
1030 "landing_rev": "<landing_rev>"
1031 }
1031 }
1032
1032
1033 Example error output:
1033 Example error output:
1034
1034
1035 .. code-block:: bash
1035 .. code-block:: bash
1036
1036
1037 id : <id_given_in_input>
1037 id : <id_given_in_input>
1038 result: {
1038 result: {
1039 "msg": "Created fork of `<reponame>` as `<forkname>`",
1039 "msg": "Created fork of `<reponame>` as `<forkname>`",
1040 "success": true,
1040 "success": true,
1041 "task": "<celery task id or None if done sync>"
1041 "task": "<celery task id or None if done sync>"
1042 }
1042 }
1043 error: null
1043 error: null
1044
1044
1045 """
1045 """
1046
1046
1047 repo = get_repo_or_error(repoid)
1047 repo = get_repo_or_error(repoid)
1048 repo_name = repo.repo_name
1048 repo_name = repo.repo_name
1049
1049
1050 if not has_superadmin_permission(apiuser):
1050 if not has_superadmin_permission(apiuser):
1051 # check if we have at least read permission for
1051 # check if we have at least read permission for
1052 # this repo that we fork !
1052 # this repo that we fork !
1053 _perms = (
1053 _perms = (
1054 'repository.admin', 'repository.write', 'repository.read')
1054 'repository.admin', 'repository.write', 'repository.read')
1055 validate_repo_permissions(apiuser, repoid, repo, _perms)
1055 validate_repo_permissions(apiuser, repoid, repo, _perms)
1056
1056
1057 # check if the regular user has at least fork permissions as well
1057 # check if the regular user has at least fork permissions as well
1058 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1058 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1059 raise JSONRPCForbidden()
1059 raise JSONRPCForbidden()
1060
1060
1061 # check if user can set owner parameter
1061 # check if user can set owner parameter
1062 owner = validate_set_owner_permissions(apiuser, owner)
1062 owner = validate_set_owner_permissions(apiuser, owner)
1063
1063
1064 description = Optional.extract(description)
1064 description = Optional.extract(description)
1065 copy_permissions = Optional.extract(copy_permissions)
1065 copy_permissions = Optional.extract(copy_permissions)
1066 clone_uri = Optional.extract(clone_uri)
1066 clone_uri = Optional.extract(clone_uri)
1067 landing_commit_ref = Optional.extract(landing_rev)
1067 landing_commit_ref = Optional.extract(landing_rev)
1068 private = Optional.extract(private)
1068 private = Optional.extract(private)
1069
1069
1070 schema = repo_schema.RepoSchema().bind(
1070 schema = repo_schema.RepoSchema().bind(
1071 repo_type_options=rhodecode.BACKENDS.keys(),
1071 repo_type_options=rhodecode.BACKENDS.keys(),
1072 # user caller
1072 # user caller
1073 user=apiuser)
1073 user=apiuser)
1074
1074
1075 try:
1075 try:
1076 schema_data = schema.deserialize(dict(
1076 schema_data = schema.deserialize(dict(
1077 repo_name=fork_name,
1077 repo_name=fork_name,
1078 repo_type=repo.repo_type,
1078 repo_type=repo.repo_type,
1079 repo_owner=owner.username,
1079 repo_owner=owner.username,
1080 repo_description=description,
1080 repo_description=description,
1081 repo_landing_commit_ref=landing_commit_ref,
1081 repo_landing_commit_ref=landing_commit_ref,
1082 repo_clone_uri=clone_uri,
1082 repo_clone_uri=clone_uri,
1083 repo_private=private,
1083 repo_private=private,
1084 repo_copy_permissions=copy_permissions))
1084 repo_copy_permissions=copy_permissions))
1085 except validation_schema.Invalid as err:
1085 except validation_schema.Invalid as err:
1086 raise JSONRPCValidationError(colander_exc=err)
1086 raise JSONRPCValidationError(colander_exc=err)
1087
1087
1088 try:
1088 try:
1089 data = {
1089 data = {
1090 'fork_parent_id': repo.repo_id,
1090 'fork_parent_id': repo.repo_id,
1091
1091
1092 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1092 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1093 'repo_name_full': schema_data['repo_name'],
1093 'repo_name_full': schema_data['repo_name'],
1094 'repo_group': schema_data['repo_group']['repo_group_id'],
1094 'repo_group': schema_data['repo_group']['repo_group_id'],
1095 'repo_type': schema_data['repo_type'],
1095 'repo_type': schema_data['repo_type'],
1096 'description': schema_data['repo_description'],
1096 'description': schema_data['repo_description'],
1097 'private': schema_data['repo_private'],
1097 'private': schema_data['repo_private'],
1098 'copy_permissions': schema_data['repo_copy_permissions'],
1098 'copy_permissions': schema_data['repo_copy_permissions'],
1099 'landing_rev': schema_data['repo_landing_commit_ref'],
1099 'landing_rev': schema_data['repo_landing_commit_ref'],
1100 }
1100 }
1101
1101
1102 task = RepoModel().create_fork(data, cur_user=owner)
1102 task = RepoModel().create_fork(data, cur_user=owner)
1103 # no commit, it's done in RepoModel, or async via celery
1103 # no commit, it's done in RepoModel, or async via celery
1104 from celery.result import BaseAsyncResult
1104 from celery.result import BaseAsyncResult
1105 task_id = None
1105 task_id = None
1106 if isinstance(task, BaseAsyncResult):
1106 if isinstance(task, BaseAsyncResult):
1107 task_id = task.task_id
1107 task_id = task.task_id
1108 return {
1108 return {
1109 'msg': 'Created fork of `%s` as `%s`' % (
1109 'msg': 'Created fork of `%s` as `%s`' % (
1110 repo.repo_name, schema_data['repo_name']),
1110 repo.repo_name, schema_data['repo_name']),
1111 'success': True, # cannot return the repo data here since fork
1111 'success': True, # cannot return the repo data here since fork
1112 # can be done async
1112 # can be done async
1113 'task': task_id
1113 'task': task_id
1114 }
1114 }
1115 except Exception:
1115 except Exception:
1116 log.exception(
1116 log.exception(
1117 u"Exception while trying to create fork %s",
1117 u"Exception while trying to create fork %s",
1118 schema_data['repo_name'])
1118 schema_data['repo_name'])
1119 raise JSONRPCError(
1119 raise JSONRPCError(
1120 'failed to fork repository `%s` as `%s`' % (
1120 'failed to fork repository `%s` as `%s`' % (
1121 repo_name, schema_data['repo_name']))
1121 repo_name, schema_data['repo_name']))
1122
1122
1123
1123
1124 @jsonrpc_method()
1124 @jsonrpc_method()
1125 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1125 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1126 """
1126 """
1127 Deletes a repository.
1127 Deletes a repository.
1128
1128
1129 * When the `forks` parameter is set it's possible to detach or delete
1129 * When the `forks` parameter is set it's possible to detach or delete
1130 forks of deleted repository.
1130 forks of deleted repository.
1131
1131
1132 This command can only be run using an |authtoken| with admin
1132 This command can only be run using an |authtoken| with admin
1133 permissions on the |repo|.
1133 permissions on the |repo|.
1134
1134
1135 :param apiuser: This is filled automatically from the |authtoken|.
1135 :param apiuser: This is filled automatically from the |authtoken|.
1136 :type apiuser: AuthUser
1136 :type apiuser: AuthUser
1137 :param repoid: Set the repository name or repository ID.
1137 :param repoid: Set the repository name or repository ID.
1138 :type repoid: str or int
1138 :type repoid: str or int
1139 :param forks: Set to `detach` or `delete` forks from the |repo|.
1139 :param forks: Set to `detach` or `delete` forks from the |repo|.
1140 :type forks: Optional(str)
1140 :type forks: Optional(str)
1141
1141
1142 Example error output:
1142 Example error output:
1143
1143
1144 .. code-block:: bash
1144 .. code-block:: bash
1145
1145
1146 id : <id_given_in_input>
1146 id : <id_given_in_input>
1147 result: {
1147 result: {
1148 "msg": "Deleted repository `<reponame>`",
1148 "msg": "Deleted repository `<reponame>`",
1149 "success": true
1149 "success": true
1150 }
1150 }
1151 error: null
1151 error: null
1152 """
1152 """
1153
1153
1154 repo = get_repo_or_error(repoid)
1154 repo = get_repo_or_error(repoid)
1155 if not has_superadmin_permission(apiuser):
1155 if not has_superadmin_permission(apiuser):
1156 _perms = ('repository.admin',)
1156 _perms = ('repository.admin',)
1157 validate_repo_permissions(apiuser, repoid, repo, _perms)
1157 validate_repo_permissions(apiuser, repoid, repo, _perms)
1158
1158
1159 try:
1159 try:
1160 handle_forks = Optional.extract(forks)
1160 handle_forks = Optional.extract(forks)
1161 _forks_msg = ''
1161 _forks_msg = ''
1162 _forks = [f for f in repo.forks]
1162 _forks = [f for f in repo.forks]
1163 if handle_forks == 'detach':
1163 if handle_forks == 'detach':
1164 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1164 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1165 elif handle_forks == 'delete':
1165 elif handle_forks == 'delete':
1166 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1166 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1167 elif _forks:
1167 elif _forks:
1168 raise JSONRPCError(
1168 raise JSONRPCError(
1169 'Cannot delete `%s` it still contains attached forks' %
1169 'Cannot delete `%s` it still contains attached forks' %
1170 (repo.repo_name,)
1170 (repo.repo_name,)
1171 )
1171 )
1172
1172
1173 RepoModel().delete(repo, forks=forks)
1173 RepoModel().delete(repo, forks=forks)
1174 Session().commit()
1174 Session().commit()
1175 return {
1175 return {
1176 'msg': 'Deleted repository `%s`%s' % (
1176 'msg': 'Deleted repository `%s`%s' % (
1177 repo.repo_name, _forks_msg),
1177 repo.repo_name, _forks_msg),
1178 'success': True
1178 'success': True
1179 }
1179 }
1180 except Exception:
1180 except Exception:
1181 log.exception("Exception occurred while trying to delete repo")
1181 log.exception("Exception occurred while trying to delete repo")
1182 raise JSONRPCError(
1182 raise JSONRPCError(
1183 'failed to delete repository `%s`' % (repo.repo_name,)
1183 'failed to delete repository `%s`' % (repo.repo_name,)
1184 )
1184 )
1185
1185
1186
1186
1187 #TODO: marcink, change name ?
1187 #TODO: marcink, change name ?
1188 @jsonrpc_method()
1188 @jsonrpc_method()
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1190 """
1190 """
1191 Invalidates the cache for the specified repository.
1191 Invalidates the cache for the specified repository.
1192
1192
1193 This command can only be run using an |authtoken| with admin rights to
1193 This command can only be run using an |authtoken| with admin rights to
1194 the specified repository.
1194 the specified repository.
1195
1195
1196 This command takes the following options:
1196 This command takes the following options:
1197
1197
1198 :param apiuser: This is filled automatically from |authtoken|.
1198 :param apiuser: This is filled automatically from |authtoken|.
1199 :type apiuser: AuthUser
1199 :type apiuser: AuthUser
1200 :param repoid: Sets the repository name or repository ID.
1200 :param repoid: Sets the repository name or repository ID.
1201 :type repoid: str or int
1201 :type repoid: str or int
1202 :param delete_keys: This deletes the invalidated keys instead of
1202 :param delete_keys: This deletes the invalidated keys instead of
1203 just flagging them.
1203 just flagging them.
1204 :type delete_keys: Optional(``True`` | ``False``)
1204 :type delete_keys: Optional(``True`` | ``False``)
1205
1205
1206 Example output:
1206 Example output:
1207
1207
1208 .. code-block:: bash
1208 .. code-block:: bash
1209
1209
1210 id : <id_given_in_input>
1210 id : <id_given_in_input>
1211 result : {
1211 result : {
1212 'msg': Cache for repository `<repository name>` was invalidated,
1212 'msg': Cache for repository `<repository name>` was invalidated,
1213 'repository': <repository name>
1213 'repository': <repository name>
1214 }
1214 }
1215 error : null
1215 error : null
1216
1216
1217 Example error output:
1217 Example error output:
1218
1218
1219 .. code-block:: bash
1219 .. code-block:: bash
1220
1220
1221 id : <id_given_in_input>
1221 id : <id_given_in_input>
1222 result : null
1222 result : null
1223 error : {
1223 error : {
1224 'Error occurred during cache invalidation action'
1224 'Error occurred during cache invalidation action'
1225 }
1225 }
1226
1226
1227 """
1227 """
1228
1228
1229 repo = get_repo_or_error(repoid)
1229 repo = get_repo_or_error(repoid)
1230 if not has_superadmin_permission(apiuser):
1230 if not has_superadmin_permission(apiuser):
1231 _perms = ('repository.admin', 'repository.write',)
1231 _perms = ('repository.admin', 'repository.write',)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1233
1233
1234 delete = Optional.extract(delete_keys)
1234 delete = Optional.extract(delete_keys)
1235 try:
1235 try:
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1237 return {
1237 return {
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1239 'repository': repo.repo_name
1239 'repository': repo.repo_name
1240 }
1240 }
1241 except Exception:
1241 except Exception:
1242 log.exception(
1242 log.exception(
1243 "Exception occurred while trying to invalidate repo cache")
1243 "Exception occurred while trying to invalidate repo cache")
1244 raise JSONRPCError(
1244 raise JSONRPCError(
1245 'Error occurred during cache invalidation action'
1245 'Error occurred during cache invalidation action'
1246 )
1246 )
1247
1247
1248
1248
1249 #TODO: marcink, change name ?
1249 #TODO: marcink, change name ?
1250 @jsonrpc_method()
1250 @jsonrpc_method()
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1252 userid=Optional(OAttr('apiuser'))):
1252 userid=Optional(OAttr('apiuser'))):
1253 """
1253 """
1254 Sets the lock state of the specified |repo| by the given user.
1254 Sets the lock state of the specified |repo| by the given user.
1255 From more information, see :ref:`repo-locking`.
1255 From more information, see :ref:`repo-locking`.
1256
1256
1257 * If the ``userid`` option is not set, the repository is locked to the
1257 * If the ``userid`` option is not set, the repository is locked to the
1258 user who called the method.
1258 user who called the method.
1259 * If the ``locked`` parameter is not set, the current lock state of the
1259 * If the ``locked`` parameter is not set, the current lock state of the
1260 repository is displayed.
1260 repository is displayed.
1261
1261
1262 This command can only be run using an |authtoken| with admin rights to
1262 This command can only be run using an |authtoken| with admin rights to
1263 the specified repository.
1263 the specified repository.
1264
1264
1265 This command takes the following options:
1265 This command takes the following options:
1266
1266
1267 :param apiuser: This is filled automatically from the |authtoken|.
1267 :param apiuser: This is filled automatically from the |authtoken|.
1268 :type apiuser: AuthUser
1268 :type apiuser: AuthUser
1269 :param repoid: Sets the repository name or repository ID.
1269 :param repoid: Sets the repository name or repository ID.
1270 :type repoid: str or int
1270 :type repoid: str or int
1271 :param locked: Sets the lock state.
1271 :param locked: Sets the lock state.
1272 :type locked: Optional(``True`` | ``False``)
1272 :type locked: Optional(``True`` | ``False``)
1273 :param userid: Set the repository lock to this user.
1273 :param userid: Set the repository lock to this user.
1274 :type userid: Optional(str or int)
1274 :type userid: Optional(str or int)
1275
1275
1276 Example error output:
1276 Example error output:
1277
1277
1278 .. code-block:: bash
1278 .. code-block:: bash
1279
1279
1280 id : <id_given_in_input>
1280 id : <id_given_in_input>
1281 result : {
1281 result : {
1282 'repo': '<reponame>',
1282 'repo': '<reponame>',
1283 'locked': <bool: lock state>,
1283 'locked': <bool: lock state>,
1284 'locked_since': <int: lock timestamp>,
1284 'locked_since': <int: lock timestamp>,
1285 'locked_by': <username of person who made the lock>,
1285 'locked_by': <username of person who made the lock>,
1286 'lock_reason': <str: reason for locking>,
1286 'lock_reason': <str: reason for locking>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1289 or
1289 or
1290 'msg': 'Repo `<repository name>` not locked.'
1290 'msg': 'Repo `<repository name>` not locked.'
1291 or
1291 or
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1293 }
1293 }
1294 error : null
1294 error : null
1295
1295
1296 Example error output:
1296 Example error output:
1297
1297
1298 .. code-block:: bash
1298 .. code-block:: bash
1299
1299
1300 id : <id_given_in_input>
1300 id : <id_given_in_input>
1301 result : null
1301 result : null
1302 error : {
1302 error : {
1303 'Error occurred locking repository `<reponame>`'
1303 'Error occurred locking repository `<reponame>`'
1304 }
1304 }
1305 """
1305 """
1306
1306
1307 repo = get_repo_or_error(repoid)
1307 repo = get_repo_or_error(repoid)
1308 if not has_superadmin_permission(apiuser):
1308 if not has_superadmin_permission(apiuser):
1309 # check if we have at least write permission for this repo !
1309 # check if we have at least write permission for this repo !
1310 _perms = ('repository.admin', 'repository.write',)
1310 _perms = ('repository.admin', 'repository.write',)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1312
1312
1313 # make sure normal user does not pass someone else userid,
1313 # make sure normal user does not pass someone else userid,
1314 # he is not allowed to do that
1314 # he is not allowed to do that
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1316 raise JSONRPCError('userid is not the same as your user')
1316 raise JSONRPCError('userid is not the same as your user')
1317
1317
1318 if isinstance(userid, Optional):
1318 if isinstance(userid, Optional):
1319 userid = apiuser.user_id
1319 userid = apiuser.user_id
1320
1320
1321 user = get_user_or_error(userid)
1321 user = get_user_or_error(userid)
1322
1322
1323 if isinstance(locked, Optional):
1323 if isinstance(locked, Optional):
1324 lockobj = repo.locked
1324 lockobj = repo.locked
1325
1325
1326 if lockobj[0] is None:
1326 if lockobj[0] is None:
1327 _d = {
1327 _d = {
1328 'repo': repo.repo_name,
1328 'repo': repo.repo_name,
1329 'locked': False,
1329 'locked': False,
1330 'locked_since': None,
1330 'locked_since': None,
1331 'locked_by': None,
1331 'locked_by': None,
1332 'lock_reason': None,
1332 'lock_reason': None,
1333 'lock_state_changed': False,
1333 'lock_state_changed': False,
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1335 }
1335 }
1336 return _d
1336 return _d
1337 else:
1337 else:
1338 _user_id, _time, _reason = lockobj
1338 _user_id, _time, _reason = lockobj
1339 lock_user = get_user_or_error(userid)
1339 lock_user = get_user_or_error(userid)
1340 _d = {
1340 _d = {
1341 'repo': repo.repo_name,
1341 'repo': repo.repo_name,
1342 'locked': True,
1342 'locked': True,
1343 'locked_since': _time,
1343 'locked_since': _time,
1344 'locked_by': lock_user.username,
1344 'locked_by': lock_user.username,
1345 'lock_reason': _reason,
1345 'lock_reason': _reason,
1346 'lock_state_changed': False,
1346 'lock_state_changed': False,
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1348 % (repo.repo_name, lock_user.username,
1348 % (repo.repo_name, lock_user.username,
1349 json.dumps(time_to_datetime(_time))))
1349 json.dumps(time_to_datetime(_time))))
1350 }
1350 }
1351 return _d
1351 return _d
1352
1352
1353 # force locked state through a flag
1353 # force locked state through a flag
1354 else:
1354 else:
1355 locked = str2bool(locked)
1355 locked = str2bool(locked)
1356 lock_reason = Repository.LOCK_API
1356 lock_reason = Repository.LOCK_API
1357 try:
1357 try:
1358 if locked:
1358 if locked:
1359 lock_time = time.time()
1359 lock_time = time.time()
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1361 else:
1361 else:
1362 lock_time = None
1362 lock_time = None
1363 Repository.unlock(repo)
1363 Repository.unlock(repo)
1364 _d = {
1364 _d = {
1365 'repo': repo.repo_name,
1365 'repo': repo.repo_name,
1366 'locked': locked,
1366 'locked': locked,
1367 'locked_since': lock_time,
1367 'locked_since': lock_time,
1368 'locked_by': user.username,
1368 'locked_by': user.username,
1369 'lock_reason': lock_reason,
1369 'lock_reason': lock_reason,
1370 'lock_state_changed': True,
1370 'lock_state_changed': True,
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1372 % (user.username, repo.repo_name, locked))
1372 % (user.username, repo.repo_name, locked))
1373 }
1373 }
1374 return _d
1374 return _d
1375 except Exception:
1375 except Exception:
1376 log.exception(
1376 log.exception(
1377 "Exception occurred while trying to lock repository")
1377 "Exception occurred while trying to lock repository")
1378 raise JSONRPCError(
1378 raise JSONRPCError(
1379 'Error occurred locking repository `%s`' % repo.repo_name
1379 'Error occurred locking repository `%s`' % repo.repo_name
1380 )
1380 )
1381
1381
1382
1382
1383 @jsonrpc_method()
1383 @jsonrpc_method()
1384 def comment_commit(
1384 def comment_commit(
1385 request, apiuser, repoid, commit_id, message,
1385 request, apiuser, repoid, commit_id, message,
1386 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1386 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1387 """
1387 """
1388 Set a commit comment, and optionally change the status of the commit.
1388 Set a commit comment, and optionally change the status of the commit.
1389
1389
1390 :param apiuser: This is filled automatically from the |authtoken|.
1390 :param apiuser: This is filled automatically from the |authtoken|.
1391 :type apiuser: AuthUser
1391 :type apiuser: AuthUser
1392 :param repoid: Set the repository name or repository ID.
1392 :param repoid: Set the repository name or repository ID.
1393 :type repoid: str or int
1393 :type repoid: str or int
1394 :param commit_id: Specify the commit_id for which to set a comment.
1394 :param commit_id: Specify the commit_id for which to set a comment.
1395 :type commit_id: str
1395 :type commit_id: str
1396 :param message: The comment text.
1396 :param message: The comment text.
1397 :type message: str
1397 :type message: str
1398 :param userid: Set the user name of the comment creator.
1398 :param userid: Set the user name of the comment creator.
1399 :type userid: Optional(str or int)
1399 :type userid: Optional(str or int)
1400 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1400 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1401 'under_review'
1401 'under_review'
1402 :type status: str
1402 :type status: str
1403
1403
1404 Example error output:
1404 Example error output:
1405
1405
1406 .. code-block:: json
1406 .. code-block:: json
1407
1407
1408 {
1408 {
1409 "id" : <id_given_in_input>,
1409 "id" : <id_given_in_input>,
1410 "result" : {
1410 "result" : {
1411 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1411 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1412 "status_change": null or <status>,
1412 "status_change": null or <status>,
1413 "success": true
1413 "success": true
1414 },
1414 },
1415 "error" : null
1415 "error" : null
1416 }
1416 }
1417
1417
1418 """
1418 """
1419 repo = get_repo_or_error(repoid)
1419 repo = get_repo_or_error(repoid)
1420 if not has_superadmin_permission(apiuser):
1420 if not has_superadmin_permission(apiuser):
1421 _perms = ('repository.read', 'repository.write', 'repository.admin')
1421 _perms = ('repository.read', 'repository.write', 'repository.admin')
1422 validate_repo_permissions(apiuser, repoid, repo, _perms)
1422 validate_repo_permissions(apiuser, repoid, repo, _perms)
1423
1423
1424 if isinstance(userid, Optional):
1424 if isinstance(userid, Optional):
1425 userid = apiuser.user_id
1425 userid = apiuser.user_id
1426
1426
1427 user = get_user_or_error(userid)
1427 user = get_user_or_error(userid)
1428 status = Optional.extract(status)
1428 status = Optional.extract(status)
1429
1429
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1431 if status and status not in allowed_statuses:
1431 if status and status not in allowed_statuses:
1432 raise JSONRPCError('Bad status, must be on '
1432 raise JSONRPCError('Bad status, must be on '
1433 'of %s got %s' % (allowed_statuses, status,))
1433 'of %s got %s' % (allowed_statuses, status,))
1434
1434
1435 try:
1435 try:
1436 rc_config = SettingsModel().get_all_settings()
1436 rc_config = SettingsModel().get_all_settings()
1437 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1437 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1438 status_change_label = ChangesetStatus.get_status_lbl(status)
1438 status_change_label = ChangesetStatus.get_status_lbl(status)
1439 comm = ChangesetCommentsModel().create(
1439 comm = ChangesetCommentsModel().create(
1440 message, repo, user, revision=commit_id,
1440 message, repo, user, revision=commit_id,
1441 status_change=status_change_label,
1441 status_change=status_change_label,
1442 status_change_type=status,
1442 status_change_type=status,
1443 renderer=renderer)
1443 renderer=renderer)
1444 if status:
1444 if status:
1445 # also do a status change
1445 # also do a status change
1446 try:
1446 try:
1447 ChangesetStatusModel().set_status(
1447 ChangesetStatusModel().set_status(
1448 repo, status, user, comm, revision=commit_id,
1448 repo, status, user, comm, revision=commit_id,
1449 dont_allow_on_closed_pull_request=True
1449 dont_allow_on_closed_pull_request=True
1450 )
1450 )
1451 except StatusChangeOnClosedPullRequestError:
1451 except StatusChangeOnClosedPullRequestError:
1452 log.exception(
1452 log.exception(
1453 "Exception occurred while trying to change repo commit status")
1453 "Exception occurred while trying to change repo commit status")
1454 msg = ('Changing status on a changeset associated with '
1454 msg = ('Changing status on a changeset associated with '
1455 'a closed pull request is not allowed')
1455 'a closed pull request is not allowed')
1456 raise JSONRPCError(msg)
1456 raise JSONRPCError(msg)
1457
1457
1458 Session().commit()
1458 Session().commit()
1459 return {
1459 return {
1460 'msg': (
1460 'msg': (
1461 'Commented on commit `%s` for repository `%s`' % (
1461 'Commented on commit `%s` for repository `%s`' % (
1462 comm.revision, repo.repo_name)),
1462 comm.revision, repo.repo_name)),
1463 'status_change': status,
1463 'status_change': status,
1464 'success': True,
1464 'success': True,
1465 }
1465 }
1466 except JSONRPCError:
1466 except JSONRPCError:
1467 # catch any inside errors, and re-raise them to prevent from
1467 # catch any inside errors, and re-raise them to prevent from
1468 # below global catch to silence them
1468 # below global catch to silence them
1469 raise
1469 raise
1470 except Exception:
1470 except Exception:
1471 log.exception("Exception occurred while trying to comment on commit")
1471 log.exception("Exception occurred while trying to comment on commit")
1472 raise JSONRPCError(
1472 raise JSONRPCError(
1473 'failed to set comment on repository `%s`' % (repo.repo_name,)
1473 'failed to set comment on repository `%s`' % (repo.repo_name,)
1474 )
1474 )
1475
1475
1476
1476
1477 @jsonrpc_method()
1477 @jsonrpc_method()
1478 def grant_user_permission(request, apiuser, repoid, userid, perm):
1478 def grant_user_permission(request, apiuser, repoid, userid, perm):
1479 """
1479 """
1480 Grant permissions for the specified user on the given repository,
1480 Grant permissions for the specified user on the given repository,
1481 or update existing permissions if found.
1481 or update existing permissions if found.
1482
1482
1483 This command can only be run using an |authtoken| with admin
1483 This command can only be run using an |authtoken| with admin
1484 permissions on the |repo|.
1484 permissions on the |repo|.
1485
1485
1486 :param apiuser: This is filled automatically from the |authtoken|.
1486 :param apiuser: This is filled automatically from the |authtoken|.
1487 :type apiuser: AuthUser
1487 :type apiuser: AuthUser
1488 :param repoid: Set the repository name or repository ID.
1488 :param repoid: Set the repository name or repository ID.
1489 :type repoid: str or int
1489 :type repoid: str or int
1490 :param userid: Set the user name.
1490 :param userid: Set the user name.
1491 :type userid: str
1491 :type userid: str
1492 :param perm: Set the user permissions, using the following format
1492 :param perm: Set the user permissions, using the following format
1493 ``(repository.(none|read|write|admin))``
1493 ``(repository.(none|read|write|admin))``
1494 :type perm: str
1494 :type perm: str
1495
1495
1496 Example output:
1496 Example output:
1497
1497
1498 .. code-block:: bash
1498 .. code-block:: bash
1499
1499
1500 id : <id_given_in_input>
1500 id : <id_given_in_input>
1501 result: {
1501 result: {
1502 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1502 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1503 "success": true
1503 "success": true
1504 }
1504 }
1505 error: null
1505 error: null
1506 """
1506 """
1507
1507
1508 repo = get_repo_or_error(repoid)
1508 repo = get_repo_or_error(repoid)
1509 user = get_user_or_error(userid)
1509 user = get_user_or_error(userid)
1510 perm = get_perm_or_error(perm)
1510 perm = get_perm_or_error(perm)
1511 if not has_superadmin_permission(apiuser):
1511 if not has_superadmin_permission(apiuser):
1512 _perms = ('repository.admin',)
1512 _perms = ('repository.admin',)
1513 validate_repo_permissions(apiuser, repoid, repo, _perms)
1513 validate_repo_permissions(apiuser, repoid, repo, _perms)
1514
1514
1515 try:
1515 try:
1516
1516
1517 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1517 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1518
1518
1519 Session().commit()
1519 Session().commit()
1520 return {
1520 return {
1521 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1521 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1522 perm.permission_name, user.username, repo.repo_name
1522 perm.permission_name, user.username, repo.repo_name
1523 ),
1523 ),
1524 'success': True
1524 'success': True
1525 }
1525 }
1526 except Exception:
1526 except Exception:
1527 log.exception(
1527 log.exception(
1528 "Exception occurred while trying edit permissions for repo")
1528 "Exception occurred while trying edit permissions for repo")
1529 raise JSONRPCError(
1529 raise JSONRPCError(
1530 'failed to edit permission for user: `%s` in repo: `%s`' % (
1530 'failed to edit permission for user: `%s` in repo: `%s`' % (
1531 userid, repoid
1531 userid, repoid
1532 )
1532 )
1533 )
1533 )
1534
1534
1535
1535
1536 @jsonrpc_method()
1536 @jsonrpc_method()
1537 def revoke_user_permission(request, apiuser, repoid, userid):
1537 def revoke_user_permission(request, apiuser, repoid, userid):
1538 """
1538 """
1539 Revoke permission for a user on the specified repository.
1539 Revoke permission for a user on the specified repository.
1540
1540
1541 This command can only be run using an |authtoken| with admin
1541 This command can only be run using an |authtoken| with admin
1542 permissions on the |repo|.
1542 permissions on the |repo|.
1543
1543
1544 :param apiuser: This is filled automatically from the |authtoken|.
1544 :param apiuser: This is filled automatically from the |authtoken|.
1545 :type apiuser: AuthUser
1545 :type apiuser: AuthUser
1546 :param repoid: Set the repository name or repository ID.
1546 :param repoid: Set the repository name or repository ID.
1547 :type repoid: str or int
1547 :type repoid: str or int
1548 :param userid: Set the user name of revoked user.
1548 :param userid: Set the user name of revoked user.
1549 :type userid: str or int
1549 :type userid: str or int
1550
1550
1551 Example error output:
1551 Example error output:
1552
1552
1553 .. code-block:: bash
1553 .. code-block:: bash
1554
1554
1555 id : <id_given_in_input>
1555 id : <id_given_in_input>
1556 result: {
1556 result: {
1557 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1557 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1558 "success": true
1558 "success": true
1559 }
1559 }
1560 error: null
1560 error: null
1561 """
1561 """
1562
1562
1563 repo = get_repo_or_error(repoid)
1563 repo = get_repo_or_error(repoid)
1564 user = get_user_or_error(userid)
1564 user = get_user_or_error(userid)
1565 if not has_superadmin_permission(apiuser):
1565 if not has_superadmin_permission(apiuser):
1566 _perms = ('repository.admin',)
1566 _perms = ('repository.admin',)
1567 validate_repo_permissions(apiuser, repoid, repo, _perms)
1567 validate_repo_permissions(apiuser, repoid, repo, _perms)
1568
1568
1569 try:
1569 try:
1570 RepoModel().revoke_user_permission(repo=repo, user=user)
1570 RepoModel().revoke_user_permission(repo=repo, user=user)
1571 Session().commit()
1571 Session().commit()
1572 return {
1572 return {
1573 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1573 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1574 user.username, repo.repo_name
1574 user.username, repo.repo_name
1575 ),
1575 ),
1576 'success': True
1576 'success': True
1577 }
1577 }
1578 except Exception:
1578 except Exception:
1579 log.exception(
1579 log.exception(
1580 "Exception occurred while trying revoke permissions to repo")
1580 "Exception occurred while trying revoke permissions to repo")
1581 raise JSONRPCError(
1581 raise JSONRPCError(
1582 'failed to edit permission for user: `%s` in repo: `%s`' % (
1582 'failed to edit permission for user: `%s` in repo: `%s`' % (
1583 userid, repoid
1583 userid, repoid
1584 )
1584 )
1585 )
1585 )
1586
1586
1587
1587
1588 @jsonrpc_method()
1588 @jsonrpc_method()
1589 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1589 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1590 """
1590 """
1591 Grant permission for a user group on the specified repository,
1591 Grant permission for a user group on the specified repository,
1592 or update existing permissions.
1592 or update existing permissions.
1593
1593
1594 This command can only be run using an |authtoken| with admin
1594 This command can only be run using an |authtoken| with admin
1595 permissions on the |repo|.
1595 permissions on the |repo|.
1596
1596
1597 :param apiuser: This is filled automatically from the |authtoken|.
1597 :param apiuser: This is filled automatically from the |authtoken|.
1598 :type apiuser: AuthUser
1598 :type apiuser: AuthUser
1599 :param repoid: Set the repository name or repository ID.
1599 :param repoid: Set the repository name or repository ID.
1600 :type repoid: str or int
1600 :type repoid: str or int
1601 :param usergroupid: Specify the ID of the user group.
1601 :param usergroupid: Specify the ID of the user group.
1602 :type usergroupid: str or int
1602 :type usergroupid: str or int
1603 :param perm: Set the user group permissions using the following
1603 :param perm: Set the user group permissions using the following
1604 format: (repository.(none|read|write|admin))
1604 format: (repository.(none|read|write|admin))
1605 :type perm: str
1605 :type perm: str
1606
1606
1607 Example output:
1607 Example output:
1608
1608
1609 .. code-block:: bash
1609 .. code-block:: bash
1610
1610
1611 id : <id_given_in_input>
1611 id : <id_given_in_input>
1612 result : {
1612 result : {
1613 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1613 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1614 "success": true
1614 "success": true
1615
1615
1616 }
1616 }
1617 error : null
1617 error : null
1618
1618
1619 Example error output:
1619 Example error output:
1620
1620
1621 .. code-block:: bash
1621 .. code-block:: bash
1622
1622
1623 id : <id_given_in_input>
1623 id : <id_given_in_input>
1624 result : null
1624 result : null
1625 error : {
1625 error : {
1626 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1626 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1627 }
1627 }
1628
1628
1629 """
1629 """
1630
1630
1631 repo = get_repo_or_error(repoid)
1631 repo = get_repo_or_error(repoid)
1632 perm = get_perm_or_error(perm)
1632 perm = get_perm_or_error(perm)
1633 if not has_superadmin_permission(apiuser):
1633 if not has_superadmin_permission(apiuser):
1634 _perms = ('repository.admin',)
1634 _perms = ('repository.admin',)
1635 validate_repo_permissions(apiuser, repoid, repo, _perms)
1635 validate_repo_permissions(apiuser, repoid, repo, _perms)
1636
1636
1637 user_group = get_user_group_or_error(usergroupid)
1637 user_group = get_user_group_or_error(usergroupid)
1638 if not has_superadmin_permission(apiuser):
1638 if not has_superadmin_permission(apiuser):
1639 # check if we have at least read permission for this user group !
1639 # check if we have at least read permission for this user group !
1640 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1640 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1641 if not HasUserGroupPermissionAnyApi(*_perms)(
1641 if not HasUserGroupPermissionAnyApi(*_perms)(
1642 user=apiuser, user_group_name=user_group.users_group_name):
1642 user=apiuser, user_group_name=user_group.users_group_name):
1643 raise JSONRPCError(
1643 raise JSONRPCError(
1644 'user group `%s` does not exist' % (usergroupid,))
1644 'user group `%s` does not exist' % (usergroupid,))
1645
1645
1646 try:
1646 try:
1647 RepoModel().grant_user_group_permission(
1647 RepoModel().grant_user_group_permission(
1648 repo=repo, group_name=user_group, perm=perm)
1648 repo=repo, group_name=user_group, perm=perm)
1649
1649
1650 Session().commit()
1650 Session().commit()
1651 return {
1651 return {
1652 'msg': 'Granted perm: `%s` for user group: `%s` in '
1652 'msg': 'Granted perm: `%s` for user group: `%s` in '
1653 'repo: `%s`' % (
1653 'repo: `%s`' % (
1654 perm.permission_name, user_group.users_group_name,
1654 perm.permission_name, user_group.users_group_name,
1655 repo.repo_name
1655 repo.repo_name
1656 ),
1656 ),
1657 'success': True
1657 'success': True
1658 }
1658 }
1659 except Exception:
1659 except Exception:
1660 log.exception(
1660 log.exception(
1661 "Exception occurred while trying change permission on repo")
1661 "Exception occurred while trying change permission on repo")
1662 raise JSONRPCError(
1662 raise JSONRPCError(
1663 'failed to edit permission for user group: `%s` in '
1663 'failed to edit permission for user group: `%s` in '
1664 'repo: `%s`' % (
1664 'repo: `%s`' % (
1665 usergroupid, repo.repo_name
1665 usergroupid, repo.repo_name
1666 )
1666 )
1667 )
1667 )
1668
1668
1669
1669
1670 @jsonrpc_method()
1670 @jsonrpc_method()
1671 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1671 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1672 """
1672 """
1673 Revoke the permissions of a user group on a given repository.
1673 Revoke the permissions of a user group on a given repository.
1674
1674
1675 This command can only be run using an |authtoken| with admin
1675 This command can only be run using an |authtoken| with admin
1676 permissions on the |repo|.
1676 permissions on the |repo|.
1677
1677
1678 :param apiuser: This is filled automatically from the |authtoken|.
1678 :param apiuser: This is filled automatically from the |authtoken|.
1679 :type apiuser: AuthUser
1679 :type apiuser: AuthUser
1680 :param repoid: Set the repository name or repository ID.
1680 :param repoid: Set the repository name or repository ID.
1681 :type repoid: str or int
1681 :type repoid: str or int
1682 :param usergroupid: Specify the user group ID.
1682 :param usergroupid: Specify the user group ID.
1683 :type usergroupid: str or int
1683 :type usergroupid: str or int
1684
1684
1685 Example output:
1685 Example output:
1686
1686
1687 .. code-block:: bash
1687 .. code-block:: bash
1688
1688
1689 id : <id_given_in_input>
1689 id : <id_given_in_input>
1690 result: {
1690 result: {
1691 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1691 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1692 "success": true
1692 "success": true
1693 }
1693 }
1694 error: null
1694 error: null
1695 """
1695 """
1696
1696
1697 repo = get_repo_or_error(repoid)
1697 repo = get_repo_or_error(repoid)
1698 if not has_superadmin_permission(apiuser):
1698 if not has_superadmin_permission(apiuser):
1699 _perms = ('repository.admin',)
1699 _perms = ('repository.admin',)
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1701
1701
1702 user_group = get_user_group_or_error(usergroupid)
1702 user_group = get_user_group_or_error(usergroupid)
1703 if not has_superadmin_permission(apiuser):
1703 if not has_superadmin_permission(apiuser):
1704 # check if we have at least read permission for this user group !
1704 # check if we have at least read permission for this user group !
1705 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1705 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1706 if not HasUserGroupPermissionAnyApi(*_perms)(
1706 if not HasUserGroupPermissionAnyApi(*_perms)(
1707 user=apiuser, user_group_name=user_group.users_group_name):
1707 user=apiuser, user_group_name=user_group.users_group_name):
1708 raise JSONRPCError(
1708 raise JSONRPCError(
1709 'user group `%s` does not exist' % (usergroupid,))
1709 'user group `%s` does not exist' % (usergroupid,))
1710
1710
1711 try:
1711 try:
1712 RepoModel().revoke_user_group_permission(
1712 RepoModel().revoke_user_group_permission(
1713 repo=repo, group_name=user_group)
1713 repo=repo, group_name=user_group)
1714
1714
1715 Session().commit()
1715 Session().commit()
1716 return {
1716 return {
1717 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1717 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1718 user_group.users_group_name, repo.repo_name
1718 user_group.users_group_name, repo.repo_name
1719 ),
1719 ),
1720 'success': True
1720 'success': True
1721 }
1721 }
1722 except Exception:
1722 except Exception:
1723 log.exception("Exception occurred while trying revoke "
1723 log.exception("Exception occurred while trying revoke "
1724 "user group permission on repo")
1724 "user group permission on repo")
1725 raise JSONRPCError(
1725 raise JSONRPCError(
1726 'failed to edit permission for user group: `%s` in '
1726 'failed to edit permission for user group: `%s` in '
1727 'repo: `%s`' % (
1727 'repo: `%s`' % (
1728 user_group.users_group_name, repo.repo_name
1728 user_group.users_group_name, repo.repo_name
1729 )
1729 )
1730 )
1730 )
1731
1731
1732
1732
1733 @jsonrpc_method()
1733 @jsonrpc_method()
1734 def pull(request, apiuser, repoid):
1734 def pull(request, apiuser, repoid):
1735 """
1735 """
1736 Triggers a pull on the given repository from a remote location. You
1736 Triggers a pull on the given repository from a remote location. You
1737 can use this to keep remote repositories up-to-date.
1737 can use this to keep remote repositories up-to-date.
1738
1738
1739 This command can only be run using an |authtoken| with admin
1739 This command can only be run using an |authtoken| with admin
1740 rights to the specified repository. For more information,
1740 rights to the specified repository. For more information,
1741 see :ref:`config-token-ref`.
1741 see :ref:`config-token-ref`.
1742
1742
1743 This command takes the following options:
1743 This command takes the following options:
1744
1744
1745 :param apiuser: This is filled automatically from the |authtoken|.
1745 :param apiuser: This is filled automatically from the |authtoken|.
1746 :type apiuser: AuthUser
1746 :type apiuser: AuthUser
1747 :param repoid: The repository name or repository ID.
1747 :param repoid: The repository name or repository ID.
1748 :type repoid: str or int
1748 :type repoid: str or int
1749
1749
1750 Example output:
1750 Example output:
1751
1751
1752 .. code-block:: bash
1752 .. code-block:: bash
1753
1753
1754 id : <id_given_in_input>
1754 id : <id_given_in_input>
1755 result : {
1755 result : {
1756 "msg": "Pulled from `<repository name>`"
1756 "msg": "Pulled from `<repository name>`"
1757 "repository": "<repository name>"
1757 "repository": "<repository name>"
1758 }
1758 }
1759 error : null
1759 error : null
1760
1760
1761 Example error output:
1761 Example error output:
1762
1762
1763 .. code-block:: bash
1763 .. code-block:: bash
1764
1764
1765 id : <id_given_in_input>
1765 id : <id_given_in_input>
1766 result : null
1766 result : null
1767 error : {
1767 error : {
1768 "Unable to pull changes from `<reponame>`"
1768 "Unable to pull changes from `<reponame>`"
1769 }
1769 }
1770
1770
1771 """
1771 """
1772
1772
1773 repo = get_repo_or_error(repoid)
1773 repo = get_repo_or_error(repoid)
1774 if not has_superadmin_permission(apiuser):
1774 if not has_superadmin_permission(apiuser):
1775 _perms = ('repository.admin',)
1775 _perms = ('repository.admin',)
1776 validate_repo_permissions(apiuser, repoid, repo, _perms)
1776 validate_repo_permissions(apiuser, repoid, repo, _perms)
1777
1777
1778 try:
1778 try:
1779 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1779 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1780 return {
1780 return {
1781 'msg': 'Pulled from `%s`' % repo.repo_name,
1781 'msg': 'Pulled from `%s`' % repo.repo_name,
1782 'repository': repo.repo_name
1782 'repository': repo.repo_name
1783 }
1783 }
1784 except Exception:
1784 except Exception:
1785 log.exception("Exception occurred while trying to "
1785 log.exception("Exception occurred while trying to "
1786 "pull changes from remote location")
1786 "pull changes from remote location")
1787 raise JSONRPCError(
1787 raise JSONRPCError(
1788 'Unable to pull changes from `%s`' % repo.repo_name
1788 'Unable to pull changes from `%s`' % repo.repo_name
1789 )
1789 )
1790
1790
1791
1791
1792 @jsonrpc_method()
1792 @jsonrpc_method()
1793 def strip(request, apiuser, repoid, revision, branch):
1793 def strip(request, apiuser, repoid, revision, branch):
1794 """
1794 """
1795 Strips the given revision from the specified repository.
1795 Strips the given revision from the specified repository.
1796
1796
1797 * This will remove the revision and all of its decendants.
1797 * This will remove the revision and all of its decendants.
1798
1798
1799 This command can only be run using an |authtoken| with admin rights to
1799 This command can only be run using an |authtoken| with admin rights to
1800 the specified repository.
1800 the specified repository.
1801
1801
1802 This command takes the following options:
1802 This command takes the following options:
1803
1803
1804 :param apiuser: This is filled automatically from the |authtoken|.
1804 :param apiuser: This is filled automatically from the |authtoken|.
1805 :type apiuser: AuthUser
1805 :type apiuser: AuthUser
1806 :param repoid: The repository name or repository ID.
1806 :param repoid: The repository name or repository ID.
1807 :type repoid: str or int
1807 :type repoid: str or int
1808 :param revision: The revision you wish to strip.
1808 :param revision: The revision you wish to strip.
1809 :type revision: str
1809 :type revision: str
1810 :param branch: The branch from which to strip the revision.
1810 :param branch: The branch from which to strip the revision.
1811 :type branch: str
1811 :type branch: str
1812
1812
1813 Example output:
1813 Example output:
1814
1814
1815 .. code-block:: bash
1815 .. code-block:: bash
1816
1816
1817 id : <id_given_in_input>
1817 id : <id_given_in_input>
1818 result : {
1818 result : {
1819 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1819 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1820 "repository": "<repository name>"
1820 "repository": "<repository name>"
1821 }
1821 }
1822 error : null
1822 error : null
1823
1823
1824 Example error output:
1824 Example error output:
1825
1825
1826 .. code-block:: bash
1826 .. code-block:: bash
1827
1827
1828 id : <id_given_in_input>
1828 id : <id_given_in_input>
1829 result : null
1829 result : null
1830 error : {
1830 error : {
1831 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1831 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1832 }
1832 }
1833
1833
1834 """
1834 """
1835
1835
1836 repo = get_repo_or_error(repoid)
1836 repo = get_repo_or_error(repoid)
1837 if not has_superadmin_permission(apiuser):
1837 if not has_superadmin_permission(apiuser):
1838 _perms = ('repository.admin',)
1838 _perms = ('repository.admin',)
1839 validate_repo_permissions(apiuser, repoid, repo, _perms)
1839 validate_repo_permissions(apiuser, repoid, repo, _perms)
1840
1840
1841 try:
1841 try:
1842 ScmModel().strip(repo, revision, branch)
1842 ScmModel().strip(repo, revision, branch)
1843 return {
1843 return {
1844 'msg': 'Stripped commit %s from repo `%s`' % (
1844 'msg': 'Stripped commit %s from repo `%s`' % (
1845 revision, repo.repo_name),
1845 revision, repo.repo_name),
1846 'repository': repo.repo_name
1846 'repository': repo.repo_name
1847 }
1847 }
1848 except Exception:
1848 except Exception:
1849 log.exception("Exception while trying to strip")
1849 log.exception("Exception while trying to strip")
1850 raise JSONRPCError(
1850 raise JSONRPCError(
1851 'Unable to strip commit %s from repo `%s`' % (
1851 'Unable to strip commit %s from repo `%s`' % (
1852 revision, repo.repo_name)
1852 revision, repo.repo_name)
1853 )
1853 )
1854
1854
1855
1855
1856 @jsonrpc_method()
1856 @jsonrpc_method()
1857 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1857 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1858 """
1858 """
1859 Returns all settings for a repository. If key is given it only returns the
1859 Returns all settings for a repository. If key is given it only returns the
1860 setting identified by the key or null.
1860 setting identified by the key or null.
1861
1861
1862 :param apiuser: This is filled automatically from the |authtoken|.
1862 :param apiuser: This is filled automatically from the |authtoken|.
1863 :type apiuser: AuthUser
1863 :type apiuser: AuthUser
1864 :param repoid: The repository name or repository id.
1864 :param repoid: The repository name or repository id.
1865 :type repoid: str or int
1865 :type repoid: str or int
1866 :param key: Key of the setting to return.
1866 :param key: Key of the setting to return.
1867 :type: key: Optional(str)
1867 :type: key: Optional(str)
1868
1868
1869 Example output:
1869 Example output:
1870
1870
1871 .. code-block:: bash
1871 .. code-block:: bash
1872
1872
1873 {
1873 {
1874 "error": null,
1874 "error": null,
1875 "id": 237,
1875 "id": 237,
1876 "result": {
1876 "result": {
1877 "extensions_largefiles": true,
1877 "extensions_largefiles": true,
1878 "hooks_changegroup_push_logger": true,
1878 "hooks_changegroup_push_logger": true,
1879 "hooks_changegroup_repo_size": false,
1879 "hooks_changegroup_repo_size": false,
1880 "hooks_outgoing_pull_logger": true,
1880 "hooks_outgoing_pull_logger": true,
1881 "phases_publish": "True",
1881 "phases_publish": "True",
1882 "rhodecode_hg_use_rebase_for_merging": true,
1882 "rhodecode_hg_use_rebase_for_merging": true,
1883 "rhodecode_pr_merge_enabled": true,
1883 "rhodecode_pr_merge_enabled": true,
1884 "rhodecode_use_outdated_comments": true
1884 "rhodecode_use_outdated_comments": true
1885 }
1885 }
1886 }
1886 }
1887 """
1887 """
1888
1888
1889 # Restrict access to this api method to admins only.
1889 # Restrict access to this api method to admins only.
1890 if not has_superadmin_permission(apiuser):
1890 if not has_superadmin_permission(apiuser):
1891 raise JSONRPCForbidden()
1891 raise JSONRPCForbidden()
1892
1892
1893 try:
1893 try:
1894 repo = get_repo_or_error(repoid)
1894 repo = get_repo_or_error(repoid)
1895 settings_model = VcsSettingsModel(repo=repo)
1895 settings_model = VcsSettingsModel(repo=repo)
1896 settings = settings_model.get_global_settings()
1896 settings = settings_model.get_global_settings()
1897 settings.update(settings_model.get_repo_settings())
1897 settings.update(settings_model.get_repo_settings())
1898
1898
1899 # If only a single setting is requested fetch it from all settings.
1899 # If only a single setting is requested fetch it from all settings.
1900 key = Optional.extract(key)
1900 key = Optional.extract(key)
1901 if key is not None:
1901 if key is not None:
1902 settings = settings.get(key, None)
1902 settings = settings.get(key, None)
1903 except Exception:
1903 except Exception:
1904 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1904 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1905 log.exception(msg)
1905 log.exception(msg)
1906 raise JSONRPCError(msg)
1906 raise JSONRPCError(msg)
1907
1907
1908 return settings
1908 return settings
1909
1909
1910
1910
1911 @jsonrpc_method()
1911 @jsonrpc_method()
1912 def set_repo_settings(request, apiuser, repoid, settings):
1912 def set_repo_settings(request, apiuser, repoid, settings):
1913 """
1913 """
1914 Update repository settings. Returns true on success.
1914 Update repository settings. Returns true on success.
1915
1915
1916 :param apiuser: This is filled automatically from the |authtoken|.
1916 :param apiuser: This is filled automatically from the |authtoken|.
1917 :type apiuser: AuthUser
1917 :type apiuser: AuthUser
1918 :param repoid: The repository name or repository id.
1918 :param repoid: The repository name or repository id.
1919 :type repoid: str or int
1919 :type repoid: str or int
1920 :param settings: The new settings for the repository.
1920 :param settings: The new settings for the repository.
1921 :type: settings: dict
1921 :type: settings: dict
1922
1922
1923 Example output:
1923 Example output:
1924
1924
1925 .. code-block:: bash
1925 .. code-block:: bash
1926
1926
1927 {
1927 {
1928 "error": null,
1928 "error": null,
1929 "id": 237,
1929 "id": 237,
1930 "result": true
1930 "result": true
1931 }
1931 }
1932 """
1932 """
1933 # Restrict access to this api method to admins only.
1933 # Restrict access to this api method to admins only.
1934 if not has_superadmin_permission(apiuser):
1934 if not has_superadmin_permission(apiuser):
1935 raise JSONRPCForbidden()
1935 raise JSONRPCForbidden()
1936
1936
1937 if type(settings) is not dict:
1937 if type(settings) is not dict:
1938 raise JSONRPCError('Settings have to be a JSON Object.')
1938 raise JSONRPCError('Settings have to be a JSON Object.')
1939
1939
1940 try:
1940 try:
1941 settings_model = VcsSettingsModel(repo=repoid)
1941 settings_model = VcsSettingsModel(repo=repoid)
1942
1942
1943 # Merge global, repo and incoming settings.
1943 # Merge global, repo and incoming settings.
1944 new_settings = settings_model.get_global_settings()
1944 new_settings = settings_model.get_global_settings()
1945 new_settings.update(settings_model.get_repo_settings())
1945 new_settings.update(settings_model.get_repo_settings())
1946 new_settings.update(settings)
1946 new_settings.update(settings)
1947
1947
1948 # Update the settings.
1948 # Update the settings.
1949 inherit_global_settings = new_settings.get(
1949 inherit_global_settings = new_settings.get(
1950 'inherit_global_settings', False)
1950 'inherit_global_settings', False)
1951 settings_model.create_or_update_repo_settings(
1951 settings_model.create_or_update_repo_settings(
1952 new_settings, inherit_global_settings=inherit_global_settings)
1952 new_settings, inherit_global_settings=inherit_global_settings)
1953 Session().commit()
1953 Session().commit()
1954 except Exception:
1954 except Exception:
1955 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1955 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1956 log.exception(msg)
1956 log.exception(msg)
1957 raise JSONRPCError(msg)
1957 raise JSONRPCError(msg)
1958
1958
1959 # Indicate success.
1959 # Indicate success.
1960 return True
1960 return True
@@ -1,702 +1,702 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import JSONRPCValidationError
24 from rhodecode.api import JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
31 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
31 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 from rhodecode.model.db import Session
32 from rhodecode.model.db import Session
33 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.scm import RepoGroupList
34 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model import validation_schema
35 from rhodecode.model import validation_schema
36 from rhodecode.model.validation_schema.schemas import repo_group_schema
36 from rhodecode.model.validation_schema.schemas import repo_group_schema
37
37
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 @jsonrpc_method()
42 @jsonrpc_method()
43 def get_repo_group(request, apiuser, repogroupid):
43 def get_repo_group(request, apiuser, repogroupid):
44 """
44 """
45 Return the specified |repo| group, along with permissions,
45 Return the specified |repo| group, along with permissions,
46 and repositories inside the group
46 and repositories inside the group
47
47
48 :param apiuser: This is filled automatically from the |authtoken|.
48 :param apiuser: This is filled automatically from the |authtoken|.
49 :type apiuser: AuthUser
49 :type apiuser: AuthUser
50 :param repogroupid: Specify the name of ID of the repository group.
50 :param repogroupid: Specify the name of ID of the repository group.
51 :type repogroupid: str or int
51 :type repogroupid: str or int
52
52
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 {
58 {
59 "error": null,
59 "error": null,
60 "id": repo-group-id,
60 "id": repo-group-id,
61 "result": {
61 "result": {
62 "group_description": "repo group description",
62 "group_description": "repo group description",
63 "group_id": 14,
63 "group_id": 14,
64 "group_name": "group name",
64 "group_name": "group name",
65 "members": [
65 "members": [
66 {
66 {
67 "name": "super-admin-username",
67 "name": "super-admin-username",
68 "origin": "super-admin",
68 "origin": "super-admin",
69 "permission": "group.admin",
69 "permission": "group.admin",
70 "type": "user"
70 "type": "user"
71 },
71 },
72 {
72 {
73 "name": "owner-name",
73 "name": "owner-name",
74 "origin": "owner",
74 "origin": "owner",
75 "permission": "group.admin",
75 "permission": "group.admin",
76 "type": "user"
76 "type": "user"
77 },
77 },
78 {
78 {
79 "name": "user-group-name",
79 "name": "user-group-name",
80 "origin": "permission",
80 "origin": "permission",
81 "permission": "group.write",
81 "permission": "group.write",
82 "type": "user_group"
82 "type": "user_group"
83 }
83 }
84 ],
84 ],
85 "owner": "owner-name",
85 "owner": "owner-name",
86 "parent_group": null,
86 "parent_group": null,
87 "repositories": [ repo-list ]
87 "repositories": [ repo-list ]
88 }
88 }
89 }
89 }
90 """
90 """
91
91
92 repo_group = get_repo_group_or_error(repogroupid)
92 repo_group = get_repo_group_or_error(repogroupid)
93 if not has_superadmin_permission(apiuser):
93 if not has_superadmin_permission(apiuser):
94 # check if we have at least read permission for this repo group !
94 # check if we have at least read permission for this repo group !
95 _perms = ('group.admin', 'group.write', 'group.read',)
95 _perms = ('group.admin', 'group.write', 'group.read',)
96 if not HasRepoGroupPermissionAnyApi(*_perms)(
96 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 user=apiuser, group_name=repo_group.group_name):
97 user=apiuser, group_name=repo_group.group_name):
98 raise JSONRPCError(
98 raise JSONRPCError(
99 'repository group `%s` does not exist' % (repogroupid,))
99 'repository group `%s` does not exist' % (repogroupid,))
100
100
101 permissions = []
101 permissions = []
102 for _user in repo_group.permissions():
102 for _user in repo_group.permissions():
103 user_data = {
103 user_data = {
104 'name': _user.username,
104 'name': _user.username,
105 'permission': _user.permission,
105 'permission': _user.permission,
106 'origin': get_origin(_user),
106 'origin': get_origin(_user),
107 'type': "user",
107 'type': "user",
108 }
108 }
109 permissions.append(user_data)
109 permissions.append(user_data)
110
110
111 for _user_group in repo_group.permission_user_groups():
111 for _user_group in repo_group.permission_user_groups():
112 user_group_data = {
112 user_group_data = {
113 'name': _user_group.users_group_name,
113 'name': _user_group.users_group_name,
114 'permission': _user_group.permission,
114 'permission': _user_group.permission,
115 'origin': get_origin(_user_group),
115 'origin': get_origin(_user_group),
116 'type': "user_group",
116 'type': "user_group",
117 }
117 }
118 permissions.append(user_group_data)
118 permissions.append(user_group_data)
119
119
120 data = repo_group.get_api_data()
120 data = repo_group.get_api_data()
121 data["members"] = permissions # TODO: this should be named permissions
121 data["members"] = permissions # TODO: this should be named permissions
122 return data
122 return data
123
123
124
124
125 @jsonrpc_method()
125 @jsonrpc_method()
126 def get_repo_groups(request, apiuser):
126 def get_repo_groups(request, apiuser):
127 """
127 """
128 Returns all repository groups.
128 Returns all repository groups.
129
129
130 :param apiuser: This is filled automatically from the |authtoken|.
130 :param apiuser: This is filled automatically from the |authtoken|.
131 :type apiuser: AuthUser
131 :type apiuser: AuthUser
132 """
132 """
133
133
134 result = []
134 result = []
135 _perms = ('group.read', 'group.write', 'group.admin',)
135 _perms = ('group.read', 'group.write', 'group.admin',)
136 extras = {'user': apiuser}
136 extras = {'user': apiuser}
137 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
137 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 perm_set=_perms, extra_kwargs=extras):
138 perm_set=_perms, extra_kwargs=extras):
139 result.append(repo_group.get_api_data())
139 result.append(repo_group.get_api_data())
140 return result
140 return result
141
141
142
142
143 @jsonrpc_method()
143 @jsonrpc_method()
144 def create_repo_group(
144 def create_repo_group(
145 request, apiuser, group_name,
145 request, apiuser, group_name,
146 owner=Optional(OAttr('apiuser')),
146 owner=Optional(OAttr('apiuser')),
147 description=Optional(''),
147 description=Optional(''),
148 copy_permissions=Optional(False)):
148 copy_permissions=Optional(False)):
149 """
149 """
150 Creates a repository group.
150 Creates a repository group.
151
151
152 * If the repository group name contains "/", repository group will be
152 * If the repository group name contains "/", repository group will be
153 created inside a repository group or nested repository groups
153 created inside a repository group or nested repository groups
154
154
155 For example "foo/bar/group1" will create repository group called "group1"
155 For example "foo/bar/group1" will create repository group called "group1"
156 inside group "foo/bar". You have to have permissions to access and
156 inside group "foo/bar". You have to have permissions to access and
157 write to the last repository group ("bar" in this example)
157 write to the last repository group ("bar" in this example)
158
158
159 This command can only be run using an |authtoken| with at least
159 This command can only be run using an |authtoken| with at least
160 permissions to create repository groups, or admin permissions to
160 permissions to create repository groups, or admin permissions to
161 parent repository groups.
161 parent repository groups.
162
162
163 :param apiuser: This is filled automatically from the |authtoken|.
163 :param apiuser: This is filled automatically from the |authtoken|.
164 :type apiuser: AuthUser
164 :type apiuser: AuthUser
165 :param group_name: Set the repository group name.
165 :param group_name: Set the repository group name.
166 :type group_name: str
166 :type group_name: str
167 :param description: Set the |repo| group description.
167 :param description: Set the |repo| group description.
168 :type description: str
168 :type description: str
169 :param owner: Set the |repo| group owner.
169 :param owner: Set the |repo| group owner.
170 :type owner: str
170 :type owner: str
171 :param copy_permissions:
171 :param copy_permissions:
172 :type copy_permissions:
172 :type copy_permissions:
173
173
174 Example output:
174 Example output:
175
175
176 .. code-block:: bash
176 .. code-block:: bash
177
177
178 id : <id_given_in_input>
178 id : <id_given_in_input>
179 result : {
179 result : {
180 "msg": "Created new repo group `<repo_group_name>`"
180 "msg": "Created new repo group `<repo_group_name>`"
181 "repo_group": <repogroup_object>
181 "repo_group": <repogroup_object>
182 }
182 }
183 error : null
183 error : null
184
184
185
185
186 Example error output:
186 Example error output:
187
187
188 .. code-block:: bash
188 .. code-block:: bash
189
189
190 id : <id_given_in_input>
190 id : <id_given_in_input>
191 result : null
191 result : null
192 error : {
192 error : {
193 failed to create repo group `<repogroupid>`
193 failed to create repo group `<repogroupid>`
194 }
194 }
195
195
196 """
196 """
197
197
198 owner = validate_set_owner_permissions(apiuser, owner)
198 owner = validate_set_owner_permissions(apiuser, owner)
199
199
200 description = Optional.extract(description)
200 description = Optional.extract(description)
201 copy_permissions = Optional.extract(copy_permissions)
201 copy_permissions = Optional.extract(copy_permissions)
202
202
203 schema = repo_group_schema.RepoGroupSchema().bind(
203 schema = repo_group_schema.RepoGroupSchema().bind(
204 # user caller
204 # user caller
205 user=apiuser)
205 user=apiuser)
206
206
207 try:
207 try:
208 schema_data = schema.deserialize(dict(
208 schema_data = schema.deserialize(dict(
209 repo_group_name=group_name,
209 repo_group_name=group_name,
210 repo_group_owner=owner.username,
210 repo_group_owner=owner.username,
211 repo_group_description=description,
211 repo_group_description=description,
212 repo_group_copy_permissions=copy_permissions,
212 repo_group_copy_permissions=copy_permissions,
213 ))
213 ))
214 except validation_schema.Invalid as err:
214 except validation_schema.Invalid as err:
215 raise JSONRPCValidationError(colander_exc=err)
215 raise JSONRPCValidationError(colander_exc=err)
216
216
217 validated_group_name = schema_data['repo_group_name']
217 validated_group_name = schema_data['repo_group_name']
218
218
219 try:
219 try:
220 repo_group = RepoGroupModel().create(
220 repo_group = RepoGroupModel().create(
221 owner=owner,
221 owner=owner,
222 group_name=validated_group_name,
222 group_name=validated_group_name,
223 group_description=schema_data['repo_group_name'],
223 group_description=schema_data['repo_group_name'],
224 copy_permissions=schema_data['repo_group_copy_permissions'])
224 copy_permissions=schema_data['repo_group_copy_permissions'])
225 Session().commit()
225 Session().commit()
226 return {
226 return {
227 'msg': 'Created new repo group `%s`' % validated_group_name,
227 'msg': 'Created new repo group `%s`' % validated_group_name,
228 'repo_group': repo_group.get_api_data()
228 'repo_group': repo_group.get_api_data()
229 }
229 }
230 except Exception:
230 except Exception:
231 log.exception("Exception occurred while trying create repo group")
231 log.exception("Exception occurred while trying create repo group")
232 raise JSONRPCError(
232 raise JSONRPCError(
233 'failed to create repo group `%s`' % (validated_group_name,))
233 'failed to create repo group `%s`' % (validated_group_name,))
234
234
235
235
236 @jsonrpc_method()
236 @jsonrpc_method()
237 def update_repo_group(
237 def update_repo_group(
238 request, apiuser, repogroupid, group_name=Optional(''),
238 request, apiuser, repogroupid, group_name=Optional(''),
239 description=Optional(''), owner=Optional(OAttr('apiuser')),
239 description=Optional(''), owner=Optional(OAttr('apiuser')),
240 enable_locking=Optional(False)):
240 enable_locking=Optional(False)):
241 """
241 """
242 Updates repository group with the details given.
242 Updates repository group with the details given.
243
243
244 This command can only be run using an |authtoken| with admin
244 This command can only be run using an |authtoken| with admin
245 permissions.
245 permissions.
246
246
247 * If the group_name name contains "/", repository group will be updated
247 * If the group_name name contains "/", repository group will be updated
248 accordingly with a repository group or nested repository groups
248 accordingly with a repository group or nested repository groups
249
249
250 For example repogroupid=group-test group_name="foo/bar/group-test"
250 For example repogroupid=group-test group_name="foo/bar/group-test"
251 will update repository group called "group-test" and place it
251 will update repository group called "group-test" and place it
252 inside group "foo/bar".
252 inside group "foo/bar".
253 You have to have permissions to access and write to the last repository
253 You have to have permissions to access and write to the last repository
254 group ("bar" in this example)
254 group ("bar" in this example)
255
255
256 :param apiuser: This is filled automatically from the |authtoken|.
256 :param apiuser: This is filled automatically from the |authtoken|.
257 :type apiuser: AuthUser
257 :type apiuser: AuthUser
258 :param repogroupid: Set the ID of repository group.
258 :param repogroupid: Set the ID of repository group.
259 :type repogroupid: str or int
259 :type repogroupid: str or int
260 :param group_name: Set the name of the |repo| group.
260 :param group_name: Set the name of the |repo| group.
261 :type group_name: str
261 :type group_name: str
262 :param description: Set a description for the group.
262 :param description: Set a description for the group.
263 :type description: str
263 :type description: str
264 :param owner: Set the |repo| group owner.
264 :param owner: Set the |repo| group owner.
265 :type owner: str
265 :type owner: str
266 :param enable_locking: Enable |repo| locking. The default is false.
266 :param enable_locking: Enable |repo| locking. The default is false.
267 :type enable_locking: bool
267 :type enable_locking: bool
268 """
268 """
269
269
270 repo_group = get_repo_group_or_error(repogroupid)
270 repo_group = get_repo_group_or_error(repogroupid)
271
271
272 if not has_superadmin_permission(apiuser):
272 if not has_superadmin_permission(apiuser):
273 validate_repo_group_permissions(
273 validate_repo_group_permissions(
274 apiuser, repogroupid, repo_group, ('group.admin',))
274 apiuser, repogroupid, repo_group, ('group.admin',))
275
275
276 updates = dict(
276 updates = dict(
277 group_name=group_name
277 group_name=group_name
278 if not isinstance(group_name, Optional) else repo_group.group_name,
278 if not isinstance(group_name, Optional) else repo_group.group_name,
279
279
280 group_description=description
280 group_description=description
281 if not isinstance(description, Optional) else repo_group.group_description,
281 if not isinstance(description, Optional) else repo_group.group_description,
282
282
283 user=owner
283 user=owner
284 if not isinstance(owner, Optional) else repo_group.user.username,
284 if not isinstance(owner, Optional) else repo_group.user.username,
285
285
286 enable_locking=enable_locking
286 enable_locking=enable_locking
287 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
287 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
288 )
288 )
289
289
290 schema = repo_group_schema.RepoGroupSchema().bind(
290 schema = repo_group_schema.RepoGroupSchema().bind(
291 # user caller
291 # user caller
292 user=apiuser,
292 user=apiuser,
293 old_values=repo_group.get_api_data())
293 old_values=repo_group.get_api_data())
294
294
295 try:
295 try:
296 schema_data = schema.deserialize(dict(
296 schema_data = schema.deserialize(dict(
297 repo_group_name=updates['group_name'],
297 repo_group_name=updates['group_name'],
298 repo_group_owner=updates['user'],
298 repo_group_owner=updates['user'],
299 repo_group_description=updates['group_description'],
299 repo_group_description=updates['group_description'],
300 repo_group_enable_locking=updates['enable_locking'],
300 repo_group_enable_locking=updates['enable_locking'],
301 ))
301 ))
302 except validation_schema.Invalid as err:
302 except validation_schema.Invalid as err:
303 raise JSONRPCValidationError(colander_exc=err)
303 raise JSONRPCValidationError(colander_exc=err)
304
304
305 validated_updates = dict(
305 validated_updates = dict(
306 group_name=schema_data['repo_group']['repo_group_name_without_group'],
306 group_name=schema_data['repo_group']['repo_group_name_without_group'],
307 group_parent_id=schema_data['repo_group']['repo_group_id'],
307 group_parent_id=schema_data['repo_group']['repo_group_id'],
308 user=schema_data['repo_group_owner'],
308 user=schema_data['repo_group_owner'],
309 group_description=schema_data['repo_group_description'],
309 group_description=schema_data['repo_group_description'],
310 enable_locking=schema_data['repo_group_enable_locking'],
310 enable_locking=schema_data['repo_group_enable_locking'],
311 )
311 )
312
312
313 try:
313 try:
314 RepoGroupModel().update(repo_group, validated_updates)
314 RepoGroupModel().update(repo_group, validated_updates)
315 Session().commit()
315 Session().commit()
316 return {
316 return {
317 'msg': 'updated repository group ID:%s %s' % (
317 'msg': 'updated repository group ID:%s %s' % (
318 repo_group.group_id, repo_group.group_name),
318 repo_group.group_id, repo_group.group_name),
319 'repo_group': repo_group.get_api_data()
319 'repo_group': repo_group.get_api_data()
320 }
320 }
321 except Exception:
321 except Exception:
322 log.exception(
322 log.exception(
323 u"Exception occurred while trying update repo group %s",
323 u"Exception occurred while trying update repo group %s",
324 repogroupid)
324 repogroupid)
325 raise JSONRPCError('failed to update repository group `%s`'
325 raise JSONRPCError('failed to update repository group `%s`'
326 % (repogroupid,))
326 % (repogroupid,))
327
327
328
328
329 @jsonrpc_method()
329 @jsonrpc_method()
330 def delete_repo_group(request, apiuser, repogroupid):
330 def delete_repo_group(request, apiuser, repogroupid):
331 """
331 """
332 Deletes a |repo| group.
332 Deletes a |repo| group.
333
333
334 :param apiuser: This is filled automatically from the |authtoken|.
334 :param apiuser: This is filled automatically from the |authtoken|.
335 :type apiuser: AuthUser
335 :type apiuser: AuthUser
336 :param repogroupid: Set the name or ID of repository group to be
336 :param repogroupid: Set the name or ID of repository group to be
337 deleted.
337 deleted.
338 :type repogroupid: str or int
338 :type repogroupid: str or int
339
339
340 Example output:
340 Example output:
341
341
342 .. code-block:: bash
342 .. code-block:: bash
343
343
344 id : <id_given_in_input>
344 id : <id_given_in_input>
345 result : {
345 result : {
346 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
346 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
347 'repo_group': null
347 'repo_group': null
348 }
348 }
349 error : null
349 error : null
350
350
351 Example error output:
351 Example error output:
352
352
353 .. code-block:: bash
353 .. code-block:: bash
354
354
355 id : <id_given_in_input>
355 id : <id_given_in_input>
356 result : null
356 result : null
357 error : {
357 error : {
358 "failed to delete repo group ID:<repogroupid> <repogroupname>"
358 "failed to delete repo group ID:<repogroupid> <repogroupname>"
359 }
359 }
360
360
361 """
361 """
362
362
363 repo_group = get_repo_group_or_error(repogroupid)
363 repo_group = get_repo_group_or_error(repogroupid)
364 if not has_superadmin_permission(apiuser):
364 if not has_superadmin_permission(apiuser):
365 validate_repo_group_permissions(
365 validate_repo_group_permissions(
366 apiuser, repogroupid, repo_group, ('group.admin',))
366 apiuser, repogroupid, repo_group, ('group.admin',))
367
367
368 try:
368 try:
369 RepoGroupModel().delete(repo_group)
369 RepoGroupModel().delete(repo_group)
370 Session().commit()
370 Session().commit()
371 return {
371 return {
372 'msg': 'deleted repo group ID:%s %s' %
372 'msg': 'deleted repo group ID:%s %s' %
373 (repo_group.group_id, repo_group.group_name),
373 (repo_group.group_id, repo_group.group_name),
374 'repo_group': None
374 'repo_group': None
375 }
375 }
376 except Exception:
376 except Exception:
377 log.exception("Exception occurred while trying to delete repo group")
377 log.exception("Exception occurred while trying to delete repo group")
378 raise JSONRPCError('failed to delete repo group ID:%s %s' %
378 raise JSONRPCError('failed to delete repo group ID:%s %s' %
379 (repo_group.group_id, repo_group.group_name))
379 (repo_group.group_id, repo_group.group_name))
380
380
381
381
382 @jsonrpc_method()
382 @jsonrpc_method()
383 def grant_user_permission_to_repo_group(
383 def grant_user_permission_to_repo_group(
384 request, apiuser, repogroupid, userid, perm,
384 request, apiuser, repogroupid, userid, perm,
385 apply_to_children=Optional('none')):
385 apply_to_children=Optional('none')):
386 """
386 """
387 Grant permission for a user on the given repository group, or update
387 Grant permission for a user on the given repository group, or update
388 existing permissions if found.
388 existing permissions if found.
389
389
390 This command can only be run using an |authtoken| with admin
390 This command can only be run using an |authtoken| with admin
391 permissions.
391 permissions.
392
392
393 :param apiuser: This is filled automatically from the |authtoken|.
393 :param apiuser: This is filled automatically from the |authtoken|.
394 :type apiuser: AuthUser
394 :type apiuser: AuthUser
395 :param repogroupid: Set the name or ID of repository group.
395 :param repogroupid: Set the name or ID of repository group.
396 :type repogroupid: str or int
396 :type repogroupid: str or int
397 :param userid: Set the user name.
397 :param userid: Set the user name.
398 :type userid: str
398 :type userid: str
399 :param perm: (group.(none|read|write|admin))
399 :param perm: (group.(none|read|write|admin))
400 :type perm: str
400 :type perm: str
401 :param apply_to_children: 'none', 'repos', 'groups', 'all'
401 :param apply_to_children: 'none', 'repos', 'groups', 'all'
402 :type apply_to_children: str
402 :type apply_to_children: str
403
403
404 Example output:
404 Example output:
405
405
406 .. code-block:: bash
406 .. code-block:: bash
407
407
408 id : <id_given_in_input>
408 id : <id_given_in_input>
409 result: {
409 result: {
410 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
410 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
411 "success": true
411 "success": true
412 }
412 }
413 error: null
413 error: null
414
414
415 Example error output:
415 Example error output:
416
416
417 .. code-block:: bash
417 .. code-block:: bash
418
418
419 id : <id_given_in_input>
419 id : <id_given_in_input>
420 result : null
420 result : null
421 error : {
421 error : {
422 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
422 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
423 }
423 }
424
424
425 """
425 """
426
426
427 repo_group = get_repo_group_or_error(repogroupid)
427 repo_group = get_repo_group_or_error(repogroupid)
428
428
429 if not has_superadmin_permission(apiuser):
429 if not has_superadmin_permission(apiuser):
430 validate_repo_group_permissions(
430 validate_repo_group_permissions(
431 apiuser, repogroupid, repo_group, ('group.admin',))
431 apiuser, repogroupid, repo_group, ('group.admin',))
432
432
433 user = get_user_or_error(userid)
433 user = get_user_or_error(userid)
434 perm = get_perm_or_error(perm, prefix='group.')
434 perm = get_perm_or_error(perm, prefix='group.')
435 apply_to_children = Optional.extract(apply_to_children)
435 apply_to_children = Optional.extract(apply_to_children)
436
436
437 perm_additions = [[user.user_id, perm, "user"]]
437 perm_additions = [[user.user_id, perm, "user"]]
438 try:
438 try:
439 RepoGroupModel().update_permissions(repo_group=repo_group,
439 RepoGroupModel().update_permissions(repo_group=repo_group,
440 perm_additions=perm_additions,
440 perm_additions=perm_additions,
441 recursive=apply_to_children,
441 recursive=apply_to_children,
442 cur_user=apiuser)
442 cur_user=apiuser)
443 Session().commit()
443 Session().commit()
444 return {
444 return {
445 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
445 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
446 '`%s` in repo group: `%s`' % (
446 '`%s` in repo group: `%s`' % (
447 perm.permission_name, apply_to_children, user.username,
447 perm.permission_name, apply_to_children, user.username,
448 repo_group.name
448 repo_group.name
449 ),
449 ),
450 'success': True
450 'success': True
451 }
451 }
452 except Exception:
452 except Exception:
453 log.exception("Exception occurred while trying to grant "
453 log.exception("Exception occurred while trying to grant "
454 "user permissions to repo group")
454 "user permissions to repo group")
455 raise JSONRPCError(
455 raise JSONRPCError(
456 'failed to edit permission for user: '
456 'failed to edit permission for user: '
457 '`%s` in repo group: `%s`' % (userid, repo_group.name))
457 '`%s` in repo group: `%s`' % (userid, repo_group.name))
458
458
459
459
460 @jsonrpc_method()
460 @jsonrpc_method()
461 def revoke_user_permission_from_repo_group(
461 def revoke_user_permission_from_repo_group(
462 request, apiuser, repogroupid, userid,
462 request, apiuser, repogroupid, userid,
463 apply_to_children=Optional('none')):
463 apply_to_children=Optional('none')):
464 """
464 """
465 Revoke permission for a user in a given repository group.
465 Revoke permission for a user in a given repository group.
466
466
467 This command can only be run using an |authtoken| with admin
467 This command can only be run using an |authtoken| with admin
468 permissions on the |repo| group.
468 permissions on the |repo| group.
469
469
470 :param apiuser: This is filled automatically from the |authtoken|.
470 :param apiuser: This is filled automatically from the |authtoken|.
471 :type apiuser: AuthUser
471 :type apiuser: AuthUser
472 :param repogroupid: Set the name or ID of the repository group.
472 :param repogroupid: Set the name or ID of the repository group.
473 :type repogroupid: str or int
473 :type repogroupid: str or int
474 :param userid: Set the user name to revoke.
474 :param userid: Set the user name to revoke.
475 :type userid: str
475 :type userid: str
476 :param apply_to_children: 'none', 'repos', 'groups', 'all'
476 :param apply_to_children: 'none', 'repos', 'groups', 'all'
477 :type apply_to_children: str
477 :type apply_to_children: str
478
478
479 Example output:
479 Example output:
480
480
481 .. code-block:: bash
481 .. code-block:: bash
482
482
483 id : <id_given_in_input>
483 id : <id_given_in_input>
484 result: {
484 result: {
485 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
485 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
486 "success": true
486 "success": true
487 }
487 }
488 error: null
488 error: null
489
489
490 Example error output:
490 Example error output:
491
491
492 .. code-block:: bash
492 .. code-block:: bash
493
493
494 id : <id_given_in_input>
494 id : <id_given_in_input>
495 result : null
495 result : null
496 error : {
496 error : {
497 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
497 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
498 }
498 }
499
499
500 """
500 """
501
501
502 repo_group = get_repo_group_or_error(repogroupid)
502 repo_group = get_repo_group_or_error(repogroupid)
503
503
504 if not has_superadmin_permission(apiuser):
504 if not has_superadmin_permission(apiuser):
505 validate_repo_group_permissions(
505 validate_repo_group_permissions(
506 apiuser, repogroupid, repo_group, ('group.admin',))
506 apiuser, repogroupid, repo_group, ('group.admin',))
507
507
508 user = get_user_or_error(userid)
508 user = get_user_or_error(userid)
509 apply_to_children = Optional.extract(apply_to_children)
509 apply_to_children = Optional.extract(apply_to_children)
510
510
511 perm_deletions = [[user.user_id, None, "user"]]
511 perm_deletions = [[user.user_id, None, "user"]]
512 try:
512 try:
513 RepoGroupModel().update_permissions(repo_group=repo_group,
513 RepoGroupModel().update_permissions(repo_group=repo_group,
514 perm_deletions=perm_deletions,
514 perm_deletions=perm_deletions,
515 recursive=apply_to_children,
515 recursive=apply_to_children,
516 cur_user=apiuser)
516 cur_user=apiuser)
517 Session().commit()
517 Session().commit()
518 return {
518 return {
519 'msg': 'Revoked perm (recursive:%s) for user: '
519 'msg': 'Revoked perm (recursive:%s) for user: '
520 '`%s` in repo group: `%s`' % (
520 '`%s` in repo group: `%s`' % (
521 apply_to_children, user.username, repo_group.name
521 apply_to_children, user.username, repo_group.name
522 ),
522 ),
523 'success': True
523 'success': True
524 }
524 }
525 except Exception:
525 except Exception:
526 log.exception("Exception occurred while trying revoke user "
526 log.exception("Exception occurred while trying revoke user "
527 "permission from repo group")
527 "permission from repo group")
528 raise JSONRPCError(
528 raise JSONRPCError(
529 'failed to edit permission for user: '
529 'failed to edit permission for user: '
530 '`%s` in repo group: `%s`' % (userid, repo_group.name))
530 '`%s` in repo group: `%s`' % (userid, repo_group.name))
531
531
532
532
533 @jsonrpc_method()
533 @jsonrpc_method()
534 def grant_user_group_permission_to_repo_group(
534 def grant_user_group_permission_to_repo_group(
535 request, apiuser, repogroupid, usergroupid, perm,
535 request, apiuser, repogroupid, usergroupid, perm,
536 apply_to_children=Optional('none'), ):
536 apply_to_children=Optional('none'), ):
537 """
537 """
538 Grant permission for a user group on given repository group, or update
538 Grant permission for a user group on given repository group, or update
539 existing permissions if found.
539 existing permissions if found.
540
540
541 This command can only be run using an |authtoken| with admin
541 This command can only be run using an |authtoken| with admin
542 permissions on the |repo| group.
542 permissions on the |repo| group.
543
543
544 :param apiuser: This is filled automatically from the |authtoken|.
544 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
545 :type apiuser: AuthUser
546 :param repogroupid: Set the name or id of repository group
546 :param repogroupid: Set the name or id of repository group
547 :type repogroupid: str or int
547 :type repogroupid: str or int
548 :param usergroupid: id of usergroup
548 :param usergroupid: id of usergroup
549 :type usergroupid: str or int
549 :type usergroupid: str or int
550 :param perm: (group.(none|read|write|admin))
550 :param perm: (group.(none|read|write|admin))
551 :type perm: str
551 :type perm: str
552 :param apply_to_children: 'none', 'repos', 'groups', 'all'
552 :param apply_to_children: 'none', 'repos', 'groups', 'all'
553 :type apply_to_children: str
553 :type apply_to_children: str
554
554
555 Example output:
555 Example output:
556
556
557 .. code-block:: bash
557 .. code-block:: bash
558
558
559 id : <id_given_in_input>
559 id : <id_given_in_input>
560 result : {
560 result : {
561 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
561 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
562 "success": true
562 "success": true
563
563
564 }
564 }
565 error : null
565 error : null
566
566
567 Example error output:
567 Example error output:
568
568
569 .. code-block:: bash
569 .. code-block:: bash
570
570
571 id : <id_given_in_input>
571 id : <id_given_in_input>
572 result : null
572 result : null
573 error : {
573 error : {
574 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
574 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
575 }
575 }
576
576
577 """
577 """
578
578
579 repo_group = get_repo_group_or_error(repogroupid)
579 repo_group = get_repo_group_or_error(repogroupid)
580 perm = get_perm_or_error(perm, prefix='group.')
580 perm = get_perm_or_error(perm, prefix='group.')
581 user_group = get_user_group_or_error(usergroupid)
581 user_group = get_user_group_or_error(usergroupid)
582 if not has_superadmin_permission(apiuser):
582 if not has_superadmin_permission(apiuser):
583 validate_repo_group_permissions(
583 validate_repo_group_permissions(
584 apiuser, repogroupid, repo_group, ('group.admin',))
584 apiuser, repogroupid, repo_group, ('group.admin',))
585
585
586 # check if we have at least read permission for this user group !
586 # check if we have at least read permission for this user group !
587 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
587 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
588 if not HasUserGroupPermissionAnyApi(*_perms)(
588 if not HasUserGroupPermissionAnyApi(*_perms)(
589 user=apiuser, user_group_name=user_group.users_group_name):
589 user=apiuser, user_group_name=user_group.users_group_name):
590 raise JSONRPCError(
590 raise JSONRPCError(
591 'user group `%s` does not exist' % (usergroupid,))
591 'user group `%s` does not exist' % (usergroupid,))
592
592
593 apply_to_children = Optional.extract(apply_to_children)
593 apply_to_children = Optional.extract(apply_to_children)
594
594
595 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
595 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
596 try:
596 try:
597 RepoGroupModel().update_permissions(repo_group=repo_group,
597 RepoGroupModel().update_permissions(repo_group=repo_group,
598 perm_additions=perm_additions,
598 perm_additions=perm_additions,
599 recursive=apply_to_children,
599 recursive=apply_to_children,
600 cur_user=apiuser)
600 cur_user=apiuser)
601 Session().commit()
601 Session().commit()
602 return {
602 return {
603 'msg': 'Granted perm: `%s` (recursive:%s) '
603 'msg': 'Granted perm: `%s` (recursive:%s) '
604 'for user group: `%s` in repo group: `%s`' % (
604 'for user group: `%s` in repo group: `%s`' % (
605 perm.permission_name, apply_to_children,
605 perm.permission_name, apply_to_children,
606 user_group.users_group_name, repo_group.name
606 user_group.users_group_name, repo_group.name
607 ),
607 ),
608 'success': True
608 'success': True
609 }
609 }
610 except Exception:
610 except Exception:
611 log.exception("Exception occurred while trying to grant user "
611 log.exception("Exception occurred while trying to grant user "
612 "group permissions to repo group")
612 "group permissions to repo group")
613 raise JSONRPCError(
613 raise JSONRPCError(
614 'failed to edit permission for user group: `%s` in '
614 'failed to edit permission for user group: `%s` in '
615 'repo group: `%s`' % (
615 'repo group: `%s`' % (
616 usergroupid, repo_group.name
616 usergroupid, repo_group.name
617 )
617 )
618 )
618 )
619
619
620
620
621 @jsonrpc_method()
621 @jsonrpc_method()
622 def revoke_user_group_permission_from_repo_group(
622 def revoke_user_group_permission_from_repo_group(
623 request, apiuser, repogroupid, usergroupid,
623 request, apiuser, repogroupid, usergroupid,
624 apply_to_children=Optional('none')):
624 apply_to_children=Optional('none')):
625 """
625 """
626 Revoke permission for user group on given repository.
626 Revoke permission for user group on given repository.
627
627
628 This command can only be run using an |authtoken| with admin
628 This command can only be run using an |authtoken| with admin
629 permissions on the |repo| group.
629 permissions on the |repo| group.
630
630
631 :param apiuser: This is filled automatically from the |authtoken|.
631 :param apiuser: This is filled automatically from the |authtoken|.
632 :type apiuser: AuthUser
632 :type apiuser: AuthUser
633 :param repogroupid: name or id of repository group
633 :param repogroupid: name or id of repository group
634 :type repogroupid: str or int
634 :type repogroupid: str or int
635 :param usergroupid:
635 :param usergroupid:
636 :param apply_to_children: 'none', 'repos', 'groups', 'all'
636 :param apply_to_children: 'none', 'repos', 'groups', 'all'
637 :type apply_to_children: str
637 :type apply_to_children: str
638
638
639 Example output:
639 Example output:
640
640
641 .. code-block:: bash
641 .. code-block:: bash
642
642
643 id : <id_given_in_input>
643 id : <id_given_in_input>
644 result: {
644 result: {
645 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
645 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
646 "success": true
646 "success": true
647 }
647 }
648 error: null
648 error: null
649
649
650 Example error output:
650 Example error output:
651
651
652 .. code-block:: bash
652 .. code-block:: bash
653
653
654 id : <id_given_in_input>
654 id : <id_given_in_input>
655 result : null
655 result : null
656 error : {
656 error : {
657 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
657 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
658 }
658 }
659
659
660
660
661 """
661 """
662
662
663 repo_group = get_repo_group_or_error(repogroupid)
663 repo_group = get_repo_group_or_error(repogroupid)
664 user_group = get_user_group_or_error(usergroupid)
664 user_group = get_user_group_or_error(usergroupid)
665 if not has_superadmin_permission(apiuser):
665 if not has_superadmin_permission(apiuser):
666 validate_repo_group_permissions(
666 validate_repo_group_permissions(
667 apiuser, repogroupid, repo_group, ('group.admin',))
667 apiuser, repogroupid, repo_group, ('group.admin',))
668
668
669 # check if we have at least read permission for this user group !
669 # check if we have at least read permission for this user group !
670 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
670 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
671 if not HasUserGroupPermissionAnyApi(*_perms)(
671 if not HasUserGroupPermissionAnyApi(*_perms)(
672 user=apiuser, user_group_name=user_group.users_group_name):
672 user=apiuser, user_group_name=user_group.users_group_name):
673 raise JSONRPCError(
673 raise JSONRPCError(
674 'user group `%s` does not exist' % (usergroupid,))
674 'user group `%s` does not exist' % (usergroupid,))
675
675
676 apply_to_children = Optional.extract(apply_to_children)
676 apply_to_children = Optional.extract(apply_to_children)
677
677
678 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
678 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
679 try:
679 try:
680 RepoGroupModel().update_permissions(repo_group=repo_group,
680 RepoGroupModel().update_permissions(repo_group=repo_group,
681 perm_deletions=perm_deletions,
681 perm_deletions=perm_deletions,
682 recursive=apply_to_children,
682 recursive=apply_to_children,
683 cur_user=apiuser)
683 cur_user=apiuser)
684 Session().commit()
684 Session().commit()
685 return {
685 return {
686 'msg': 'Revoked perm (recursive:%s) for user group: '
686 'msg': 'Revoked perm (recursive:%s) for user group: '
687 '`%s` in repo group: `%s`' % (
687 '`%s` in repo group: `%s`' % (
688 apply_to_children, user_group.users_group_name,
688 apply_to_children, user_group.users_group_name,
689 repo_group.name
689 repo_group.name
690 ),
690 ),
691 'success': True
691 'success': True
692 }
692 }
693 except Exception:
693 except Exception:
694 log.exception("Exception occurred while trying revoke user group "
694 log.exception("Exception occurred while trying revoke user group "
695 "permissions from repo group")
695 "permissions from repo group")
696 raise JSONRPCError(
696 raise JSONRPCError(
697 'failed to edit permission for user group: '
697 'failed to edit permission for user group: '
698 '`%s` in repo group: `%s`' % (
698 '`%s` in repo group: `%s`' % (
699 user_group.users_group_name, repo_group.name
699 user_group.users_group_name, repo_group.name
700 )
700 )
701 )
701 )
702
702
@@ -1,178 +1,178 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
25
25
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
28 from rhodecode.lib.utils import repo2db_mapper
28 from rhodecode.lib.utils import repo2db_mapper
29 from rhodecode.model.db import UserIpMap
29 from rhodecode.model.db import UserIpMap
30 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.scm import ScmModel
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 @jsonrpc_method()
35 @jsonrpc_method()
36 def get_server_info(request, apiuser):
36 def get_server_info(request, apiuser):
37 """
37 """
38 Returns the |RCE| server information.
38 Returns the |RCE| server information.
39
39
40 This includes the running version of |RCE| and all installed
40 This includes the running version of |RCE| and all installed
41 packages. This command takes the following options:
41 packages. This command takes the following options:
42
42
43 :param apiuser: This is filled automatically from the |authtoken|.
43 :param apiuser: This is filled automatically from the |authtoken|.
44 :type apiuser: AuthUser
44 :type apiuser: AuthUser
45
45
46 Example output:
46 Example output:
47
47
48 .. code-block:: bash
48 .. code-block:: bash
49
49
50 id : <id_given_in_input>
50 id : <id_given_in_input>
51 result : {
51 result : {
52 'modules': [<module name>,...]
52 'modules': [<module name>,...]
53 'py_version': <python version>,
53 'py_version': <python version>,
54 'platform': <platform type>,
54 'platform': <platform type>,
55 'rhodecode_version': <rhodecode version>
55 'rhodecode_version': <rhodecode version>
56 }
56 }
57 error : null
57 error : null
58 """
58 """
59
59
60 if not has_superadmin_permission(apiuser):
60 if not has_superadmin_permission(apiuser):
61 raise JSONRPCForbidden()
61 raise JSONRPCForbidden()
62
62
63 server_info = ScmModel().get_server_info(request.environ)
63 server_info = ScmModel().get_server_info(request.environ)
64 # rhodecode-index requires those
64 # rhodecode-index requires those
65
65
66 server_info['index_storage'] = server_info['search']['value']['location']
66 server_info['index_storage'] = server_info['search']['value']['location']
67 server_info['storage'] = server_info['storage']['value']['path']
67 server_info['storage'] = server_info['storage']['value']['path']
68
68
69 return server_info
69 return server_info
70
70
71
71
72 @jsonrpc_method()
72 @jsonrpc_method()
73 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
73 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
74 """
74 """
75 Displays the IP Address as seen from the |RCE| server.
75 Displays the IP Address as seen from the |RCE| server.
76
76
77 * This command displays the IP Address, as well as all the defined IP
77 * This command displays the IP Address, as well as all the defined IP
78 addresses for the specified user. If the ``userid`` is not set, the
78 addresses for the specified user. If the ``userid`` is not set, the
79 data returned is for the user calling the method.
79 data returned is for the user calling the method.
80
80
81 This command can only be run using an |authtoken| with admin rights to
81 This command can only be run using an |authtoken| with admin rights to
82 the specified repository.
82 the specified repository.
83
83
84 This command takes the following options:
84 This command takes the following options:
85
85
86 :param apiuser: This is filled automatically from |authtoken|.
86 :param apiuser: This is filled automatically from |authtoken|.
87 :type apiuser: AuthUser
87 :type apiuser: AuthUser
88 :param userid: Sets the userid for which associated IP Address data
88 :param userid: Sets the userid for which associated IP Address data
89 is returned.
89 is returned.
90 :type userid: Optional(str or int)
90 :type userid: Optional(str or int)
91
91
92 Example output:
92 Example output:
93
93
94 .. code-block:: bash
94 .. code-block:: bash
95
95
96 id : <id_given_in_input>
96 id : <id_given_in_input>
97 result : {
97 result : {
98 "server_ip_addr": "<ip_from_clien>",
98 "server_ip_addr": "<ip_from_clien>",
99 "user_ips": [
99 "user_ips": [
100 {
100 {
101 "ip_addr": "<ip_with_mask>",
101 "ip_addr": "<ip_with_mask>",
102 "ip_range": ["<start_ip>", "<end_ip>"],
102 "ip_range": ["<start_ip>", "<end_ip>"],
103 },
103 },
104 ...
104 ...
105 ]
105 ]
106 }
106 }
107
107
108 """
108 """
109 if not has_superadmin_permission(apiuser):
109 if not has_superadmin_permission(apiuser):
110 raise JSONRPCForbidden()
110 raise JSONRPCForbidden()
111
111
112 userid = Optional.extract(userid, evaluate_locals=locals())
112 userid = Optional.extract(userid, evaluate_locals=locals())
113 userid = getattr(userid, 'user_id', userid)
113 userid = getattr(userid, 'user_id', userid)
114
114
115 user = get_user_or_error(userid)
115 user = get_user_or_error(userid)
116 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
116 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
117 return {
117 return {
118 'server_ip_addr': request.rpc_ip_addr,
118 'server_ip_addr': request.rpc_ip_addr,
119 'user_ips': ips
119 'user_ips': ips
120 }
120 }
121
121
122
122
123 @jsonrpc_method()
123 @jsonrpc_method()
124 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
124 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
125 """
125 """
126 Triggers a rescan of the specified repositories.
126 Triggers a rescan of the specified repositories.
127
127
128 * If the ``remove_obsolete`` option is set, it also deletes repositories
128 * If the ``remove_obsolete`` option is set, it also deletes repositories
129 that are found in the database but not on the file system, so called
129 that are found in the database but not on the file system, so called
130 "clean zombies".
130 "clean zombies".
131
131
132 This command can only be run using an |authtoken| with admin rights to
132 This command can only be run using an |authtoken| with admin rights to
133 the specified repository.
133 the specified repository.
134
134
135 This command takes the following options:
135 This command takes the following options:
136
136
137 :param apiuser: This is filled automatically from the |authtoken|.
137 :param apiuser: This is filled automatically from the |authtoken|.
138 :type apiuser: AuthUser
138 :type apiuser: AuthUser
139 :param remove_obsolete: Deletes repositories from the database that
139 :param remove_obsolete: Deletes repositories from the database that
140 are not found on the filesystem.
140 are not found on the filesystem.
141 :type remove_obsolete: Optional(``True`` | ``False``)
141 :type remove_obsolete: Optional(``True`` | ``False``)
142
142
143 Example output:
143 Example output:
144
144
145 .. code-block:: bash
145 .. code-block:: bash
146
146
147 id : <id_given_in_input>
147 id : <id_given_in_input>
148 result : {
148 result : {
149 'added': [<added repository name>,...]
149 'added': [<added repository name>,...]
150 'removed': [<removed repository name>,...]
150 'removed': [<removed repository name>,...]
151 }
151 }
152 error : null
152 error : null
153
153
154 Example error output:
154 Example error output:
155
155
156 .. code-block:: bash
156 .. code-block:: bash
157
157
158 id : <id_given_in_input>
158 id : <id_given_in_input>
159 result : null
159 result : null
160 error : {
160 error : {
161 'Error occurred during rescan repositories action'
161 'Error occurred during rescan repositories action'
162 }
162 }
163
163
164 """
164 """
165 if not has_superadmin_permission(apiuser):
165 if not has_superadmin_permission(apiuser):
166 raise JSONRPCForbidden()
166 raise JSONRPCForbidden()
167
167
168 try:
168 try:
169 rm_obsolete = Optional.extract(remove_obsolete)
169 rm_obsolete = Optional.extract(remove_obsolete)
170 added, removed = repo2db_mapper(ScmModel().repo_scan(),
170 added, removed = repo2db_mapper(ScmModel().repo_scan(),
171 remove_obsolete=rm_obsolete)
171 remove_obsolete=rm_obsolete)
172 return {'added': added, 'removed': removed}
172 return {'added': added, 'removed': removed}
173 except Exception:
173 except Exception:
174 log.exception('Failed to run repo rescann')
174 log.exception('Failed to run repo rescann')
175 raise JSONRPCError(
175 raise JSONRPCError(
176 'Error occurred during rescan repositories action'
176 'Error occurred during rescan repositories action'
177 )
177 )
178
178
@@ -1,86 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import datetime
22 import datetime
23 import decimal
23 import decimal
24 import logging
24 import logging
25 import time
25 import time
26
26
27 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method, JSONRPCError, JSONRPCForbidden
27 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method, JSONRPCError, JSONRPCForbidden
28
28
29 from rhodecode.api.utils import Optional, OAttr
29 from rhodecode.api.utils import Optional, OAttr
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 @jsonrpc_method()
34 @jsonrpc_method()
35 def test(request, apiuser, args):
35 def test(request, apiuser, args):
36 return args
36 return args
37
37
38
38
39 @jsonrpc_method()
39 @jsonrpc_method()
40 def test_ok(request, apiuser):
40 def test_ok(request, apiuser):
41 return {
41 return {
42 'who': u'hello {} '.format(apiuser),
42 'who': u'hello {} '.format(apiuser),
43 'obj': {
43 'obj': {
44 'time': time.time(),
44 'time': time.time(),
45 'dt': datetime.datetime.now(),
45 'dt': datetime.datetime.now(),
46 'decimal': decimal.Decimal('0.123')
46 'decimal': decimal.Decimal('0.123')
47 }
47 }
48 }
48 }
49
49
50
50
51 @jsonrpc_method()
51 @jsonrpc_method()
52 def test_error(request, apiuser):
52 def test_error(request, apiuser):
53 raise JSONRPCError('error happened')
53 raise JSONRPCError('error happened')
54
54
55
55
56 @jsonrpc_method()
56 @jsonrpc_method()
57 def test_exception(request, apiuser):
57 def test_exception(request, apiuser):
58 raise Exception('something unhanddled')
58 raise Exception('something unhanddled')
59
59
60
60
61 @jsonrpc_method()
61 @jsonrpc_method()
62 def test_params(request, apiuser, params):
62 def test_params(request, apiuser, params):
63 return u'hello apiuser:{} params:{}'.format(apiuser, params)
63 return u'hello apiuser:{} params:{}'.format(apiuser, params)
64
64
65
65
66 @jsonrpc_method()
66 @jsonrpc_method()
67 def test_params_opt(
67 def test_params_opt(
68 request, apiuser, params, opt1=False, opt2=Optional(True),
68 request, apiuser, params, opt1=False, opt2=Optional(True),
69 opt3=Optional(OAttr('apiuser'))):
69 opt3=Optional(OAttr('apiuser'))):
70 opt2 = Optional.extract(opt2)
70 opt2 = Optional.extract(opt2)
71 opt3 = Optional.extract(opt3, evaluate_locals=locals())
71 opt3 = Optional.extract(opt3, evaluate_locals=locals())
72
72
73 return u'hello apiuser:{} params:{}, opt:[{},{},{}]'.format(
73 return u'hello apiuser:{} params:{}, opt:[{},{},{}]'.format(
74 apiuser, params, opt1, opt2, opt3)
74 apiuser, params, opt1, opt2, opt3)
75
75
76
76
77 @jsonrpc_method()
77 @jsonrpc_method()
78 @jsonrpc_deprecated_method(
78 @jsonrpc_deprecated_method(
79 use_method='test_ok', deprecated_at_version='4.0.0')
79 use_method='test_ok', deprecated_at_version='4.0.0')
80 def test_deprecated_method(request, apiuser):
80 def test_deprecated_method(request, apiuser):
81 return u'value'
81 return u'value'
82
82
83
83
84 @jsonrpc_method()
84 @jsonrpc_method()
85 def test_forbidden_method(request, apiuser):
85 def test_forbidden_method(request, apiuser):
86 raise JSONRPCForbidden()
86 raise JSONRPCForbidden()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
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