##// END OF EJS Templates
integrations: fixed code for python3
super-admin -
r5064:9a69dbc2 default
parent child Browse files
Show More
@@ -1,243 +1,243 b''
1
1
2
2
3 # Copyright (C) 2012-2020 RhodeCode GmbH
3 # Copyright (C) 2012-2020 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
22
23 from rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
23 from rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
24 from rhodecode.lib.utils2 import safe_int
24 from rhodecode.lib.utils2 import safe_int
25 from rhodecode.model.db import Repository, Integration, RepoGroup
25 from rhodecode.model.db import Repository, Integration, RepoGroup
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27 from rhodecode.integrations.views import GlobalIntegrationsView
27 from rhodecode.integrations.views import GlobalIntegrationsView
28 from rhodecode.integrations.views import RepoGroupIntegrationsView
28 from rhodecode.integrations.views import RepoGroupIntegrationsView
29 from rhodecode.integrations.views import RepoIntegrationsView
29 from rhodecode.integrations.views import RepoIntegrationsView
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class ValidIntegrationPredicate(object):
34 class ValidIntegrationPredicate(object):
35 def __init__(self, val, config):
35 def __init__(self, val, config):
36 self.val = val
36 self.val = val
37
37
38 def text(self):
38 def text(self):
39 return 'valid_integration_route = %s' % self.val
39 return f'valid_integration_route = {self.val}'
40
40
41 phash = text
41 phash = text
42
42
43 def __call__(self, info, request):
43 def __call__(self, info, request):
44 integration_type = info['match']['integration']
44 integration_type = info['match']['integration']
45 integration_id = info['match'].get('integration_id')
45 integration_id = info['match'].get('integration_id')
46
46
47 if integration_type not in integration_type_registry:
47 if integration_type not in integration_type_registry:
48 return False
48 return False
49
49
50 if integration_id:
50 if integration_id:
51 if not safe_int(integration_id):
51 if not safe_int(integration_id):
52 return False
52 return False
53
53
54 integration = Integration.get(integration_id)
54 integration = Integration.get(integration_id)
55 if not integration:
55 if not integration:
56 return False
56 return False
57 if integration.integration_type != integration_type:
57 if integration.integration_type != integration_type:
58 return False
58 return False
59
59
60 # match types to repo or repo group
60 # match types to repo or repo group
61 repo_name = info['match'].get('repo_name')
61 repo_name = info['match'].get('repo_name')
62 repo_group_name = info['match'].get('repo_group_name')
62 repo_group_name = info['match'].get('repo_group_name')
63 repo, repo_group = None, None
63 repo, repo_group = None, None
64 if repo_name:
64 if repo_name:
65 repo = Repository.get_by_repo_name(repo_name)
65 repo = Repository.get_by_repo_name(repo_name)
66 if not repo:
66 if not repo:
67 return False
67 return False
68
68
69 if repo_group_name:
69 if repo_group_name:
70 repo_group = RepoGroup.get_by_group_name(repo_group_name)
70 repo_group = RepoGroup.get_by_group_name(repo_group_name)
71 if not repo_group:
71 if not repo_group:
72 return False
72 return False
73
73
74 if repo and repo.repo_id != integration.repo_id:
74 if repo and repo.repo_id != integration.repo_id:
75 return False
75 return False
76 if repo_group and repo_group.group_id != integration.repo_group_id:
76 if repo_group and repo_group.group_id != integration.repo_group_id:
77 return False
77 return False
78
78
79 return True
79 return True
80
80
81
81
82 def includeme(config):
82 def includeme(config):
83 config.add_route_predicate(
83 config.add_route_predicate(
84 'valid_integration', ValidIntegrationPredicate)
84 'valid_integration', ValidIntegrationPredicate)
85
85
86 # global integrations
86 # global integrations
87 config.add_route('global_integrations_new',
87 config.add_route('global_integrations_new',
88 ADMIN_PREFIX + '/integrations/new')
88 ADMIN_PREFIX + '/integrations/new')
89 config.add_view(GlobalIntegrationsView,
89 config.add_view(GlobalIntegrationsView,
90 attr='new_integration',
90 attr='new_integration',
91 renderer='rhodecode:templates/admin/integrations/new.mako',
91 renderer='rhodecode:templates/admin/integrations/new.mako',
92 request_method='GET',
92 request_method='GET',
93 route_name='global_integrations_new')
93 route_name='global_integrations_new')
94
94
95 config.add_route('global_integrations_home',
95 config.add_route('global_integrations_home',
96 ADMIN_PREFIX + '/integrations')
96 ADMIN_PREFIX + '/integrations')
97 config.add_route('global_integrations_list',
97 config.add_route('global_integrations_list',
98 ADMIN_PREFIX + '/integrations/{integration}')
98 ADMIN_PREFIX + '/integrations/{integration}')
99 for route_name in ['global_integrations_home', 'global_integrations_list']:
99 for route_name in ['global_integrations_home', 'global_integrations_list']:
100 config.add_view(GlobalIntegrationsView,
100 config.add_view(GlobalIntegrationsView,
101 attr='integration_list',
101 attr='integration_list',
102 renderer='rhodecode:templates/admin/integrations/list.mako',
102 renderer='rhodecode:templates/admin/integrations/list.mako',
103 request_method='GET',
103 request_method='GET',
104 route_name=route_name)
104 route_name=route_name)
105
105
106 config.add_route('global_integrations_create',
106 config.add_route('global_integrations_create',
107 ADMIN_PREFIX + '/integrations/{integration}/new',
107 ADMIN_PREFIX + '/integrations/{integration}/new',
108 valid_integration=True)
108 valid_integration=True)
109 config.add_route('global_integrations_edit',
109 config.add_route('global_integrations_edit',
110 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
110 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
111 valid_integration=True)
111 valid_integration=True)
112
112
113 for route_name in ['global_integrations_create', 'global_integrations_edit']:
113 for route_name in ['global_integrations_create', 'global_integrations_edit']:
114 config.add_view(GlobalIntegrationsView,
114 config.add_view(GlobalIntegrationsView,
115 attr='settings_get',
115 attr='settings_get',
116 renderer='rhodecode:templates/admin/integrations/form.mako',
116 renderer='rhodecode:templates/admin/integrations/form.mako',
117 request_method='GET',
117 request_method='GET',
118 route_name=route_name)
118 route_name=route_name)
119 config.add_view(GlobalIntegrationsView,
119 config.add_view(GlobalIntegrationsView,
120 attr='settings_post',
120 attr='settings_post',
121 renderer='rhodecode:templates/admin/integrations/form.mako',
121 renderer='rhodecode:templates/admin/integrations/form.mako',
122 request_method='POST',
122 request_method='POST',
123 route_name=route_name)
123 route_name=route_name)
124
124
125 # repo group integrations
125 # repo group integrations
126 config.add_route('repo_group_integrations_home',
126 config.add_route('repo_group_integrations_home',
127 add_route_requirements('/{repo_group_name}/_settings/integrations'),
127 add_route_requirements('/{repo_group_name}/_settings/integrations'),
128 repo_group_route=True)
128 repo_group_route=True)
129
129
130 config.add_view(RepoGroupIntegrationsView,
130 config.add_view(RepoGroupIntegrationsView,
131 attr='integration_list',
131 attr='integration_list',
132 renderer='rhodecode:templates/admin/integrations/list.mako',
132 renderer='rhodecode:templates/admin/integrations/list.mako',
133 request_method='GET',
133 request_method='GET',
134 route_name='repo_group_integrations_home')
134 route_name='repo_group_integrations_home')
135
135
136 config.add_route('repo_group_integrations_new',
136 config.add_route('repo_group_integrations_new',
137 add_route_requirements('/{repo_group_name}/_settings/integrations/new'),
137 add_route_requirements('/{repo_group_name}/_settings/integrations/new'),
138 repo_group_route=True)
138 repo_group_route=True)
139 config.add_view(RepoGroupIntegrationsView,
139 config.add_view(RepoGroupIntegrationsView,
140 attr='new_integration',
140 attr='new_integration',
141 renderer='rhodecode:templates/admin/integrations/new.mako',
141 renderer='rhodecode:templates/admin/integrations/new.mako',
142 request_method='GET',
142 request_method='GET',
143 route_name='repo_group_integrations_new')
143 route_name='repo_group_integrations_new')
144
144
145 config.add_route('repo_group_integrations_list',
145 config.add_route('repo_group_integrations_list',
146 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}'),
146 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}'),
147 repo_group_route=True,
147 repo_group_route=True,
148 valid_integration=True)
148 valid_integration=True)
149 config.add_view(RepoGroupIntegrationsView,
149 config.add_view(RepoGroupIntegrationsView,
150 attr='integration_list',
150 attr='integration_list',
151 renderer='rhodecode:templates/admin/integrations/list.mako',
151 renderer='rhodecode:templates/admin/integrations/list.mako',
152 request_method='GET',
152 request_method='GET',
153 route_name='repo_group_integrations_list')
153 route_name='repo_group_integrations_list')
154
154
155 config.add_route('repo_group_integrations_create',
155 config.add_route('repo_group_integrations_create',
156 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/new'),
156 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/new'),
157 repo_group_route=True,
157 repo_group_route=True,
158 valid_integration=True)
158 valid_integration=True)
159 config.add_view(RepoGroupIntegrationsView,
159 config.add_view(RepoGroupIntegrationsView,
160 attr='settings_get',
160 attr='settings_get',
161 renderer='rhodecode:templates/admin/integrations/form.mako',
161 renderer='rhodecode:templates/admin/integrations/form.mako',
162 request_method='GET',
162 request_method='GET',
163 route_name='repo_group_integrations_create')
163 route_name='repo_group_integrations_create')
164 config.add_view(RepoGroupIntegrationsView,
164 config.add_view(RepoGroupIntegrationsView,
165 attr='settings_post',
165 attr='settings_post',
166 renderer='rhodecode:templates/admin/integrations/form.mako',
166 renderer='rhodecode:templates/admin/integrations/form.mako',
167 request_method='POST',
167 request_method='POST',
168 route_name='repo_group_integrations_create')
168 route_name='repo_group_integrations_create')
169
169
170 config.add_route('repo_group_integrations_edit',
170 config.add_route('repo_group_integrations_edit',
171 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/{integration_id}'),
171 add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/{integration_id}'),
172 repo_group_route=True,
172 repo_group_route=True,
173 valid_integration=True)
173 valid_integration=True)
174
174
175 config.add_view(RepoGroupIntegrationsView,
175 config.add_view(RepoGroupIntegrationsView,
176 attr='settings_get',
176 attr='settings_get',
177 renderer='rhodecode:templates/admin/integrations/form.mako',
177 renderer='rhodecode:templates/admin/integrations/form.mako',
178 request_method='GET',
178 request_method='GET',
179 route_name='repo_group_integrations_edit')
179 route_name='repo_group_integrations_edit')
180 config.add_view(RepoGroupIntegrationsView,
180 config.add_view(RepoGroupIntegrationsView,
181 attr='settings_post',
181 attr='settings_post',
182 renderer='rhodecode:templates/admin/integrations/form.mako',
182 renderer='rhodecode:templates/admin/integrations/form.mako',
183 request_method='POST',
183 request_method='POST',
184 route_name='repo_group_integrations_edit')
184 route_name='repo_group_integrations_edit')
185
185
186 # repo integrations
186 # repo integrations
187 config.add_route('repo_integrations_home',
187 config.add_route('repo_integrations_home',
188 add_route_requirements('/{repo_name}/settings/integrations'),
188 add_route_requirements('/{repo_name}/settings/integrations'),
189 repo_route=True)
189 repo_route=True)
190 config.add_view(RepoIntegrationsView,
190 config.add_view(RepoIntegrationsView,
191 attr='integration_list',
191 attr='integration_list',
192 request_method='GET',
192 request_method='GET',
193 renderer='rhodecode:templates/admin/integrations/list.mako',
193 renderer='rhodecode:templates/admin/integrations/list.mako',
194 route_name='repo_integrations_home')
194 route_name='repo_integrations_home')
195
195
196 config.add_route('repo_integrations_new',
196 config.add_route('repo_integrations_new',
197 add_route_requirements('/{repo_name}/settings/integrations/new'),
197 add_route_requirements('/{repo_name}/settings/integrations/new'),
198 repo_route=True)
198 repo_route=True)
199 config.add_view(RepoIntegrationsView,
199 config.add_view(RepoIntegrationsView,
200 attr='new_integration',
200 attr='new_integration',
201 renderer='rhodecode:templates/admin/integrations/new.mako',
201 renderer='rhodecode:templates/admin/integrations/new.mako',
202 request_method='GET',
202 request_method='GET',
203 route_name='repo_integrations_new')
203 route_name='repo_integrations_new')
204
204
205 config.add_route('repo_integrations_list',
205 config.add_route('repo_integrations_list',
206 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
206 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
207 repo_route=True,
207 repo_route=True,
208 valid_integration=True)
208 valid_integration=True)
209 config.add_view(RepoIntegrationsView,
209 config.add_view(RepoIntegrationsView,
210 attr='integration_list',
210 attr='integration_list',
211 request_method='GET',
211 request_method='GET',
212 renderer='rhodecode:templates/admin/integrations/list.mako',
212 renderer='rhodecode:templates/admin/integrations/list.mako',
213 route_name='repo_integrations_list')
213 route_name='repo_integrations_list')
214
214
215 config.add_route('repo_integrations_create',
215 config.add_route('repo_integrations_create',
216 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
216 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
217 repo_route=True,
217 repo_route=True,
218 valid_integration=True)
218 valid_integration=True)
219 config.add_view(RepoIntegrationsView,
219 config.add_view(RepoIntegrationsView,
220 attr='settings_get',
220 attr='settings_get',
221 renderer='rhodecode:templates/admin/integrations/form.mako',
221 renderer='rhodecode:templates/admin/integrations/form.mako',
222 request_method='GET',
222 request_method='GET',
223 route_name='repo_integrations_create')
223 route_name='repo_integrations_create')
224 config.add_view(RepoIntegrationsView,
224 config.add_view(RepoIntegrationsView,
225 attr='settings_post',
225 attr='settings_post',
226 renderer='rhodecode:templates/admin/integrations/form.mako',
226 renderer='rhodecode:templates/admin/integrations/form.mako',
227 request_method='POST',
227 request_method='POST',
228 route_name='repo_integrations_create')
228 route_name='repo_integrations_create')
229
229
230 config.add_route('repo_integrations_edit',
230 config.add_route('repo_integrations_edit',
231 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
231 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
232 repo_route=True,
232 repo_route=True,
233 valid_integration=True)
233 valid_integration=True)
234 config.add_view(RepoIntegrationsView,
234 config.add_view(RepoIntegrationsView,
235 attr='settings_get',
235 attr='settings_get',
236 renderer='rhodecode:templates/admin/integrations/form.mako',
236 renderer='rhodecode:templates/admin/integrations/form.mako',
237 request_method='GET',
237 request_method='GET',
238 route_name='repo_integrations_edit')
238 route_name='repo_integrations_edit')
239 config.add_view(RepoIntegrationsView,
239 config.add_view(RepoIntegrationsView,
240 attr='settings_post',
240 attr='settings_post',
241 renderer='rhodecode:templates/admin/integrations/form.mako',
241 renderer='rhodecode:templates/admin/integrations/form.mako',
242 request_method='POST',
242 request_method='POST',
243 route_name='repo_integrations_edit')
243 route_name='repo_integrations_edit')
@@ -1,269 +1,269 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23 from rhodecode.model.db import Integration
23 from rhodecode.model.db import Integration
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.integrations import integration_type_registry
25 from rhodecode.integrations import integration_type_registry
26
26
27
27
28 def route_path(name, **kwargs):
28 def route_path(name, **kwargs):
29 return {
29 return {
30 'home': '/',
30 'home': '/',
31 }[name].format(**kwargs)
31 }[name].format(**kwargs)
32
32
33
33
34 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
34 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
35 admin_view):
35 admin_view):
36 """
36 """
37 Posts form data to create integration at the url given then deletes it and
37 Posts form data to create integration at the url given then deletes it and
38 checks if the redirect url is correct.
38 checks if the redirect url is correct.
39 """
39 """
40 repo_name = repo.repo_name
40 repo_name = repo.repo_name
41 repo_group_name = repo_group.group_name
41 repo_group_name = repo_group.group_name
42 app.post(url, params={}, status=403) # missing csrf check
42 app.post(url, params={}, status=403) # missing csrf check
43 response = app.post(url, params={'csrf_token': csrf_token})
43 response = app.post(url, params={'csrf_token': csrf_token})
44 assert response.status_code == 200
44 assert response.status_code == 200
45 response.mustcontain('Errors exist')
45 response.mustcontain('Errors exist')
46
46
47 scopes_destinations = [
47 scopes_destinations = [
48 ('global',
48 ('global',
49 ADMIN_PREFIX + '/integrations'),
49 ADMIN_PREFIX + '/integrations'),
50 ('root-repos',
50 ('root-repos',
51 ADMIN_PREFIX + '/integrations'),
51 ADMIN_PREFIX + '/integrations'),
52 ('repo:%s' % repo_name,
52 ('repo:%s' % repo_name,
53 '/%s/settings/integrations' % repo_name),
53 '/%s/settings/integrations' % repo_name),
54 ('repogroup:%s' % repo_group_name,
54 ('repogroup:%s' % repo_group_name,
55 '/%s/_settings/integrations' % repo_group_name),
55 '/%s/_settings/integrations' % repo_group_name),
56 ('repogroup-recursive:%s' % repo_group_name,
56 ('repogroup-recursive:%s' % repo_group_name,
57 '/%s/_settings/integrations' % repo_group_name),
57 '/%s/_settings/integrations' % repo_group_name),
58 ]
58 ]
59
59
60 for scope, destination in scopes_destinations:
60 for scope, destination in scopes_destinations:
61 if admin_view:
61 if admin_view:
62 destination = ADMIN_PREFIX + '/integrations'
62 destination = ADMIN_PREFIX + '/integrations'
63
63
64 form_data = [
64 form_data = [
65 ('csrf_token', csrf_token),
65 ('csrf_token', csrf_token),
66 ('__start__', 'options:mapping'),
66 ('__start__', 'options:mapping'),
67 ('name', 'test integration'),
67 ('name', 'test integration'),
68 ('scope', scope),
68 ('scope', scope),
69 ('enabled', 'true'),
69 ('enabled', 'true'),
70 ('__end__', 'options:mapping'),
70 ('__end__', 'options:mapping'),
71 ('__start__', 'settings:mapping'),
71 ('__start__', 'settings:mapping'),
72 ('test_int_field', '34'),
72 ('test_int_field', '34'),
73 ('test_string_field', ''), # empty value on purpose as it's required
73 ('test_string_field', ''), # empty value on purpose as it's required
74 ('__end__', 'settings:mapping'),
74 ('__end__', 'settings:mapping'),
75 ]
75 ]
76 errors_response = app.post(url, form_data)
76 errors_response = app.post(url, form_data)
77 assert 'Errors exist' in errors_response.body
77 assert 'Errors exist' in errors_response.text
78
78
79 form_data[-2] = ('test_string_field', 'data!')
79 form_data[-2] = ('test_string_field', 'data!')
80 assert Session().query(Integration).count() == 0
80 assert Session().query(Integration).count() == 0
81 created_response = app.post(url, form_data)
81 created_response = app.post(url, form_data)
82 assert Session().query(Integration).count() == 1
82 assert Session().query(Integration).count() == 1
83
83
84 delete_response = app.post(
84 delete_response = app.post(
85 created_response.location,
85 created_response.location,
86 params={'csrf_token': csrf_token, 'delete': 'delete'})
86 params={'csrf_token': csrf_token, 'delete': 'delete'})
87
87
88 assert Session().query(Integration).count() == 0
88 assert Session().query(Integration).count() == 0
89 assert delete_response.location.endswith(destination)
89 assert delete_response.location.endswith(destination)
90
90
91
91
92
92
93 @pytest.mark.usefixtures('app', 'autologin_user')
93 @pytest.mark.usefixtures('app', 'autologin_user')
94 class TestIntegrationsView(object):
94 class TestIntegrationsView(object):
95 pass
95 pass
96
96
97
97
98 class TestGlobalIntegrationsView(TestIntegrationsView):
98 class TestGlobalIntegrationsView(TestIntegrationsView):
99 def test_index_no_integrations(self):
99 def test_index_no_integrations(self):
100 url = ADMIN_PREFIX + '/integrations'
100 url = ADMIN_PREFIX + '/integrations'
101 response = self.app.get(url)
101 response = self.app.get(url)
102
102
103 assert response.status_code == 200
103 assert response.status_code == 200
104 response.mustcontain('exist yet')
104 response.mustcontain('exist yet')
105
105
106 def test_index_with_integrations(self, global_integration_stub):
106 def test_index_with_integrations(self, global_integration_stub):
107 url = ADMIN_PREFIX + '/integrations'
107 url = ADMIN_PREFIX + '/integrations'
108 response = self.app.get(url)
108 response = self.app.get(url)
109
109
110 assert response.status_code == 200
110 assert response.status_code == 200
111 response.mustcontain(no=['exist yet'])
111 response.mustcontain(no=['exist yet'])
112 response.mustcontain(global_integration_stub.name)
112 response.mustcontain(global_integration_stub.name)
113
113
114 @pytest.mark.parametrize(
114 @pytest.mark.parametrize(
115 'IntegrationType', integration_type_registry.values())
115 'IntegrationType', integration_type_registry.values())
116 def test_new_integration_page(self, IntegrationType):
116 def test_new_integration_page(self, IntegrationType):
117 url = ADMIN_PREFIX + '/integrations/new'
117 url = ADMIN_PREFIX + '/integrations/new'
118
118
119 response = self.app.get(url, status=200)
119 response = self.app.get(url, status=200)
120 if not IntegrationType.is_dummy:
120 if not IntegrationType.is_dummy:
121 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
121 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
122 integration=IntegrationType.key)
122 integration=IntegrationType.key)
123 response.mustcontain(url)
123 response.mustcontain(url)
124
124
125 @pytest.mark.parametrize(
125 @pytest.mark.parametrize(
126 'IntegrationType', integration_type_registry.values())
126 'IntegrationType', integration_type_registry.values())
127 def test_get_create_integration_page(self, IntegrationType):
127 def test_get_create_integration_page(self, IntegrationType):
128 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
128 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
129 integration_key=IntegrationType.key)
129 integration_key=IntegrationType.key)
130 if IntegrationType.is_dummy:
130 if IntegrationType.is_dummy:
131 self.app.get(url, status=404)
131 self.app.get(url, status=404)
132 else:
132 else:
133 response = self.app.get(url, status=200)
133 response = self.app.get(url, status=200)
134 response.mustcontain(IntegrationType.display_name)
134 response.mustcontain(IntegrationType.display_name)
135
135
136 def test_post_integration_page(self, StubIntegrationType, csrf_token,
136 def test_post_integration_page(self, StubIntegrationType, csrf_token,
137 test_repo_group, backend_random):
137 test_repo_group, backend_random):
138 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
138 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
139 integration_key=StubIntegrationType.key)
139 integration_key=StubIntegrationType.key)
140
140
141 _post_integration_test_helper(
141 _post_integration_test_helper(
142 self.app, url, csrf_token, admin_view=True,
142 self.app, url, csrf_token, admin_view=True,
143 repo=backend_random.repo, repo_group=test_repo_group)
143 repo=backend_random.repo, repo_group=test_repo_group)
144
144
145
145
146 class TestRepoIntegrationsView(TestIntegrationsView):
146 class TestRepoIntegrationsView(TestIntegrationsView):
147 def test_index_no_integrations(self, backend_random):
147 def test_index_no_integrations(self, backend_random):
148 url = '/{repo_name}/settings/integrations'.format(
148 url = '/{repo_name}/settings/integrations'.format(
149 repo_name=backend_random.repo.repo_name)
149 repo_name=backend_random.repo.repo_name)
150 response = self.app.get(url)
150 response = self.app.get(url)
151
151
152 assert response.status_code == 200
152 assert response.status_code == 200
153 response.mustcontain('exist yet')
153 response.mustcontain('exist yet')
154
154
155 def test_index_with_integrations(self, repo_integration_stub):
155 def test_index_with_integrations(self, repo_integration_stub):
156 url = '/{repo_name}/settings/integrations'.format(
156 url = '/{repo_name}/settings/integrations'.format(
157 repo_name=repo_integration_stub.repo.repo_name)
157 repo_name=repo_integration_stub.repo.repo_name)
158 stub_name = repo_integration_stub.name
158 stub_name = repo_integration_stub.name
159
159
160 response = self.app.get(url)
160 response = self.app.get(url)
161
161
162 assert response.status_code == 200
162 assert response.status_code == 200
163 response.mustcontain(stub_name)
163 response.mustcontain(stub_name)
164 response.mustcontain(no=['exist yet'])
164 response.mustcontain(no=['exist yet'])
165
165
166 @pytest.mark.parametrize(
166 @pytest.mark.parametrize(
167 'IntegrationType', integration_type_registry.values())
167 'IntegrationType', integration_type_registry.values())
168 def test_new_integration_page(self, backend_random, IntegrationType):
168 def test_new_integration_page(self, backend_random, IntegrationType):
169 repo_name = backend_random.repo.repo_name
169 repo_name = backend_random.repo.repo_name
170 url = '/{repo_name}/settings/integrations/new'.format(
170 url = '/{repo_name}/settings/integrations/new'.format(
171 repo_name=repo_name)
171 repo_name=repo_name)
172
172
173 response = self.app.get(url, status=200)
173 response = self.app.get(url, status=200)
174
174
175 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
175 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
176 repo_name=repo_name,
176 repo_name=repo_name,
177 integration=IntegrationType.key)
177 integration=IntegrationType.key)
178 if not IntegrationType.is_dummy:
178 if not IntegrationType.is_dummy:
179 response.mustcontain(url)
179 response.mustcontain(url)
180
180
181 @pytest.mark.parametrize(
181 @pytest.mark.parametrize(
182 'IntegrationType', integration_type_registry.values())
182 'IntegrationType', integration_type_registry.values())
183 def test_get_create_integration_page(self, backend_random, IntegrationType):
183 def test_get_create_integration_page(self, backend_random, IntegrationType):
184 repo_name = backend_random.repo.repo_name
184 repo_name = backend_random.repo.repo_name
185 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
185 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
186 repo_name=repo_name, integration_key=IntegrationType.key)
186 repo_name=repo_name, integration_key=IntegrationType.key)
187 if IntegrationType.is_dummy:
187 if IntegrationType.is_dummy:
188 self.app.get(url, status=404)
188 self.app.get(url, status=404)
189 else:
189 else:
190 response = self.app.get(url, status=200)
190 response = self.app.get(url, status=200)
191 response.mustcontain(IntegrationType.display_name)
191 response.mustcontain(IntegrationType.display_name)
192
192
193 def test_post_integration_page(self, backend_random, test_repo_group,
193 def test_post_integration_page(self, backend_random, test_repo_group,
194 StubIntegrationType, csrf_token):
194 StubIntegrationType, csrf_token):
195 repo_name = backend_random.repo.repo_name
195 repo_name = backend_random.repo.repo_name
196 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
196 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
197 repo_name=repo_name, integration_key=StubIntegrationType.key)
197 repo_name=repo_name, integration_key=StubIntegrationType.key)
198
198
199 _post_integration_test_helper(
199 _post_integration_test_helper(
200 self.app, url, csrf_token, admin_view=False,
200 self.app, url, csrf_token, admin_view=False,
201 repo=backend_random.repo, repo_group=test_repo_group)
201 repo=backend_random.repo, repo_group=test_repo_group)
202
202
203
203
204 class TestRepoGroupIntegrationsView(TestIntegrationsView):
204 class TestRepoGroupIntegrationsView(TestIntegrationsView):
205 def test_index_no_integrations(self, test_repo_group):
205 def test_index_no_integrations(self, test_repo_group):
206 url = '/{repo_group_name}/_settings/integrations'.format(
206 url = '/{repo_group_name}/_settings/integrations'.format(
207 repo_group_name=test_repo_group.group_name)
207 repo_group_name=test_repo_group.group_name)
208 response = self.app.get(url)
208 response = self.app.get(url)
209
209
210 assert response.status_code == 200
210 assert response.status_code == 200
211 response.mustcontain('exist yet')
211 response.mustcontain('exist yet')
212
212
213 def test_index_with_integrations(
213 def test_index_with_integrations(
214 self, test_repo_group, repogroup_integration_stub):
214 self, test_repo_group, repogroup_integration_stub):
215
215
216 url = '/{repo_group_name}/_settings/integrations'.format(
216 url = '/{repo_group_name}/_settings/integrations'.format(
217 repo_group_name=test_repo_group.group_name)
217 repo_group_name=test_repo_group.group_name)
218
218
219 stub_name = repogroup_integration_stub.name
219 stub_name = repogroup_integration_stub.name
220 response = self.app.get(url)
220 response = self.app.get(url)
221
221
222 assert response.status_code == 200
222 assert response.status_code == 200
223 response.mustcontain(no=['exist yet'])
223 response.mustcontain(no=['exist yet'])
224 response.mustcontain(stub_name)
224 response.mustcontain(stub_name)
225
225
226 def test_new_integration_page(self, test_repo_group):
226 def test_new_integration_page(self, test_repo_group):
227 repo_group_name = test_repo_group.group_name
227 repo_group_name = test_repo_group.group_name
228 url = '/{repo_group_name}/_settings/integrations/new'.format(
228 url = '/{repo_group_name}/_settings/integrations/new'.format(
229 repo_group_name=test_repo_group.group_name)
229 repo_group_name=test_repo_group.group_name)
230
230
231 response = self.app.get(url)
231 response = self.app.get(url)
232
232
233 assert response.status_code == 200
233 assert response.status_code == 200
234
234
235 for integration_key, integration_obj in integration_type_registry.items():
235 for integration_key, integration_obj in integration_type_registry.items():
236 if not integration_obj.is_dummy:
236 if not integration_obj.is_dummy:
237 nurl = (
237 nurl = (
238 '/{repo_group_name}/_settings/integrations/{integration}/new').format(
238 '/{repo_group_name}/_settings/integrations/{integration}/new').format(
239 repo_group_name=repo_group_name,
239 repo_group_name=repo_group_name,
240 integration=integration_key)
240 integration=integration_key)
241 response.mustcontain(nurl)
241 response.mustcontain(nurl)
242
242
243 @pytest.mark.parametrize(
243 @pytest.mark.parametrize(
244 'IntegrationType', integration_type_registry.values())
244 'IntegrationType', integration_type_registry.values())
245 def test_get_create_integration_page(
245 def test_get_create_integration_page(
246 self, test_repo_group, IntegrationType):
246 self, test_repo_group, IntegrationType):
247
247
248 repo_group_name = test_repo_group.group_name
248 repo_group_name = test_repo_group.group_name
249 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
249 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
250 ).format(repo_group_name=repo_group_name,
250 ).format(repo_group_name=repo_group_name,
251 integration_key=IntegrationType.key)
251 integration_key=IntegrationType.key)
252
252
253 if not IntegrationType.is_dummy:
253 if not IntegrationType.is_dummy:
254 response = self.app.get(url, status=200)
254 response = self.app.get(url, status=200)
255 response.mustcontain(IntegrationType.display_name)
255 response.mustcontain(IntegrationType.display_name)
256 else:
256 else:
257 self.app.get(url, status=404)
257 self.app.get(url, status=404)
258
258
259 def test_post_integration_page(self, test_repo_group, backend_random,
259 def test_post_integration_page(self, test_repo_group, backend_random,
260 StubIntegrationType, csrf_token):
260 StubIntegrationType, csrf_token):
261
261
262 repo_group_name = test_repo_group.group_name
262 repo_group_name = test_repo_group.group_name
263 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
263 url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new'
264 ).format(repo_group_name=repo_group_name,
264 ).format(repo_group_name=repo_group_name,
265 integration_key=StubIntegrationType.key)
265 integration_key=StubIntegrationType.key)
266
266
267 _post_integration_test_helper(
267 _post_integration_test_helper(
268 self.app, url, csrf_token, admin_view=False,
268 self.app, url, csrf_token, admin_view=False,
269 repo=backend_random.repo, repo_group=test_repo_group)
269 repo=backend_random.repo, repo_group=test_repo_group)
@@ -1,450 +1,452 b''
1
1
2
2
3 # Copyright (C) 2012-2020 RhodeCode GmbH
3 # Copyright (C) 2012-2020 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 colander
21 import colander
22 import string
22 import string
23 import collections
23 import collections
24 import logging
24 import logging
25 import requests
25 import requests
26 import urllib.request, urllib.parse, urllib.error
26 import urllib.request
27 import urllib.parse
28 import urllib.error
27 from requests.adapters import HTTPAdapter
29 from requests.adapters import HTTPAdapter
28 from requests.packages.urllib3.util.retry import Retry
30 from requests.packages.urllib3.util.retry import Retry
29
31
30 from mako import exceptions
32 from mako import exceptions
31
33
32 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.translation import _
35 from rhodecode.translation import _
34
36
35
37
36 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
37
39
38
40
39 class UrlTmpl(string.Template):
41 class UrlTmpl(string.Template):
40
42
41 def safe_substitute(self, **kws):
43 def safe_substitute(self, **kws):
42 # url encode the kw for usage in url
44 # url encode the kw for usage in url
43 kws = {k: urllib.parse.quote(safe_str(v)) for k, v in kws.items()}
45 kws = {k: urllib.parse.quote(safe_str(v)) for k, v in kws.items()}
44 return super(UrlTmpl, self).safe_substitute(**kws)
46 return super(UrlTmpl, self).safe_substitute(**kws)
45
47
46
48
47 class IntegrationTypeBase(object):
49 class IntegrationTypeBase(object):
48 """ Base class for IntegrationType plugins """
50 """ Base class for IntegrationType plugins """
49 is_dummy = False
51 is_dummy = False
50 description = ''
52 description = ''
51
53
52 @classmethod
54 @classmethod
53 def icon(cls):
55 def icon(cls):
54 return '''
56 return '''
55 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
57 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
56 <svg
58 <svg
57 xmlns:dc="http://purl.org/dc/elements/1.1/"
59 xmlns:dc="http://purl.org/dc/elements/1.1/"
58 xmlns:cc="http://creativecommons.org/ns#"
60 xmlns:cc="http://creativecommons.org/ns#"
59 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
61 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
60 xmlns:svg="http://www.w3.org/2000/svg"
62 xmlns:svg="http://www.w3.org/2000/svg"
61 xmlns="http://www.w3.org/2000/svg"
63 xmlns="http://www.w3.org/2000/svg"
62 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
64 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
63 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
65 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
64 viewBox="0 -256 1792 1792"
66 viewBox="0 -256 1792 1792"
65 id="svg3025"
67 id="svg3025"
66 version="1.1"
68 version="1.1"
67 inkscape:version="0.48.3.1 r9886"
69 inkscape:version="0.48.3.1 r9886"
68 width="100%"
70 width="100%"
69 height="100%"
71 height="100%"
70 sodipodi:docname="cog_font_awesome.svg">
72 sodipodi:docname="cog_font_awesome.svg">
71 <metadata
73 <metadata
72 id="metadata3035">
74 id="metadata3035">
73 <rdf:RDF>
75 <rdf:RDF>
74 <cc:Work
76 <cc:Work
75 rdf:about="">
77 rdf:about="">
76 <dc:format>image/svg+xml</dc:format>
78 <dc:format>image/svg+xml</dc:format>
77 <dc:type
79 <dc:type
78 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
80 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
79 </cc:Work>
81 </cc:Work>
80 </rdf:RDF>
82 </rdf:RDF>
81 </metadata>
83 </metadata>
82 <defs
84 <defs
83 id="defs3033" />
85 id="defs3033" />
84 <sodipodi:namedview
86 <sodipodi:namedview
85 pagecolor="#ffffff"
87 pagecolor="#ffffff"
86 bordercolor="#666666"
88 bordercolor="#666666"
87 borderopacity="1"
89 borderopacity="1"
88 objecttolerance="10"
90 objecttolerance="10"
89 gridtolerance="10"
91 gridtolerance="10"
90 guidetolerance="10"
92 guidetolerance="10"
91 inkscape:pageopacity="0"
93 inkscape:pageopacity="0"
92 inkscape:pageshadow="2"
94 inkscape:pageshadow="2"
93 inkscape:window-width="640"
95 inkscape:window-width="640"
94 inkscape:window-height="480"
96 inkscape:window-height="480"
95 id="namedview3031"
97 id="namedview3031"
96 showgrid="false"
98 showgrid="false"
97 inkscape:zoom="0.13169643"
99 inkscape:zoom="0.13169643"
98 inkscape:cx="896"
100 inkscape:cx="896"
99 inkscape:cy="896"
101 inkscape:cy="896"
100 inkscape:window-x="0"
102 inkscape:window-x="0"
101 inkscape:window-y="25"
103 inkscape:window-y="25"
102 inkscape:window-maximized="0"
104 inkscape:window-maximized="0"
103 inkscape:current-layer="svg3025" />
105 inkscape:current-layer="svg3025" />
104 <g
106 <g
105 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
107 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
106 id="g3027">
108 id="g3027">
107 <path
109 <path
108 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
110 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
109 id="path3029"
111 id="path3029"
110 inkscape:connector-curvature="0"
112 inkscape:connector-curvature="0"
111 style="fill:currentColor" />
113 style="fill:currentColor" />
112 </g>
114 </g>
113 </svg>
115 </svg>
114 '''
116 '''
115
117
116 def __init__(self, settings):
118 def __init__(self, settings):
117 """
119 """
118 :param settings: dict of settings to be used for the integration
120 :param settings: dict of settings to be used for the integration
119 """
121 """
120 self.settings = settings
122 self.settings = settings
121
123
122 def settings_schema(self):
124 def settings_schema(self):
123 """
125 """
124 A colander schema of settings for the integration type
126 A colander schema of settings for the integration type
125 """
127 """
126 return colander.Schema()
128 return colander.Schema()
127
129
128 def event_enabled(self, event):
130 def event_enabled(self, event):
129 """
131 """
130 Checks if submitted event is enabled based on the plugin settings
132 Checks if submitted event is enabled based on the plugin settings
131 :param event:
133 :param event:
132 :return: bool
134 :return: bool
133 """
135 """
134 allowed_events = self.settings.get('events') or []
136 allowed_events = self.settings.get('events') or []
135 if event.name not in allowed_events:
137 if event.name not in allowed_events:
136 log.debug('event ignored: %r event %s not in allowed set of events %s',
138 log.debug('event ignored: %r event %s not in allowed set of events %s',
137 event, event.name, allowed_events)
139 event, event.name, allowed_events)
138 return False
140 return False
139 return True
141 return True
140
142
141
143
142 class EEIntegration(IntegrationTypeBase):
144 class EEIntegration(IntegrationTypeBase):
143 description = 'Integration available in RhodeCode EE edition.'
145 description = 'Integration available in RhodeCode EE edition.'
144 is_dummy = True
146 is_dummy = True
145
147
146 def __init__(self, name, key, settings=None):
148 def __init__(self, name, key, settings=None):
147 self.display_name = name
149 self.display_name = name
148 self.key = key
150 self.key = key
149 super(EEIntegration, self).__init__(settings)
151 super(EEIntegration, self).__init__(settings)
150
152
151
153
152 # Helpers #
154 # Helpers #
153 # updating this required to update the `common_vars` as well.
155 # updating this required to update the `common_vars` as well.
154 WEBHOOK_URL_VARS = [
156 WEBHOOK_URL_VARS = [
155 # GENERAL
157 # GENERAL
156 ('General', [
158 ('General', [
157 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
159 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
158 ('repo_name', 'Full name of the repository'),
160 ('repo_name', 'Full name of the repository'),
159 ('repo_type', 'VCS type of repository'),
161 ('repo_type', 'VCS type of repository'),
160 ('repo_id', 'Unique id of repository'),
162 ('repo_id', 'Unique id of repository'),
161 ('repo_url', 'Repository url'),
163 ('repo_url', 'Repository url'),
162 ]
164 ]
163 ),
165 ),
164 # extra repo fields
166 # extra repo fields
165 ('Repository', [
167 ('Repository', [
166 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
168 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
167 ]
169 ]
168 ),
170 ),
169 # special attrs below that we handle, using multi-call
171 # special attrs below that we handle, using multi-call
170 ('Commit push - Multicalls', [
172 ('Commit push - Multicalls', [
171 ('branch', 'Name of each branch submitted, if any.'),
173 ('branch', 'Name of each branch submitted, if any.'),
172 ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'),
174 ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'),
173 ('commit_id', 'ID (full sha) of each commit submitted, if any.'),
175 ('commit_id', 'ID (full sha) of each commit submitted, if any.'),
174 ]
176 ]
175 ),
177 ),
176 # pr events vars
178 # pr events vars
177 ('Pull request', [
179 ('Pull request', [
178 ('pull_request_id', 'Unique ID of the pull request.'),
180 ('pull_request_id', 'Unique ID of the pull request.'),
179 ('pull_request_title', 'Title of the pull request.'),
181 ('pull_request_title', 'Title of the pull request.'),
180 ('pull_request_url', 'Pull request url.'),
182 ('pull_request_url', 'Pull request url.'),
181 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
183 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
182 ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. '
184 ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. '
183 'Changes after PR update'),
185 'Changes after PR update'),
184 ]
186 ]
185 ),
187 ),
186 # commit comment event vars
188 # commit comment event vars
187 ('Commit comment', [
189 ('Commit comment', [
188 ('commit_comment_id', 'Unique ID of the comment made on a commit.'),
190 ('commit_comment_id', 'Unique ID of the comment made on a commit.'),
189 ('commit_comment_text', 'Text of commit comment.'),
191 ('commit_comment_text', 'Text of commit comment.'),
190 ('commit_comment_type', 'Type of comment, e.g note/todo.'),
192 ('commit_comment_type', 'Type of comment, e.g note/todo.'),
191
193
192 ('commit_comment_f_path', 'Optionally path of file for inline comments.'),
194 ('commit_comment_f_path', 'Optionally path of file for inline comments.'),
193 ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'),
195 ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'),
194
196
195 ('commit_comment_commit_id', 'Commit id that comment was left at.'),
197 ('commit_comment_commit_id', 'Commit id that comment was left at.'),
196 ('commit_comment_commit_branch', 'Commit branch that comment was left at'),
198 ('commit_comment_commit_branch', 'Commit branch that comment was left at'),
197 ('commit_comment_commit_message', 'Commit message that comment was left at'),
199 ('commit_comment_commit_message', 'Commit message that comment was left at'),
198 ]
200 ]
199 ),
201 ),
200 # user who triggers the call
202 # user who triggers the call
201 ('Caller', [
203 ('Caller', [
202 ('username', 'User who triggered the call.'),
204 ('username', 'User who triggered the call.'),
203 ('user_id', 'User id who triggered the call.'),
205 ('user_id', 'User id who triggered the call.'),
204 ]
206 ]
205 ),
207 ),
206 ]
208 ]
207
209
208 # common vars for url template used for CI plugins. Shared with webhook
210 # common vars for url template used for CI plugins. Shared with webhook
209 CI_URL_VARS = WEBHOOK_URL_VARS
211 CI_URL_VARS = WEBHOOK_URL_VARS
210
212
211
213
212 class CommitParsingDataHandler(object):
214 class CommitParsingDataHandler(object):
213
215
214 def aggregate_branch_data(self, branches, commits):
216 def aggregate_branch_data(self, branches, commits):
215 branch_data = collections.OrderedDict()
217 branch_data = collections.OrderedDict()
216 for obj in branches:
218 for obj in branches:
217 branch_data[obj['name']] = obj
219 branch_data[obj['name']] = obj
218
220
219 branches_commits = collections.OrderedDict()
221 branches_commits = collections.OrderedDict()
220 for commit in commits:
222 for commit in commits:
221 if commit.get('git_ref_change'):
223 if commit.get('git_ref_change'):
222 # special case for GIT that allows creating tags,
224 # special case for GIT that allows creating tags,
223 # deleting branches without associated commit
225 # deleting branches without associated commit
224 continue
226 continue
225 commit_branch = commit['branch']
227 commit_branch = commit['branch']
226
228
227 if commit_branch not in branches_commits:
229 if commit_branch not in branches_commits:
228 _branch = branch_data[commit_branch] \
230 _branch = branch_data[commit_branch] \
229 if commit_branch else commit_branch
231 if commit_branch else commit_branch
230 branch_commits = {'branch': _branch,
232 branch_commits = {'branch': _branch,
231 'branch_head': '',
233 'branch_head': '',
232 'commits': []}
234 'commits': []}
233 branches_commits[commit_branch] = branch_commits
235 branches_commits[commit_branch] = branch_commits
234
236
235 branch_commits = branches_commits[commit_branch]
237 branch_commits = branches_commits[commit_branch]
236 branch_commits['commits'].append(commit)
238 branch_commits['commits'].append(commit)
237 branch_commits['branch_head'] = commit['raw_id']
239 branch_commits['branch_head'] = commit['raw_id']
238 return branches_commits
240 return branches_commits
239
241
240
242
241 class WebhookDataHandler(CommitParsingDataHandler):
243 class WebhookDataHandler(CommitParsingDataHandler):
242 name = 'webhook'
244 name = 'webhook'
243
245
244 def __init__(self, template_url, headers):
246 def __init__(self, template_url, headers):
245 self.template_url = template_url
247 self.template_url = template_url
246 self.headers = headers
248 self.headers = headers
247
249
248 def get_base_parsed_template(self, data):
250 def get_base_parsed_template(self, data):
249 """
251 """
250 initially parses the passed in template with some common variables
252 initially parses the passed in template with some common variables
251 available on ALL calls
253 available on ALL calls
252 """
254 """
253 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
255 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
254 common_vars = {
256 common_vars = {
255 'repo_name': data['repo']['repo_name'],
257 'repo_name': data['repo']['repo_name'],
256 'repo_type': data['repo']['repo_type'],
258 'repo_type': data['repo']['repo_type'],
257 'repo_id': data['repo']['repo_id'],
259 'repo_id': data['repo']['repo_id'],
258 'repo_url': data['repo']['url'],
260 'repo_url': data['repo']['url'],
259 'username': data['actor']['username'],
261 'username': data['actor']['username'],
260 'user_id': data['actor']['user_id'],
262 'user_id': data['actor']['user_id'],
261 'event_name': data['name']
263 'event_name': data['name']
262 }
264 }
263
265
264 extra_vars = {}
266 extra_vars = {}
265 for extra_key, extra_val in data['repo']['extra_fields'].items():
267 for extra_key, extra_val in data['repo']['extra_fields'].items():
266 extra_vars['extra__{}'.format(extra_key)] = extra_val
268 extra_vars['extra__{}'.format(extra_key)] = extra_val
267 common_vars.update(extra_vars)
269 common_vars.update(extra_vars)
268
270
269 template_url = self.template_url.replace('${extra:', '${extra__')
271 template_url = self.template_url.replace('${extra:', '${extra__')
270 for k, v in common_vars.items():
272 for k, v in common_vars.items():
271 template_url = UrlTmpl(template_url).safe_substitute(**{k: v})
273 template_url = UrlTmpl(template_url).safe_substitute(**{k: v})
272 return template_url
274 return template_url
273
275
274 def repo_push_event_handler(self, event, data):
276 def repo_push_event_handler(self, event, data):
275 url = self.get_base_parsed_template(data)
277 url = self.get_base_parsed_template(data)
276 url_calls = []
278 url_calls = []
277
279
278 branches_commits = self.aggregate_branch_data(
280 branches_commits = self.aggregate_branch_data(
279 data['push']['branches'], data['push']['commits'])
281 data['push']['branches'], data['push']['commits'])
280 if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url:
282 if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url:
281 # call it multiple times, for each branch if used in variables
283 # call it multiple times, for each branch if used in variables
282 for branch, commit_ids in branches_commits.items():
284 for branch, commit_ids in branches_commits.items():
283 branch_url = UrlTmpl(url).safe_substitute(branch=branch)
285 branch_url = UrlTmpl(url).safe_substitute(branch=branch)
284
286
285 if '${branch_head}' in branch_url:
287 if '${branch_head}' in branch_url:
286 # last commit in the aggregate is the head of the branch
288 # last commit in the aggregate is the head of the branch
287 branch_head = commit_ids['branch_head']
289 branch_head = commit_ids['branch_head']
288 branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head)
290 branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head)
289
291
290 # call further down for each commit if used
292 # call further down for each commit if used
291 if '${commit_id}' in branch_url:
293 if '${commit_id}' in branch_url:
292 for commit_data in commit_ids['commits']:
294 for commit_data in commit_ids['commits']:
293 commit_id = commit_data['raw_id']
295 commit_id = commit_data['raw_id']
294 commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id)
296 commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id)
295 # register per-commit call
297 # register per-commit call
296 log.debug(
298 log.debug(
297 'register %s call(%s) to url %s',
299 'register %s call(%s) to url %s',
298 self.name, event, commit_url)
300 self.name, event, commit_url)
299 url_calls.append(
301 url_calls.append(
300 (commit_url, self.headers, data))
302 (commit_url, self.headers, data))
301
303
302 else:
304 else:
303 # register per-branch call
305 # register per-branch call
304 log.debug('register %s call(%s) to url %s',
306 log.debug('register %s call(%s) to url %s',
305 self.name, event, branch_url)
307 self.name, event, branch_url)
306 url_calls.append((branch_url, self.headers, data))
308 url_calls.append((branch_url, self.headers, data))
307
309
308 else:
310 else:
309 log.debug('register %s call(%s) to url %s', self.name, event, url)
311 log.debug('register %s call(%s) to url %s', self.name, event, url)
310 url_calls.append((url, self.headers, data))
312 url_calls.append((url, self.headers, data))
311
313
312 return url_calls
314 return url_calls
313
315
314 def repo_commit_comment_handler(self, event, data):
316 def repo_commit_comment_handler(self, event, data):
315 url = self.get_base_parsed_template(data)
317 url = self.get_base_parsed_template(data)
316 log.debug('register %s call(%s) to url %s', self.name, event, url)
318 log.debug('register %s call(%s) to url %s', self.name, event, url)
317 comment_vars = [
319 comment_vars = [
318 ('commit_comment_id', data['comment']['comment_id']),
320 ('commit_comment_id', data['comment']['comment_id']),
319 ('commit_comment_text', data['comment']['comment_text']),
321 ('commit_comment_text', data['comment']['comment_text']),
320 ('commit_comment_type', data['comment']['comment_type']),
322 ('commit_comment_type', data['comment']['comment_type']),
321
323
322 ('commit_comment_f_path', data['comment']['comment_f_path']),
324 ('commit_comment_f_path', data['comment']['comment_f_path']),
323 ('commit_comment_line_no', data['comment']['comment_line_no']),
325 ('commit_comment_line_no', data['comment']['comment_line_no']),
324
326
325 ('commit_comment_commit_id', data['commit']['commit_id']),
327 ('commit_comment_commit_id', data['commit']['commit_id']),
326 ('commit_comment_commit_branch', data['commit']['commit_branch']),
328 ('commit_comment_commit_branch', data['commit']['commit_branch']),
327 ('commit_comment_commit_message', data['commit']['commit_message']),
329 ('commit_comment_commit_message', data['commit']['commit_message']),
328 ]
330 ]
329 for k, v in comment_vars:
331 for k, v in comment_vars:
330 url = UrlTmpl(url).safe_substitute(**{k: v})
332 url = UrlTmpl(url).safe_substitute(**{k: v})
331
333
332 return [(url, self.headers, data)]
334 return [(url, self.headers, data)]
333
335
334 def repo_commit_comment_edit_handler(self, event, data):
336 def repo_commit_comment_edit_handler(self, event, data):
335 url = self.get_base_parsed_template(data)
337 url = self.get_base_parsed_template(data)
336 log.debug('register %s call(%s) to url %s', self.name, event, url)
338 log.debug('register %s call(%s) to url %s', self.name, event, url)
337 comment_vars = [
339 comment_vars = [
338 ('commit_comment_id', data['comment']['comment_id']),
340 ('commit_comment_id', data['comment']['comment_id']),
339 ('commit_comment_text', data['comment']['comment_text']),
341 ('commit_comment_text', data['comment']['comment_text']),
340 ('commit_comment_type', data['comment']['comment_type']),
342 ('commit_comment_type', data['comment']['comment_type']),
341
343
342 ('commit_comment_f_path', data['comment']['comment_f_path']),
344 ('commit_comment_f_path', data['comment']['comment_f_path']),
343 ('commit_comment_line_no', data['comment']['comment_line_no']),
345 ('commit_comment_line_no', data['comment']['comment_line_no']),
344
346
345 ('commit_comment_commit_id', data['commit']['commit_id']),
347 ('commit_comment_commit_id', data['commit']['commit_id']),
346 ('commit_comment_commit_branch', data['commit']['commit_branch']),
348 ('commit_comment_commit_branch', data['commit']['commit_branch']),
347 ('commit_comment_commit_message', data['commit']['commit_message']),
349 ('commit_comment_commit_message', data['commit']['commit_message']),
348 ]
350 ]
349 for k, v in comment_vars:
351 for k, v in comment_vars:
350 url = UrlTmpl(url).safe_substitute(**{k: v})
352 url = UrlTmpl(url).safe_substitute(**{k: v})
351
353
352 return [(url, self.headers, data)]
354 return [(url, self.headers, data)]
353
355
354 def repo_create_event_handler(self, event, data):
356 def repo_create_event_handler(self, event, data):
355 url = self.get_base_parsed_template(data)
357 url = self.get_base_parsed_template(data)
356 log.debug('register %s call(%s) to url %s', self.name, event, url)
358 log.debug('register %s call(%s) to url %s', self.name, event, url)
357 return [(url, self.headers, data)]
359 return [(url, self.headers, data)]
358
360
359 def pull_request_event_handler(self, event, data):
361 def pull_request_event_handler(self, event, data):
360 url = self.get_base_parsed_template(data)
362 url = self.get_base_parsed_template(data)
361 log.debug('register %s call(%s) to url %s', self.name, event, url)
363 log.debug('register %s call(%s) to url %s', self.name, event, url)
362 pr_vars = [
364 pr_vars = [
363 ('pull_request_id', data['pullrequest']['pull_request_id']),
365 ('pull_request_id', data['pullrequest']['pull_request_id']),
364 ('pull_request_title', data['pullrequest']['title']),
366 ('pull_request_title', data['pullrequest']['title']),
365 ('pull_request_url', data['pullrequest']['url']),
367 ('pull_request_url', data['pullrequest']['url']),
366 ('pull_request_shadow_url', data['pullrequest']['shadow_url']),
368 ('pull_request_shadow_url', data['pullrequest']['shadow_url']),
367 ('pull_request_commits_uid', data['pullrequest']['commits_uid']),
369 ('pull_request_commits_uid', data['pullrequest']['commits_uid']),
368 ]
370 ]
369 for k, v in pr_vars:
371 for k, v in pr_vars:
370 url = UrlTmpl(url).safe_substitute(**{k: v})
372 url = UrlTmpl(url).safe_substitute(**{k: v})
371
373
372 return [(url, self.headers, data)]
374 return [(url, self.headers, data)]
373
375
374 def __call__(self, event, data):
376 def __call__(self, event, data):
375 from rhodecode import events
377 from rhodecode import events
376
378
377 if isinstance(event, events.RepoPushEvent):
379 if isinstance(event, events.RepoPushEvent):
378 return self.repo_push_event_handler(event, data)
380 return self.repo_push_event_handler(event, data)
379 elif isinstance(event, events.RepoCreateEvent):
381 elif isinstance(event, events.RepoCreateEvent):
380 return self.repo_create_event_handler(event, data)
382 return self.repo_create_event_handler(event, data)
381 elif isinstance(event, events.RepoCommitCommentEvent):
383 elif isinstance(event, events.RepoCommitCommentEvent):
382 return self.repo_commit_comment_handler(event, data)
384 return self.repo_commit_comment_handler(event, data)
383 elif isinstance(event, events.RepoCommitCommentEditEvent):
385 elif isinstance(event, events.RepoCommitCommentEditEvent):
384 return self.repo_commit_comment_edit_handler(event, data)
386 return self.repo_commit_comment_edit_handler(event, data)
385 elif isinstance(event, events.PullRequestEvent):
387 elif isinstance(event, events.PullRequestEvent):
386 return self.pull_request_event_handler(event, data)
388 return self.pull_request_event_handler(event, data)
387 else:
389 else:
388 raise ValueError(
390 raise ValueError(
389 'event type `{}` has no handler defined'.format(event.__class__))
391 'event type `{}` has no handler defined'.format(event.__class__))
390
392
391
393
392 def get_auth(settings):
394 def get_auth(settings):
393 from requests.auth import HTTPBasicAuth
395 from requests.auth import HTTPBasicAuth
394 username = settings.get('username')
396 username = settings.get('username')
395 password = settings.get('password')
397 password = settings.get('password')
396 if username and password:
398 if username and password:
397 return HTTPBasicAuth(username, password)
399 return HTTPBasicAuth(username, password)
398 return None
400 return None
399
401
400
402
401 def get_web_token(settings):
403 def get_web_token(settings):
402 return settings['secret_token']
404 return settings['secret_token']
403
405
404
406
405 def get_url_vars(url_vars):
407 def get_url_vars(url_vars):
406 items = []
408 items = []
407
409
408 for section, section_items in url_vars:
410 for section, section_items in url_vars:
409 items.append('\n*{}*'.format(section))
411 items.append('\n*{}*'.format(section))
410 for key, explanation in section_items:
412 for key, explanation in section_items:
411 items.append(' {} - {}'.format('${' + key + '}', explanation))
413 items.append(' {} - {}'.format('${' + key + '}', explanation))
412 return '\n'.join(items)
414 return '\n'.join(items)
413
415
414
416
415 def render_with_traceback(template, *args, **kwargs):
417 def render_with_traceback(template, *args, **kwargs):
416 try:
418 try:
417 return template.render(*args, **kwargs)
419 return template.render(*args, **kwargs)
418 except Exception:
420 except Exception:
419 log.error(exceptions.text_error_template().render())
421 log.error(exceptions.text_error_template().render())
420 raise
422 raise
421
423
422
424
423 STATUS_400 = (400, 401, 403)
425 STATUS_400 = (400, 401, 403)
424 STATUS_500 = (500, 502, 504)
426 STATUS_500 = (500, 502, 504)
425
427
426
428
427 def requests_retry_call(
429 def requests_retry_call(
428 retries=3, backoff_factor=0.3, status_forcelist=STATUS_400+STATUS_500,
430 retries=3, backoff_factor=0.3, status_forcelist=STATUS_400+STATUS_500,
429 session=None):
431 session=None):
430 """
432 """
431 session = requests_retry_session()
433 session = requests_retry_session()
432 response = session.get('http://example.com')
434 response = session.get('http://example.com')
433
435
434 :param retries:
436 :param retries:
435 :param backoff_factor:
437 :param backoff_factor:
436 :param status_forcelist:
438 :param status_forcelist:
437 :param session:
439 :param session:
438 """
440 """
439 session = session or requests.Session()
441 session = session or requests.Session()
440 retry = Retry(
442 retry = Retry(
441 total=retries,
443 total=retries,
442 read=retries,
444 read=retries,
443 connect=retries,
445 connect=retries,
444 backoff_factor=backoff_factor,
446 backoff_factor=backoff_factor,
445 status_forcelist=status_forcelist,
447 status_forcelist=status_forcelist,
446 )
448 )
447 adapter = HTTPAdapter(max_retries=retry)
449 adapter = HTTPAdapter(max_retries=retry)
448 session.mount('http://', adapter)
450 session.mount('http://', adapter)
449 session.mount('https://', adapter)
451 session.mount('https://', adapter)
450 return session
452 return session
@@ -1,469 +1,469 b''
1
1
2
2
3 # Copyright (C) 2012-2020 RhodeCode GmbH
3 # Copyright (C) 2012-2020 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 deform
21 import deform
22 import logging
22 import logging
23 import peppercorn
23 import peppercorn
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
26
26
27 from rhodecode.integrations import integration_type_registry
27 from rhodecode.integrations import integration_type_registry
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
31 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
31 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
32 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
32 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
33 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.helpers import Page
34 from rhodecode.lib.helpers import Page
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 make_integration_schema, IntegrationScopeType)
40 make_integration_schema, IntegrationScopeType)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class IntegrationSettingsViewBase(BaseAppView):
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
46 """
47 Base Integration settings view used by both repo / global settings
47 Base Integration settings view used by both repo / global settings
48 """
48 """
49
49
50 def __init__(self, context, request):
50 def __init__(self, context, request):
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
52 self._load_view_context()
53
53
54 def _load_view_context(self):
54 def _load_view_context(self):
55 """
55 """
56 This avoids boilerplate for repo/global+list/edit+views/templates
56 This avoids boilerplate for repo/global+list/edit+views/templates
57 by doing all possible contexts at the same time however it should
57 by doing all possible contexts at the same time however it should
58 be split up into separate functions once more "contexts" exist
58 be split up into separate functions once more "contexts" exist
59 """
59 """
60
60
61 self.IntegrationType = None
61 self.IntegrationType = None
62 self.repo = None
62 self.repo = None
63 self.repo_group = None
63 self.repo_group = None
64 self.integration = None
64 self.integration = None
65 self.integrations = {}
65 self.integrations = {}
66
66
67 request = self.request
67 request = self.request
68
68
69 if 'repo_name' in request.matchdict: # in repo settings context
69 if 'repo_name' in request.matchdict: # in repo settings context
70 repo_name = request.matchdict['repo_name']
70 repo_name = request.matchdict['repo_name']
71 self.repo = Repository.get_by_repo_name(repo_name)
71 self.repo = Repository.get_by_repo_name(repo_name)
72
72
73 if 'repo_group_name' in request.matchdict: # in group settings context
73 if 'repo_group_name' in request.matchdict: # in group settings context
74 repo_group_name = request.matchdict['repo_group_name']
74 repo_group_name = request.matchdict['repo_group_name']
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76
76
77 if 'integration' in request.matchdict: # integration type context
77 if 'integration' in request.matchdict: # integration type context
78 integration_type = request.matchdict['integration']
78 integration_type = request.matchdict['integration']
79 if integration_type not in integration_type_registry:
79 if integration_type not in integration_type_registry:
80 raise HTTPNotFound()
80 raise HTTPNotFound()
81
81
82 self.IntegrationType = integration_type_registry[integration_type]
82 self.IntegrationType = integration_type_registry[integration_type]
83 if self.IntegrationType.is_dummy:
83 if self.IntegrationType.is_dummy:
84 raise HTTPNotFound()
84 raise HTTPNotFound()
85
85
86 if 'integration_id' in request.matchdict: # single integration context
86 if 'integration_id' in request.matchdict: # single integration context
87 integration_id = request.matchdict['integration_id']
87 integration_id = request.matchdict['integration_id']
88 self.integration = Integration.get(integration_id)
88 self.integration = Integration.get(integration_id)
89
89
90 # extra perms check just in case
90 # extra perms check just in case
91 if not self._has_perms_for_integration(self.integration):
91 if not self._has_perms_for_integration(self.integration):
92 raise HTTPForbidden()
92 raise HTTPForbidden()
93
93
94 self.settings = self.integration and self.integration.settings or {}
94 self.settings = self.integration and self.integration.settings or {}
95 self.admin_view = not (self.repo or self.repo_group)
95 self.admin_view = not (self.repo or self.repo_group)
96
96
97 def _has_perms_for_integration(self, integration):
97 def _has_perms_for_integration(self, integration):
98 perms = self.request.user.permissions
98 perms = self.request.user.permissions
99
99
100 if 'hg.admin' in perms['global']:
100 if 'hg.admin' in perms['global']:
101 return True
101 return True
102
102
103 if integration.repo:
103 if integration.repo:
104 return perms['repositories'].get(
104 return perms['repositories'].get(
105 integration.repo.repo_name) == 'repository.admin'
105 integration.repo.repo_name) == 'repository.admin'
106
106
107 if integration.repo_group:
107 if integration.repo_group:
108 return perms['repositories_groups'].get(
108 return perms['repositories_groups'].get(
109 integration.repo_group.group_name) == 'group.admin'
109 integration.repo_group.group_name) == 'group.admin'
110
110
111 return False
111 return False
112
112
113 def _get_local_tmpl_context(self, include_app_defaults=True):
113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 _ = self.request.translate
114 _ = self.request.translate
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 include_app_defaults=include_app_defaults)
116 include_app_defaults=include_app_defaults)
117 c.active = 'integrations'
117 c.active = 'integrations'
118
118
119 return c
119 return c
120
120
121 def _form_schema(self):
121 def _form_schema(self):
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 settings=self.settings)
123 settings=self.settings)
124
124
125 # returns a clone, important if mutating the schema later
125 # returns a clone, important if mutating the schema later
126 return schema.bind(
126 return schema.bind(
127 permissions=self.request.user.permissions,
127 permissions=self.request.user.permissions,
128 no_scope=not self.admin_view)
128 no_scope=not self.admin_view)
129
129
130 def _form_defaults(self):
130 def _form_defaults(self):
131 _ = self.request.translate
131 _ = self.request.translate
132 defaults = {}
132 defaults = {}
133
133
134 if self.integration:
134 if self.integration:
135 defaults['settings'] = self.integration.settings or {}
135 defaults['settings'] = self.integration.settings or {}
136 defaults['options'] = {
136 defaults['options'] = {
137 'name': self.integration.name,
137 'name': self.integration.name,
138 'enabled': self.integration.enabled,
138 'enabled': self.integration.enabled,
139 'scope': {
139 'scope': {
140 'repo': self.integration.repo,
140 'repo': self.integration.repo,
141 'repo_group': self.integration.repo_group,
141 'repo_group': self.integration.repo_group,
142 'child_repos_only': self.integration.child_repos_only,
142 'child_repos_only': self.integration.child_repos_only,
143 },
143 },
144 }
144 }
145 else:
145 else:
146 if self.repo:
146 if self.repo:
147 scope = _('{repo_name} repository').format(
147 scope = _('{repo_name} repository').format(
148 repo_name=self.repo.repo_name)
148 repo_name=self.repo.repo_name)
149 elif self.repo_group:
149 elif self.repo_group:
150 scope = _('{repo_group_name} repo group').format(
150 scope = _('{repo_group_name} repo group').format(
151 repo_group_name=self.repo_group.group_name)
151 repo_group_name=self.repo_group.group_name)
152 else:
152 else:
153 scope = _('Global')
153 scope = _('Global')
154
154
155 defaults['options'] = {
155 defaults['options'] = {
156 'enabled': True,
156 'enabled': True,
157 'name': _('{name} integration').format(
157 'name': _('{name} integration').format(
158 name=self.IntegrationType.display_name),
158 name=self.IntegrationType.display_name),
159 }
159 }
160 defaults['options']['scope'] = {
160 defaults['options']['scope'] = {
161 'repo': self.repo,
161 'repo': self.repo,
162 'repo_group': self.repo_group,
162 'repo_group': self.repo_group,
163 }
163 }
164
164
165 return defaults
165 return defaults
166
166
167 def _delete_integration(self, integration):
167 def _delete_integration(self, integration):
168 _ = self.request.translate
168 _ = self.request.translate
169 Session().delete(integration)
169 Session().delete(integration)
170 Session().commit()
170 Session().commit()
171 h.flash(
171 h.flash(
172 _('Integration {integration_name} deleted successfully.').format(
172 _('Integration {integration_name} deleted successfully.').format(
173 integration_name=integration.name),
173 integration_name=integration.name),
174 category='success')
174 category='success')
175
175
176 if self.repo:
176 if self.repo:
177 redirect_to = self.request.route_path(
177 redirect_to = self.request.route_path(
178 'repo_integrations_home', repo_name=self.repo.repo_name)
178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 elif self.repo_group:
179 elif self.repo_group:
180 redirect_to = self.request.route_path(
180 redirect_to = self.request.route_path(
181 'repo_group_integrations_home',
181 'repo_group_integrations_home',
182 repo_group_name=self.repo_group.group_name)
182 repo_group_name=self.repo_group.group_name)
183 else:
183 else:
184 redirect_to = self.request.route_path('global_integrations_home')
184 redirect_to = self.request.route_path('global_integrations_home')
185 raise HTTPFound(redirect_to)
185 raise HTTPFound(redirect_to)
186
186
187 def _integration_list(self):
187 def _integration_list(self):
188 """ List integrations """
188 """ List integrations """
189
189
190 c = self.load_default_context()
190 c = self.load_default_context()
191 if self.repo:
191 if self.repo:
192 scope = self.repo
192 scope = self.repo
193 elif self.repo_group:
193 elif self.repo_group:
194 scope = self.repo_group
194 scope = self.repo_group
195 else:
195 else:
196 scope = 'all'
196 scope = 'all'
197
197
198 integrations = []
198 integrations = []
199
199
200 for IntType, integration in IntegrationModel().get_integrations(
200 for IntType, integration in IntegrationModel().get_integrations(
201 scope=scope, IntegrationType=self.IntegrationType):
201 scope=scope, IntegrationType=self.IntegrationType):
202
202
203 # extra permissions check *just in case*
203 # extra permissions check *just in case*
204 if not self._has_perms_for_integration(integration):
204 if not self._has_perms_for_integration(integration):
205 continue
205 continue
206
206
207 integrations.append((IntType, integration))
207 integrations.append((IntType, integration))
208
208
209 sort_arg = self.request.GET.get('sort', 'name:asc')
209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 sort_dir = 'asc'
210 sort_dir = 'asc'
211 if ':' in sort_arg:
211 if ':' in sort_arg:
212 sort_field, sort_dir = sort_arg.split(':')
212 sort_field, sort_dir = sort_arg.split(':')
213 else:
213 else:
214 sort_field = sort_arg, 'asc'
214 sort_field = sort_arg, 'asc'
215
215
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217
217
218 integrations.sort(
218 integrations.sort(
219 key=lambda x: getattr(x[1], sort_field),
219 key=lambda x: getattr(x[1], sort_field),
220 reverse=(sort_dir == 'desc'))
220 reverse=(sort_dir == 'desc'))
221
221
222 def url_generator(page_num):
222 def url_generator(page_num):
223 query_params = {
223 query_params = {
224 'page': page_num
224 'page': page_num
225 }
225 }
226 return self.request.current_route_path(_query=query_params)
226 return self.request.current_route_path(_query=query_params)
227
227
228 page = safe_int(self.request.GET.get('page', 1), 1)
228 page = safe_int(self.request.GET.get('page', 1), 1)
229
229
230 integrations = Page(
230 integrations = Page(
231 integrations, page=page, items_per_page=10, url_maker=url_generator)
231 integrations, page=page, items_per_page=10, url_maker=url_generator)
232
232
233 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
233 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
234
234
235 c.current_IntegrationType = self.IntegrationType
235 c.current_IntegrationType = self.IntegrationType
236 c.integrations_list = integrations
236 c.integrations_list = integrations
237 c.available_integrations = integration_type_registry
237 c.available_integrations = integration_type_registry
238
238
239 return self._get_template_context(c)
239 return self._get_template_context(c)
240
240
241 def _settings_get(self, defaults=None, form=None):
241 def _settings_get(self, defaults=None, form=None):
242 """
242 """
243 View that displays the integration settings as a form.
243 View that displays the integration settings as a form.
244 """
244 """
245 c = self.load_default_context()
245 c = self.load_default_context()
246
246
247 defaults = defaults or self._form_defaults()
247 defaults = defaults or self._form_defaults()
248 schema = self._form_schema()
248 schema = self._form_schema()
249
249
250 if self.integration:
250 if self.integration:
251 buttons = ('submit', 'delete')
251 buttons = ('submit', 'delete')
252 else:
252 else:
253 buttons = ('submit',)
253 buttons = ('submit',)
254
254
255 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
255 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
256
256
257 c.form = form
257 c.form = form
258 c.current_IntegrationType = self.IntegrationType
258 c.current_IntegrationType = self.IntegrationType
259 c.integration = self.integration
259 c.integration = self.integration
260
260
261 return self._get_template_context(c)
261 return self._get_template_context(c)
262
262
263 def _settings_post(self):
263 def _settings_post(self):
264 """
264 """
265 View that validates and stores the integration settings.
265 View that validates and stores the integration settings.
266 """
266 """
267 _ = self.request.translate
267 _ = self.request.translate
268
268
269 controls = self.request.POST.items()
269 controls = list(self.request.POST.items())
270 pstruct = peppercorn.parse(controls)
270 pstruct = peppercorn.parse(controls)
271
271
272 if self.integration and pstruct.get('delete'):
272 if self.integration and pstruct.get('delete'):
273 return self._delete_integration(self.integration)
273 return self._delete_integration(self.integration)
274
274
275 schema = self._form_schema()
275 schema = self._form_schema()
276
276
277 skip_settings_validation = False
277 skip_settings_validation = False
278 if self.integration and 'enabled' not in pstruct.get('options', {}):
278 if self.integration and 'enabled' not in pstruct.get('options', {}):
279 skip_settings_validation = True
279 skip_settings_validation = True
280 schema['settings'].validator = None
280 schema['settings'].validator = None
281 for field in schema['settings'].children:
281 for field in schema['settings'].children:
282 field.validator = None
282 field.validator = None
283 field.missing = ''
283 field.missing = ''
284
284
285 if self.integration:
285 if self.integration:
286 buttons = ('submit', 'delete')
286 buttons = ('submit', 'delete')
287 else:
287 else:
288 buttons = ('submit',)
288 buttons = ('submit',)
289
289
290 form = deform.Form(schema, buttons=buttons)
290 form = deform.Form(schema, buttons=buttons)
291
291
292 if not self.admin_view:
292 if not self.admin_view:
293 # scope is read only field in these cases, and has to be added
293 # scope is read only field in these cases, and has to be added
294 options = pstruct.setdefault('options', {})
294 options = pstruct.setdefault('options', {})
295 if 'scope' not in options:
295 if 'scope' not in options:
296 options['scope'] = IntegrationScopeType().serialize(None, {
296 options['scope'] = IntegrationScopeType().serialize(None, {
297 'repo': self.repo,
297 'repo': self.repo,
298 'repo_group': self.repo_group,
298 'repo_group': self.repo_group,
299 })
299 })
300
300
301 try:
301 try:
302 valid_data = form.validate_pstruct(pstruct)
302 valid_data = form.validate_pstruct(pstruct)
303 except deform.ValidationFailure as e:
303 except deform.ValidationFailure as e:
304 h.flash(
304 h.flash(
305 _('Errors exist when saving integration settings. '
305 _('Errors exist when saving integration settings. '
306 'Please check the form inputs.'),
306 'Please check the form inputs.'),
307 category='error')
307 category='error')
308 return self._settings_get(form=e)
308 return self._settings_get(form=e)
309
309
310 if not self.integration:
310 if not self.integration:
311 self.integration = Integration()
311 self.integration = Integration()
312 self.integration.integration_type = self.IntegrationType.key
312 self.integration.integration_type = self.IntegrationType.key
313 Session().add(self.integration)
313 Session().add(self.integration)
314
314
315 scope = valid_data['options']['scope']
315 scope = valid_data['options']['scope']
316
316
317 IntegrationModel().update_integration(self.integration,
317 IntegrationModel().update_integration(self.integration,
318 name=valid_data['options']['name'],
318 name=valid_data['options']['name'],
319 enabled=valid_data['options']['enabled'],
319 enabled=valid_data['options']['enabled'],
320 settings=valid_data['settings'],
320 settings=valid_data['settings'],
321 repo=scope['repo'],
321 repo=scope['repo'],
322 repo_group=scope['repo_group'],
322 repo_group=scope['repo_group'],
323 child_repos_only=scope['child_repos_only'],
323 child_repos_only=scope['child_repos_only'],
324 )
324 )
325
325
326 self.integration.settings = valid_data['settings']
326 self.integration.settings = valid_data['settings']
327 Session().commit()
327 Session().commit()
328 # Display success message and redirect.
328 # Display success message and redirect.
329 h.flash(
329 h.flash(
330 _('Integration {integration_name} updated successfully.').format(
330 _('Integration {integration_name} updated successfully.').format(
331 integration_name=self.IntegrationType.display_name),
331 integration_name=self.IntegrationType.display_name),
332 category='success')
332 category='success')
333
333
334 # if integration scope changes, we must redirect to the right place
334 # if integration scope changes, we must redirect to the right place
335 # keeping in mind if the original view was for /repo/ or /_admin/
335 # keeping in mind if the original view was for /repo/ or /_admin/
336 admin_view = not (self.repo or self.repo_group)
336 admin_view = not (self.repo or self.repo_group)
337
337
338 if self.integration.repo and not admin_view:
338 if self.integration.repo and not admin_view:
339 redirect_to = self.request.route_path(
339 redirect_to = self.request.route_path(
340 'repo_integrations_edit',
340 'repo_integrations_edit',
341 repo_name=self.integration.repo.repo_name,
341 repo_name=self.integration.repo.repo_name,
342 integration=self.integration.integration_type,
342 integration=self.integration.integration_type,
343 integration_id=self.integration.integration_id)
343 integration_id=self.integration.integration_id)
344 elif self.integration.repo_group and not admin_view:
344 elif self.integration.repo_group and not admin_view:
345 redirect_to = self.request.route_path(
345 redirect_to = self.request.route_path(
346 'repo_group_integrations_edit',
346 'repo_group_integrations_edit',
347 repo_group_name=self.integration.repo_group.group_name,
347 repo_group_name=self.integration.repo_group.group_name,
348 integration=self.integration.integration_type,
348 integration=self.integration.integration_type,
349 integration_id=self.integration.integration_id)
349 integration_id=self.integration.integration_id)
350 else:
350 else:
351 redirect_to = self.request.route_path(
351 redirect_to = self.request.route_path(
352 'global_integrations_edit',
352 'global_integrations_edit',
353 integration=self.integration.integration_type,
353 integration=self.integration.integration_type,
354 integration_id=self.integration.integration_id)
354 integration_id=self.integration.integration_id)
355
355
356 return HTTPFound(redirect_to)
356 return HTTPFound(redirect_to)
357
357
358 def _new_integration(self):
358 def _new_integration(self):
359 c = self.load_default_context()
359 c = self.load_default_context()
360 c.available_integrations = integration_type_registry
360 c.available_integrations = integration_type_registry
361 return self._get_template_context(c)
361 return self._get_template_context(c)
362
362
363 def load_default_context(self):
363 def load_default_context(self):
364 raise NotImplementedError()
364 raise NotImplementedError()
365
365
366
366
367 class GlobalIntegrationsView(IntegrationSettingsViewBase):
367 class GlobalIntegrationsView(IntegrationSettingsViewBase):
368 def load_default_context(self):
368 def load_default_context(self):
369 c = self._get_local_tmpl_context()
369 c = self._get_local_tmpl_context()
370 c.repo = self.repo
370 c.repo = self.repo
371 c.repo_group = self.repo_group
371 c.repo_group = self.repo_group
372 c.navlist = navigation_list(self.request)
372 c.navlist = navigation_list(self.request)
373
373
374 return c
374 return c
375
375
376 @LoginRequired()
376 @LoginRequired()
377 @HasPermissionAnyDecorator('hg.admin')
377 @HasPermissionAnyDecorator('hg.admin')
378 def integration_list(self):
378 def integration_list(self):
379 return self._integration_list()
379 return self._integration_list()
380
380
381 @LoginRequired()
381 @LoginRequired()
382 @HasPermissionAnyDecorator('hg.admin')
382 @HasPermissionAnyDecorator('hg.admin')
383 def settings_get(self):
383 def settings_get(self):
384 return self._settings_get()
384 return self._settings_get()
385
385
386 @LoginRequired()
386 @LoginRequired()
387 @HasPermissionAnyDecorator('hg.admin')
387 @HasPermissionAnyDecorator('hg.admin')
388 @CSRFRequired()
388 @CSRFRequired()
389 def settings_post(self):
389 def settings_post(self):
390 return self._settings_post()
390 return self._settings_post()
391
391
392 @LoginRequired()
392 @LoginRequired()
393 @HasPermissionAnyDecorator('hg.admin')
393 @HasPermissionAnyDecorator('hg.admin')
394 def new_integration(self):
394 def new_integration(self):
395 return self._new_integration()
395 return self._new_integration()
396
396
397
397
398 class RepoIntegrationsView(IntegrationSettingsViewBase):
398 class RepoIntegrationsView(IntegrationSettingsViewBase):
399 def load_default_context(self):
399 def load_default_context(self):
400 c = self._get_local_tmpl_context()
400 c = self._get_local_tmpl_context()
401
401
402 c.repo = self.repo
402 c.repo = self.repo
403 c.repo_group = self.repo_group
403 c.repo_group = self.repo_group
404
404
405 self.db_repo = self.repo
405 self.db_repo = self.repo
406 c.rhodecode_db_repo = self.repo
406 c.rhodecode_db_repo = self.repo
407 c.repo_name = self.db_repo.repo_name
407 c.repo_name = self.db_repo.repo_name
408 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
408 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
409 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
409 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
410 c.repository_is_user_following = ScmModel().is_following_repo(
410 c.repository_is_user_following = ScmModel().is_following_repo(
411 c.repo_name, self._rhodecode_user.user_id)
411 c.repo_name, self._rhodecode_user.user_id)
412 c.has_origin_repo_read_perm = False
412 c.has_origin_repo_read_perm = False
413 if self.db_repo.fork:
413 if self.db_repo.fork:
414 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
414 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
415 'repository.write', 'repository.read', 'repository.admin')(
415 'repository.write', 'repository.read', 'repository.admin')(
416 self.db_repo.fork.repo_name, 'summary fork link')
416 self.db_repo.fork.repo_name, 'summary fork link')
417 return c
417 return c
418
418
419 @LoginRequired()
419 @LoginRequired()
420 @HasRepoPermissionAnyDecorator('repository.admin')
420 @HasRepoPermissionAnyDecorator('repository.admin')
421 def integration_list(self):
421 def integration_list(self):
422 return self._integration_list()
422 return self._integration_list()
423
423
424 @LoginRequired()
424 @LoginRequired()
425 @HasRepoPermissionAnyDecorator('repository.admin')
425 @HasRepoPermissionAnyDecorator('repository.admin')
426 def settings_get(self):
426 def settings_get(self):
427 return self._settings_get()
427 return self._settings_get()
428
428
429 @LoginRequired()
429 @LoginRequired()
430 @HasRepoPermissionAnyDecorator('repository.admin')
430 @HasRepoPermissionAnyDecorator('repository.admin')
431 @CSRFRequired()
431 @CSRFRequired()
432 def settings_post(self):
432 def settings_post(self):
433 return self._settings_post()
433 return self._settings_post()
434
434
435 @LoginRequired()
435 @LoginRequired()
436 @HasRepoPermissionAnyDecorator('repository.admin')
436 @HasRepoPermissionAnyDecorator('repository.admin')
437 def new_integration(self):
437 def new_integration(self):
438 return self._new_integration()
438 return self._new_integration()
439
439
440
440
441 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
441 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
442 def load_default_context(self):
442 def load_default_context(self):
443 c = self._get_local_tmpl_context()
443 c = self._get_local_tmpl_context()
444 c.repo = self.repo
444 c.repo = self.repo
445 c.repo_group = self.repo_group
445 c.repo_group = self.repo_group
446 c.navlist = navigation_list(self.request)
446 c.navlist = navigation_list(self.request)
447
447
448 return c
448 return c
449
449
450 @LoginRequired()
450 @LoginRequired()
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 def integration_list(self):
452 def integration_list(self):
453 return self._integration_list()
453 return self._integration_list()
454
454
455 @LoginRequired()
455 @LoginRequired()
456 @HasRepoGroupPermissionAnyDecorator('group.admin')
456 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 def settings_get(self):
457 def settings_get(self):
458 return self._settings_get()
458 return self._settings_get()
459
459
460 @LoginRequired()
460 @LoginRequired()
461 @HasRepoGroupPermissionAnyDecorator('group.admin')
461 @HasRepoGroupPermissionAnyDecorator('group.admin')
462 @CSRFRequired()
462 @CSRFRequired()
463 def settings_post(self):
463 def settings_post(self):
464 return self._settings_post()
464 return self._settings_post()
465
465
466 @LoginRequired()
466 @LoginRequired()
467 @HasRepoGroupPermissionAnyDecorator('group.admin')
467 @HasRepoGroupPermissionAnyDecorator('group.admin')
468 def new_integration(self):
468 def new_integration(self):
469 return self._new_integration()
469 return self._new_integration()
General Comments 0
You need to be logged in to leave comments. Login now