##// END OF EJS Templates
updated with a latest changes.
ilin.s -
r5536:2c8dbdc5 merge default
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1
2 def apply_license(*args, **kwargs):
3 pass
4
5 try:
6 from rc_license.models import apply_license
7 except ImportError:
8 pass
9
10
11 def apply_license_from_file(*args, **kwargs):
12 pass
13
14 try:
15 from rc_license.models import apply_license_from_file
16 except ImportError:
17 pass
@@ -1,299 +1,299 b''
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
2
2
3 alembic==1.13.1
3 alembic==1.13.1
4 mako==1.2.4
4 mako==1.2.4
5 markupsafe==2.1.2
5 markupsafe==2.1.2
6 sqlalchemy==1.4.52
6 sqlalchemy==1.4.52
7 greenlet==3.0.3
7 greenlet==3.0.3
8 typing_extensions==4.12.2
8 typing_extensions==4.12.2
9 async-timeout==4.0.3
9 async-timeout==4.0.3
10 babel==2.12.1
10 babel==2.12.1
11 beaker==1.12.1
11 beaker==1.12.1
12 celery==5.3.6
12 celery==5.3.6
13 billiard==4.2.0
13 billiard==4.2.0
14 click==8.1.3
14 click==8.1.3
15 click-didyoumean==0.3.0
15 click-didyoumean==0.3.0
16 click==8.1.3
16 click==8.1.3
17 click-plugins==1.1.1
17 click-plugins==1.1.1
18 click==8.1.3
18 click==8.1.3
19 click-repl==0.2.0
19 click-repl==0.2.0
20 click==8.1.3
20 click==8.1.3
21 prompt_toolkit==3.0.47
21 prompt_toolkit==3.0.47
22 wcwidth==0.2.13
22 wcwidth==0.2.13
23 six==1.16.0
23 six==1.16.0
24 kombu==5.3.5
24 kombu==5.3.5
25 amqp==5.2.0
25 amqp==5.2.0
26 vine==5.1.0
26 vine==5.1.0
27 vine==5.1.0
27 vine==5.1.0
28 python-dateutil==2.8.2
28 python-dateutil==2.8.2
29 six==1.16.0
29 six==1.16.0
30 tzdata==2024.1
30 tzdata==2024.1
31 vine==5.1.0
31 vine==5.1.0
32 channelstream==0.7.1
32 channelstream==0.7.1
33 gevent==24.2.1
33 gevent==24.2.1
34 greenlet==3.0.3
34 greenlet==3.0.3
35 zope.event==5.0.0
35 zope.event==5.0.0
36 zope.interface==6.4.post2
36 zope.interface==7.0.3
37 itsdangerous==1.1.0
37 itsdangerous==1.1.0
38 marshmallow==2.18.0
38 marshmallow==2.18.0
39 pyramid==2.0.2
39 pyramid==2.0.2
40 hupper==1.12
40 hupper==1.12
41 plaster==1.1.2
41 plaster==1.1.2
42 plaster-pastedeploy==1.0.1
42 plaster-pastedeploy==1.0.1
43 pastedeploy==3.1.0
43 pastedeploy==3.1.0
44 plaster==1.1.2
44 plaster==1.1.2
45 translationstring==1.4
45 translationstring==1.4
46 venusian==3.0.0
46 venusian==3.0.0
47 webob==1.8.7
47 webob==1.8.7
48 zope.deprecation==5.0.0
48 zope.deprecation==5.0.0
49 zope.interface==6.4.post2
49 zope.interface==7.0.3
50 pyramid-jinja2==2.10
50 pyramid-jinja2==2.10
51 jinja2==3.1.2
51 jinja2==3.1.2
52 markupsafe==2.1.2
52 markupsafe==2.1.2
53 markupsafe==2.1.2
53 markupsafe==2.1.2
54 pyramid==2.0.2
54 pyramid==2.0.2
55 hupper==1.12
55 hupper==1.12
56 plaster==1.1.2
56 plaster==1.1.2
57 plaster-pastedeploy==1.0.1
57 plaster-pastedeploy==1.0.1
58 pastedeploy==3.1.0
58 pastedeploy==3.1.0
59 plaster==1.1.2
59 plaster==1.1.2
60 translationstring==1.4
60 translationstring==1.4
61 venusian==3.0.0
61 venusian==3.0.0
62 webob==1.8.7
62 webob==1.8.7
63 zope.deprecation==5.0.0
63 zope.deprecation==5.0.0
64 zope.interface==6.4.post2
64 zope.interface==7.0.3
65 zope.deprecation==5.0.0
65 zope.deprecation==5.0.0
66 python-dateutil==2.8.2
66 python-dateutil==2.8.2
67 six==1.16.0
67 six==1.16.0
68 requests==2.28.2
68 requests==2.28.2
69 certifi==2022.12.7
69 certifi==2022.12.7
70 charset-normalizer==3.1.0
70 charset-normalizer==3.1.0
71 idna==3.4
71 idna==3.4
72 urllib3==1.26.14
72 urllib3==1.26.14
73 ws4py==0.5.1
73 ws4py==0.5.1
74 deform==2.0.15
74 deform==2.0.15
75 chameleon==3.10.2
75 chameleon==3.10.2
76 colander==2.0
76 colander==2.0
77 iso8601==1.1.0
77 iso8601==1.1.0
78 translationstring==1.4
78 translationstring==1.4
79 iso8601==1.1.0
79 iso8601==1.1.0
80 peppercorn==0.6
80 peppercorn==0.6
81 translationstring==1.4
81 translationstring==1.4
82 zope.deprecation==5.0.0
82 zope.deprecation==5.0.0
83 docutils==0.19
83 docutils==0.19
84 dogpile.cache==1.3.3
84 dogpile.cache==1.3.3
85 decorator==5.1.1
85 decorator==5.1.1
86 stevedore==5.1.0
86 stevedore==5.1.0
87 pbr==5.11.1
87 pbr==5.11.1
88 formencode==2.1.0
88 formencode==2.1.0
89 six==1.16.0
89 six==1.16.0
90 fsspec==2024.6.0
90 fsspec==2024.9.0
91 gunicorn==23.0.0
91 gunicorn==23.0.0
92 packaging==24.1
92 packaging==24.1
93 gevent==24.2.1
93 gevent==24.2.1
94 greenlet==3.0.3
94 greenlet==3.0.3
95 zope.event==5.0.0
95 zope.event==5.0.0
96 zope.interface==6.4.post2
96 zope.interface==7.0.3
97 ipython==8.26.0
97 ipython==8.26.0
98 decorator==5.1.1
98 decorator==5.1.1
99 jedi==0.19.1
99 jedi==0.19.1
100 parso==0.8.4
100 parso==0.8.4
101 matplotlib-inline==0.1.7
101 matplotlib-inline==0.1.7
102 traitlets==5.14.3
102 traitlets==5.14.3
103 pexpect==4.9.0
103 pexpect==4.9.0
104 ptyprocess==0.7.0
104 ptyprocess==0.7.0
105 prompt_toolkit==3.0.47
105 prompt_toolkit==3.0.47
106 wcwidth==0.2.13
106 wcwidth==0.2.13
107 pygments==2.18.0
107 pygments==2.18.0
108 stack-data==0.6.3
108 stack-data==0.6.3
109 asttokens==2.4.1
109 asttokens==2.4.1
110 six==1.16.0
110 six==1.16.0
111 executing==2.0.1
111 executing==2.0.1
112 pure_eval==0.2.3
112 pure_eval==0.2.3
113 traitlets==5.14.3
113 traitlets==5.14.3
114 typing_extensions==4.12.2
114 typing_extensions==4.12.2
115 markdown==3.4.3
115 markdown==3.4.3
116 msgpack==1.0.8
116 msgpack==1.0.8
117 mysqlclient==2.1.1
117 mysqlclient==2.1.1
118 nbconvert==7.7.3
118 nbconvert==7.7.3
119 beautifulsoup4==4.12.3
119 beautifulsoup4==4.12.3
120 soupsieve==2.5
120 soupsieve==2.5
121 bleach==6.1.0
121 bleach==6.1.0
122 six==1.16.0
122 six==1.16.0
123 webencodings==0.5.1
123 webencodings==0.5.1
124 defusedxml==0.7.1
124 defusedxml==0.7.1
125 jinja2==3.1.2
125 jinja2==3.1.2
126 markupsafe==2.1.2
126 markupsafe==2.1.2
127 jupyter_core==5.3.1
127 jupyter_core==5.3.1
128 platformdirs==3.10.0
128 platformdirs==3.10.0
129 traitlets==5.14.3
129 traitlets==5.14.3
130 jupyterlab-pygments==0.2.2
130 jupyterlab-pygments==0.2.2
131 markupsafe==2.1.2
131 markupsafe==2.1.2
132 mistune==2.0.5
132 mistune==2.0.5
133 nbclient==0.8.0
133 nbclient==0.8.0
134 jupyter_client==8.3.0
134 jupyter_client==8.3.0
135 jupyter_core==5.3.1
135 jupyter_core==5.3.1
136 platformdirs==3.10.0
136 platformdirs==3.10.0
137 traitlets==5.14.3
137 traitlets==5.14.3
138 python-dateutil==2.8.2
138 python-dateutil==2.8.2
139 six==1.16.0
139 six==1.16.0
140 pyzmq==25.0.0
140 pyzmq==25.0.0
141 tornado==6.2
141 tornado==6.2
142 traitlets==5.14.3
142 traitlets==5.14.3
143 jupyter_core==5.3.1
143 jupyter_core==5.3.1
144 platformdirs==3.10.0
144 platformdirs==3.10.0
145 traitlets==5.14.3
145 traitlets==5.14.3
146 nbformat==5.9.2
146 nbformat==5.9.2
147 fastjsonschema==2.18.0
147 fastjsonschema==2.18.0
148 jsonschema==4.18.6
148 jsonschema==4.18.6
149 attrs==22.2.0
149 attrs==22.2.0
150 pyrsistent==0.19.3
150 pyrsistent==0.19.3
151 jupyter_core==5.3.1
151 jupyter_core==5.3.1
152 platformdirs==3.10.0
152 platformdirs==3.10.0
153 traitlets==5.14.3
153 traitlets==5.14.3
154 traitlets==5.14.3
154 traitlets==5.14.3
155 traitlets==5.14.3
155 traitlets==5.14.3
156 nbformat==5.9.2
156 nbformat==5.9.2
157 fastjsonschema==2.18.0
157 fastjsonschema==2.18.0
158 jsonschema==4.18.6
158 jsonschema==4.18.6
159 attrs==22.2.0
159 attrs==22.2.0
160 pyrsistent==0.19.3
160 pyrsistent==0.19.3
161 jupyter_core==5.3.1
161 jupyter_core==5.3.1
162 platformdirs==3.10.0
162 platformdirs==3.10.0
163 traitlets==5.14.3
163 traitlets==5.14.3
164 traitlets==5.14.3
164 traitlets==5.14.3
165 pandocfilters==1.5.0
165 pandocfilters==1.5.0
166 pygments==2.18.0
166 pygments==2.18.0
167 tinycss2==1.2.1
167 tinycss2==1.2.1
168 webencodings==0.5.1
168 webencodings==0.5.1
169 traitlets==5.14.3
169 traitlets==5.14.3
170 orjson==3.10.6
170 orjson==3.10.7
171 paste==3.10.1
171 paste==3.10.1
172 premailer==3.10.0
172 premailer==3.10.0
173 cachetools==5.3.3
173 cachetools==5.3.3
174 cssselect==1.2.0
174 cssselect==1.2.0
175 cssutils==2.6.0
175 cssutils==2.6.0
176 lxml==5.3.0
176 lxml==5.3.0
177 requests==2.28.2
177 requests==2.28.2
178 certifi==2022.12.7
178 certifi==2022.12.7
179 charset-normalizer==3.1.0
179 charset-normalizer==3.1.0
180 idna==3.4
180 idna==3.4
181 urllib3==1.26.14
181 urllib3==1.26.14
182 psutil==5.9.8
182 psutil==5.9.8
183 psycopg2==2.9.9
183 psycopg2==2.9.9
184 py-bcrypt==0.4
184 py-bcrypt==0.4
185 pycmarkgfm==1.2.0
185 pycmarkgfm==1.2.0
186 cffi==1.16.0
186 cffi==1.16.0
187 pycparser==2.21
187 pycparser==2.21
188 pycryptodome==3.17
188 pycryptodome==3.17
189 pycurl==7.45.3
189 pycurl==7.45.3
190 pymysql==1.0.3
190 pymysql==1.0.3
191 pyotp==2.8.0
191 pyotp==2.8.0
192 pyparsing==3.1.1
192 pyparsing==3.1.1
193 pyramid-mailer==0.15.1
193 pyramid-mailer==0.15.1
194 pyramid==2.0.2
194 pyramid==2.0.2
195 hupper==1.12
195 hupper==1.12
196 plaster==1.1.2
196 plaster==1.1.2
197 plaster-pastedeploy==1.0.1
197 plaster-pastedeploy==1.0.1
198 pastedeploy==3.1.0
198 pastedeploy==3.1.0
199 plaster==1.1.2
199 plaster==1.1.2
200 translationstring==1.4
200 translationstring==1.4
201 venusian==3.0.0
201 venusian==3.0.0
202 webob==1.8.7
202 webob==1.8.7
203 zope.deprecation==5.0.0
203 zope.deprecation==5.0.0
204 zope.interface==6.4.post2
204 zope.interface==7.0.3
205 repoze.sendmail==4.4.1
205 repoze.sendmail==4.4.1
206 transaction==3.1.0
206 transaction==5.0.0
207 zope.interface==6.4.post2
207 zope.interface==7.0.3
208 zope.interface==6.4.post2
208 zope.interface==7.0.3
209 transaction==3.1.0
209 transaction==5.0.0
210 zope.interface==6.4.post2
210 zope.interface==7.0.3
211 pyramid-mako==1.1.0
211 pyramid-mako==1.1.0
212 mako==1.2.4
212 mako==1.2.4
213 markupsafe==2.1.2
213 markupsafe==2.1.2
214 pyramid==2.0.2
214 pyramid==2.0.2
215 hupper==1.12
215 hupper==1.12
216 plaster==1.1.2
216 plaster==1.1.2
217 plaster-pastedeploy==1.0.1
217 plaster-pastedeploy==1.0.1
218 pastedeploy==3.1.0
218 pastedeploy==3.1.0
219 plaster==1.1.2
219 plaster==1.1.2
220 translationstring==1.4
220 translationstring==1.4
221 venusian==3.0.0
221 venusian==3.0.0
222 webob==1.8.7
222 webob==1.8.7
223 zope.deprecation==5.0.0
223 zope.deprecation==5.0.0
224 zope.interface==6.4.post2
224 zope.interface==7.0.3
225 python-ldap==3.4.3
225 python-ldap==3.4.3
226 pyasn1==0.4.8
226 pyasn1==0.4.8
227 pyasn1-modules==0.2.8
227 pyasn1-modules==0.2.8
228 pyasn1==0.4.8
228 pyasn1==0.4.8
229 python-memcached==1.59
229 python-memcached==1.59
230 six==1.16.0
230 six==1.16.0
231 python-pam==2.0.2
231 python-pam==2.0.2
232 python3-saml==1.16.0
232 python3-saml==1.16.0
233 isodate==0.6.1
233 isodate==0.6.1
234 six==1.16.0
234 six==1.16.0
235 lxml==5.3.0
235 lxml==5.3.0
236 xmlsec==1.3.14
236 xmlsec==1.3.14
237 lxml==5.3.0
237 lxml==5.3.0
238 pyyaml==6.0.1
238 pyyaml==6.0.1
239 redis==5.0.4
239 redis==5.1.0
240 async-timeout==4.0.3
240 async-timeout==4.0.3
241 regex==2022.10.31
241 regex==2022.10.31
242 routes==2.5.1
242 routes==2.5.1
243 repoze.lru==0.7
243 repoze.lru==0.7
244 six==1.16.0
244 six==1.16.0
245 s3fs==2024.6.0
245 s3fs==2024.9.0
246 aiobotocore==2.13.0
246 aiobotocore==2.13.0
247 aiohttp==3.9.5
247 aiohttp==3.9.5
248 aiosignal==1.3.1
248 aiosignal==1.3.1
249 frozenlist==1.4.1
249 frozenlist==1.4.1
250 attrs==22.2.0
250 attrs==22.2.0
251 frozenlist==1.4.1
251 frozenlist==1.4.1
252 multidict==6.0.5
252 multidict==6.0.5
253 yarl==1.9.4
253 yarl==1.9.4
254 idna==3.4
254 idna==3.4
255 multidict==6.0.5
255 multidict==6.0.5
256 aioitertools==0.11.0
256 aioitertools==0.11.0
257 botocore==1.34.106
257 botocore==1.34.106
258 jmespath==1.0.1
258 jmespath==1.0.1
259 python-dateutil==2.8.2
259 python-dateutil==2.8.2
260 six==1.16.0
260 six==1.16.0
261 urllib3==1.26.14
261 urllib3==1.26.14
262 wrapt==1.16.0
262 wrapt==1.16.0
263 aiohttp==3.9.5
263 aiohttp==3.9.5
264 aiosignal==1.3.1
264 aiosignal==1.3.1
265 frozenlist==1.4.1
265 frozenlist==1.4.1
266 attrs==22.2.0
266 attrs==22.2.0
267 frozenlist==1.4.1
267 frozenlist==1.4.1
268 multidict==6.0.5
268 multidict==6.0.5
269 yarl==1.9.4
269 yarl==1.9.4
270 idna==3.4
270 idna==3.4
271 multidict==6.0.5
271 multidict==6.0.5
272 fsspec==2024.6.0
272 fsspec==2024.9.0
273 simplejson==3.19.2
273 simplejson==3.19.2
274 sshpubkeys==3.3.1
274 sshpubkeys==3.3.1
275 cryptography==40.0.2
275 cryptography==40.0.2
276 cffi==1.16.0
276 cffi==1.16.0
277 pycparser==2.21
277 pycparser==2.21
278 ecdsa==0.18.0
278 ecdsa==0.18.0
279 six==1.16.0
279 six==1.16.0
280 sqlalchemy==1.4.52
280 sqlalchemy==1.4.52
281 greenlet==3.0.3
281 greenlet==3.0.3
282 typing_extensions==4.12.2
282 typing_extensions==4.12.2
283 supervisor==4.2.5
283 supervisor==4.2.5
284 tzlocal==4.3
284 tzlocal==4.3
285 pytz-deprecation-shim==0.1.0.post0
285 pytz-deprecation-shim==0.1.0.post0
286 tzdata==2024.1
286 tzdata==2024.1
287 tempita==0.5.2
287 tempita==0.5.2
288 unidecode==1.3.6
288 unidecode==1.3.6
289 urlobject==2.4.3
289 urlobject==2.4.3
290 waitress==3.0.0
290 waitress==3.0.0
291 webhelpers2==2.1
291 webhelpers2==2.1
292 markupsafe==2.1.2
292 markupsafe==2.1.2
293 six==1.16.0
293 six==1.16.0
294 whoosh==2.7.4
294 whoosh==2.7.4
295 zope.cachedescriptors==5.0.0
295 zope.cachedescriptors==5.0.0
296 qrcode==7.4.2
296 qrcode==7.4.2
297
297
298 ## uncomment to add the debug libraries
298 ## uncomment to add the debug libraries
299 #-r requirements_debug.txt
299 #-r requirements_debug.txt
@@ -1,1124 +1,1124 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 from rhodecode.apps._base import ADMIN_PREFIX
20 from rhodecode.apps._base import ADMIN_PREFIX
21 from rhodecode.apps._base.navigation import includeme as nav_includeme
21 from rhodecode.apps._base.navigation import includeme as nav_includeme
22 from rhodecode.apps.admin.views.main_views import AdminMainView
22 from rhodecode.apps.admin.views.main_views import AdminMainView
23
23
24
24
25 def admin_routes(config):
25 def admin_routes(config):
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
30 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
30 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
31 from rhodecode.apps.admin.views.automation import AdminAutomationView
31 from rhodecode.apps.admin.views.automation import AdminAutomationView
32 from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
32 from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
33 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
33 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
34 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
34 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
35 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
35 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
36 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
36 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
37 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
37 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
38 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
38 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
39 from rhodecode.apps.admin.views.repositories import AdminReposView
39 from rhodecode.apps.admin.views.repositories import AdminReposView
40 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
40 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
41 from rhodecode.apps.admin.views.settings import AdminSettingsView
41 from rhodecode.apps.admin.views.settings import AdminSettingsView
42 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
42 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
43 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
43 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
44 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
44 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
45 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
45 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
46 from rhodecode.apps.admin.views.security import AdminSecurityView
46 from rhodecode.apps.admin.views.security import AdminSecurityView
47
47
48 # Security EE feature
48 # Security EE feature
49
49
50 config.add_route(
50 config.add_route(
51 'admin_security',
51 'admin_security',
52 pattern='/security')
52 pattern='/security')
53 config.add_view(
53 config.add_view(
54 AdminSecurityView,
54 AdminSecurityView,
55 attr='security',
55 attr='security',
56 route_name='admin_security', request_method='GET',
56 route_name='admin_security', request_method='GET',
57 renderer='rhodecode:templates/admin/security/security.mako')
57 renderer='rhodecode:templates/admin/security/security.mako')
58
58
59 config.add_route(
59 config.add_route(
60 name='admin_security_update',
60 name='admin_security_update',
61 pattern='/security/update')
61 pattern='/security/update')
62 config.add_view(
62 config.add_view(
63 AdminSecurityView,
63 AdminSecurityView,
64 attr='security_update',
64 attr='security_update',
65 route_name='admin_security_update', request_method='POST',
65 route_name='admin_security_update', request_method='POST',
66 renderer='rhodecode:templates/admin/security/security.mako')
66 renderer='rhodecode:templates/admin/security/security.mako')
67
67
68 config.add_route(
68 config.add_route(
69 name='admin_security_modify_allowed_vcs_client_versions',
69 name='admin_security_modify_allowed_vcs_client_versions',
70 pattern='/security/modify/allowed_vcs_client_versions')
70 pattern=ADMIN_PREFIX + '/security/modify/allowed_vcs_client_versions')
71 config.add_view(
71 config.add_view(
72 AdminSecurityView,
72 AdminSecurityView,
73 attr='vcs_whitelisted_client_versions_edit',
73 attr='vcs_whitelisted_client_versions_edit',
74 route_name='admin_security_modify_allowed_vcs_client_versions', request_method=('GET', 'POST'),
74 route_name='admin_security_modify_allowed_vcs_client_versions', request_method=('GET', 'POST'),
75 renderer='rhodecode:templates/admin/security/edit_allowed_vcs_client_versions.mako')
75 renderer='rhodecode:templates/admin/security/edit_allowed_vcs_client_versions.mako')
76
76
77
77
78 config.add_route(
78 config.add_route(
79 name='admin_audit_logs',
79 name='admin_audit_logs',
80 pattern='/audit_logs')
80 pattern='/audit_logs')
81 config.add_view(
81 config.add_view(
82 AdminAuditLogsView,
82 AdminAuditLogsView,
83 attr='admin_audit_logs',
83 attr='admin_audit_logs',
84 route_name='admin_audit_logs', request_method='GET',
84 route_name='admin_audit_logs', request_method='GET',
85 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
85 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
86
86
87 config.add_route(
87 config.add_route(
88 name='admin_audit_log_entry',
88 name='admin_audit_log_entry',
89 pattern='/audit_logs/{audit_log_id}')
89 pattern='/audit_logs/{audit_log_id}')
90 config.add_view(
90 config.add_view(
91 AdminAuditLogsView,
91 AdminAuditLogsView,
92 attr='admin_audit_log_entry',
92 attr='admin_audit_log_entry',
93 route_name='admin_audit_log_entry', request_method='GET',
93 route_name='admin_audit_log_entry', request_method='GET',
94 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
94 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
95
95
96 # Artifacts EE feature
96 # Artifacts EE feature
97 config.add_route(
97 config.add_route(
98 'admin_artifacts',
98 'admin_artifacts',
99 pattern=ADMIN_PREFIX + '/artifacts')
99 pattern=ADMIN_PREFIX + '/artifacts')
100 config.add_route(
100 config.add_route(
101 'admin_artifacts_show_all',
101 'admin_artifacts_show_all',
102 pattern=ADMIN_PREFIX + '/artifacts')
102 pattern=ADMIN_PREFIX + '/artifacts')
103 config.add_view(
103 config.add_view(
104 AdminArtifactsView,
104 AdminArtifactsView,
105 attr='artifacts',
105 attr='artifacts',
106 route_name='admin_artifacts', request_method='GET',
106 route_name='admin_artifacts', request_method='GET',
107 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
107 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
108 config.add_view(
108 config.add_view(
109 AdminArtifactsView,
109 AdminArtifactsView,
110 attr='artifacts',
110 attr='artifacts',
111 route_name='admin_artifacts_show_all', request_method='GET',
111 route_name='admin_artifacts_show_all', request_method='GET',
112 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
112 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
113
113
114 # EE views
114 # EE views
115 config.add_route(
115 config.add_route(
116 name='admin_artifacts_show_info',
116 name='admin_artifacts_show_info',
117 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
117 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
118 config.add_route(
118 config.add_route(
119 name='admin_artifacts_delete',
119 name='admin_artifacts_delete',
120 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
120 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
121 config.add_route(
121 config.add_route(
122 name='admin_artifacts_update',
122 name='admin_artifacts_update',
123 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
123 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
124
124
125 # Automation EE feature
125 # Automation EE feature
126 config.add_route(
126 config.add_route(
127 'admin_automation',
127 'admin_automation',
128 pattern=ADMIN_PREFIX + '/automation')
128 pattern=ADMIN_PREFIX + '/automation')
129 config.add_view(
129 config.add_view(
130 AdminAutomationView,
130 AdminAutomationView,
131 attr='automation',
131 attr='automation',
132 route_name='admin_automation', request_method='GET',
132 route_name='admin_automation', request_method='GET',
133 renderer='rhodecode:templates/admin/automation/automation.mako')
133 renderer='rhodecode:templates/admin/automation/automation.mako')
134
134
135 # Scheduler EE feature
135 # Scheduler EE feature
136 config.add_route(
136 config.add_route(
137 'admin_scheduler',
137 'admin_scheduler',
138 pattern=ADMIN_PREFIX + '/scheduler')
138 pattern=ADMIN_PREFIX + '/scheduler')
139 config.add_view(
139 config.add_view(
140 AdminSchedulerView,
140 AdminSchedulerView,
141 attr='scheduler',
141 attr='scheduler',
142 route_name='admin_scheduler', request_method='GET',
142 route_name='admin_scheduler', request_method='GET',
143 renderer='rhodecode:templates/admin/scheduler/scheduler.mako')
143 renderer='rhodecode:templates/admin/scheduler/scheduler.mako')
144
144
145 config.add_route(
145 config.add_route(
146 name='admin_settings_open_source',
146 name='admin_settings_open_source',
147 pattern='/settings/open_source')
147 pattern='/settings/open_source')
148 config.add_view(
148 config.add_view(
149 OpenSourceLicensesAdminSettingsView,
149 OpenSourceLicensesAdminSettingsView,
150 attr='open_source_licenses',
150 attr='open_source_licenses',
151 route_name='admin_settings_open_source', request_method='GET',
151 route_name='admin_settings_open_source', request_method='GET',
152 renderer='rhodecode:templates/admin/settings/settings.mako')
152 renderer='rhodecode:templates/admin/settings/settings.mako')
153
153
154 config.add_route(
154 config.add_route(
155 name='admin_settings_vcs_svn_generate_cfg',
155 name='admin_settings_vcs_svn_generate_cfg',
156 pattern='/settings/vcs/svn_generate_cfg')
156 pattern='/settings/vcs/svn_generate_cfg')
157 config.add_view(
157 config.add_view(
158 AdminSvnConfigView,
158 AdminSvnConfigView,
159 attr='vcs_svn_generate_config',
159 attr='vcs_svn_generate_config',
160 route_name='admin_settings_vcs_svn_generate_cfg',
160 route_name='admin_settings_vcs_svn_generate_cfg',
161 request_method='POST', renderer='json')
161 request_method='POST', renderer='json')
162
162
163 config.add_route(
163 config.add_route(
164 name='admin_settings_system',
164 name='admin_settings_system',
165 pattern='/settings/system')
165 pattern='/settings/system')
166 config.add_view(
166 config.add_view(
167 AdminSystemInfoSettingsView,
167 AdminSystemInfoSettingsView,
168 attr='settings_system_info',
168 attr='settings_system_info',
169 route_name='admin_settings_system', request_method='GET',
169 route_name='admin_settings_system', request_method='GET',
170 renderer='rhodecode:templates/admin/settings/settings.mako')
170 renderer='rhodecode:templates/admin/settings/settings.mako')
171
171
172 config.add_route(
172 config.add_route(
173 name='admin_settings_system_update',
173 name='admin_settings_system_update',
174 pattern='/settings/system/updates')
174 pattern='/settings/system/updates')
175 config.add_view(
175 config.add_view(
176 AdminSystemInfoSettingsView,
176 AdminSystemInfoSettingsView,
177 attr='settings_system_info_check_update',
177 attr='settings_system_info_check_update',
178 route_name='admin_settings_system_update', request_method='GET',
178 route_name='admin_settings_system_update', request_method='GET',
179 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
179 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
180
180
181 config.add_route(
181 config.add_route(
182 name='admin_settings_exception_tracker',
182 name='admin_settings_exception_tracker',
183 pattern='/settings/exceptions')
183 pattern='/settings/exceptions')
184 config.add_view(
184 config.add_view(
185 ExceptionsTrackerView,
185 ExceptionsTrackerView,
186 attr='browse_exceptions',
186 attr='browse_exceptions',
187 route_name='admin_settings_exception_tracker', request_method='GET',
187 route_name='admin_settings_exception_tracker', request_method='GET',
188 renderer='rhodecode:templates/admin/settings/settings.mako')
188 renderer='rhodecode:templates/admin/settings/settings.mako')
189
189
190 config.add_route(
190 config.add_route(
191 name='admin_settings_exception_tracker_delete_all',
191 name='admin_settings_exception_tracker_delete_all',
192 pattern='/settings/exceptions_delete_all')
192 pattern='/settings/exceptions_delete_all')
193 config.add_view(
193 config.add_view(
194 ExceptionsTrackerView,
194 ExceptionsTrackerView,
195 attr='exception_delete_all',
195 attr='exception_delete_all',
196 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
196 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
197 renderer='rhodecode:templates/admin/settings/settings.mako')
197 renderer='rhodecode:templates/admin/settings/settings.mako')
198
198
199 config.add_route(
199 config.add_route(
200 name='admin_settings_exception_tracker_show',
200 name='admin_settings_exception_tracker_show',
201 pattern='/settings/exceptions/{exception_id}')
201 pattern='/settings/exceptions/{exception_id}')
202 config.add_view(
202 config.add_view(
203 ExceptionsTrackerView,
203 ExceptionsTrackerView,
204 attr='exception_show',
204 attr='exception_show',
205 route_name='admin_settings_exception_tracker_show', request_method='GET',
205 route_name='admin_settings_exception_tracker_show', request_method='GET',
206 renderer='rhodecode:templates/admin/settings/settings.mako')
206 renderer='rhodecode:templates/admin/settings/settings.mako')
207
207
208 config.add_route(
208 config.add_route(
209 name='admin_settings_exception_tracker_delete',
209 name='admin_settings_exception_tracker_delete',
210 pattern='/settings/exceptions/{exception_id}/delete')
210 pattern='/settings/exceptions/{exception_id}/delete')
211 config.add_view(
211 config.add_view(
212 ExceptionsTrackerView,
212 ExceptionsTrackerView,
213 attr='exception_delete',
213 attr='exception_delete',
214 route_name='admin_settings_exception_tracker_delete', request_method='POST',
214 route_name='admin_settings_exception_tracker_delete', request_method='POST',
215 renderer='rhodecode:templates/admin/settings/settings.mako')
215 renderer='rhodecode:templates/admin/settings/settings.mako')
216
216
217 config.add_route(
217 config.add_route(
218 name='admin_settings_sessions',
218 name='admin_settings_sessions',
219 pattern='/settings/sessions')
219 pattern='/settings/sessions')
220 config.add_view(
220 config.add_view(
221 AdminSessionSettingsView,
221 AdminSessionSettingsView,
222 attr='settings_sessions',
222 attr='settings_sessions',
223 route_name='admin_settings_sessions', request_method='GET',
223 route_name='admin_settings_sessions', request_method='GET',
224 renderer='rhodecode:templates/admin/settings/settings.mako')
224 renderer='rhodecode:templates/admin/settings/settings.mako')
225
225
226 config.add_route(
226 config.add_route(
227 name='admin_settings_sessions_cleanup',
227 name='admin_settings_sessions_cleanup',
228 pattern='/settings/sessions/cleanup')
228 pattern='/settings/sessions/cleanup')
229 config.add_view(
229 config.add_view(
230 AdminSessionSettingsView,
230 AdminSessionSettingsView,
231 attr='settings_sessions_cleanup',
231 attr='settings_sessions_cleanup',
232 route_name='admin_settings_sessions_cleanup', request_method='POST')
232 route_name='admin_settings_sessions_cleanup', request_method='POST')
233
233
234 config.add_route(
234 config.add_route(
235 name='admin_settings_process_management',
235 name='admin_settings_process_management',
236 pattern='/settings/process_management')
236 pattern='/settings/process_management')
237 config.add_view(
237 config.add_view(
238 AdminProcessManagementView,
238 AdminProcessManagementView,
239 attr='process_management',
239 attr='process_management',
240 route_name='admin_settings_process_management', request_method='GET',
240 route_name='admin_settings_process_management', request_method='GET',
241 renderer='rhodecode:templates/admin/settings/settings.mako')
241 renderer='rhodecode:templates/admin/settings/settings.mako')
242
242
243 config.add_route(
243 config.add_route(
244 name='admin_settings_process_management_data',
244 name='admin_settings_process_management_data',
245 pattern='/settings/process_management/data')
245 pattern='/settings/process_management/data')
246 config.add_view(
246 config.add_view(
247 AdminProcessManagementView,
247 AdminProcessManagementView,
248 attr='process_management_data',
248 attr='process_management_data',
249 route_name='admin_settings_process_management_data', request_method='GET',
249 route_name='admin_settings_process_management_data', request_method='GET',
250 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
250 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
251
251
252 config.add_route(
252 config.add_route(
253 name='admin_settings_process_management_signal',
253 name='admin_settings_process_management_signal',
254 pattern='/settings/process_management/signal')
254 pattern='/settings/process_management/signal')
255 config.add_view(
255 config.add_view(
256 AdminProcessManagementView,
256 AdminProcessManagementView,
257 attr='process_management_signal',
257 attr='process_management_signal',
258 route_name='admin_settings_process_management_signal',
258 route_name='admin_settings_process_management_signal',
259 request_method='POST', renderer='json_ext')
259 request_method='POST', renderer='json_ext')
260
260
261 config.add_route(
261 config.add_route(
262 name='admin_settings_process_management_master_signal',
262 name='admin_settings_process_management_master_signal',
263 pattern='/settings/process_management/master_signal')
263 pattern='/settings/process_management/master_signal')
264 config.add_view(
264 config.add_view(
265 AdminProcessManagementView,
265 AdminProcessManagementView,
266 attr='process_management_master_signal',
266 attr='process_management_master_signal',
267 route_name='admin_settings_process_management_master_signal',
267 route_name='admin_settings_process_management_master_signal',
268 request_method='POST', renderer='json_ext')
268 request_method='POST', renderer='json_ext')
269
269
270 # default settings
270 # default settings
271 config.add_route(
271 config.add_route(
272 name='admin_defaults_repositories',
272 name='admin_defaults_repositories',
273 pattern='/defaults/repositories')
273 pattern='/defaults/repositories')
274 config.add_view(
274 config.add_view(
275 AdminDefaultSettingsView,
275 AdminDefaultSettingsView,
276 attr='defaults_repository_show',
276 attr='defaults_repository_show',
277 route_name='admin_defaults_repositories', request_method='GET',
277 route_name='admin_defaults_repositories', request_method='GET',
278 renderer='rhodecode:templates/admin/defaults/defaults.mako')
278 renderer='rhodecode:templates/admin/defaults/defaults.mako')
279
279
280 config.add_route(
280 config.add_route(
281 name='admin_defaults_repositories_update',
281 name='admin_defaults_repositories_update',
282 pattern='/defaults/repositories/update')
282 pattern='/defaults/repositories/update')
283 config.add_view(
283 config.add_view(
284 AdminDefaultSettingsView,
284 AdminDefaultSettingsView,
285 attr='defaults_repository_update',
285 attr='defaults_repository_update',
286 route_name='admin_defaults_repositories_update', request_method='POST',
286 route_name='admin_defaults_repositories_update', request_method='POST',
287 renderer='rhodecode:templates/admin/defaults/defaults.mako')
287 renderer='rhodecode:templates/admin/defaults/defaults.mako')
288
288
289 # admin settings
289 # admin settings
290
290
291 config.add_route(
291 config.add_route(
292 name='admin_settings',
292 name='admin_settings',
293 pattern='/settings')
293 pattern='/settings')
294 config.add_view(
294 config.add_view(
295 AdminSettingsView,
295 AdminSettingsView,
296 attr='settings_global',
296 attr='settings_global',
297 route_name='admin_settings', request_method='GET',
297 route_name='admin_settings', request_method='GET',
298 renderer='rhodecode:templates/admin/settings/settings.mako')
298 renderer='rhodecode:templates/admin/settings/settings.mako')
299
299
300 config.add_route(
300 config.add_route(
301 name='admin_settings_update',
301 name='admin_settings_update',
302 pattern='/settings/update')
302 pattern='/settings/update')
303 config.add_view(
303 config.add_view(
304 AdminSettingsView,
304 AdminSettingsView,
305 attr='settings_global_update',
305 attr='settings_global_update',
306 route_name='admin_settings_update', request_method='POST',
306 route_name='admin_settings_update', request_method='POST',
307 renderer='rhodecode:templates/admin/settings/settings.mako')
307 renderer='rhodecode:templates/admin/settings/settings.mako')
308
308
309 config.add_route(
309 config.add_route(
310 name='admin_settings_global',
310 name='admin_settings_global',
311 pattern='/settings/global')
311 pattern='/settings/global')
312 config.add_view(
312 config.add_view(
313 AdminSettingsView,
313 AdminSettingsView,
314 attr='settings_global',
314 attr='settings_global',
315 route_name='admin_settings_global', request_method='GET',
315 route_name='admin_settings_global', request_method='GET',
316 renderer='rhodecode:templates/admin/settings/settings.mako')
316 renderer='rhodecode:templates/admin/settings/settings.mako')
317
317
318 config.add_route(
318 config.add_route(
319 name='admin_settings_global_update',
319 name='admin_settings_global_update',
320 pattern='/settings/global/update')
320 pattern='/settings/global/update')
321 config.add_view(
321 config.add_view(
322 AdminSettingsView,
322 AdminSettingsView,
323 attr='settings_global_update',
323 attr='settings_global_update',
324 route_name='admin_settings_global_update', request_method='POST',
324 route_name='admin_settings_global_update', request_method='POST',
325 renderer='rhodecode:templates/admin/settings/settings.mako')
325 renderer='rhodecode:templates/admin/settings/settings.mako')
326
326
327 config.add_route(
327 config.add_route(
328 name='admin_settings_vcs',
328 name='admin_settings_vcs',
329 pattern='/settings/vcs')
329 pattern='/settings/vcs')
330 config.add_view(
330 config.add_view(
331 AdminSettingsView,
331 AdminSettingsView,
332 attr='settings_vcs',
332 attr='settings_vcs',
333 route_name='admin_settings_vcs', request_method='GET',
333 route_name='admin_settings_vcs', request_method='GET',
334 renderer='rhodecode:templates/admin/settings/settings.mako')
334 renderer='rhodecode:templates/admin/settings/settings.mako')
335
335
336 config.add_route(
336 config.add_route(
337 name='admin_settings_vcs_update',
337 name='admin_settings_vcs_update',
338 pattern='/settings/vcs/update')
338 pattern='/settings/vcs/update')
339 config.add_view(
339 config.add_view(
340 AdminSettingsView,
340 AdminSettingsView,
341 attr='settings_vcs_update',
341 attr='settings_vcs_update',
342 route_name='admin_settings_vcs_update', request_method='POST',
342 route_name='admin_settings_vcs_update', request_method='POST',
343 renderer='rhodecode:templates/admin/settings/settings.mako')
343 renderer='rhodecode:templates/admin/settings/settings.mako')
344
344
345 config.add_route(
345 config.add_route(
346 name='admin_settings_vcs_svn_pattern_delete',
346 name='admin_settings_vcs_svn_pattern_delete',
347 pattern='/settings/vcs/svn_pattern_delete')
347 pattern='/settings/vcs/svn_pattern_delete')
348 config.add_view(
348 config.add_view(
349 AdminSettingsView,
349 AdminSettingsView,
350 attr='settings_vcs_delete_svn_pattern',
350 attr='settings_vcs_delete_svn_pattern',
351 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
351 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
352 renderer='json_ext', xhr=True)
352 renderer='json_ext', xhr=True)
353
353
354 config.add_route(
354 config.add_route(
355 name='admin_settings_mapping',
355 name='admin_settings_mapping',
356 pattern='/settings/mapping')
356 pattern='/settings/mapping')
357 config.add_view(
357 config.add_view(
358 AdminSettingsView,
358 AdminSettingsView,
359 attr='settings_mapping',
359 attr='settings_mapping',
360 route_name='admin_settings_mapping', request_method='GET',
360 route_name='admin_settings_mapping', request_method='GET',
361 renderer='rhodecode:templates/admin/settings/settings.mako')
361 renderer='rhodecode:templates/admin/settings/settings.mako')
362
362
363 config.add_route(
363 config.add_route(
364 name='admin_settings_mapping_update',
364 name='admin_settings_mapping_update',
365 pattern='/settings/mapping/update')
365 pattern='/settings/mapping/update')
366 config.add_view(
366 config.add_view(
367 AdminSettingsView,
367 AdminSettingsView,
368 attr='settings_mapping_update',
368 attr='settings_mapping_update',
369 route_name='admin_settings_mapping_update', request_method='POST',
369 route_name='admin_settings_mapping_update', request_method='POST',
370 renderer='rhodecode:templates/admin/settings/settings.mako')
370 renderer='rhodecode:templates/admin/settings/settings.mako')
371
371
372 config.add_route(
372 config.add_route(
373 name='admin_settings_visual',
373 name='admin_settings_visual',
374 pattern='/settings/visual')
374 pattern='/settings/visual')
375 config.add_view(
375 config.add_view(
376 AdminSettingsView,
376 AdminSettingsView,
377 attr='settings_visual',
377 attr='settings_visual',
378 route_name='admin_settings_visual', request_method='GET',
378 route_name='admin_settings_visual', request_method='GET',
379 renderer='rhodecode:templates/admin/settings/settings.mako')
379 renderer='rhodecode:templates/admin/settings/settings.mako')
380
380
381 config.add_route(
381 config.add_route(
382 name='admin_settings_visual_update',
382 name='admin_settings_visual_update',
383 pattern='/settings/visual/update')
383 pattern='/settings/visual/update')
384 config.add_view(
384 config.add_view(
385 AdminSettingsView,
385 AdminSettingsView,
386 attr='settings_visual_update',
386 attr='settings_visual_update',
387 route_name='admin_settings_visual_update', request_method='POST',
387 route_name='admin_settings_visual_update', request_method='POST',
388 renderer='rhodecode:templates/admin/settings/settings.mako')
388 renderer='rhodecode:templates/admin/settings/settings.mako')
389
389
390 config.add_route(
390 config.add_route(
391 name='admin_settings_issuetracker',
391 name='admin_settings_issuetracker',
392 pattern='/settings/issue-tracker')
392 pattern='/settings/issue-tracker')
393 config.add_view(
393 config.add_view(
394 AdminSettingsView,
394 AdminSettingsView,
395 attr='settings_issuetracker',
395 attr='settings_issuetracker',
396 route_name='admin_settings_issuetracker', request_method='GET',
396 route_name='admin_settings_issuetracker', request_method='GET',
397 renderer='rhodecode:templates/admin/settings/settings.mako')
397 renderer='rhodecode:templates/admin/settings/settings.mako')
398
398
399 config.add_route(
399 config.add_route(
400 name='admin_settings_issuetracker_update',
400 name='admin_settings_issuetracker_update',
401 pattern='/settings/issue-tracker/update')
401 pattern='/settings/issue-tracker/update')
402 config.add_view(
402 config.add_view(
403 AdminSettingsView,
403 AdminSettingsView,
404 attr='settings_issuetracker_update',
404 attr='settings_issuetracker_update',
405 route_name='admin_settings_issuetracker_update', request_method='POST',
405 route_name='admin_settings_issuetracker_update', request_method='POST',
406 renderer='rhodecode:templates/admin/settings/settings.mako')
406 renderer='rhodecode:templates/admin/settings/settings.mako')
407
407
408 config.add_route(
408 config.add_route(
409 name='admin_settings_issuetracker_test',
409 name='admin_settings_issuetracker_test',
410 pattern='/settings/issue-tracker/test')
410 pattern='/settings/issue-tracker/test')
411 config.add_view(
411 config.add_view(
412 AdminSettingsView,
412 AdminSettingsView,
413 attr='settings_issuetracker_test',
413 attr='settings_issuetracker_test',
414 route_name='admin_settings_issuetracker_test', request_method='POST',
414 route_name='admin_settings_issuetracker_test', request_method='POST',
415 renderer='string', xhr=True)
415 renderer='string', xhr=True)
416
416
417 config.add_route(
417 config.add_route(
418 name='admin_settings_issuetracker_delete',
418 name='admin_settings_issuetracker_delete',
419 pattern='/settings/issue-tracker/delete')
419 pattern='/settings/issue-tracker/delete')
420 config.add_view(
420 config.add_view(
421 AdminSettingsView,
421 AdminSettingsView,
422 attr='settings_issuetracker_delete',
422 attr='settings_issuetracker_delete',
423 route_name='admin_settings_issuetracker_delete', request_method='POST',
423 route_name='admin_settings_issuetracker_delete', request_method='POST',
424 renderer='json_ext', xhr=True)
424 renderer='json_ext', xhr=True)
425
425
426 config.add_route(
426 config.add_route(
427 name='admin_settings_email',
427 name='admin_settings_email',
428 pattern='/settings/email')
428 pattern='/settings/email')
429 config.add_view(
429 config.add_view(
430 AdminSettingsView,
430 AdminSettingsView,
431 attr='settings_email',
431 attr='settings_email',
432 route_name='admin_settings_email', request_method='GET',
432 route_name='admin_settings_email', request_method='GET',
433 renderer='rhodecode:templates/admin/settings/settings.mako')
433 renderer='rhodecode:templates/admin/settings/settings.mako')
434
434
435 config.add_route(
435 config.add_route(
436 name='admin_settings_email_update',
436 name='admin_settings_email_update',
437 pattern='/settings/email/update')
437 pattern='/settings/email/update')
438 config.add_view(
438 config.add_view(
439 AdminSettingsView,
439 AdminSettingsView,
440 attr='settings_email_update',
440 attr='settings_email_update',
441 route_name='admin_settings_email_update', request_method='POST',
441 route_name='admin_settings_email_update', request_method='POST',
442 renderer='rhodecode:templates/admin/settings/settings.mako')
442 renderer='rhodecode:templates/admin/settings/settings.mako')
443
443
444 config.add_route(
444 config.add_route(
445 name='admin_settings_hooks',
445 name='admin_settings_hooks',
446 pattern='/settings/hooks')
446 pattern='/settings/hooks')
447 config.add_view(
447 config.add_view(
448 AdminSettingsView,
448 AdminSettingsView,
449 attr='settings_hooks',
449 attr='settings_hooks',
450 route_name='admin_settings_hooks', request_method='GET',
450 route_name='admin_settings_hooks', request_method='GET',
451 renderer='rhodecode:templates/admin/settings/settings.mako')
451 renderer='rhodecode:templates/admin/settings/settings.mako')
452
452
453 config.add_route(
453 config.add_route(
454 name='admin_settings_hooks_update',
454 name='admin_settings_hooks_update',
455 pattern='/settings/hooks/update')
455 pattern='/settings/hooks/update')
456 config.add_view(
456 config.add_view(
457 AdminSettingsView,
457 AdminSettingsView,
458 attr='settings_hooks_update',
458 attr='settings_hooks_update',
459 route_name='admin_settings_hooks_update', request_method='POST',
459 route_name='admin_settings_hooks_update', request_method='POST',
460 renderer='rhodecode:templates/admin/settings/settings.mako')
460 renderer='rhodecode:templates/admin/settings/settings.mako')
461
461
462 config.add_route(
462 config.add_route(
463 name='admin_settings_hooks_delete',
463 name='admin_settings_hooks_delete',
464 pattern='/settings/hooks/delete')
464 pattern='/settings/hooks/delete')
465 config.add_view(
465 config.add_view(
466 AdminSettingsView,
466 AdminSettingsView,
467 attr='settings_hooks_update',
467 attr='settings_hooks_update',
468 route_name='admin_settings_hooks_delete', request_method='POST',
468 route_name='admin_settings_hooks_delete', request_method='POST',
469 renderer='rhodecode:templates/admin/settings/settings.mako')
469 renderer='rhodecode:templates/admin/settings/settings.mako')
470
470
471 config.add_route(
471 config.add_route(
472 name='admin_settings_search',
472 name='admin_settings_search',
473 pattern='/settings/search')
473 pattern='/settings/search')
474 config.add_view(
474 config.add_view(
475 AdminSettingsView,
475 AdminSettingsView,
476 attr='settings_search',
476 attr='settings_search',
477 route_name='admin_settings_search', request_method='GET',
477 route_name='admin_settings_search', request_method='GET',
478 renderer='rhodecode:templates/admin/settings/settings.mako')
478 renderer='rhodecode:templates/admin/settings/settings.mako')
479
479
480 config.add_route(
480 config.add_route(
481 name='admin_settings_labs',
481 name='admin_settings_labs',
482 pattern='/settings/labs')
482 pattern='/settings/labs')
483 config.add_view(
483 config.add_view(
484 AdminSettingsView,
484 AdminSettingsView,
485 attr='settings_labs',
485 attr='settings_labs',
486 route_name='admin_settings_labs', request_method='GET',
486 route_name='admin_settings_labs', request_method='GET',
487 renderer='rhodecode:templates/admin/settings/settings.mako')
487 renderer='rhodecode:templates/admin/settings/settings.mako')
488
488
489 config.add_route(
489 config.add_route(
490 name='admin_settings_labs_update',
490 name='admin_settings_labs_update',
491 pattern='/settings/labs/update')
491 pattern='/settings/labs/update')
492 config.add_view(
492 config.add_view(
493 AdminSettingsView,
493 AdminSettingsView,
494 attr='settings_labs_update',
494 attr='settings_labs_update',
495 route_name='admin_settings_labs_update', request_method='POST',
495 route_name='admin_settings_labs_update', request_method='POST',
496 renderer='rhodecode:templates/admin/settings/settings.mako')
496 renderer='rhodecode:templates/admin/settings/settings.mako')
497
497
498 # global permissions
498 # global permissions
499
499
500 config.add_route(
500 config.add_route(
501 name='admin_permissions_application',
501 name='admin_permissions_application',
502 pattern='/permissions/application')
502 pattern='/permissions/application')
503 config.add_view(
503 config.add_view(
504 AdminPermissionsView,
504 AdminPermissionsView,
505 attr='permissions_application',
505 attr='permissions_application',
506 route_name='admin_permissions_application', request_method='GET',
506 route_name='admin_permissions_application', request_method='GET',
507 renderer='rhodecode:templates/admin/permissions/permissions.mako')
507 renderer='rhodecode:templates/admin/permissions/permissions.mako')
508
508
509 config.add_route(
509 config.add_route(
510 name='admin_permissions_application_update',
510 name='admin_permissions_application_update',
511 pattern='/permissions/application/update')
511 pattern='/permissions/application/update')
512 config.add_view(
512 config.add_view(
513 AdminPermissionsView,
513 AdminPermissionsView,
514 attr='permissions_application_update',
514 attr='permissions_application_update',
515 route_name='admin_permissions_application_update', request_method='POST',
515 route_name='admin_permissions_application_update', request_method='POST',
516 renderer='rhodecode:templates/admin/permissions/permissions.mako')
516 renderer='rhodecode:templates/admin/permissions/permissions.mako')
517
517
518 config.add_route(
518 config.add_route(
519 name='admin_permissions_global',
519 name='admin_permissions_global',
520 pattern='/permissions/global')
520 pattern='/permissions/global')
521 config.add_view(
521 config.add_view(
522 AdminPermissionsView,
522 AdminPermissionsView,
523 attr='permissions_global',
523 attr='permissions_global',
524 route_name='admin_permissions_global', request_method='GET',
524 route_name='admin_permissions_global', request_method='GET',
525 renderer='rhodecode:templates/admin/permissions/permissions.mako')
525 renderer='rhodecode:templates/admin/permissions/permissions.mako')
526
526
527 config.add_route(
527 config.add_route(
528 name='admin_permissions_global_update',
528 name='admin_permissions_global_update',
529 pattern='/permissions/global/update')
529 pattern='/permissions/global/update')
530 config.add_view(
530 config.add_view(
531 AdminPermissionsView,
531 AdminPermissionsView,
532 attr='permissions_global_update',
532 attr='permissions_global_update',
533 route_name='admin_permissions_global_update', request_method='POST',
533 route_name='admin_permissions_global_update', request_method='POST',
534 renderer='rhodecode:templates/admin/permissions/permissions.mako')
534 renderer='rhodecode:templates/admin/permissions/permissions.mako')
535
535
536 config.add_route(
536 config.add_route(
537 name='admin_permissions_object',
537 name='admin_permissions_object',
538 pattern='/permissions/object')
538 pattern='/permissions/object')
539 config.add_view(
539 config.add_view(
540 AdminPermissionsView,
540 AdminPermissionsView,
541 attr='permissions_objects',
541 attr='permissions_objects',
542 route_name='admin_permissions_object', request_method='GET',
542 route_name='admin_permissions_object', request_method='GET',
543 renderer='rhodecode:templates/admin/permissions/permissions.mako')
543 renderer='rhodecode:templates/admin/permissions/permissions.mako')
544
544
545 config.add_route(
545 config.add_route(
546 name='admin_permissions_object_update',
546 name='admin_permissions_object_update',
547 pattern='/permissions/object/update')
547 pattern='/permissions/object/update')
548 config.add_view(
548 config.add_view(
549 AdminPermissionsView,
549 AdminPermissionsView,
550 attr='permissions_objects_update',
550 attr='permissions_objects_update',
551 route_name='admin_permissions_object_update', request_method='POST',
551 route_name='admin_permissions_object_update', request_method='POST',
552 renderer='rhodecode:templates/admin/permissions/permissions.mako')
552 renderer='rhodecode:templates/admin/permissions/permissions.mako')
553
553
554 # Branch perms EE feature
554 # Branch perms EE feature
555 config.add_route(
555 config.add_route(
556 name='admin_permissions_branch',
556 name='admin_permissions_branch',
557 pattern='/permissions/branch')
557 pattern='/permissions/branch')
558 config.add_view(
558 config.add_view(
559 AdminPermissionsView,
559 AdminPermissionsView,
560 attr='permissions_branch',
560 attr='permissions_branch',
561 route_name='admin_permissions_branch', request_method='GET',
561 route_name='admin_permissions_branch', request_method='GET',
562 renderer='rhodecode:templates/admin/permissions/permissions.mako')
562 renderer='rhodecode:templates/admin/permissions/permissions.mako')
563
563
564 config.add_route(
564 config.add_route(
565 name='admin_permissions_ips',
565 name='admin_permissions_ips',
566 pattern='/permissions/ips')
566 pattern='/permissions/ips')
567 config.add_view(
567 config.add_view(
568 AdminPermissionsView,
568 AdminPermissionsView,
569 attr='permissions_ips',
569 attr='permissions_ips',
570 route_name='admin_permissions_ips', request_method='GET',
570 route_name='admin_permissions_ips', request_method='GET',
571 renderer='rhodecode:templates/admin/permissions/permissions.mako')
571 renderer='rhodecode:templates/admin/permissions/permissions.mako')
572
572
573 config.add_route(
573 config.add_route(
574 name='admin_permissions_overview',
574 name='admin_permissions_overview',
575 pattern='/permissions/overview')
575 pattern='/permissions/overview')
576 config.add_view(
576 config.add_view(
577 AdminPermissionsView,
577 AdminPermissionsView,
578 attr='permissions_overview',
578 attr='permissions_overview',
579 route_name='admin_permissions_overview', request_method='GET',
579 route_name='admin_permissions_overview', request_method='GET',
580 renderer='rhodecode:templates/admin/permissions/permissions.mako')
580 renderer='rhodecode:templates/admin/permissions/permissions.mako')
581
581
582 config.add_route(
582 config.add_route(
583 name='admin_permissions_auth_token_access',
583 name='admin_permissions_auth_token_access',
584 pattern='/permissions/auth_token_access')
584 pattern='/permissions/auth_token_access')
585 config.add_view(
585 config.add_view(
586 AdminPermissionsView,
586 AdminPermissionsView,
587 attr='auth_token_access',
587 attr='auth_token_access',
588 route_name='admin_permissions_auth_token_access', request_method='GET',
588 route_name='admin_permissions_auth_token_access', request_method='GET',
589 renderer='rhodecode:templates/admin/permissions/permissions.mako')
589 renderer='rhodecode:templates/admin/permissions/permissions.mako')
590
590
591 config.add_route(
591 config.add_route(
592 name='admin_permissions_ssh_keys',
592 name='admin_permissions_ssh_keys',
593 pattern='/permissions/ssh_keys')
593 pattern='/permissions/ssh_keys')
594 config.add_view(
594 config.add_view(
595 AdminPermissionsView,
595 AdminPermissionsView,
596 attr='ssh_keys',
596 attr='ssh_keys',
597 route_name='admin_permissions_ssh_keys', request_method='GET',
597 route_name='admin_permissions_ssh_keys', request_method='GET',
598 renderer='rhodecode:templates/admin/permissions/permissions.mako')
598 renderer='rhodecode:templates/admin/permissions/permissions.mako')
599
599
600 config.add_route(
600 config.add_route(
601 name='admin_permissions_ssh_keys_data',
601 name='admin_permissions_ssh_keys_data',
602 pattern='/permissions/ssh_keys/data')
602 pattern='/permissions/ssh_keys/data')
603 config.add_view(
603 config.add_view(
604 AdminPermissionsView,
604 AdminPermissionsView,
605 attr='ssh_keys_data',
605 attr='ssh_keys_data',
606 route_name='admin_permissions_ssh_keys_data', request_method='GET',
606 route_name='admin_permissions_ssh_keys_data', request_method='GET',
607 renderer='json_ext', xhr=True)
607 renderer='json_ext', xhr=True)
608
608
609 config.add_route(
609 config.add_route(
610 name='admin_permissions_ssh_keys_update',
610 name='admin_permissions_ssh_keys_update',
611 pattern='/permissions/ssh_keys/update')
611 pattern='/permissions/ssh_keys/update')
612 config.add_view(
612 config.add_view(
613 AdminPermissionsView,
613 AdminPermissionsView,
614 attr='ssh_keys_update',
614 attr='ssh_keys_update',
615 route_name='admin_permissions_ssh_keys_update', request_method='POST',
615 route_name='admin_permissions_ssh_keys_update', request_method='POST',
616 renderer='rhodecode:templates/admin/permissions/permissions.mako')
616 renderer='rhodecode:templates/admin/permissions/permissions.mako')
617
617
618 # users admin
618 # users admin
619 config.add_route(
619 config.add_route(
620 name='users',
620 name='users',
621 pattern='/users')
621 pattern='/users')
622 config.add_view(
622 config.add_view(
623 AdminUsersView,
623 AdminUsersView,
624 attr='users_list',
624 attr='users_list',
625 route_name='users', request_method='GET',
625 route_name='users', request_method='GET',
626 renderer='rhodecode:templates/admin/users/users.mako')
626 renderer='rhodecode:templates/admin/users/users.mako')
627
627
628 config.add_route(
628 config.add_route(
629 name='users_data',
629 name='users_data',
630 pattern='/users_data')
630 pattern='/users_data')
631 config.add_view(
631 config.add_view(
632 AdminUsersView,
632 AdminUsersView,
633 attr='users_list_data',
633 attr='users_list_data',
634 # renderer defined below
634 # renderer defined below
635 route_name='users_data', request_method='GET',
635 route_name='users_data', request_method='GET',
636 renderer='json_ext', xhr=True)
636 renderer='json_ext', xhr=True)
637
637
638 config.add_route(
638 config.add_route(
639 name='users_create',
639 name='users_create',
640 pattern='/users/create')
640 pattern='/users/create')
641 config.add_view(
641 config.add_view(
642 AdminUsersView,
642 AdminUsersView,
643 attr='users_create',
643 attr='users_create',
644 route_name='users_create', request_method='POST',
644 route_name='users_create', request_method='POST',
645 renderer='rhodecode:templates/admin/users/user_add.mako')
645 renderer='rhodecode:templates/admin/users/user_add.mako')
646
646
647 config.add_route(
647 config.add_route(
648 name='users_new',
648 name='users_new',
649 pattern='/users/new')
649 pattern='/users/new')
650 config.add_view(
650 config.add_view(
651 AdminUsersView,
651 AdminUsersView,
652 attr='users_new',
652 attr='users_new',
653 route_name='users_new', request_method='GET',
653 route_name='users_new', request_method='GET',
654 renderer='rhodecode:templates/admin/users/user_add.mako')
654 renderer='rhodecode:templates/admin/users/user_add.mako')
655
655
656 # user management
656 # user management
657 config.add_route(
657 config.add_route(
658 name='user_edit',
658 name='user_edit',
659 pattern=r'/users/{user_id:\d+}/edit',
659 pattern=r'/users/{user_id:\d+}/edit',
660 user_route=True)
660 user_route=True)
661 config.add_view(
661 config.add_view(
662 UsersView,
662 UsersView,
663 attr='user_edit',
663 attr='user_edit',
664 route_name='user_edit', request_method='GET',
664 route_name='user_edit', request_method='GET',
665 renderer='rhodecode:templates/admin/users/user_edit.mako')
665 renderer='rhodecode:templates/admin/users/user_edit.mako')
666
666
667 config.add_route(
667 config.add_route(
668 name='user_edit_advanced',
668 name='user_edit_advanced',
669 pattern=r'/users/{user_id:\d+}/edit/advanced',
669 pattern=r'/users/{user_id:\d+}/edit/advanced',
670 user_route=True)
670 user_route=True)
671 config.add_view(
671 config.add_view(
672 UsersView,
672 UsersView,
673 attr='user_edit_advanced',
673 attr='user_edit_advanced',
674 route_name='user_edit_advanced', request_method='GET',
674 route_name='user_edit_advanced', request_method='GET',
675 renderer='rhodecode:templates/admin/users/user_edit.mako')
675 renderer='rhodecode:templates/admin/users/user_edit.mako')
676
676
677 config.add_route(
677 config.add_route(
678 name='user_edit_global_perms',
678 name='user_edit_global_perms',
679 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
679 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
680 user_route=True)
680 user_route=True)
681 config.add_view(
681 config.add_view(
682 UsersView,
682 UsersView,
683 attr='user_edit_global_perms',
683 attr='user_edit_global_perms',
684 route_name='user_edit_global_perms', request_method='GET',
684 route_name='user_edit_global_perms', request_method='GET',
685 renderer='rhodecode:templates/admin/users/user_edit.mako')
685 renderer='rhodecode:templates/admin/users/user_edit.mako')
686
686
687 config.add_route(
687 config.add_route(
688 name='user_edit_global_perms_update',
688 name='user_edit_global_perms_update',
689 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
689 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
690 user_route=True)
690 user_route=True)
691 config.add_view(
691 config.add_view(
692 UsersView,
692 UsersView,
693 attr='user_edit_global_perms_update',
693 attr='user_edit_global_perms_update',
694 route_name='user_edit_global_perms_update', request_method='POST',
694 route_name='user_edit_global_perms_update', request_method='POST',
695 renderer='rhodecode:templates/admin/users/user_edit.mako')
695 renderer='rhodecode:templates/admin/users/user_edit.mako')
696
696
697 config.add_route(
697 config.add_route(
698 name='user_update',
698 name='user_update',
699 pattern=r'/users/{user_id:\d+}/update',
699 pattern=r'/users/{user_id:\d+}/update',
700 user_route=True)
700 user_route=True)
701 config.add_view(
701 config.add_view(
702 UsersView,
702 UsersView,
703 attr='user_update',
703 attr='user_update',
704 route_name='user_update', request_method='POST',
704 route_name='user_update', request_method='POST',
705 renderer='rhodecode:templates/admin/users/user_edit.mako')
705 renderer='rhodecode:templates/admin/users/user_edit.mako')
706
706
707 config.add_route(
707 config.add_route(
708 name='user_delete',
708 name='user_delete',
709 pattern=r'/users/{user_id:\d+}/delete',
709 pattern=r'/users/{user_id:\d+}/delete',
710 user_route=True)
710 user_route=True)
711 config.add_view(
711 config.add_view(
712 UsersView,
712 UsersView,
713 attr='user_delete',
713 attr='user_delete',
714 route_name='user_delete', request_method='POST',
714 route_name='user_delete', request_method='POST',
715 renderer='rhodecode:templates/admin/users/user_edit.mako')
715 renderer='rhodecode:templates/admin/users/user_edit.mako')
716
716
717 config.add_route(
717 config.add_route(
718 name='user_enable_force_password_reset',
718 name='user_enable_force_password_reset',
719 pattern=r'/users/{user_id:\d+}/password_reset_enable',
719 pattern=r'/users/{user_id:\d+}/password_reset_enable',
720 user_route=True)
720 user_route=True)
721 config.add_view(
721 config.add_view(
722 UsersView,
722 UsersView,
723 attr='user_enable_force_password_reset',
723 attr='user_enable_force_password_reset',
724 route_name='user_enable_force_password_reset', request_method='POST',
724 route_name='user_enable_force_password_reset', request_method='POST',
725 renderer='rhodecode:templates/admin/users/user_edit.mako')
725 renderer='rhodecode:templates/admin/users/user_edit.mako')
726
726
727 config.add_route(
727 config.add_route(
728 name='user_disable_force_password_reset',
728 name='user_disable_force_password_reset',
729 pattern=r'/users/{user_id:\d+}/password_reset_disable',
729 pattern=r'/users/{user_id:\d+}/password_reset_disable',
730 user_route=True)
730 user_route=True)
731 config.add_view(
731 config.add_view(
732 UsersView,
732 UsersView,
733 attr='user_disable_force_password_reset',
733 attr='user_disable_force_password_reset',
734 route_name='user_disable_force_password_reset', request_method='POST',
734 route_name='user_disable_force_password_reset', request_method='POST',
735 renderer='rhodecode:templates/admin/users/user_edit.mako')
735 renderer='rhodecode:templates/admin/users/user_edit.mako')
736
736
737 config.add_route(
737 config.add_route(
738 name='user_create_personal_repo_group',
738 name='user_create_personal_repo_group',
739 pattern=r'/users/{user_id:\d+}/create_repo_group',
739 pattern=r'/users/{user_id:\d+}/create_repo_group',
740 user_route=True)
740 user_route=True)
741 config.add_view(
741 config.add_view(
742 UsersView,
742 UsersView,
743 attr='user_create_personal_repo_group',
743 attr='user_create_personal_repo_group',
744 route_name='user_create_personal_repo_group', request_method='POST',
744 route_name='user_create_personal_repo_group', request_method='POST',
745 renderer='rhodecode:templates/admin/users/user_edit.mako')
745 renderer='rhodecode:templates/admin/users/user_edit.mako')
746
746
747 # user notice
747 # user notice
748 config.add_route(
748 config.add_route(
749 name='user_notice_dismiss',
749 name='user_notice_dismiss',
750 pattern=r'/users/{user_id:\d+}/notice_dismiss',
750 pattern=r'/users/{user_id:\d+}/notice_dismiss',
751 user_route=True)
751 user_route=True)
752 config.add_view(
752 config.add_view(
753 UsersView,
753 UsersView,
754 attr='user_notice_dismiss',
754 attr='user_notice_dismiss',
755 route_name='user_notice_dismiss', request_method='POST',
755 route_name='user_notice_dismiss', request_method='POST',
756 renderer='json_ext', xhr=True)
756 renderer='json_ext', xhr=True)
757
757
758 # user auth tokens
758 # user auth tokens
759 config.add_route(
759 config.add_route(
760 name='edit_user_auth_tokens',
760 name='edit_user_auth_tokens',
761 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
761 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
762 user_route=True)
762 user_route=True)
763 config.add_view(
763 config.add_view(
764 UsersView,
764 UsersView,
765 attr='auth_tokens',
765 attr='auth_tokens',
766 route_name='edit_user_auth_tokens', request_method='GET',
766 route_name='edit_user_auth_tokens', request_method='GET',
767 renderer='rhodecode:templates/admin/users/user_edit.mako')
767 renderer='rhodecode:templates/admin/users/user_edit.mako')
768
768
769 config.add_route(
769 config.add_route(
770 name='edit_user_auth_tokens_view',
770 name='edit_user_auth_tokens_view',
771 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
771 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
772 user_route=True)
772 user_route=True)
773 config.add_view(
773 config.add_view(
774 UsersView,
774 UsersView,
775 attr='auth_tokens_view',
775 attr='auth_tokens_view',
776 route_name='edit_user_auth_tokens_view', request_method='POST',
776 route_name='edit_user_auth_tokens_view', request_method='POST',
777 renderer='json_ext', xhr=True)
777 renderer='json_ext', xhr=True)
778
778
779 config.add_route(
779 config.add_route(
780 name='edit_user_auth_tokens_add',
780 name='edit_user_auth_tokens_add',
781 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
781 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
782 user_route=True)
782 user_route=True)
783 config.add_view(
783 config.add_view(
784 UsersView,
784 UsersView,
785 attr='auth_tokens_add',
785 attr='auth_tokens_add',
786 route_name='edit_user_auth_tokens_add', request_method='POST')
786 route_name='edit_user_auth_tokens_add', request_method='POST')
787
787
788 config.add_route(
788 config.add_route(
789 name='edit_user_auth_tokens_delete',
789 name='edit_user_auth_tokens_delete',
790 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
790 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
791 user_route=True)
791 user_route=True)
792 config.add_view(
792 config.add_view(
793 UsersView,
793 UsersView,
794 attr='auth_tokens_delete',
794 attr='auth_tokens_delete',
795 route_name='edit_user_auth_tokens_delete', request_method='POST')
795 route_name='edit_user_auth_tokens_delete', request_method='POST')
796
796
797 # user ssh keys
797 # user ssh keys
798 config.add_route(
798 config.add_route(
799 name='edit_user_ssh_keys',
799 name='edit_user_ssh_keys',
800 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
800 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
801 user_route=True)
801 user_route=True)
802 config.add_view(
802 config.add_view(
803 UsersView,
803 UsersView,
804 attr='ssh_keys',
804 attr='ssh_keys',
805 route_name='edit_user_ssh_keys', request_method='GET',
805 route_name='edit_user_ssh_keys', request_method='GET',
806 renderer='rhodecode:templates/admin/users/user_edit.mako')
806 renderer='rhodecode:templates/admin/users/user_edit.mako')
807
807
808 config.add_route(
808 config.add_route(
809 name='edit_user_ssh_keys_generate_keypair',
809 name='edit_user_ssh_keys_generate_keypair',
810 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
810 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
811 user_route=True)
811 user_route=True)
812 config.add_view(
812 config.add_view(
813 UsersView,
813 UsersView,
814 attr='ssh_keys_generate_keypair',
814 attr='ssh_keys_generate_keypair',
815 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
815 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
816 renderer='rhodecode:templates/admin/users/user_edit.mako')
816 renderer='rhodecode:templates/admin/users/user_edit.mako')
817
817
818 config.add_route(
818 config.add_route(
819 name='edit_user_ssh_keys_add',
819 name='edit_user_ssh_keys_add',
820 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
820 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
821 user_route=True)
821 user_route=True)
822 config.add_view(
822 config.add_view(
823 UsersView,
823 UsersView,
824 attr='ssh_keys_add',
824 attr='ssh_keys_add',
825 route_name='edit_user_ssh_keys_add', request_method='POST')
825 route_name='edit_user_ssh_keys_add', request_method='POST')
826
826
827 config.add_route(
827 config.add_route(
828 name='edit_user_ssh_keys_delete',
828 name='edit_user_ssh_keys_delete',
829 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
829 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
830 user_route=True)
830 user_route=True)
831 config.add_view(
831 config.add_view(
832 UsersView,
832 UsersView,
833 attr='ssh_keys_delete',
833 attr='ssh_keys_delete',
834 route_name='edit_user_ssh_keys_delete', request_method='POST')
834 route_name='edit_user_ssh_keys_delete', request_method='POST')
835
835
836 # user emails
836 # user emails
837 config.add_route(
837 config.add_route(
838 name='edit_user_emails',
838 name='edit_user_emails',
839 pattern=r'/users/{user_id:\d+}/edit/emails',
839 pattern=r'/users/{user_id:\d+}/edit/emails',
840 user_route=True)
840 user_route=True)
841 config.add_view(
841 config.add_view(
842 UsersView,
842 UsersView,
843 attr='emails',
843 attr='emails',
844 route_name='edit_user_emails', request_method='GET',
844 route_name='edit_user_emails', request_method='GET',
845 renderer='rhodecode:templates/admin/users/user_edit.mako')
845 renderer='rhodecode:templates/admin/users/user_edit.mako')
846
846
847 config.add_route(
847 config.add_route(
848 name='edit_user_emails_add',
848 name='edit_user_emails_add',
849 pattern=r'/users/{user_id:\d+}/edit/emails/new',
849 pattern=r'/users/{user_id:\d+}/edit/emails/new',
850 user_route=True)
850 user_route=True)
851 config.add_view(
851 config.add_view(
852 UsersView,
852 UsersView,
853 attr='emails_add',
853 attr='emails_add',
854 route_name='edit_user_emails_add', request_method='POST')
854 route_name='edit_user_emails_add', request_method='POST')
855
855
856 config.add_route(
856 config.add_route(
857 name='edit_user_emails_delete',
857 name='edit_user_emails_delete',
858 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
858 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
859 user_route=True)
859 user_route=True)
860 config.add_view(
860 config.add_view(
861 UsersView,
861 UsersView,
862 attr='emails_delete',
862 attr='emails_delete',
863 route_name='edit_user_emails_delete', request_method='POST')
863 route_name='edit_user_emails_delete', request_method='POST')
864
864
865 # user IPs
865 # user IPs
866 config.add_route(
866 config.add_route(
867 name='edit_user_ips',
867 name='edit_user_ips',
868 pattern=r'/users/{user_id:\d+}/edit/ips',
868 pattern=r'/users/{user_id:\d+}/edit/ips',
869 user_route=True)
869 user_route=True)
870 config.add_view(
870 config.add_view(
871 UsersView,
871 UsersView,
872 attr='ips',
872 attr='ips',
873 route_name='edit_user_ips', request_method='GET',
873 route_name='edit_user_ips', request_method='GET',
874 renderer='rhodecode:templates/admin/users/user_edit.mako')
874 renderer='rhodecode:templates/admin/users/user_edit.mako')
875
875
876 config.add_route(
876 config.add_route(
877 name='edit_user_ips_add',
877 name='edit_user_ips_add',
878 pattern=r'/users/{user_id:\d+}/edit/ips/new',
878 pattern=r'/users/{user_id:\d+}/edit/ips/new',
879 user_route_with_default=True) # enabled for default user too
879 user_route_with_default=True) # enabled for default user too
880 config.add_view(
880 config.add_view(
881 UsersView,
881 UsersView,
882 attr='ips_add',
882 attr='ips_add',
883 route_name='edit_user_ips_add', request_method='POST')
883 route_name='edit_user_ips_add', request_method='POST')
884
884
885 config.add_route(
885 config.add_route(
886 name='edit_user_ips_delete',
886 name='edit_user_ips_delete',
887 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
887 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
888 user_route_with_default=True) # enabled for default user too
888 user_route_with_default=True) # enabled for default user too
889 config.add_view(
889 config.add_view(
890 UsersView,
890 UsersView,
891 attr='ips_delete',
891 attr='ips_delete',
892 route_name='edit_user_ips_delete', request_method='POST')
892 route_name='edit_user_ips_delete', request_method='POST')
893
893
894 # user perms
894 # user perms
895 config.add_route(
895 config.add_route(
896 name='edit_user_perms_summary',
896 name='edit_user_perms_summary',
897 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
897 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
898 user_route=True)
898 user_route=True)
899 config.add_view(
899 config.add_view(
900 UsersView,
900 UsersView,
901 attr='user_perms_summary',
901 attr='user_perms_summary',
902 route_name='edit_user_perms_summary', request_method='GET',
902 route_name='edit_user_perms_summary', request_method='GET',
903 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 renderer='rhodecode:templates/admin/users/user_edit.mako')
904
904
905 config.add_route(
905 config.add_route(
906 name='edit_user_perms_summary_json',
906 name='edit_user_perms_summary_json',
907 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
907 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
908 user_route=True)
908 user_route=True)
909 config.add_view(
909 config.add_view(
910 UsersView,
910 UsersView,
911 attr='user_perms_summary_json',
911 attr='user_perms_summary_json',
912 route_name='edit_user_perms_summary_json', request_method='GET',
912 route_name='edit_user_perms_summary_json', request_method='GET',
913 renderer='json_ext')
913 renderer='json_ext')
914
914
915 # user user groups management
915 # user user groups management
916 config.add_route(
916 config.add_route(
917 name='edit_user_groups_management',
917 name='edit_user_groups_management',
918 pattern=r'/users/{user_id:\d+}/edit/groups_management',
918 pattern=r'/users/{user_id:\d+}/edit/groups_management',
919 user_route=True)
919 user_route=True)
920 config.add_view(
920 config.add_view(
921 UsersView,
921 UsersView,
922 attr='groups_management',
922 attr='groups_management',
923 route_name='edit_user_groups_management', request_method='GET',
923 route_name='edit_user_groups_management', request_method='GET',
924 renderer='rhodecode:templates/admin/users/user_edit.mako')
924 renderer='rhodecode:templates/admin/users/user_edit.mako')
925
925
926 config.add_route(
926 config.add_route(
927 name='edit_user_groups_management_updates',
927 name='edit_user_groups_management_updates',
928 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
928 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
929 user_route=True)
929 user_route=True)
930 config.add_view(
930 config.add_view(
931 UsersView,
931 UsersView,
932 attr='groups_management_updates',
932 attr='groups_management_updates',
933 route_name='edit_user_groups_management_updates', request_method='POST')
933 route_name='edit_user_groups_management_updates', request_method='POST')
934
934
935 # user audit logs
935 # user audit logs
936 config.add_route(
936 config.add_route(
937 name='edit_user_audit_logs',
937 name='edit_user_audit_logs',
938 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
938 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
939 config.add_view(
939 config.add_view(
940 UsersView,
940 UsersView,
941 attr='user_audit_logs',
941 attr='user_audit_logs',
942 route_name='edit_user_audit_logs', request_method='GET',
942 route_name='edit_user_audit_logs', request_method='GET',
943 renderer='rhodecode:templates/admin/users/user_edit.mako')
943 renderer='rhodecode:templates/admin/users/user_edit.mako')
944
944
945 config.add_route(
945 config.add_route(
946 name='edit_user_audit_logs_download',
946 name='edit_user_audit_logs_download',
947 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
947 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
948 config.add_view(
948 config.add_view(
949 UsersView,
949 UsersView,
950 attr='user_audit_logs_download',
950 attr='user_audit_logs_download',
951 route_name='edit_user_audit_logs_download', request_method='GET',
951 route_name='edit_user_audit_logs_download', request_method='GET',
952 renderer='string')
952 renderer='string')
953
953
954 # user caches
954 # user caches
955 config.add_route(
955 config.add_route(
956 name='edit_user_caches',
956 name='edit_user_caches',
957 pattern=r'/users/{user_id:\d+}/edit/caches',
957 pattern=r'/users/{user_id:\d+}/edit/caches',
958 user_route=True)
958 user_route=True)
959 config.add_view(
959 config.add_view(
960 UsersView,
960 UsersView,
961 attr='user_caches',
961 attr='user_caches',
962 route_name='edit_user_caches', request_method='GET',
962 route_name='edit_user_caches', request_method='GET',
963 renderer='rhodecode:templates/admin/users/user_edit.mako')
963 renderer='rhodecode:templates/admin/users/user_edit.mako')
964
964
965 config.add_route(
965 config.add_route(
966 name='edit_user_caches_update',
966 name='edit_user_caches_update',
967 pattern=r'/users/{user_id:\d+}/edit/caches/update',
967 pattern=r'/users/{user_id:\d+}/edit/caches/update',
968 user_route=True)
968 user_route=True)
969 config.add_view(
969 config.add_view(
970 UsersView,
970 UsersView,
971 attr='user_caches_update',
971 attr='user_caches_update',
972 route_name='edit_user_caches_update', request_method='POST')
972 route_name='edit_user_caches_update', request_method='POST')
973
973
974 # user-groups admin
974 # user-groups admin
975 config.add_route(
975 config.add_route(
976 name='user_groups',
976 name='user_groups',
977 pattern='/user_groups')
977 pattern='/user_groups')
978 config.add_view(
978 config.add_view(
979 AdminUserGroupsView,
979 AdminUserGroupsView,
980 attr='user_groups_list',
980 attr='user_groups_list',
981 route_name='user_groups', request_method='GET',
981 route_name='user_groups', request_method='GET',
982 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
982 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
983
983
984 config.add_route(
984 config.add_route(
985 name='user_groups_data',
985 name='user_groups_data',
986 pattern='/user_groups_data')
986 pattern='/user_groups_data')
987 config.add_view(
987 config.add_view(
988 AdminUserGroupsView,
988 AdminUserGroupsView,
989 attr='user_groups_list_data',
989 attr='user_groups_list_data',
990 route_name='user_groups_data', request_method='GET',
990 route_name='user_groups_data', request_method='GET',
991 renderer='json_ext', xhr=True)
991 renderer='json_ext', xhr=True)
992
992
993 config.add_route(
993 config.add_route(
994 name='user_groups_new',
994 name='user_groups_new',
995 pattern='/user_groups/new')
995 pattern='/user_groups/new')
996 config.add_view(
996 config.add_view(
997 AdminUserGroupsView,
997 AdminUserGroupsView,
998 attr='user_groups_new',
998 attr='user_groups_new',
999 route_name='user_groups_new', request_method='GET',
999 route_name='user_groups_new', request_method='GET',
1000 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
1000 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
1001
1001
1002 config.add_route(
1002 config.add_route(
1003 name='user_groups_create',
1003 name='user_groups_create',
1004 pattern='/user_groups/create')
1004 pattern='/user_groups/create')
1005 config.add_view(
1005 config.add_view(
1006 AdminUserGroupsView,
1006 AdminUserGroupsView,
1007 attr='user_groups_create',
1007 attr='user_groups_create',
1008 route_name='user_groups_create', request_method='POST',
1008 route_name='user_groups_create', request_method='POST',
1009 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
1009 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
1010
1010
1011 # repos admin
1011 # repos admin
1012 config.add_route(
1012 config.add_route(
1013 name='repos',
1013 name='repos',
1014 pattern='/repos')
1014 pattern='/repos')
1015 config.add_view(
1015 config.add_view(
1016 AdminReposView,
1016 AdminReposView,
1017 attr='repository_list',
1017 attr='repository_list',
1018 route_name='repos', request_method='GET',
1018 route_name='repos', request_method='GET',
1019 renderer='rhodecode:templates/admin/repos/repos.mako')
1019 renderer='rhodecode:templates/admin/repos/repos.mako')
1020
1020
1021 config.add_route(
1021 config.add_route(
1022 name='repos_data',
1022 name='repos_data',
1023 pattern='/repos_data')
1023 pattern='/repos_data')
1024 config.add_view(
1024 config.add_view(
1025 AdminReposView,
1025 AdminReposView,
1026 attr='repository_list_data',
1026 attr='repository_list_data',
1027 route_name='repos_data', request_method='GET',
1027 route_name='repos_data', request_method='GET',
1028 renderer='json_ext', xhr=True)
1028 renderer='json_ext', xhr=True)
1029
1029
1030 config.add_route(
1030 config.add_route(
1031 name='repo_new',
1031 name='repo_new',
1032 pattern='/repos/new')
1032 pattern='/repos/new')
1033 config.add_view(
1033 config.add_view(
1034 AdminReposView,
1034 AdminReposView,
1035 attr='repository_new',
1035 attr='repository_new',
1036 route_name='repo_new', request_method='GET',
1036 route_name='repo_new', request_method='GET',
1037 renderer='rhodecode:templates/admin/repos/repo_add.mako')
1037 renderer='rhodecode:templates/admin/repos/repo_add.mako')
1038
1038
1039 config.add_route(
1039 config.add_route(
1040 name='repo_create',
1040 name='repo_create',
1041 pattern='/repos/create')
1041 pattern='/repos/create')
1042 config.add_view(
1042 config.add_view(
1043 AdminReposView,
1043 AdminReposView,
1044 attr='repository_create',
1044 attr='repository_create',
1045 route_name='repo_create', request_method='POST',
1045 route_name='repo_create', request_method='POST',
1046 renderer='rhodecode:templates/admin/repos/repos.mako')
1046 renderer='rhodecode:templates/admin/repos/repos.mako')
1047
1047
1048 # repo groups admin
1048 # repo groups admin
1049 config.add_route(
1049 config.add_route(
1050 name='repo_groups',
1050 name='repo_groups',
1051 pattern='/repo_groups')
1051 pattern='/repo_groups')
1052 config.add_view(
1052 config.add_view(
1053 AdminRepoGroupsView,
1053 AdminRepoGroupsView,
1054 attr='repo_group_list',
1054 attr='repo_group_list',
1055 route_name='repo_groups', request_method='GET',
1055 route_name='repo_groups', request_method='GET',
1056 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1056 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1057
1057
1058 config.add_route(
1058 config.add_route(
1059 name='repo_groups_data',
1059 name='repo_groups_data',
1060 pattern='/repo_groups_data')
1060 pattern='/repo_groups_data')
1061 config.add_view(
1061 config.add_view(
1062 AdminRepoGroupsView,
1062 AdminRepoGroupsView,
1063 attr='repo_group_list_data',
1063 attr='repo_group_list_data',
1064 route_name='repo_groups_data', request_method='GET',
1064 route_name='repo_groups_data', request_method='GET',
1065 renderer='json_ext', xhr=True)
1065 renderer='json_ext', xhr=True)
1066
1066
1067 config.add_route(
1067 config.add_route(
1068 name='repo_group_new',
1068 name='repo_group_new',
1069 pattern='/repo_group/new')
1069 pattern='/repo_group/new')
1070 config.add_view(
1070 config.add_view(
1071 AdminRepoGroupsView,
1071 AdminRepoGroupsView,
1072 attr='repo_group_new',
1072 attr='repo_group_new',
1073 route_name='repo_group_new', request_method='GET',
1073 route_name='repo_group_new', request_method='GET',
1074 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1074 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1075
1075
1076 config.add_route(
1076 config.add_route(
1077 name='repo_group_create',
1077 name='repo_group_create',
1078 pattern='/repo_group/create')
1078 pattern='/repo_group/create')
1079 config.add_view(
1079 config.add_view(
1080 AdminRepoGroupsView,
1080 AdminRepoGroupsView,
1081 attr='repo_group_create',
1081 attr='repo_group_create',
1082 route_name='repo_group_create', request_method='POST',
1082 route_name='repo_group_create', request_method='POST',
1083 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1083 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1084
1084
1085
1085
1086 def includeme(config):
1086 def includeme(config):
1087 # Create admin navigation registry and add it to the pyramid registry.
1087 # Create admin navigation registry and add it to the pyramid registry.
1088 nav_includeme(config)
1088 nav_includeme(config)
1089
1089
1090 # main admin routes
1090 # main admin routes
1091 config.add_route(
1091 config.add_route(
1092 name='admin_home', pattern=ADMIN_PREFIX)
1092 name='admin_home', pattern=ADMIN_PREFIX)
1093 config.add_view(
1093 config.add_view(
1094 AdminMainView,
1094 AdminMainView,
1095 attr='admin_main',
1095 attr='admin_main',
1096 route_name='admin_home', request_method='GET',
1096 route_name='admin_home', request_method='GET',
1097 renderer='rhodecode:templates/admin/main.mako')
1097 renderer='rhodecode:templates/admin/main.mako')
1098
1098
1099 # pr global redirect
1099 # pr global redirect
1100 config.add_route(
1100 config.add_route(
1101 name='pull_requests_global_0', # backward compat
1101 name='pull_requests_global_0', # backward compat
1102 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1102 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1103 config.add_view(
1103 config.add_view(
1104 AdminMainView,
1104 AdminMainView,
1105 attr='pull_requests',
1105 attr='pull_requests',
1106 route_name='pull_requests_global_0', request_method='GET')
1106 route_name='pull_requests_global_0', request_method='GET')
1107
1107
1108 config.add_route(
1108 config.add_route(
1109 name='pull_requests_global_1', # backward compat
1109 name='pull_requests_global_1', # backward compat
1110 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1110 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1111 config.add_view(
1111 config.add_view(
1112 AdminMainView,
1112 AdminMainView,
1113 attr='pull_requests',
1113 attr='pull_requests',
1114 route_name='pull_requests_global_1', request_method='GET')
1114 route_name='pull_requests_global_1', request_method='GET')
1115
1115
1116 config.add_route(
1116 config.add_route(
1117 name='pull_requests_global',
1117 name='pull_requests_global',
1118 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1118 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1119 config.add_view(
1119 config.add_view(
1120 AdminMainView,
1120 AdminMainView,
1121 attr='pull_requests',
1121 attr='pull_requests',
1122 route_name='pull_requests_global', request_method='GET')
1122 route_name='pull_requests_global', request_method='GET')
1123
1123
1124 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
1124 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
@@ -1,72 +1,46 b''
1 # Copyright (C) 2010-2024 RhodeCode GmbH
1 # Copyright (C) 2010-2024 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 import formencode
21
20
22 from rhodecode import BACKENDS
23 from rhodecode.apps._base import BaseAppView
21 from rhodecode.apps._base import BaseAppView
24 from rhodecode.model.meta import Session
25 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.forms import WhitelistedVcsClientsForm
27 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
28
23
29 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
30
25
31
26
32 class AdminSecurityView(BaseAppView):
27 class AdminSecurityView(BaseAppView):
33
28
34 def load_default_context(self):
29 def load_default_context(self):
35 c = self._get_local_tmpl_context()
30 c = self._get_local_tmpl_context()
36 return c
31 return c
37
32
38 @LoginRequired()
33 @LoginRequired()
39 @HasPermissionAllDecorator('hg.admin')
34 @HasPermissionAllDecorator('hg.admin')
40 def security(self):
35 def security(self):
41 c = self.load_default_context()
36 c = self.load_default_context()
42 c.active = 'security'
37 c.active = 'security'
43 return self._get_template_context(c)
38 return self._get_template_context(c)
44
39
40
45 @LoginRequired()
41 @LoginRequired()
46 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
47 def vcs_whitelisted_client_versions_edit(self):
43 def admin_security_modify_allowed_vcs_client_versions(self):
48 _ = self.request.translate
49 c = self.load_default_context()
44 c = self.load_default_context()
50 render_ctx = {}
45 c.active = 'security'
51 settings = SettingsModel()
46 return self._get_template_context(c)
52 form = WhitelistedVcsClientsForm(_, )()
53 if self.request.method == 'POST':
54 try:
55 result = form.to_python(self.request.POST)
56 for k, v in result.items():
57 if v:
58 setting = settings.create_or_update_setting(name=f'{k}_allowed_clients', val=v)
59 Session().add(setting)
60 Session().commit()
61
62 except formencode.Invalid as errors:
63 render_ctx.update({
64 'errors': errors.error_dict
65 })
66 for key in BACKENDS.keys():
67 verbose_name = f"initial_{key}"
68 if existing := settings.get_setting_by_name(name=f'{key}_allowed_clients'):
69 render_ctx[verbose_name] = existing.app_settings_value
70 else:
71 render_ctx[verbose_name] = '*'
72 return self._get_template_context(c, **render_ctx)
@@ -1,708 +1,707 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 import rhodecode
27 import rhodecode
28
28
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
35 from rhodecode.apps.svn_support import config_keys
35 from rhodecode.apps.svn_support import config_keys
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
41 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
41 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
44
44
45 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 return c
70 return c
71
71
72 @classmethod
72 @classmethod
73 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
74 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
75
75
76 if not ret:
76 if not ret:
77 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
78 settings = {}
78 settings = {}
79 for each in ret:
79 for each in ret:
80 k = each.ui_key
80 k = each.ui_key
81 v = each.ui_value
81 v = each.ui_value
82 if k == '/':
82 if k == '/':
83 k = 'root_path'
83 k = 'root_path'
84
84
85 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['publish', 'enabled']:
86 v = str2bool(v)
86 v = str2bool(v)
87
87
88 if k.find('.') != -1:
88 if k.find('.') != -1:
89 k = k.replace('.', '_')
89 k = k.replace('.', '_')
90
90
91 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
92 v = each.ui_active
92 v = each.ui_active
93
93
94 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
95 return settings
95 return settings
96
96
97 @classmethod
97 @classmethod
98 def _form_defaults(cls):
98 def _form_defaults(cls):
99 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
100 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
101
101
102 defaults.update({
102 defaults.update({
103 'new_svn_branch': '',
103 'new_svn_branch': '',
104 'new_svn_tag': '',
104 'new_svn_tag': '',
105 })
105 })
106 return defaults
106 return defaults
107
107
108 @LoginRequired()
108 @LoginRequired()
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def settings_vcs(self):
110 def settings_vcs(self):
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'vcs'
112 c.active = 'vcs'
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
116 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
117 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
117 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
118 defaults = self._form_defaults()
118 defaults = self._form_defaults()
119
119
120 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
120 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
121
121
122 data = render('rhodecode:templates/admin/settings/settings.mako',
122 data = render('rhodecode:templates/admin/settings/settings.mako',
123 self._get_template_context(c), self.request)
123 self._get_template_context(c), self.request)
124 html = formencode.htmlfill.render(
124 html = formencode.htmlfill.render(
125 data,
125 data,
126 defaults=defaults,
126 defaults=defaults,
127 encoding="UTF-8",
127 encoding="UTF-8",
128 force_defaults=False
128 force_defaults=False
129 )
129 )
130 return Response(html)
130 return Response(html)
131
131
132 @LoginRequired()
132 @LoginRequired()
133 @HasPermissionAllDecorator('hg.admin')
133 @HasPermissionAllDecorator('hg.admin')
134 @CSRFRequired()
134 @CSRFRequired()
135 def settings_vcs_update(self):
135 def settings_vcs_update(self):
136 _ = self.request.translate
136 _ = self.request.translate
137 c = self.load_default_context()
137 c = self.load_default_context()
138 c.active = 'vcs'
138 c.active = 'vcs'
139
139
140 model = VcsSettingsModel()
140 model = VcsSettingsModel()
141 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
141 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
142 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
142 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
143
143
144 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
144 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
145 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
145 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
146 application_form = ApplicationUiSettingsForm(self.request.translate)()
146 application_form = ApplicationUiSettingsForm(self.request.translate)()
147
147
148 try:
148 try:
149 form_result = application_form.to_python(dict(self.request.POST))
149 form_result = application_form.to_python(dict(self.request.POST))
150 except formencode.Invalid as errors:
150 except formencode.Invalid as errors:
151 h.flash(
151 h.flash(
152 _("Some form inputs contain invalid data."),
152 _("Some form inputs contain invalid data."),
153 category='error')
153 category='error')
154 data = render('rhodecode:templates/admin/settings/settings.mako',
154 data = render('rhodecode:templates/admin/settings/settings.mako',
155 self._get_template_context(c), self.request)
155 self._get_template_context(c), self.request)
156 html = formencode.htmlfill.render(
156 html = formencode.htmlfill.render(
157 data,
157 data,
158 defaults=errors.value,
158 defaults=errors.value,
159 errors=errors.unpack_errors() or {},
159 errors=errors.unpack_errors() or {},
160 prefix_error=False,
160 prefix_error=False,
161 encoding="UTF-8",
161 encoding="UTF-8",
162 force_defaults=False
162 force_defaults=False
163 )
163 )
164 return Response(html)
164 return Response(html)
165
165
166 try:
166 try:
167 model.update_global_ssl_setting(form_result['web_push_ssl'])
168 model.update_global_hook_settings(form_result)
167 model.update_global_hook_settings(form_result)
169
168
170 model.create_or_update_global_svn_settings(form_result)
169 model.create_or_update_global_svn_settings(form_result)
171 model.create_or_update_global_hg_settings(form_result)
170 model.create_or_update_global_hg_settings(form_result)
172 model.create_or_update_global_git_settings(form_result)
171 model.create_or_update_global_git_settings(form_result)
173 model.create_or_update_global_pr_settings(form_result)
172 model.create_or_update_global_pr_settings(form_result)
174 except Exception:
173 except Exception:
175 log.exception("Exception while updating settings")
174 log.exception("Exception while updating settings")
176 h.flash(_('Error occurred during updating '
175 h.flash(_('Error occurred during updating '
177 'application settings'), category='error')
176 'application settings'), category='error')
178 else:
177 else:
179 Session().commit()
178 Session().commit()
180 h.flash(_('Updated VCS settings'), category='success')
179 h.flash(_('Updated VCS settings'), category='success')
181 raise HTTPFound(h.route_path('admin_settings_vcs'))
180 raise HTTPFound(h.route_path('admin_settings_vcs'))
182
181
183 data = render('rhodecode:templates/admin/settings/settings.mako',
182 data = render('rhodecode:templates/admin/settings/settings.mako',
184 self._get_template_context(c), self.request)
183 self._get_template_context(c), self.request)
185 html = formencode.htmlfill.render(
184 html = formencode.htmlfill.render(
186 data,
185 data,
187 defaults=self._form_defaults(),
186 defaults=self._form_defaults(),
188 encoding="UTF-8",
187 encoding="UTF-8",
189 force_defaults=False
188 force_defaults=False
190 )
189 )
191 return Response(html)
190 return Response(html)
192
191
193 @LoginRequired()
192 @LoginRequired()
194 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
195 @CSRFRequired()
194 @CSRFRequired()
196 def settings_vcs_delete_svn_pattern(self):
195 def settings_vcs_delete_svn_pattern(self):
197 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
196 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
198 model = VcsSettingsModel()
197 model = VcsSettingsModel()
199 try:
198 try:
200 model.delete_global_svn_pattern(delete_pattern_id)
199 model.delete_global_svn_pattern(delete_pattern_id)
201 except SettingNotFound:
200 except SettingNotFound:
202 log.exception(
201 log.exception(
203 'Failed to delete svn_pattern with id %s', delete_pattern_id)
202 'Failed to delete svn_pattern with id %s', delete_pattern_id)
204 raise HTTPNotFound()
203 raise HTTPNotFound()
205
204
206 Session().commit()
205 Session().commit()
207 return True
206 return True
208
207
209 @LoginRequired()
208 @LoginRequired()
210 @HasPermissionAllDecorator('hg.admin')
209 @HasPermissionAllDecorator('hg.admin')
211 def settings_mapping(self):
210 def settings_mapping(self):
212 c = self.load_default_context()
211 c = self.load_default_context()
213 c.active = 'mapping'
212 c.active = 'mapping'
214 c.storage_path = get_rhodecode_repo_store_path()
213 c.storage_path = get_rhodecode_repo_store_path()
215 data = render('rhodecode:templates/admin/settings/settings.mako',
214 data = render('rhodecode:templates/admin/settings/settings.mako',
216 self._get_template_context(c), self.request)
215 self._get_template_context(c), self.request)
217 html = formencode.htmlfill.render(
216 html = formencode.htmlfill.render(
218 data,
217 data,
219 defaults=self._form_defaults(),
218 defaults=self._form_defaults(),
220 encoding="UTF-8",
219 encoding="UTF-8",
221 force_defaults=False
220 force_defaults=False
222 )
221 )
223 return Response(html)
222 return Response(html)
224
223
225 @LoginRequired()
224 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
225 @HasPermissionAllDecorator('hg.admin')
227 @CSRFRequired()
226 @CSRFRequired()
228 def settings_mapping_update(self):
227 def settings_mapping_update(self):
229 _ = self.request.translate
228 _ = self.request.translate
230 c = self.load_default_context()
229 c = self.load_default_context()
231 c.active = 'mapping'
230 c.active = 'mapping'
232 rm_obsolete = self.request.POST.get('destroy', False)
231 rm_obsolete = self.request.POST.get('destroy', False)
233 invalidate_cache = self.request.POST.get('invalidate', False)
232 invalidate_cache = self.request.POST.get('invalidate', False)
234 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
233 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
235
234
236 if invalidate_cache:
235 if invalidate_cache:
237 log.debug('invalidating all repositories cache')
236 log.debug('invalidating all repositories cache')
238 for repo in Repository.get_all():
237 for repo in Repository.get_all():
239 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
238 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
240
239
241 filesystem_repos = ScmModel().repo_scan()
240 filesystem_repos = ScmModel().repo_scan()
242 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
241 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
243 PermissionModel().trigger_permission_flush()
242 PermissionModel().trigger_permission_flush()
244
243
245 def _repr(rm_repo):
244 def _repr(rm_repo):
246 return ', '.join(map(safe_str, rm_repo)) or '-'
245 return ', '.join(map(safe_str, rm_repo)) or '-'
247
246
248 h.flash(_('Repositories successfully '
247 h.flash(_('Repositories successfully '
249 'rescanned added: %s ; removed: %s') %
248 'rescanned added: %s ; removed: %s') %
250 (_repr(added), _repr(removed)),
249 (_repr(added), _repr(removed)),
251 category='success')
250 category='success')
252 raise HTTPFound(h.route_path('admin_settings_mapping'))
251 raise HTTPFound(h.route_path('admin_settings_mapping'))
253
252
254 @LoginRequired()
253 @LoginRequired()
255 @HasPermissionAllDecorator('hg.admin')
254 @HasPermissionAllDecorator('hg.admin')
256 def settings_global(self):
255 def settings_global(self):
257 c = self.load_default_context()
256 c = self.load_default_context()
258 c.active = 'global'
257 c.active = 'global'
259 c.personal_repo_group_default_pattern = RepoGroupModel()\
258 c.personal_repo_group_default_pattern = RepoGroupModel()\
260 .get_personal_group_name_pattern()
259 .get_personal_group_name_pattern()
261
260
262 data = render('rhodecode:templates/admin/settings/settings.mako',
261 data = render('rhodecode:templates/admin/settings/settings.mako',
263 self._get_template_context(c), self.request)
262 self._get_template_context(c), self.request)
264 html = formencode.htmlfill.render(
263 html = formencode.htmlfill.render(
265 data,
264 data,
266 defaults=self._form_defaults(),
265 defaults=self._form_defaults(),
267 encoding="UTF-8",
266 encoding="UTF-8",
268 force_defaults=False
267 force_defaults=False
269 )
268 )
270 return Response(html)
269 return Response(html)
271
270
272 @LoginRequired()
271 @LoginRequired()
273 @HasPermissionAllDecorator('hg.admin')
272 @HasPermissionAllDecorator('hg.admin')
274 @CSRFRequired()
273 @CSRFRequired()
275 def settings_global_update(self):
274 def settings_global_update(self):
276 _ = self.request.translate
275 _ = self.request.translate
277 c = self.load_default_context()
276 c = self.load_default_context()
278 c.active = 'global'
277 c.active = 'global'
279 c.personal_repo_group_default_pattern = RepoGroupModel()\
278 c.personal_repo_group_default_pattern = RepoGroupModel()\
280 .get_personal_group_name_pattern()
279 .get_personal_group_name_pattern()
281 application_form = ApplicationSettingsForm(self.request.translate)()
280 application_form = ApplicationSettingsForm(self.request.translate)()
282 try:
281 try:
283 form_result = application_form.to_python(dict(self.request.POST))
282 form_result = application_form.to_python(dict(self.request.POST))
284 except formencode.Invalid as errors:
283 except formencode.Invalid as errors:
285 h.flash(
284 h.flash(
286 _("Some form inputs contain invalid data."),
285 _("Some form inputs contain invalid data."),
287 category='error')
286 category='error')
288 data = render('rhodecode:templates/admin/settings/settings.mako',
287 data = render('rhodecode:templates/admin/settings/settings.mako',
289 self._get_template_context(c), self.request)
288 self._get_template_context(c), self.request)
290 html = formencode.htmlfill.render(
289 html = formencode.htmlfill.render(
291 data,
290 data,
292 defaults=errors.value,
291 defaults=errors.value,
293 errors=errors.unpack_errors() or {},
292 errors=errors.unpack_errors() or {},
294 prefix_error=False,
293 prefix_error=False,
295 encoding="UTF-8",
294 encoding="UTF-8",
296 force_defaults=False
295 force_defaults=False
297 )
296 )
298 return Response(html)
297 return Response(html)
299
298
300 settings = [
299 settings = [
301 ('title', 'rhodecode_title', 'unicode'),
300 ('title', 'rhodecode_title', 'unicode'),
302 ('realm', 'rhodecode_realm', 'unicode'),
301 ('realm', 'rhodecode_realm', 'unicode'),
303 ('pre_code', 'rhodecode_pre_code', 'unicode'),
302 ('pre_code', 'rhodecode_pre_code', 'unicode'),
304 ('post_code', 'rhodecode_post_code', 'unicode'),
303 ('post_code', 'rhodecode_post_code', 'unicode'),
305 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
304 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
306 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
305 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
307 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
306 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
308 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
307 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
309 ]
308 ]
310
309
311 try:
310 try:
312 for setting, form_key, type_ in settings:
311 for setting, form_key, type_ in settings:
313 sett = SettingsModel().create_or_update_setting(
312 sett = SettingsModel().create_or_update_setting(
314 setting, form_result[form_key], type_)
313 setting, form_result[form_key], type_)
315 Session().add(sett)
314 Session().add(sett)
316
315
317 Session().commit()
316 Session().commit()
318 SettingsModel().invalidate_settings_cache()
317 SettingsModel().invalidate_settings_cache()
319 h.flash(_('Updated application settings'), category='success')
318 h.flash(_('Updated application settings'), category='success')
320 except Exception:
319 except Exception:
321 log.exception("Exception while updating application settings")
320 log.exception("Exception while updating application settings")
322 h.flash(
321 h.flash(
323 _('Error occurred during updating application settings'),
322 _('Error occurred during updating application settings'),
324 category='error')
323 category='error')
325
324
326 raise HTTPFound(h.route_path('admin_settings_global'))
325 raise HTTPFound(h.route_path('admin_settings_global'))
327
326
328 @LoginRequired()
327 @LoginRequired()
329 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
330 def settings_visual(self):
329 def settings_visual(self):
331 c = self.load_default_context()
330 c = self.load_default_context()
332 c.active = 'visual'
331 c.active = 'visual'
333
332
334 data = render('rhodecode:templates/admin/settings/settings.mako',
333 data = render('rhodecode:templates/admin/settings/settings.mako',
335 self._get_template_context(c), self.request)
334 self._get_template_context(c), self.request)
336 html = formencode.htmlfill.render(
335 html = formencode.htmlfill.render(
337 data,
336 data,
338 defaults=self._form_defaults(),
337 defaults=self._form_defaults(),
339 encoding="UTF-8",
338 encoding="UTF-8",
340 force_defaults=False
339 force_defaults=False
341 )
340 )
342 return Response(html)
341 return Response(html)
343
342
344 @LoginRequired()
343 @LoginRequired()
345 @HasPermissionAllDecorator('hg.admin')
344 @HasPermissionAllDecorator('hg.admin')
346 @CSRFRequired()
345 @CSRFRequired()
347 def settings_visual_update(self):
346 def settings_visual_update(self):
348 _ = self.request.translate
347 _ = self.request.translate
349 c = self.load_default_context()
348 c = self.load_default_context()
350 c.active = 'visual'
349 c.active = 'visual'
351 application_form = ApplicationVisualisationForm(self.request.translate)()
350 application_form = ApplicationVisualisationForm(self.request.translate)()
352 try:
351 try:
353 form_result = application_form.to_python(dict(self.request.POST))
352 form_result = application_form.to_python(dict(self.request.POST))
354 except formencode.Invalid as errors:
353 except formencode.Invalid as errors:
355 h.flash(
354 h.flash(
356 _("Some form inputs contain invalid data."),
355 _("Some form inputs contain invalid data."),
357 category='error')
356 category='error')
358 data = render('rhodecode:templates/admin/settings/settings.mako',
357 data = render('rhodecode:templates/admin/settings/settings.mako',
359 self._get_template_context(c), self.request)
358 self._get_template_context(c), self.request)
360 html = formencode.htmlfill.render(
359 html = formencode.htmlfill.render(
361 data,
360 data,
362 defaults=errors.value,
361 defaults=errors.value,
363 errors=errors.unpack_errors() or {},
362 errors=errors.unpack_errors() or {},
364 prefix_error=False,
363 prefix_error=False,
365 encoding="UTF-8",
364 encoding="UTF-8",
366 force_defaults=False
365 force_defaults=False
367 )
366 )
368 return Response(html)
367 return Response(html)
369
368
370 try:
369 try:
371 settings = [
370 settings = [
372 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
371 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
373 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
372 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
374 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
373 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
375 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
374 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
376 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
375 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
377 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
376 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
378 ('show_version', 'rhodecode_show_version', 'bool'),
377 ('show_version', 'rhodecode_show_version', 'bool'),
379 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
378 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
380 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
379 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
381 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
380 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
382 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
381 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
383 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
382 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
384 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
383 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
385 ('support_url', 'rhodecode_support_url', 'unicode'),
384 ('support_url', 'rhodecode_support_url', 'unicode'),
386 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
385 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
387 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
386 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
388 ]
387 ]
389 for setting, form_key, type_ in settings:
388 for setting, form_key, type_ in settings:
390 sett = SettingsModel().create_or_update_setting(
389 sett = SettingsModel().create_or_update_setting(
391 setting, form_result[form_key], type_)
390 setting, form_result[form_key], type_)
392 Session().add(sett)
391 Session().add(sett)
393
392
394 Session().commit()
393 Session().commit()
395 SettingsModel().invalidate_settings_cache()
394 SettingsModel().invalidate_settings_cache()
396 h.flash(_('Updated visualisation settings'), category='success')
395 h.flash(_('Updated visualisation settings'), category='success')
397 except Exception:
396 except Exception:
398 log.exception("Exception updating visualization settings")
397 log.exception("Exception updating visualization settings")
399 h.flash(_('Error occurred during updating '
398 h.flash(_('Error occurred during updating '
400 'visualisation settings'),
399 'visualisation settings'),
401 category='error')
400 category='error')
402
401
403 raise HTTPFound(h.route_path('admin_settings_visual'))
402 raise HTTPFound(h.route_path('admin_settings_visual'))
404
403
405 @LoginRequired()
404 @LoginRequired()
406 @HasPermissionAllDecorator('hg.admin')
405 @HasPermissionAllDecorator('hg.admin')
407 def settings_issuetracker(self):
406 def settings_issuetracker(self):
408 c = self.load_default_context()
407 c = self.load_default_context()
409 c.active = 'issuetracker'
408 c.active = 'issuetracker'
410 defaults = c.rc_config
409 defaults = c.rc_config
411
410
412 entry_key = 'rhodecode_issuetracker_pat_'
411 entry_key = 'rhodecode_issuetracker_pat_'
413
412
414 c.issuetracker_entries = {}
413 c.issuetracker_entries = {}
415 for k, v in defaults.items():
414 for k, v in defaults.items():
416 if k.startswith(entry_key):
415 if k.startswith(entry_key):
417 uid = k[len(entry_key):]
416 uid = k[len(entry_key):]
418 c.issuetracker_entries[uid] = None
417 c.issuetracker_entries[uid] = None
419
418
420 for uid in c.issuetracker_entries:
419 for uid in c.issuetracker_entries:
421 c.issuetracker_entries[uid] = AttributeDict({
420 c.issuetracker_entries[uid] = AttributeDict({
422 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
421 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
423 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
422 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
424 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
423 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
425 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
424 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
426 })
425 })
427
426
428 return self._get_template_context(c)
427 return self._get_template_context(c)
429
428
430 @LoginRequired()
429 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
430 @HasPermissionAllDecorator('hg.admin')
432 @CSRFRequired()
431 @CSRFRequired()
433 def settings_issuetracker_test(self):
432 def settings_issuetracker_test(self):
434 error_container = []
433 error_container = []
435
434
436 urlified_commit = h.urlify_commit_message(
435 urlified_commit = h.urlify_commit_message(
437 self.request.POST.get('test_text', ''),
436 self.request.POST.get('test_text', ''),
438 'repo_group/test_repo1', error_container=error_container)
437 'repo_group/test_repo1', error_container=error_container)
439 if error_container:
438 if error_container:
440 def converter(inp):
439 def converter(inp):
441 return h.html_escape(inp)
440 return h.html_escape(inp)
442
441
443 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
442 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
444
443
445 return urlified_commit
444 return urlified_commit
446
445
447 @LoginRequired()
446 @LoginRequired()
448 @HasPermissionAllDecorator('hg.admin')
447 @HasPermissionAllDecorator('hg.admin')
449 @CSRFRequired()
448 @CSRFRequired()
450 def settings_issuetracker_update(self):
449 def settings_issuetracker_update(self):
451 _ = self.request.translate
450 _ = self.request.translate
452 self.load_default_context()
451 self.load_default_context()
453 settings_model = IssueTrackerSettingsModel()
452 settings_model = IssueTrackerSettingsModel()
454
453
455 try:
454 try:
456 form = IssueTrackerPatternsForm(self.request.translate)()
455 form = IssueTrackerPatternsForm(self.request.translate)()
457 data = form.to_python(self.request.POST)
456 data = form.to_python(self.request.POST)
458 except formencode.Invalid as errors:
457 except formencode.Invalid as errors:
459 log.exception('Failed to add new pattern')
458 log.exception('Failed to add new pattern')
460 error = errors
459 error = errors
461 h.flash(_(f'Invalid issue tracker pattern: {error}'),
460 h.flash(_(f'Invalid issue tracker pattern: {error}'),
462 category='error')
461 category='error')
463 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
462 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
464
463
465 if data:
464 if data:
466 for uid in data.get('delete_patterns', []):
465 for uid in data.get('delete_patterns', []):
467 settings_model.delete_entries(uid)
466 settings_model.delete_entries(uid)
468
467
469 for pattern in data.get('patterns', []):
468 for pattern in data.get('patterns', []):
470 for setting, value, type_ in pattern:
469 for setting, value, type_ in pattern:
471 sett = settings_model.create_or_update_setting(
470 sett = settings_model.create_or_update_setting(
472 setting, value, type_)
471 setting, value, type_)
473 Session().add(sett)
472 Session().add(sett)
474
473
475 Session().commit()
474 Session().commit()
476
475
477 SettingsModel().invalidate_settings_cache()
476 SettingsModel().invalidate_settings_cache()
478 h.flash(_('Updated issue tracker entries'), category='success')
477 h.flash(_('Updated issue tracker entries'), category='success')
479 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
478 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
480
479
481 @LoginRequired()
480 @LoginRequired()
482 @HasPermissionAllDecorator('hg.admin')
481 @HasPermissionAllDecorator('hg.admin')
483 @CSRFRequired()
482 @CSRFRequired()
484 def settings_issuetracker_delete(self):
483 def settings_issuetracker_delete(self):
485 _ = self.request.translate
484 _ = self.request.translate
486 self.load_default_context()
485 self.load_default_context()
487 uid = self.request.POST.get('uid')
486 uid = self.request.POST.get('uid')
488 try:
487 try:
489 IssueTrackerSettingsModel().delete_entries(uid)
488 IssueTrackerSettingsModel().delete_entries(uid)
490 except Exception:
489 except Exception:
491 log.exception('Failed to delete issue tracker setting %s', uid)
490 log.exception('Failed to delete issue tracker setting %s', uid)
492 raise HTTPNotFound()
491 raise HTTPNotFound()
493
492
494 SettingsModel().invalidate_settings_cache()
493 SettingsModel().invalidate_settings_cache()
495 h.flash(_('Removed issue tracker entry.'), category='success')
494 h.flash(_('Removed issue tracker entry.'), category='success')
496
495
497 return {'deleted': uid}
496 return {'deleted': uid}
498
497
499 @LoginRequired()
498 @LoginRequired()
500 @HasPermissionAllDecorator('hg.admin')
499 @HasPermissionAllDecorator('hg.admin')
501 def settings_email(self):
500 def settings_email(self):
502 c = self.load_default_context()
501 c = self.load_default_context()
503 c.active = 'email'
502 c.active = 'email'
504 c.rhodecode_ini = rhodecode.CONFIG
503 c.rhodecode_ini = rhodecode.CONFIG
505
504
506 data = render('rhodecode:templates/admin/settings/settings.mako',
505 data = render('rhodecode:templates/admin/settings/settings.mako',
507 self._get_template_context(c), self.request)
506 self._get_template_context(c), self.request)
508 html = formencode.htmlfill.render(
507 html = formencode.htmlfill.render(
509 data,
508 data,
510 defaults=self._form_defaults(),
509 defaults=self._form_defaults(),
511 encoding="UTF-8",
510 encoding="UTF-8",
512 force_defaults=False
511 force_defaults=False
513 )
512 )
514 return Response(html)
513 return Response(html)
515
514
516 @LoginRequired()
515 @LoginRequired()
517 @HasPermissionAllDecorator('hg.admin')
516 @HasPermissionAllDecorator('hg.admin')
518 @CSRFRequired()
517 @CSRFRequired()
519 def settings_email_update(self):
518 def settings_email_update(self):
520 _ = self.request.translate
519 _ = self.request.translate
521 c = self.load_default_context()
520 c = self.load_default_context()
522 c.active = 'email'
521 c.active = 'email'
523
522
524 test_email = self.request.POST.get('test_email')
523 test_email = self.request.POST.get('test_email')
525
524
526 if not test_email:
525 if not test_email:
527 h.flash(_('Please enter email address'), category='error')
526 h.flash(_('Please enter email address'), category='error')
528 raise HTTPFound(h.route_path('admin_settings_email'))
527 raise HTTPFound(h.route_path('admin_settings_email'))
529
528
530 email_kwargs = {
529 email_kwargs = {
531 'date': datetime.datetime.now(),
530 'date': datetime.datetime.now(),
532 'user': self._rhodecode_db_user
531 'user': self._rhodecode_db_user
533 }
532 }
534
533
535 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
534 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
536 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
535 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
537
536
538 recipients = [test_email] if test_email else None
537 recipients = [test_email] if test_email else None
539
538
540 run_task(tasks.send_email, recipients, subject,
539 run_task(tasks.send_email, recipients, subject,
541 email_body_plaintext, email_body)
540 email_body_plaintext, email_body)
542
541
543 h.flash(_('Send email task created'), category='success')
542 h.flash(_('Send email task created'), category='success')
544 raise HTTPFound(h.route_path('admin_settings_email'))
543 raise HTTPFound(h.route_path('admin_settings_email'))
545
544
546 @LoginRequired()
545 @LoginRequired()
547 @HasPermissionAllDecorator('hg.admin')
546 @HasPermissionAllDecorator('hg.admin')
548 def settings_hooks(self):
547 def settings_hooks(self):
549 c = self.load_default_context()
548 c = self.load_default_context()
550 c.active = 'hooks'
549 c.active = 'hooks'
551
550
552 model = SettingsModel()
551 model = SettingsModel()
553 c.hooks = model.get_builtin_hooks()
552 c.hooks = model.get_builtin_hooks()
554 c.custom_hooks = model.get_custom_hooks()
553 c.custom_hooks = model.get_custom_hooks()
555
554
556 data = render('rhodecode:templates/admin/settings/settings.mako',
555 data = render('rhodecode:templates/admin/settings/settings.mako',
557 self._get_template_context(c), self.request)
556 self._get_template_context(c), self.request)
558 html = formencode.htmlfill.render(
557 html = formencode.htmlfill.render(
559 data,
558 data,
560 defaults=self._form_defaults(),
559 defaults=self._form_defaults(),
561 encoding="UTF-8",
560 encoding="UTF-8",
562 force_defaults=False
561 force_defaults=False
563 )
562 )
564 return Response(html)
563 return Response(html)
565
564
566 @LoginRequired()
565 @LoginRequired()
567 @HasPermissionAllDecorator('hg.admin')
566 @HasPermissionAllDecorator('hg.admin')
568 @CSRFRequired()
567 @CSRFRequired()
569 def settings_hooks_update(self):
568 def settings_hooks_update(self):
570 _ = self.request.translate
569 _ = self.request.translate
571 c = self.load_default_context()
570 c = self.load_default_context()
572 c.active = 'hooks'
571 c.active = 'hooks'
573 if c.visual.allow_custom_hooks_settings:
572 if c.visual.allow_custom_hooks_settings:
574 ui_key = self.request.POST.get('new_hook_ui_key')
573 ui_key = self.request.POST.get('new_hook_ui_key')
575 ui_value = self.request.POST.get('new_hook_ui_value')
574 ui_value = self.request.POST.get('new_hook_ui_value')
576
575
577 hook_id = self.request.POST.get('hook_id')
576 hook_id = self.request.POST.get('hook_id')
578 new_hook = False
577 new_hook = False
579
578
580 model = SettingsModel()
579 model = SettingsModel()
581 try:
580 try:
582 if ui_value and ui_key:
581 if ui_value and ui_key:
583 model.create_or_update_hook(ui_key, ui_value)
582 model.create_or_update_hook(ui_key, ui_value)
584 h.flash(_('Added new hook'), category='success')
583 h.flash(_('Added new hook'), category='success')
585 new_hook = True
584 new_hook = True
586 elif hook_id:
585 elif hook_id:
587 RhodeCodeUi.delete(hook_id)
586 RhodeCodeUi.delete(hook_id)
588 Session().commit()
587 Session().commit()
589
588
590 # check for edits
589 # check for edits
591 update = False
590 update = False
592 _d = self.request.POST.dict_of_lists()
591 _d = self.request.POST.dict_of_lists()
593 for k, v in zip(_d.get('hook_ui_key', []),
592 for k, v in zip(_d.get('hook_ui_key', []),
594 _d.get('hook_ui_value_new', [])):
593 _d.get('hook_ui_value_new', [])):
595 model.create_or_update_hook(k, v)
594 model.create_or_update_hook(k, v)
596 update = True
595 update = True
597
596
598 if update and not new_hook:
597 if update and not new_hook:
599 h.flash(_('Updated hooks'), category='success')
598 h.flash(_('Updated hooks'), category='success')
600 Session().commit()
599 Session().commit()
601 except Exception:
600 except Exception:
602 log.exception("Exception during hook creation")
601 log.exception("Exception during hook creation")
603 h.flash(_('Error occurred during hook creation'),
602 h.flash(_('Error occurred during hook creation'),
604 category='error')
603 category='error')
605
604
606 raise HTTPFound(h.route_path('admin_settings_hooks'))
605 raise HTTPFound(h.route_path('admin_settings_hooks'))
607
606
608 @LoginRequired()
607 @LoginRequired()
609 @HasPermissionAllDecorator('hg.admin')
608 @HasPermissionAllDecorator('hg.admin')
610 def settings_search(self):
609 def settings_search(self):
611 c = self.load_default_context()
610 c = self.load_default_context()
612 c.active = 'search'
611 c.active = 'search'
613
612
614 c.searcher = searcher_from_config(self.request.registry.settings)
613 c.searcher = searcher_from_config(self.request.registry.settings)
615 c.statistics = c.searcher.statistics(self.request.translate)
614 c.statistics = c.searcher.statistics(self.request.translate)
616
615
617 return self._get_template_context(c)
616 return self._get_template_context(c)
618
617
619 @LoginRequired()
618 @LoginRequired()
620 @HasPermissionAllDecorator('hg.admin')
619 @HasPermissionAllDecorator('hg.admin')
621 def settings_labs(self):
620 def settings_labs(self):
622 c = self.load_default_context()
621 c = self.load_default_context()
623 if not c.labs_active:
622 if not c.labs_active:
624 raise HTTPFound(h.route_path('admin_settings'))
623 raise HTTPFound(h.route_path('admin_settings'))
625
624
626 c.active = 'labs'
625 c.active = 'labs'
627 c.lab_settings = _LAB_SETTINGS
626 c.lab_settings = _LAB_SETTINGS
628
627
629 data = render('rhodecode:templates/admin/settings/settings.mako',
628 data = render('rhodecode:templates/admin/settings/settings.mako',
630 self._get_template_context(c), self.request)
629 self._get_template_context(c), self.request)
631 html = formencode.htmlfill.render(
630 html = formencode.htmlfill.render(
632 data,
631 data,
633 defaults=self._form_defaults(),
632 defaults=self._form_defaults(),
634 encoding="UTF-8",
633 encoding="UTF-8",
635 force_defaults=False
634 force_defaults=False
636 )
635 )
637 return Response(html)
636 return Response(html)
638
637
639 @LoginRequired()
638 @LoginRequired()
640 @HasPermissionAllDecorator('hg.admin')
639 @HasPermissionAllDecorator('hg.admin')
641 @CSRFRequired()
640 @CSRFRequired()
642 def settings_labs_update(self):
641 def settings_labs_update(self):
643 _ = self.request.translate
642 _ = self.request.translate
644 c = self.load_default_context()
643 c = self.load_default_context()
645 c.active = 'labs'
644 c.active = 'labs'
646
645
647 application_form = LabsSettingsForm(self.request.translate)()
646 application_form = LabsSettingsForm(self.request.translate)()
648 try:
647 try:
649 form_result = application_form.to_python(dict(self.request.POST))
648 form_result = application_form.to_python(dict(self.request.POST))
650 except formencode.Invalid as errors:
649 except formencode.Invalid as errors:
651 h.flash(
650 h.flash(
652 _("Some form inputs contain invalid data."),
651 _("Some form inputs contain invalid data."),
653 category='error')
652 category='error')
654 data = render('rhodecode:templates/admin/settings/settings.mako',
653 data = render('rhodecode:templates/admin/settings/settings.mako',
655 self._get_template_context(c), self.request)
654 self._get_template_context(c), self.request)
656 html = formencode.htmlfill.render(
655 html = formencode.htmlfill.render(
657 data,
656 data,
658 defaults=errors.value,
657 defaults=errors.value,
659 errors=errors.unpack_errors() or {},
658 errors=errors.unpack_errors() or {},
660 prefix_error=False,
659 prefix_error=False,
661 encoding="UTF-8",
660 encoding="UTF-8",
662 force_defaults=False
661 force_defaults=False
663 )
662 )
664 return Response(html)
663 return Response(html)
665
664
666 try:
665 try:
667 session = Session()
666 session = Session()
668 for setting in _LAB_SETTINGS:
667 for setting in _LAB_SETTINGS:
669 setting_name = setting.key[len('rhodecode_'):]
668 setting_name = setting.key[len('rhodecode_'):]
670 sett = SettingsModel().create_or_update_setting(
669 sett = SettingsModel().create_or_update_setting(
671 setting_name, form_result[setting.key], setting.type)
670 setting_name, form_result[setting.key], setting.type)
672 session.add(sett)
671 session.add(sett)
673
672
674 except Exception:
673 except Exception:
675 log.exception('Exception while updating lab settings')
674 log.exception('Exception while updating lab settings')
676 h.flash(_('Error occurred during updating labs settings'),
675 h.flash(_('Error occurred during updating labs settings'),
677 category='error')
676 category='error')
678 else:
677 else:
679 Session().commit()
678 Session().commit()
680 SettingsModel().invalidate_settings_cache()
679 SettingsModel().invalidate_settings_cache()
681 h.flash(_('Updated Labs settings'), category='success')
680 h.flash(_('Updated Labs settings'), category='success')
682 raise HTTPFound(h.route_path('admin_settings_labs'))
681 raise HTTPFound(h.route_path('admin_settings_labs'))
683
682
684 data = render('rhodecode:templates/admin/settings/settings.mako',
683 data = render('rhodecode:templates/admin/settings/settings.mako',
685 self._get_template_context(c), self.request)
684 self._get_template_context(c), self.request)
686 html = formencode.htmlfill.render(
685 html = formencode.htmlfill.render(
687 data,
686 data,
688 defaults=self._form_defaults(),
687 defaults=self._form_defaults(),
689 encoding="UTF-8",
688 encoding="UTF-8",
690 force_defaults=False
689 force_defaults=False
691 )
690 )
692 return Response(html)
691 return Response(html)
693
692
694
693
695 # :param key: name of the setting including the 'rhodecode_' prefix
694 # :param key: name of the setting including the 'rhodecode_' prefix
696 # :param type: the RhodeCodeSetting type to use.
695 # :param type: the RhodeCodeSetting type to use.
697 # :param group: the i18ned group in which we should dispaly this setting
696 # :param group: the i18ned group in which we should dispaly this setting
698 # :param label: the i18ned label we should display for this setting
697 # :param label: the i18ned label we should display for this setting
699 # :param help: the i18ned help we should dispaly for this setting
698 # :param help: the i18ned help we should dispaly for this setting
700 LabSetting = collections.namedtuple(
699 LabSetting = collections.namedtuple(
701 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
700 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
702
701
703
702
704 # This list has to be kept in sync with the form
703 # This list has to be kept in sync with the form
705 # rhodecode.model.forms.LabsSettingsForm.
704 # rhodecode.model.forms.LabsSettingsForm.
706 _LAB_SETTINGS = [
705 _LAB_SETTINGS = [
707
706
708 ]
707 ]
@@ -1,231 +1,237 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import tempfile
20 import tempfile
21 import logging
21 import logging
22
22
23 from pyramid.settings import asbool
23 from pyramid.settings import asbool
24
24
25 from rhodecode.config.settings_maker import SettingsMaker
25 from rhodecode.config.settings_maker import SettingsMaker
26 from rhodecode.config import utils as config_utils
26 from rhodecode.config import utils as config_utils
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def sanitize_settings_and_apply_defaults(global_config, settings):
31 def sanitize_settings_and_apply_defaults(global_config, settings):
32 """
32 """
33 Applies settings defaults and does all type conversion.
33 Applies settings defaults and does all type conversion.
34
34
35 We would move all settings parsing and preparation into this place, so that
35 We would move all settings parsing and preparation into this place, so that
36 we have only one place left which deals with this part. The remaining parts
36 we have only one place left which deals with this part. The remaining parts
37 of the application would start to rely fully on well-prepared settings.
37 of the application would start to rely fully on well-prepared settings.
38
38
39 This piece would later be split up per topic to avoid a big fat monster
39 This piece would later be split up per topic to avoid a big fat monster
40 function.
40 function.
41 """
41 """
42 jn = os.path.join
42 jn = os.path.join
43
43
44 global_settings_maker = SettingsMaker(global_config)
44 global_settings_maker = SettingsMaker(global_config)
45 global_settings_maker.make_setting('debug', default=False, parser='bool')
45 global_settings_maker.make_setting('debug', default=False, parser='bool')
46 debug_enabled = asbool(global_config.get('debug'))
46 debug_enabled = asbool(global_config.get('debug'))
47
47
48 settings_maker = SettingsMaker(settings)
48 settings_maker = SettingsMaker(settings)
49
49
50 settings_maker.make_setting(
50 settings_maker.make_setting(
51 'logging.autoconfigure',
51 'logging.autoconfigure',
52 default=False,
52 default=False,
53 parser='bool')
53 parser='bool')
54
54
55 logging_conf = jn(os.path.dirname(global_config.get('__file__')), 'logging.ini')
55 ini_loc = os.path.dirname(global_config.get('__file__'))
56 logging_conf = jn(ini_loc, 'logging.ini')
56 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
57 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
57
58
58 # Default includes, possible to change as a user
59 # Default includes, possible to change as a user
59 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
60 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
60 log.debug(
61 log.debug(
61 "Using the following pyramid.includes: %s",
62 "Using the following pyramid.includes: %s",
62 pyramid_includes)
63 pyramid_includes)
63
64
64 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
65 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
65 settings_maker.make_setting('rhodecode.edition_id', 'CE')
66 settings_maker.make_setting('rhodecode.edition_id', 'CE')
66
67
67 if 'mako.default_filters' not in settings:
68 if 'mako.default_filters' not in settings:
68 # set custom default filters if we don't have it defined
69 # set custom default filters if we don't have it defined
69 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
70 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
70 settings['mako.default_filters'] = 'h_filter'
71 settings['mako.default_filters'] = 'h_filter'
71
72
72 if 'mako.directories' not in settings:
73 if 'mako.directories' not in settings:
73 mako_directories = settings.setdefault('mako.directories', [
74 mako_directories = settings.setdefault('mako.directories', [
74 # Base templates of the original application
75 # Base templates of the original application
75 'rhodecode:templates',
76 'rhodecode:templates',
76 ])
77 ])
77 log.debug(
78 log.debug(
78 "Using the following Mako template directories: %s",
79 "Using the following Mako template directories: %s",
79 mako_directories)
80 mako_directories)
80
81
81 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
82 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
82 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
83 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
83 raw_url = settings['beaker.session.url']
84 raw_url = settings['beaker.session.url']
84 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
85 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
85 settings['beaker.session.url'] = 'redis://' + raw_url
86 settings['beaker.session.url'] = 'redis://' + raw_url
86
87
87 settings_maker.make_setting('__file__', global_config.get('__file__'))
88 settings_maker.make_setting('__file__', global_config.get('__file__'))
88
89
89 # TODO: johbo: Re-think this, usually the call to config.include
90 # TODO: johbo: Re-think this, usually the call to config.include
90 # should allow to pass in a prefix.
91 # should allow to pass in a prefix.
91 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
92 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
92
93
93 # Sanitize generic settings.
94 # Sanitize generic settings.
94 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
95 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
95 settings_maker.make_setting('gzip_responses', False, parser='bool')
96 settings_maker.make_setting('gzip_responses', False, parser='bool')
96 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
97 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
97
98
99 # License settings.
100 settings_maker.make_setting('license.hide_license_info', False, parser='bool')
101 settings_maker.make_setting('license.import_path', jn(ini_loc, 'rhodecode_enterprise.license'))
102 settings_maker.make_setting('license.import_path_mode', 'if-missing')
103
98 # statsd
104 # statsd
99 settings_maker.make_setting('statsd.enabled', False, parser='bool')
105 settings_maker.make_setting('statsd.enabled', False, parser='bool')
100 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
106 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
101 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
107 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
102 settings_maker.make_setting('statsd.statsd_prefix', '')
108 settings_maker.make_setting('statsd.statsd_prefix', '')
103 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
109 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
104
110
105 settings_maker.make_setting('vcs.svn.compatible_version', '')
111 settings_maker.make_setting('vcs.svn.compatible_version', '')
106 settings_maker.make_setting('vcs.svn.redis_conn', 'redis://redis:6379/0')
112 settings_maker.make_setting('vcs.svn.redis_conn', 'redis://redis:6379/0')
107 settings_maker.make_setting('vcs.svn.proxy.enabled', True, parser='bool')
113 settings_maker.make_setting('vcs.svn.proxy.enabled', True, parser='bool')
108 settings_maker.make_setting('vcs.svn.proxy.host', 'http://svn:8090', parser='string')
114 settings_maker.make_setting('vcs.svn.proxy.host', 'http://svn:8090', parser='string')
109 settings_maker.make_setting('vcs.hooks.protocol.v2', 'celery')
115 settings_maker.make_setting('vcs.hooks.protocol.v2', 'celery')
110 settings_maker.make_setting('vcs.hooks.host', '*')
116 settings_maker.make_setting('vcs.hooks.host', '*')
111 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
117 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
112 settings_maker.make_setting('vcs.server', '')
118 settings_maker.make_setting('vcs.server', '')
113 settings_maker.make_setting('vcs.server.protocol', 'http')
119 settings_maker.make_setting('vcs.server.protocol', 'http')
114 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
120 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
115 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
121 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
116 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
122 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
117 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
123 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
118 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
124 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
119 settings_maker.make_setting('vcs.git.lfs.storage_location', '/var/opt/rhodecode_repo_store/.cache/git_lfs_store')
125 settings_maker.make_setting('vcs.git.lfs.storage_location', '/var/opt/rhodecode_repo_store/.cache/git_lfs_store')
120 settings_maker.make_setting('vcs.hg.largefiles.storage_location',
126 settings_maker.make_setting('vcs.hg.largefiles.storage_location',
121 '/var/opt/rhodecode_repo_store/.cache/hg_largefiles_store')
127 '/var/opt/rhodecode_repo_store/.cache/hg_largefiles_store')
122
128
123 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
129 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
124
130
125 # repo_store path
131 # repo_store path
126 settings_maker.make_setting('repo_store.path', '/var/opt/rhodecode_repo_store')
132 settings_maker.make_setting('repo_store.path', '/var/opt/rhodecode_repo_store')
127 # Support legacy values of vcs.scm_app_implementation. Legacy
133 # Support legacy values of vcs.scm_app_implementation. Legacy
128 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
134 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
129 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
135 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
130 scm_app_impl = settings['vcs.scm_app_implementation']
136 scm_app_impl = settings['vcs.scm_app_implementation']
131 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
137 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
132 settings['vcs.scm_app_implementation'] = 'http'
138 settings['vcs.scm_app_implementation'] = 'http'
133
139
134 settings_maker.make_setting('appenlight', False, parser='bool')
140 settings_maker.make_setting('appenlight', False, parser='bool')
135
141
136 temp_store = tempfile.gettempdir()
142 temp_store = tempfile.gettempdir()
137 tmp_cache_dir = jn(temp_store, 'rc_cache')
143 tmp_cache_dir = jn(temp_store, 'rc_cache')
138
144
139 # save default, cache dir, and use it for all backends later.
145 # save default, cache dir, and use it for all backends later.
140 default_cache_dir = settings_maker.make_setting(
146 default_cache_dir = settings_maker.make_setting(
141 'cache_dir',
147 'cache_dir',
142 default=tmp_cache_dir, default_when_empty=True,
148 default=tmp_cache_dir, default_when_empty=True,
143 parser='dir:ensured')
149 parser='dir:ensured')
144
150
145 # exception store cache
151 # exception store cache
146 settings_maker.make_setting(
152 settings_maker.make_setting(
147 'exception_tracker.store_path',
153 'exception_tracker.store_path',
148 default=jn(default_cache_dir, 'exc_store'), default_when_empty=True,
154 default=jn(default_cache_dir, 'exc_store'), default_when_empty=True,
149 parser='dir:ensured'
155 parser='dir:ensured'
150 )
156 )
151
157
152 settings_maker.make_setting(
158 settings_maker.make_setting(
153 'celerybeat-schedule.path',
159 'celerybeat-schedule.path',
154 default=jn(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
160 default=jn(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
155 parser='file:ensured'
161 parser='file:ensured'
156 )
162 )
157
163
158 # celery
164 # celery
159 broker_url = settings_maker.make_setting('celery.broker_url', 'redis://redis:6379/8')
165 broker_url = settings_maker.make_setting('celery.broker_url', 'redis://redis:6379/8')
160 settings_maker.make_setting('celery.result_backend', broker_url)
166 settings_maker.make_setting('celery.result_backend', broker_url)
161
167
162 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
168 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
163 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
169 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
164
170
165 # sessions, ensure file since no-value is memory
171 # sessions, ensure file since no-value is memory
166 settings_maker.make_setting('beaker.session.type', 'file')
172 settings_maker.make_setting('beaker.session.type', 'file')
167 settings_maker.make_setting('beaker.session.data_dir', jn(default_cache_dir, 'session_data'))
173 settings_maker.make_setting('beaker.session.data_dir', jn(default_cache_dir, 'session_data'))
168
174
169 # cache_general
175 # cache_general
170 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
176 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
171 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
177 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
172 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_general.db'))
178 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_general.db'))
173
179
174 # cache_perms
180 # cache_perms
175 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
181 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
176 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
182 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
177 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_perms_db'))
183 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_perms_db'))
178
184
179 # cache_repo
185 # cache_repo
180 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
186 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
181 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
187 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
182 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_repo_db'))
188 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_repo_db'))
183
189
184 # cache_license
190 # cache_license
185 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
191 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
186 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
192 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
187 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_license_db'))
193 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_license_db'))
188
194
189 # cache_repo_longterm memory, 96H
195 # cache_repo_longterm memory, 96H
190 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
196 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
191 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
197 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
192 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
198 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
193
199
194 # sql_cache_short
200 # sql_cache_short
195 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
201 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
196 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
202 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
197 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
203 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
198
204
199 # archive_cache
205 # archive_cache
200 settings_maker.make_setting('archive_cache.locking.url', 'redis://redis:6379/1')
206 settings_maker.make_setting('archive_cache.locking.url', 'redis://redis:6379/1')
201 settings_maker.make_setting('archive_cache.backend.type', 'filesystem')
207 settings_maker.make_setting('archive_cache.backend.type', 'filesystem')
202
208
203 settings_maker.make_setting('archive_cache.filesystem.store_dir', jn(default_cache_dir, 'archive_cache'), default_when_empty=True,)
209 settings_maker.make_setting('archive_cache.filesystem.store_dir', jn(default_cache_dir, 'archive_cache'), default_when_empty=True,)
204 settings_maker.make_setting('archive_cache.filesystem.cache_shards', 8, parser='int')
210 settings_maker.make_setting('archive_cache.filesystem.cache_shards', 8, parser='int')
205 settings_maker.make_setting('archive_cache.filesystem.cache_size_gb', 10, parser='float')
211 settings_maker.make_setting('archive_cache.filesystem.cache_size_gb', 10, parser='float')
206 settings_maker.make_setting('archive_cache.filesystem.eviction_policy', 'least-recently-stored')
212 settings_maker.make_setting('archive_cache.filesystem.eviction_policy', 'least-recently-stored')
207
213
208 settings_maker.make_setting('archive_cache.filesystem.retry', False, parser='bool')
214 settings_maker.make_setting('archive_cache.filesystem.retry', False, parser='bool')
209 settings_maker.make_setting('archive_cache.filesystem.retry_backoff', 1, parser='int')
215 settings_maker.make_setting('archive_cache.filesystem.retry_backoff', 1, parser='int')
210 settings_maker.make_setting('archive_cache.filesystem.retry_attempts', 10, parser='int')
216 settings_maker.make_setting('archive_cache.filesystem.retry_attempts', 10, parser='int')
211
217
212 settings_maker.make_setting('archive_cache.objectstore.url', 'http://s3-minio:9000', default_when_empty=True,)
218 settings_maker.make_setting('archive_cache.objectstore.url', 'http://s3-minio:9000', default_when_empty=True,)
213 settings_maker.make_setting('archive_cache.objectstore.key', '')
219 settings_maker.make_setting('archive_cache.objectstore.key', '')
214 settings_maker.make_setting('archive_cache.objectstore.secret', '')
220 settings_maker.make_setting('archive_cache.objectstore.secret', '')
215 settings_maker.make_setting('archive_cache.objectstore.region', 'eu-central-1')
221 settings_maker.make_setting('archive_cache.objectstore.region', 'eu-central-1')
216 settings_maker.make_setting('archive_cache.objectstore.bucket', 'rhodecode-archive-cache', default_when_empty=True,)
222 settings_maker.make_setting('archive_cache.objectstore.bucket', 'rhodecode-archive-cache', default_when_empty=True,)
217 settings_maker.make_setting('archive_cache.objectstore.bucket_shards', 8, parser='int')
223 settings_maker.make_setting('archive_cache.objectstore.bucket_shards', 8, parser='int')
218
224
219 settings_maker.make_setting('archive_cache.objectstore.cache_size_gb', 10, parser='float')
225 settings_maker.make_setting('archive_cache.objectstore.cache_size_gb', 10, parser='float')
220 settings_maker.make_setting('archive_cache.objectstore.eviction_policy', 'least-recently-stored')
226 settings_maker.make_setting('archive_cache.objectstore.eviction_policy', 'least-recently-stored')
221
227
222 settings_maker.make_setting('archive_cache.objectstore.retry', False, parser='bool')
228 settings_maker.make_setting('archive_cache.objectstore.retry', False, parser='bool')
223 settings_maker.make_setting('archive_cache.objectstore.retry_backoff', 1, parser='int')
229 settings_maker.make_setting('archive_cache.objectstore.retry_backoff', 1, parser='int')
224 settings_maker.make_setting('archive_cache.objectstore.retry_attempts', 10, parser='int')
230 settings_maker.make_setting('archive_cache.objectstore.retry_attempts', 10, parser='int')
225
231
226 settings_maker.env_expand()
232 settings_maker.env_expand()
227
233
228 # configure instance id
234 # configure instance id
229 config_utils.set_instance_id(settings)
235 config_utils.set_instance_id(settings)
230
236
231 return settings
237 return settings
@@ -1,470 +1,471 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import sys
20 import sys
21 import collections
21 import collections
22
22
23 import time
23 import time
24 import logging.config
24 import logging.config
25
25
26 from paste.gzipper import make_gzip_middleware
26 from paste.gzipper import make_gzip_middleware
27 import pyramid.events
27 import pyramid.events
28 from pyramid.wsgi import wsgiapp
28 from pyramid.wsgi import wsgiapp
29 from pyramid.config import Configurator
29 from pyramid.config import Configurator
30 from pyramid.settings import asbool, aslist
30 from pyramid.settings import asbool, aslist
31 from pyramid.httpexceptions import (
31 from pyramid.httpexceptions import (
32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
33 from pyramid.renderers import render_to_response
33 from pyramid.renderers import render_to_response
34
34
35 from rhodecode.model import meta
35 from rhodecode.model import meta
36 from rhodecode.config import patches
36 from rhodecode.config import patches
37
37
38 from rhodecode.config.environment import load_pyramid_environment, propagate_rhodecode_config
38 from rhodecode.config.environment import load_pyramid_environment, propagate_rhodecode_config
39
39
40 import rhodecode.events
40 import rhodecode.events
41 from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults
41 from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults
42 from rhodecode.lib.middleware.vcs import VCSMiddleware
42 from rhodecode.lib.middleware.vcs import VCSMiddleware
43 from rhodecode.lib.request import Request
43 from rhodecode.lib.request import Request
44 from rhodecode.lib.vcs import VCSCommunicationError
44 from rhodecode.lib.vcs import VCSCommunicationError
45 from rhodecode.lib.exceptions import VCSServerUnavailable
45 from rhodecode.lib.exceptions import VCSServerUnavailable
46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 from rhodecode.lib.utils2 import AttributeDict
49 from rhodecode.lib.utils2 import AttributeDict
50 from rhodecode.lib.exc_tracking import store_exception, format_exc
50 from rhodecode.lib.exc_tracking import store_exception, format_exc
51 from rhodecode.subscribers import (
51 from rhodecode.subscribers import (
52 scan_repositories_if_enabled, write_js_routes_if_enabled,
52 scan_repositories_if_enabled, write_js_routes_if_enabled,
53 write_metadata_if_needed, write_usage_data)
53 write_metadata_if_needed, write_usage_data, import_license_if_present)
54 from rhodecode.lib.statsd_client import StatsdClient
54 from rhodecode.lib.statsd_client import StatsdClient
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def is_http_error(response):
59 def is_http_error(response):
60 # error which should have traceback
60 # error which should have traceback
61 return response.status_code > 499
61 return response.status_code > 499
62
62
63
63
64 def should_load_all():
64 def should_load_all():
65 """
65 """
66 Returns if all application components should be loaded. In some cases it's
66 Returns if all application components should be loaded. In some cases it's
67 desired to skip apps loading for faster shell script execution
67 desired to skip apps loading for faster shell script execution
68 """
68 """
69 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
69 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
70 if ssh_cmd:
70 if ssh_cmd:
71 return False
71 return False
72
72
73 return True
73 return True
74
74
75
75
76 def make_pyramid_app(global_config, **settings):
76 def make_pyramid_app(global_config, **settings):
77 """
77 """
78 Constructs the WSGI application based on Pyramid.
78 Constructs the WSGI application based on Pyramid.
79
79
80 Specials:
80 Specials:
81
81
82 * The application can also be integrated like a plugin via the call to
82 * The application can also be integrated like a plugin via the call to
83 `includeme`. This is accompanied with the other utility functions which
83 `includeme`. This is accompanied with the other utility functions which
84 are called. Changing this should be done with great care to not break
84 are called. Changing this should be done with great care to not break
85 cases when these fragments are assembled from another place.
85 cases when these fragments are assembled from another place.
86
86
87 """
87 """
88 start_time = time.time()
88 start_time = time.time()
89 log.info('Pyramid app config starting')
89 log.info('Pyramid app config starting')
90
90
91 sanitize_settings_and_apply_defaults(global_config, settings)
91 sanitize_settings_and_apply_defaults(global_config, settings)
92
92
93 # init and bootstrap StatsdClient
93 # init and bootstrap StatsdClient
94 StatsdClient.setup(settings)
94 StatsdClient.setup(settings)
95
95
96 config = Configurator(settings=settings)
96 config = Configurator(settings=settings)
97 # Init our statsd at very start
97 # Init our statsd at very start
98 config.registry.statsd = StatsdClient.statsd
98 config.registry.statsd = StatsdClient.statsd
99
99
100 # Apply compatibility patches
100 # Apply compatibility patches
101 patches.inspect_getargspec()
101 patches.inspect_getargspec()
102 patches.repoze_sendmail_lf_fix()
102 patches.repoze_sendmail_lf_fix()
103
103
104 load_pyramid_environment(global_config, settings)
104 load_pyramid_environment(global_config, settings)
105
105
106 # Static file view comes first
106 # Static file view comes first
107 includeme_first(config)
107 includeme_first(config)
108
108
109 includeme(config)
109 includeme(config)
110
110
111 pyramid_app = config.make_wsgi_app()
111 pyramid_app = config.make_wsgi_app()
112 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
112 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
113 pyramid_app.config = config
113 pyramid_app.config = config
114
114
115 celery_settings = get_celery_config(settings)
115 celery_settings = get_celery_config(settings)
116 config.configure_celery(celery_settings)
116 config.configure_celery(celery_settings)
117
117
118 # final config set...
118 # final config set...
119 propagate_rhodecode_config(global_config, settings, config.registry.settings)
119 propagate_rhodecode_config(global_config, settings, config.registry.settings)
120
120
121 # creating the app uses a connection - return it after we are done
121 # creating the app uses a connection - return it after we are done
122 meta.Session.remove()
122 meta.Session.remove()
123
123
124 total_time = time.time() - start_time
124 total_time = time.time() - start_time
125 log.info('Pyramid app created and configured in %.2fs', total_time)
125 log.info('Pyramid app created and configured in %.2fs', total_time)
126 return pyramid_app
126 return pyramid_app
127
127
128
128
129 def get_celery_config(settings):
129 def get_celery_config(settings):
130 """
130 """
131 Converts basic ini configuration into celery 4.X options
131 Converts basic ini configuration into celery 4.X options
132 """
132 """
133
133
134 def key_converter(key_name):
134 def key_converter(key_name):
135 pref = 'celery.'
135 pref = 'celery.'
136 if key_name.startswith(pref):
136 if key_name.startswith(pref):
137 return key_name[len(pref):].replace('.', '_').lower()
137 return key_name[len(pref):].replace('.', '_').lower()
138
138
139 def type_converter(parsed_key, value):
139 def type_converter(parsed_key, value):
140 # cast to int
140 # cast to int
141 if value.isdigit():
141 if value.isdigit():
142 return int(value)
142 return int(value)
143
143
144 # cast to bool
144 # cast to bool
145 if value.lower() in ['true', 'false', 'True', 'False']:
145 if value.lower() in ['true', 'false', 'True', 'False']:
146 return value.lower() == 'true'
146 return value.lower() == 'true'
147 return value
147 return value
148
148
149 celery_config = {}
149 celery_config = {}
150 for k, v in settings.items():
150 for k, v in settings.items():
151 pref = 'celery.'
151 pref = 'celery.'
152 if k.startswith(pref):
152 if k.startswith(pref):
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
154
154
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
156 # beat_config = {}
156 # beat_config = {}
157 # for section in parser.sections():
157 # for section in parser.sections():
158 # if section.startswith('celerybeat:'):
158 # if section.startswith('celerybeat:'):
159 # name = section.split(':', 1)[1]
159 # name = section.split(':', 1)[1]
160 # beat_config[name] = get_beat_config(parser, section)
160 # beat_config[name] = get_beat_config(parser, section)
161
161
162 # final compose of settings
162 # final compose of settings
163 celery_settings = {}
163 celery_settings = {}
164
164
165 if celery_config:
165 if celery_config:
166 celery_settings.update(celery_config)
166 celery_settings.update(celery_config)
167 # if beat_config:
167 # if beat_config:
168 # celery_settings.update({'beat_schedule': beat_config})
168 # celery_settings.update({'beat_schedule': beat_config})
169
169
170 return celery_settings
170 return celery_settings
171
171
172
172
173 def not_found_view(request):
173 def not_found_view(request):
174 """
174 """
175 This creates the view which should be registered as not-found-view to
175 This creates the view which should be registered as not-found-view to
176 pyramid.
176 pyramid.
177 """
177 """
178
178
179 if not getattr(request, 'vcs_call', None):
179 if not getattr(request, 'vcs_call', None):
180 # handle like regular case with our error_handler
180 # handle like regular case with our error_handler
181 return error_handler(HTTPNotFound(), request)
181 return error_handler(HTTPNotFound(), request)
182
182
183 # handle not found view as a vcs call
183 # handle not found view as a vcs call
184 settings = request.registry.settings
184 settings = request.registry.settings
185 ae_client = getattr(request, 'ae_client', None)
185 ae_client = getattr(request, 'ae_client', None)
186 vcs_app = VCSMiddleware(
186 vcs_app = VCSMiddleware(
187 HTTPNotFound(), request.registry, settings,
187 HTTPNotFound(), request.registry, settings,
188 appenlight_client=ae_client)
188 appenlight_client=ae_client)
189
189
190 return wsgiapp(vcs_app)(None, request)
190 return wsgiapp(vcs_app)(None, request)
191
191
192
192
193 def error_handler(exception, request):
193 def error_handler(exception, request):
194 import rhodecode
194 import rhodecode
195 from rhodecode.lib import helpers
195 from rhodecode.lib import helpers
196
196
197 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
197 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
198
198
199 base_response = HTTPInternalServerError()
199 base_response = HTTPInternalServerError()
200 # prefer original exception for the response since it may have headers set
200 # prefer original exception for the response since it may have headers set
201 if isinstance(exception, HTTPException):
201 if isinstance(exception, HTTPException):
202 base_response = exception
202 base_response = exception
203 elif isinstance(exception, VCSCommunicationError):
203 elif isinstance(exception, VCSCommunicationError):
204 base_response = VCSServerUnavailable()
204 base_response = VCSServerUnavailable()
205
205
206 if is_http_error(base_response):
206 if is_http_error(base_response):
207 traceback_info = format_exc(request.exc_info)
207 traceback_info = format_exc(request.exc_info)
208 log.error(
208 log.error(
209 'error occurred handling this request for path: %s, \n%s',
209 'error occurred handling this request for path: %s, \n%s',
210 request.path, traceback_info)
210 request.path, traceback_info)
211
211
212 error_explanation = base_response.explanation or str(base_response)
212 error_explanation = base_response.explanation or str(base_response)
213 if base_response.status_code == 404:
213 if base_response.status_code == 404:
214 error_explanation += " Optionally you don't have permission to access this page."
214 error_explanation += " Optionally you don't have permission to access this page."
215 c = AttributeDict()
215 c = AttributeDict()
216 c.error_message = base_response.status
216 c.error_message = base_response.status
217 c.error_explanation = error_explanation
217 c.error_explanation = error_explanation
218 c.visual = AttributeDict()
218 c.visual = AttributeDict()
219
219
220 c.visual.rhodecode_support_url = (
220 c.visual.rhodecode_support_url = (
221 request.registry.settings.get('rhodecode_support_url') or
221 request.registry.settings.get('rhodecode_support_url') or
222 request.route_url('rhodecode_support')
222 request.route_url('rhodecode_support')
223 )
223 )
224 c.redirect_time = 0
224 c.redirect_time = 0
225 c.rhodecode_name = rhodecode_title
225 c.rhodecode_name = rhodecode_title
226 if not c.rhodecode_name:
226 if not c.rhodecode_name:
227 c.rhodecode_name = 'Rhodecode'
227 c.rhodecode_name = 'Rhodecode'
228
228
229 c.causes = []
229 c.causes = []
230 if is_http_error(base_response):
230 if is_http_error(base_response):
231 c.causes.append('Server is overloaded.')
231 c.causes.append('Server is overloaded.')
232 c.causes.append('Server database connection is lost.')
232 c.causes.append('Server database connection is lost.')
233 c.causes.append('Server expected unhandled error.')
233 c.causes.append('Server expected unhandled error.')
234
234
235 if hasattr(base_response, 'causes'):
235 if hasattr(base_response, 'causes'):
236 c.causes = base_response.causes
236 c.causes = base_response.causes
237
237
238 c.messages = helpers.flash.pop_messages(request=request)
238 c.messages = helpers.flash.pop_messages(request=request)
239 exc_info = sys.exc_info()
239 exc_info = sys.exc_info()
240 c.exception_id = id(exc_info)
240 c.exception_id = id(exc_info)
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
242 or base_response.status_code > 499
242 or base_response.status_code > 499
243 c.exception_id_url = request.route_url(
243 c.exception_id_url = request.route_url(
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
245
245
246 debug_mode = rhodecode.ConfigGet().get_bool('debug')
246 debug_mode = rhodecode.ConfigGet().get_bool('debug')
247 if c.show_exception_id:
247 if c.show_exception_id:
248 store_exception(c.exception_id, exc_info)
248 store_exception(c.exception_id, exc_info)
249 c.exception_debug = debug_mode
249 c.exception_debug = debug_mode
250 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
250 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
251
251
252 if debug_mode:
252 if debug_mode:
253 try:
253 try:
254 from rich.traceback import install
254 from rich.traceback import install
255 install(show_locals=True)
255 install(show_locals=True)
256 log.debug('Installing rich tracebacks...')
256 log.debug('Installing rich tracebacks...')
257 except ImportError:
257 except ImportError:
258 pass
258 pass
259
259
260 response = render_to_response(
260 response = render_to_response(
261 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
261 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
262 response=base_response)
262 response=base_response)
263
263
264 response.headers["X-RC-Exception-Id"] = str(c.exception_id)
264 response.headers["X-RC-Exception-Id"] = str(c.exception_id)
265
265
266 statsd = request.registry.statsd
266 statsd = request.registry.statsd
267 if statsd and base_response.status_code > 499:
267 if statsd and base_response.status_code > 499:
268 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
268 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
269 statsd.incr('rhodecode_exception_total',
269 statsd.incr('rhodecode_exception_total',
270 tags=["exc_source:web",
270 tags=["exc_source:web",
271 f"http_code:{base_response.status_code}",
271 f"http_code:{base_response.status_code}",
272 f"type:{exc_type}"])
272 f"type:{exc_type}"])
273
273
274 return response
274 return response
275
275
276
276
277 def includeme_first(config):
277 def includeme_first(config):
278 # redirect automatic browser favicon.ico requests to correct place
278 # redirect automatic browser favicon.ico requests to correct place
279 def favicon_redirect(context, request):
279 def favicon_redirect(context, request):
280 return HTTPFound(
280 return HTTPFound(
281 request.static_path('rhodecode:public/images/favicon.ico'))
281 request.static_path('rhodecode:public/images/favicon.ico'))
282
282
283 config.add_view(favicon_redirect, route_name='favicon')
283 config.add_view(favicon_redirect, route_name='favicon')
284 config.add_route('favicon', '/favicon.ico')
284 config.add_route('favicon', '/favicon.ico')
285
285
286 def robots_redirect(context, request):
286 def robots_redirect(context, request):
287 return HTTPFound(
287 return HTTPFound(
288 request.static_path('rhodecode:public/robots.txt'))
288 request.static_path('rhodecode:public/robots.txt'))
289
289
290 config.add_view(robots_redirect, route_name='robots')
290 config.add_view(robots_redirect, route_name='robots')
291 config.add_route('robots', '/robots.txt')
291 config.add_route('robots', '/robots.txt')
292
292
293 config.add_static_view(
293 config.add_static_view(
294 '_static/deform', 'deform:static')
294 '_static/deform', 'deform:static')
295 config.add_static_view(
295 config.add_static_view(
296 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
296 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
297
297
298
298
299 ce_auth_resources = [
299 ce_auth_resources = [
300 'rhodecode.authentication.plugins.auth_crowd',
300 'rhodecode.authentication.plugins.auth_crowd',
301 'rhodecode.authentication.plugins.auth_headers',
301 'rhodecode.authentication.plugins.auth_headers',
302 'rhodecode.authentication.plugins.auth_jasig_cas',
302 'rhodecode.authentication.plugins.auth_jasig_cas',
303 'rhodecode.authentication.plugins.auth_ldap',
303 'rhodecode.authentication.plugins.auth_ldap',
304 'rhodecode.authentication.plugins.auth_pam',
304 'rhodecode.authentication.plugins.auth_pam',
305 'rhodecode.authentication.plugins.auth_rhodecode',
305 'rhodecode.authentication.plugins.auth_rhodecode',
306 'rhodecode.authentication.plugins.auth_token',
306 'rhodecode.authentication.plugins.auth_token',
307 ]
307 ]
308
308
309
309
310 def includeme(config, auth_resources=None):
310 def includeme(config, auth_resources=None):
311 from rhodecode.lib.celerylib.loader import configure_celery
311 from rhodecode.lib.celerylib.loader import configure_celery
312 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
312 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
313 settings = config.registry.settings
313 settings = config.registry.settings
314 config.set_request_factory(Request)
314 config.set_request_factory(Request)
315
315
316 # plugin information
316 # plugin information
317 config.registry.rhodecode_plugins = collections.OrderedDict()
317 config.registry.rhodecode_plugins = collections.OrderedDict()
318
318
319 config.add_directive(
319 config.add_directive(
320 'register_rhodecode_plugin', register_rhodecode_plugin)
320 'register_rhodecode_plugin', register_rhodecode_plugin)
321
321
322 config.add_directive('configure_celery', configure_celery)
322 config.add_directive('configure_celery', configure_celery)
323
323
324 if settings.get('appenlight', False):
324 if settings.get('appenlight', False):
325 config.include('appenlight_client.ext.pyramid_tween')
325 config.include('appenlight_client.ext.pyramid_tween')
326
326
327 load_all = should_load_all()
327 load_all = should_load_all()
328
328
329 # Includes which are required. The application would fail without them.
329 # Includes which are required. The application would fail without them.
330 config.include('pyramid_mako')
330 config.include('pyramid_mako')
331 config.include('rhodecode.lib.rc_beaker')
331 config.include('rhodecode.lib.rc_beaker')
332 config.include('rhodecode.lib.rc_cache')
332 config.include('rhodecode.lib.rc_cache')
333 config.include('rhodecode.lib.archive_cache')
333 config.include('rhodecode.lib.archive_cache')
334
334
335 config.include('rhodecode.apps._base.navigation')
335 config.include('rhodecode.apps._base.navigation')
336 config.include('rhodecode.apps._base.subscribers')
336 config.include('rhodecode.apps._base.subscribers')
337 config.include('rhodecode.tweens')
337 config.include('rhodecode.tweens')
338 config.include('rhodecode.authentication')
338 config.include('rhodecode.authentication')
339
339
340 if load_all:
340 if load_all:
341
341
342 # load CE authentication plugins
342 # load CE authentication plugins
343
343
344 if auth_resources:
344 if auth_resources:
345 ce_auth_resources.extend(auth_resources)
345 ce_auth_resources.extend(auth_resources)
346
346
347 for resource in ce_auth_resources:
347 for resource in ce_auth_resources:
348 config.include(resource)
348 config.include(resource)
349
349
350 # Auto discover authentication plugins and include their configuration.
350 # Auto discover authentication plugins and include their configuration.
351 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
351 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
352 from rhodecode.authentication import discover_legacy_plugins
352 from rhodecode.authentication import discover_legacy_plugins
353 discover_legacy_plugins(config)
353 discover_legacy_plugins(config)
354
354
355 # apps
355 # apps
356 if load_all:
356 if load_all:
357 log.debug('Starting config.include() calls')
357 log.debug('Starting config.include() calls')
358 config.include('rhodecode.api.includeme')
358 config.include('rhodecode.api.includeme')
359 config.include('rhodecode.apps._base.includeme')
359 config.include('rhodecode.apps._base.includeme')
360 config.include('rhodecode.apps._base.navigation.includeme')
360 config.include('rhodecode.apps._base.navigation.includeme')
361 config.include('rhodecode.apps._base.subscribers.includeme')
361 config.include('rhodecode.apps._base.subscribers.includeme')
362 config.include('rhodecode.apps.hovercards.includeme')
362 config.include('rhodecode.apps.hovercards.includeme')
363 config.include('rhodecode.apps.ops.includeme')
363 config.include('rhodecode.apps.ops.includeme')
364 config.include('rhodecode.apps.channelstream.includeme')
364 config.include('rhodecode.apps.channelstream.includeme')
365 config.include('rhodecode.apps.file_store.includeme')
365 config.include('rhodecode.apps.file_store.includeme')
366 config.include('rhodecode.apps.admin.includeme')
366 config.include('rhodecode.apps.admin.includeme')
367 config.include('rhodecode.apps.login.includeme')
367 config.include('rhodecode.apps.login.includeme')
368 config.include('rhodecode.apps.home.includeme')
368 config.include('rhodecode.apps.home.includeme')
369 config.include('rhodecode.apps.journal.includeme')
369 config.include('rhodecode.apps.journal.includeme')
370
370
371 config.include('rhodecode.apps.repository.includeme')
371 config.include('rhodecode.apps.repository.includeme')
372 config.include('rhodecode.apps.repo_group.includeme')
372 config.include('rhodecode.apps.repo_group.includeme')
373 config.include('rhodecode.apps.user_group.includeme')
373 config.include('rhodecode.apps.user_group.includeme')
374 config.include('rhodecode.apps.search.includeme')
374 config.include('rhodecode.apps.search.includeme')
375 config.include('rhodecode.apps.user_profile.includeme')
375 config.include('rhodecode.apps.user_profile.includeme')
376 config.include('rhodecode.apps.user_group_profile.includeme')
376 config.include('rhodecode.apps.user_group_profile.includeme')
377 config.include('rhodecode.apps.my_account.includeme')
377 config.include('rhodecode.apps.my_account.includeme')
378 config.include('rhodecode.apps.gist.includeme')
378 config.include('rhodecode.apps.gist.includeme')
379
379
380 config.include('rhodecode.apps.svn_support.includeme')
380 config.include('rhodecode.apps.svn_support.includeme')
381 config.include('rhodecode.apps.ssh_support.includeme')
381 config.include('rhodecode.apps.ssh_support.includeme')
382 config.include('rhodecode.apps.debug_style')
382 config.include('rhodecode.apps.debug_style')
383
383
384 if load_all:
384 if load_all:
385 config.include('rhodecode.integrations.includeme')
385 config.include('rhodecode.integrations.includeme')
386 config.include('rhodecode.integrations.routes.includeme')
386 config.include('rhodecode.integrations.routes.includeme')
387
387
388 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
388 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
389 settings['default_locale_name'] = settings.get('lang', 'en')
389 settings['default_locale_name'] = settings.get('lang', 'en')
390 config.add_translation_dirs('rhodecode:i18n/')
390 config.add_translation_dirs('rhodecode:i18n/')
391
391
392 # Add subscribers.
392 # Add subscribers.
393 if load_all:
393 if load_all:
394 log.debug('Adding subscribers...')
394 log.debug('Adding subscribers...')
395 config.add_subscriber(scan_repositories_if_enabled,
395 config.add_subscriber(scan_repositories_if_enabled,
396 pyramid.events.ApplicationCreated)
396 pyramid.events.ApplicationCreated)
397 config.add_subscriber(write_metadata_if_needed,
397 config.add_subscriber(write_metadata_if_needed,
398 pyramid.events.ApplicationCreated)
398 pyramid.events.ApplicationCreated)
399 config.add_subscriber(write_usage_data,
399 config.add_subscriber(write_usage_data,
400 pyramid.events.ApplicationCreated)
400 pyramid.events.ApplicationCreated)
401 config.add_subscriber(write_js_routes_if_enabled,
401 config.add_subscriber(write_js_routes_if_enabled,
402 pyramid.events.ApplicationCreated)
402 pyramid.events.ApplicationCreated)
403
403 config.add_subscriber(import_license_if_present,
404 pyramid.events.ApplicationCreated)
404
405
405 # Set the default renderer for HTML templates to mako.
406 # Set the default renderer for HTML templates to mako.
406 config.add_mako_renderer('.html')
407 config.add_mako_renderer('.html')
407
408
408 config.add_renderer(
409 config.add_renderer(
409 name='json_ext',
410 name='json_ext',
410 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
411 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
411
412
412 config.add_renderer(
413 config.add_renderer(
413 name='string_html',
414 name='string_html',
414 factory='rhodecode.lib.string_renderer.html')
415 factory='rhodecode.lib.string_renderer.html')
415
416
416 # include RhodeCode plugins
417 # include RhodeCode plugins
417 includes = aslist(settings.get('rhodecode.includes', []))
418 includes = aslist(settings.get('rhodecode.includes', []))
418 log.debug('processing rhodecode.includes data...')
419 log.debug('processing rhodecode.includes data...')
419 for inc in includes:
420 for inc in includes:
420 config.include(inc)
421 config.include(inc)
421
422
422 # custom not found view, if our pyramid app doesn't know how to handle
423 # custom not found view, if our pyramid app doesn't know how to handle
423 # the request pass it to potential VCS handling ap
424 # the request pass it to potential VCS handling ap
424 config.add_notfound_view(not_found_view)
425 config.add_notfound_view(not_found_view)
425 if not settings.get('debugtoolbar.enabled', False):
426 if not settings.get('debugtoolbar.enabled', False):
426 # disabled debugtoolbar handle all exceptions via the error_handlers
427 # disabled debugtoolbar handle all exceptions via the error_handlers
427 config.add_view(error_handler, context=Exception)
428 config.add_view(error_handler, context=Exception)
428
429
429 # all errors including 403/404/50X
430 # all errors including 403/404/50X
430 config.add_view(error_handler, context=HTTPError)
431 config.add_view(error_handler, context=HTTPError)
431
432
432
433
433 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
434 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
434 """
435 """
435 Apply outer WSGI middlewares around the application.
436 Apply outer WSGI middlewares around the application.
436 """
437 """
437 registry = config.registry
438 registry = config.registry
438 settings = registry.settings
439 settings = registry.settings
439
440
440 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
441 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
441 pyramid_app = HttpsFixup(pyramid_app, settings)
442 pyramid_app = HttpsFixup(pyramid_app, settings)
442
443
443 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
444 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
444 pyramid_app, settings)
445 pyramid_app, settings)
445 registry.ae_client = _ae_client
446 registry.ae_client = _ae_client
446
447
447 if settings['gzip_responses']:
448 if settings['gzip_responses']:
448 pyramid_app = make_gzip_middleware(
449 pyramid_app = make_gzip_middleware(
449 pyramid_app, settings, compress_level=1)
450 pyramid_app, settings, compress_level=1)
450
451
451 # this should be the outer most middleware in the wsgi stack since
452 # this should be the outer most middleware in the wsgi stack since
452 # middleware like Routes make database calls
453 # middleware like Routes make database calls
453 def pyramid_app_with_cleanup(environ, start_response):
454 def pyramid_app_with_cleanup(environ, start_response):
454 start = time.time()
455 start = time.time()
455 try:
456 try:
456 return pyramid_app(environ, start_response)
457 return pyramid_app(environ, start_response)
457 finally:
458 finally:
458 # Dispose current database session and rollback uncommitted
459 # Dispose current database session and rollback uncommitted
459 # transactions.
460 # transactions.
460 meta.Session.remove()
461 meta.Session.remove()
461
462
462 # In a single threaded mode server, on non sqlite db we should have
463 # In a single threaded mode server, on non sqlite db we should have
463 # '0 Current Checked out connections' at the end of a request,
464 # '0 Current Checked out connections' at the end of a request,
464 # if not, then something, somewhere is leaving a connection open
465 # if not, then something, somewhere is leaving a connection open
465 pool = meta.get_engine().pool
466 pool = meta.get_engine().pool
466 log.debug('sa pool status: %s', pool.status())
467 log.debug('sa pool status: %s', pool.status())
467 total = time.time() - start
468 total = time.time() - start
468 log.debug('Request processing finalized: %.4fs', total)
469 log.debug('Request processing finalized: %.4fs', total)
469
470
470 return pyramid_app_with_cleanup
471 return pyramid_app_with_cleanup
@@ -1,679 +1,678 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Database creation, and setup module for RhodeCode Enterprise. Used for creation
20 Database creation, and setup module for RhodeCode Enterprise. Used for creation
21 of database as well as for migration operations
21 of database as well as for migration operations
22 """
22 """
23
23
24 import os
24 import os
25 import sys
25 import sys
26 import time
26 import time
27 import uuid
27 import uuid
28 import logging
28 import logging
29 import getpass
29 import getpass
30 from os.path import dirname as dn, join as jn
30 from os.path import dirname as dn, join as jn
31
31
32 from sqlalchemy.engine import create_engine
32 from sqlalchemy.engine import create_engine
33
33
34 from rhodecode import __dbversion__
34 from rhodecode import __dbversion__
35 from rhodecode.model import init_model
35 from rhodecode.model import init_model
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
38 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
39 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
39 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
40 from rhodecode.model.meta import Session, Base
40 from rhodecode.model.meta import Session, Base
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def notify(msg):
50 def notify(msg):
51 """
51 """
52 Notification for migrations messages
52 Notification for migrations messages
53 """
53 """
54 ml = len(msg) + (4 * 2)
54 ml = len(msg) + (4 * 2)
55 print((('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()))
55 print((('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()))
56
56
57
57
58 class DbManage(object):
58 class DbManage(object):
59
59
60 def __init__(self, log_sql, dbconf, root, tests=False,
60 def __init__(self, log_sql, dbconf, root, tests=False,
61 SESSION=None, cli_args=None, enc_key=b''):
61 SESSION=None, cli_args=None, enc_key=b''):
62
62
63 self.dbname = dbconf.split('/')[-1]
63 self.dbname = dbconf.split('/')[-1]
64 self.tests = tests
64 self.tests = tests
65 self.root = root
65 self.root = root
66 self.dburi = dbconf
66 self.dburi = dbconf
67 self.log_sql = log_sql
67 self.log_sql = log_sql
68 self.cli_args = cli_args or {}
68 self.cli_args = cli_args or {}
69 self.sa = None
69 self.sa = None
70 self.engine = None
70 self.engine = None
71 self.enc_key = enc_key
71 self.enc_key = enc_key
72 # sets .sa .engine
72 # sets .sa .engine
73 self.init_db(SESSION=SESSION)
73 self.init_db(SESSION=SESSION)
74
74
75 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
75 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
76
76
77 def db_exists(self):
77 def db_exists(self):
78 if not self.sa:
78 if not self.sa:
79 self.init_db()
79 self.init_db()
80 try:
80 try:
81 self.sa.query(RhodeCodeUi)\
81 self.sa.query(RhodeCodeUi)\
82 .filter(RhodeCodeUi.ui_key == '/')\
82 .filter(RhodeCodeUi.ui_key == '/')\
83 .scalar()
83 .scalar()
84 return True
84 return True
85 except Exception:
85 except Exception:
86 return False
86 return False
87 finally:
87 finally:
88 self.sa.rollback()
88 self.sa.rollback()
89
89
90 def get_ask_ok_func(self, param):
90 def get_ask_ok_func(self, param):
91 if param not in [None]:
91 if param not in [None]:
92 # return a function lambda that has a default set to param
92 # return a function lambda that has a default set to param
93 return lambda *args, **kwargs: param
93 return lambda *args, **kwargs: param
94 else:
94 else:
95 from rhodecode.lib.utils import ask_ok
95 from rhodecode.lib.utils import ask_ok
96 return ask_ok
96 return ask_ok
97
97
98 def init_db(self, SESSION=None):
98 def init_db(self, SESSION=None):
99
99
100 if SESSION:
100 if SESSION:
101 self.sa = SESSION
101 self.sa = SESSION
102 self.engine = SESSION.bind
102 self.engine = SESSION.bind
103 else:
103 else:
104 # init new sessions
104 # init new sessions
105 engine = create_engine(self.dburi, echo=self.log_sql)
105 engine = create_engine(self.dburi, echo=self.log_sql)
106 init_model(engine, encryption_key=self.enc_key)
106 init_model(engine, encryption_key=self.enc_key)
107 self.sa = Session()
107 self.sa = Session()
108 self.engine = engine
108 self.engine = engine
109
109
110 def create_tables(self, override=False):
110 def create_tables(self, override=False):
111 """
111 """
112 Create a auth database
112 Create a auth database
113 """
113 """
114
114
115 log.info("Existing database with the same name is going to be destroyed.")
115 log.info("Existing database with the same name is going to be destroyed.")
116 log.info("Setup command will run DROP ALL command on that database.")
116 log.info("Setup command will run DROP ALL command on that database.")
117 engine = self.engine
117 engine = self.engine
118
118
119 if self.tests:
119 if self.tests:
120 destroy = True
120 destroy = True
121 else:
121 else:
122 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
122 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
123 if not destroy:
123 if not destroy:
124 log.info('db tables bootstrap: Nothing done.')
124 log.info('db tables bootstrap: Nothing done.')
125 sys.exit(0)
125 sys.exit(0)
126 if destroy:
126 if destroy:
127 Base.metadata.drop_all(bind=engine)
127 Base.metadata.drop_all(bind=engine)
128
128
129 checkfirst = not override
129 checkfirst = not override
130 Base.metadata.create_all(bind=engine, checkfirst=checkfirst)
130 Base.metadata.create_all(bind=engine, checkfirst=checkfirst)
131 log.info('Created tables for %s', self.dbname)
131 log.info('Created tables for %s', self.dbname)
132
132
133 def set_db_version(self):
133 def set_db_version(self):
134 ver = DbMigrateVersion()
134 ver = DbMigrateVersion()
135 ver.version = __dbversion__
135 ver.version = __dbversion__
136 ver.repository_id = 'rhodecode_db_migrations'
136 ver.repository_id = 'rhodecode_db_migrations'
137 ver.repository_path = 'versions'
137 ver.repository_path = 'versions'
138 self.sa.add(ver)
138 self.sa.add(ver)
139 log.info('db version set to: %s', __dbversion__)
139 log.info('db version set to: %s', __dbversion__)
140
140
141 def run_post_migration_tasks(self):
141 def run_post_migration_tasks(self):
142 """
142 """
143 Run various tasks before actually doing migrations
143 Run various tasks before actually doing migrations
144 """
144 """
145 # delete cache keys on each upgrade
145 # delete cache keys on each upgrade
146 total = CacheKey.query().count()
146 total = CacheKey.query().count()
147 log.info("Deleting (%s) cache keys now...", total)
147 log.info("Deleting (%s) cache keys now...", total)
148 CacheKey.delete_all_cache()
148 CacheKey.delete_all_cache()
149
149
150 def upgrade(self, version=None):
150 def upgrade(self, version=None):
151 """
151 """
152 Upgrades given database schema to given revision following
152 Upgrades given database schema to given revision following
153 all needed steps, to perform the upgrade
153 all needed steps, to perform the upgrade
154
154
155 """
155 """
156
156
157 from rhodecode.lib.dbmigrate.migrate.versioning import api
157 from rhodecode.lib.dbmigrate.migrate.versioning import api
158 from rhodecode.lib.dbmigrate.migrate.exceptions import DatabaseNotControlledError
158 from rhodecode.lib.dbmigrate.migrate.exceptions import DatabaseNotControlledError
159
159
160 if 'sqlite' in self.dburi:
160 if 'sqlite' in self.dburi:
161 print(
161 print(
162 '********************** WARNING **********************\n'
162 '********************** WARNING **********************\n'
163 'Make sure your version of sqlite is at least 3.7.X. \n'
163 'Make sure your version of sqlite is at least 3.7.X. \n'
164 'Earlier versions are known to fail on some migrations\n'
164 'Earlier versions are known to fail on some migrations\n'
165 '*****************************************************\n')
165 '*****************************************************\n')
166
166
167 upgrade = self.ask_ok(
167 upgrade = self.ask_ok(
168 'You are about to perform a database upgrade. Make '
168 'You are about to perform a database upgrade. Make '
169 'sure you have backed up your database. '
169 'sure you have backed up your database. '
170 'Continue ? [y/n]')
170 'Continue ? [y/n]')
171 if not upgrade:
171 if not upgrade:
172 log.info('No upgrade performed')
172 log.info('No upgrade performed')
173 sys.exit(0)
173 sys.exit(0)
174
174
175 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
175 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
176 'rhodecode/lib/dbmigrate')
176 'rhodecode/lib/dbmigrate')
177 db_uri = self.dburi
177 db_uri = self.dburi
178
178
179 if version:
179 if version:
180 DbMigrateVersion.set_version(version)
180 DbMigrateVersion.set_version(version)
181
181
182 try:
182 try:
183 curr_version = api.db_version(db_uri, repository_path)
183 curr_version = api.db_version(db_uri, repository_path)
184 msg = (f'Found current database db_uri under version '
184 msg = (f'Found current database db_uri under version '
185 f'control with version {curr_version}')
185 f'control with version {curr_version}')
186
186
187 except (RuntimeError, DatabaseNotControlledError):
187 except (RuntimeError, DatabaseNotControlledError):
188 curr_version = 1
188 curr_version = 1
189 msg = f'Current database is not under version control. ' \
189 msg = f'Current database is not under version control. ' \
190 f'Setting as version {curr_version}'
190 f'Setting as version {curr_version}'
191 api.version_control(db_uri, repository_path, curr_version)
191 api.version_control(db_uri, repository_path, curr_version)
192
192
193 notify(msg)
193 notify(msg)
194
194
195 if curr_version == __dbversion__:
195 if curr_version == __dbversion__:
196 log.info('This database is already at the newest version')
196 log.info('This database is already at the newest version')
197 sys.exit(0)
197 sys.exit(0)
198
198
199 upgrade_steps = list(range(curr_version + 1, __dbversion__ + 1))
199 upgrade_steps = list(range(curr_version + 1, __dbversion__ + 1))
200 notify(f'attempting to upgrade database from '
200 notify(f'attempting to upgrade database from '
201 f'version {curr_version} to version {__dbversion__}')
201 f'version {curr_version} to version {__dbversion__}')
202
202
203 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
203 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
204 final_step = 'latest'
204 final_step = 'latest'
205 for step in upgrade_steps:
205 for step in upgrade_steps:
206 notify(f'performing upgrade step {step}')
206 notify(f'performing upgrade step {step}')
207 time.sleep(0.5)
207 time.sleep(0.5)
208
208
209 api.upgrade(db_uri, repository_path, step)
209 api.upgrade(db_uri, repository_path, step)
210 self.sa.rollback()
210 self.sa.rollback()
211 notify(f'schema upgrade for step {step} completed')
211 notify(f'schema upgrade for step {step} completed')
212
212
213 final_step = step
213 final_step = step
214
214
215 self.run_post_migration_tasks()
215 self.run_post_migration_tasks()
216 notify(f'upgrade to version {final_step} successful')
216 notify(f'upgrade to version {final_step} successful')
217
217
218 def fix_repo_paths(self):
218 def fix_repo_paths(self):
219 """
219 """
220 Fixes an old RhodeCode version path into new one without a '*'
220 Fixes an old RhodeCode version path into new one without a '*'
221 """
221 """
222
222
223 paths = self.sa.query(RhodeCodeUi)\
223 paths = self.sa.query(RhodeCodeUi)\
224 .filter(RhodeCodeUi.ui_key == '/')\
224 .filter(RhodeCodeUi.ui_key == '/')\
225 .scalar()
225 .scalar()
226
226
227 paths.ui_value = paths.ui_value.replace('*', '')
227 paths.ui_value = paths.ui_value.replace('*', '')
228
228
229 try:
229 try:
230 self.sa.add(paths)
230 self.sa.add(paths)
231 self.sa.commit()
231 self.sa.commit()
232 except Exception:
232 except Exception:
233 self.sa.rollback()
233 self.sa.rollback()
234 raise
234 raise
235
235
236 def fix_default_user(self):
236 def fix_default_user(self):
237 """
237 """
238 Fixes an old default user with some 'nicer' default values,
238 Fixes an old default user with some 'nicer' default values,
239 used mostly for anonymous access
239 used mostly for anonymous access
240 """
240 """
241 def_user = self.sa.query(User)\
241 def_user = self.sa.query(User)\
242 .filter(User.username == User.DEFAULT_USER)\
242 .filter(User.username == User.DEFAULT_USER)\
243 .one()
243 .one()
244
244
245 def_user.name = 'Anonymous'
245 def_user.name = 'Anonymous'
246 def_user.lastname = 'User'
246 def_user.lastname = 'User'
247 def_user.email = User.DEFAULT_USER_EMAIL
247 def_user.email = User.DEFAULT_USER_EMAIL
248
248
249 try:
249 try:
250 self.sa.add(def_user)
250 self.sa.add(def_user)
251 self.sa.commit()
251 self.sa.commit()
252 except Exception:
252 except Exception:
253 self.sa.rollback()
253 self.sa.rollback()
254 raise
254 raise
255
255
256 def fix_settings(self):
256 def fix_settings(self):
257 """
257 """
258 Fixes rhodecode settings and adds ga_code key for google analytics
258 Fixes rhodecode settings and adds ga_code key for google analytics
259 """
259 """
260
260
261 hgsettings3 = RhodeCodeSetting('ga_code', '')
261 hgsettings3 = RhodeCodeSetting('ga_code', '')
262
262
263 try:
263 try:
264 self.sa.add(hgsettings3)
264 self.sa.add(hgsettings3)
265 self.sa.commit()
265 self.sa.commit()
266 except Exception:
266 except Exception:
267 self.sa.rollback()
267 self.sa.rollback()
268 raise
268 raise
269
269
270 def create_admin_and_prompt(self):
270 def create_admin_and_prompt(self):
271
271
272 # defaults
272 # defaults
273 defaults = self.cli_args
273 defaults = self.cli_args
274 username = defaults.get('username')
274 username = defaults.get('username')
275 password = defaults.get('password')
275 password = defaults.get('password')
276 email = defaults.get('email')
276 email = defaults.get('email')
277
277
278 if username is None:
278 if username is None:
279 username = input('Specify admin username:')
279 username = input('Specify admin username:')
280 if password is None:
280 if password is None:
281 password = self._get_admin_password()
281 password = self._get_admin_password()
282 if not password:
282 if not password:
283 # second try
283 # second try
284 password = self._get_admin_password()
284 password = self._get_admin_password()
285 if not password:
285 if not password:
286 sys.exit()
286 sys.exit()
287 if email is None:
287 if email is None:
288 email = input('Specify admin email:')
288 email = input('Specify admin email:')
289 api_key = self.cli_args.get('api_key')
289 api_key = self.cli_args.get('api_key')
290 self.create_user(username, password, email, True,
290 self.create_user(username, password, email, True,
291 strict_creation_check=False,
291 strict_creation_check=False,
292 api_key=api_key)
292 api_key=api_key)
293
293
294 def _get_admin_password(self):
294 def _get_admin_password(self):
295 password = getpass.getpass('Specify admin password '
295 password = getpass.getpass('Specify admin password '
296 '(min 6 chars):')
296 '(min 6 chars):')
297 confirm = getpass.getpass('Confirm password:')
297 confirm = getpass.getpass('Confirm password:')
298
298
299 if password != confirm:
299 if password != confirm:
300 log.error('passwords mismatch')
300 log.error('passwords mismatch')
301 return False
301 return False
302 if len(password) < 6:
302 if len(password) < 6:
303 log.error('password is too short - use at least 6 characters')
303 log.error('password is too short - use at least 6 characters')
304 return False
304 return False
305
305
306 return password
306 return password
307
307
308 def create_test_admin_and_users(self):
308 def create_test_admin_and_users(self):
309 log.info('creating admin and regular test users')
309 log.info('creating admin and regular test users')
310 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
310 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
311 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
311 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
312 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
312 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
313 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
313 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
314 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
314 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
315
315
316 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
316 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
317 TEST_USER_ADMIN_EMAIL, True, api_key=True)
317 TEST_USER_ADMIN_EMAIL, True, api_key=True)
318
318
319 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
319 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
320 TEST_USER_REGULAR_EMAIL, False, api_key=True)
320 TEST_USER_REGULAR_EMAIL, False, api_key=True)
321
321
322 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
322 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
323 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
323 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
324
324
325 def create_ui_settings(self, repo_store_path):
325 def create_ui_settings(self, repo_store_path):
326 """
326 """
327 Creates ui settings, fills out hooks
327 Creates ui settings, fills out hooks
328 and disables dotencode
328 and disables dotencode
329 """
329 """
330 settings_model = SettingsModel(sa=self.sa)
330 settings_model = SettingsModel(sa=self.sa)
331 from rhodecode.lib.vcs.backends.hg import largefiles_store
331 from rhodecode.lib.vcs.backends.hg import largefiles_store
332 from rhodecode.lib.vcs.backends.git import lfs_store
332 from rhodecode.lib.vcs.backends.git import lfs_store
333
333
334 # Build HOOKS
334 # Build HOOKS
335 hooks = [
335 hooks = [
336 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
336 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
337
337
338 # HG
338 # HG
339 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
339 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
340 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
340 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
341 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
341 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
342 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
342 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
343 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
343 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
344 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
344 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
345
345
346 ]
346 ]
347
347
348 for key, value in hooks:
348 for key, value in hooks:
349 hook_obj = settings_model.get_ui_by_key(key)
349 hook_obj = settings_model.get_ui_by_key(key)
350 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
350 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
351 hooks2.ui_section = 'hooks'
351 hooks2.ui_section = 'hooks'
352 hooks2.ui_key = key
352 hooks2.ui_key = key
353 hooks2.ui_value = value
353 hooks2.ui_value = value
354 self.sa.add(hooks2)
354 self.sa.add(hooks2)
355
355
356 # enable largefiles
356 # enable largefiles
357 largefiles = RhodeCodeUi()
357 largefiles = RhodeCodeUi()
358 largefiles.ui_section = 'extensions'
358 largefiles.ui_section = 'extensions'
359 largefiles.ui_key = 'largefiles'
359 largefiles.ui_key = 'largefiles'
360 largefiles.ui_value = ''
360 largefiles.ui_value = ''
361 self.sa.add(largefiles)
361 self.sa.add(largefiles)
362
362
363 # set default largefiles cache dir, defaults to
363 # set default largefiles cache dir, defaults to
364 # /repo_store_location/.cache/largefiles
364 # /repo_store_location/.cache/largefiles
365 largefiles = RhodeCodeUi()
365 largefiles = RhodeCodeUi()
366 largefiles.ui_section = 'largefiles'
366 largefiles.ui_section = 'largefiles'
367 largefiles.ui_key = 'usercache'
367 largefiles.ui_key = 'usercache'
368 largefiles.ui_value = largefiles_store(repo_store_path)
368 largefiles.ui_value = largefiles_store(repo_store_path)
369
369
370 self.sa.add(largefiles)
370 self.sa.add(largefiles)
371
371
372 # set default lfs cache dir, defaults to
372 # set default lfs cache dir, defaults to
373 # /repo_store_location/.cache/lfs_store
373 # /repo_store_location/.cache/lfs_store
374 lfsstore = RhodeCodeUi()
374 lfsstore = RhodeCodeUi()
375 lfsstore.ui_section = 'vcs_git_lfs'
375 lfsstore.ui_section = 'vcs_git_lfs'
376 lfsstore.ui_key = 'store_location'
376 lfsstore.ui_key = 'store_location'
377 lfsstore.ui_value = lfs_store(repo_store_path)
377 lfsstore.ui_value = lfs_store(repo_store_path)
378
378
379 self.sa.add(lfsstore)
379 self.sa.add(lfsstore)
380
380
381 # enable hgevolve disabled by default
381 # enable hgevolve disabled by default
382 hgevolve = RhodeCodeUi()
382 hgevolve = RhodeCodeUi()
383 hgevolve.ui_section = 'extensions'
383 hgevolve.ui_section = 'extensions'
384 hgevolve.ui_key = 'evolve'
384 hgevolve.ui_key = 'evolve'
385 hgevolve.ui_value = ''
385 hgevolve.ui_value = ''
386 hgevolve.ui_active = False
386 hgevolve.ui_active = False
387 self.sa.add(hgevolve)
387 self.sa.add(hgevolve)
388
388
389 hgevolve = RhodeCodeUi()
389 hgevolve = RhodeCodeUi()
390 hgevolve.ui_section = 'experimental'
390 hgevolve.ui_section = 'experimental'
391 hgevolve.ui_key = 'evolution'
391 hgevolve.ui_key = 'evolution'
392 hgevolve.ui_value = ''
392 hgevolve.ui_value = ''
393 hgevolve.ui_active = False
393 hgevolve.ui_active = False
394 self.sa.add(hgevolve)
394 self.sa.add(hgevolve)
395
395
396 hgevolve = RhodeCodeUi()
396 hgevolve = RhodeCodeUi()
397 hgevolve.ui_section = 'experimental'
397 hgevolve.ui_section = 'experimental'
398 hgevolve.ui_key = 'evolution.exchange'
398 hgevolve.ui_key = 'evolution.exchange'
399 hgevolve.ui_value = ''
399 hgevolve.ui_value = ''
400 hgevolve.ui_active = False
400 hgevolve.ui_active = False
401 self.sa.add(hgevolve)
401 self.sa.add(hgevolve)
402
402
403 hgevolve = RhodeCodeUi()
403 hgevolve = RhodeCodeUi()
404 hgevolve.ui_section = 'extensions'
404 hgevolve.ui_section = 'extensions'
405 hgevolve.ui_key = 'topic'
405 hgevolve.ui_key = 'topic'
406 hgevolve.ui_value = ''
406 hgevolve.ui_value = ''
407 hgevolve.ui_active = False
407 hgevolve.ui_active = False
408 self.sa.add(hgevolve)
408 self.sa.add(hgevolve)
409
409
410 # enable hggit disabled by default
410 # enable hggit disabled by default
411 hggit = RhodeCodeUi()
411 hggit = RhodeCodeUi()
412 hggit.ui_section = 'extensions'
412 hggit.ui_section = 'extensions'
413 hggit.ui_key = 'hggit'
413 hggit.ui_key = 'hggit'
414 hggit.ui_value = ''
414 hggit.ui_value = ''
415 hggit.ui_active = False
415 hggit.ui_active = False
416 self.sa.add(hggit)
416 self.sa.add(hggit)
417
417
418 # set svn branch defaults
418 # set svn branch defaults
419 branches = ["/branches/*", "/trunk"]
419 branches = ["/branches/*", "/trunk"]
420 tags = ["/tags/*"]
420 tags = ["/tags/*"]
421
421
422 for branch in branches:
422 for branch in branches:
423 settings_model.create_ui_section_value(
423 settings_model.create_ui_section_value(
424 RhodeCodeUi.SVN_BRANCH_ID, branch)
424 RhodeCodeUi.SVN_BRANCH_ID, branch)
425
425
426 for tag in tags:
426 for tag in tags:
427 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
427 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
428
428
429 def create_auth_plugin_options(self, skip_existing=False):
429 def create_auth_plugin_options(self, skip_existing=False):
430 """
430 """
431 Create default auth plugin settings, and make it active
431 Create default auth plugin settings, and make it active
432
432
433 :param skip_existing:
433 :param skip_existing:
434 """
434 """
435 defaults = [
435 defaults = [
436 ('auth_plugins',
436 ('auth_plugins',
437 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
437 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
438 'list'),
438 'list'),
439
439
440 ('auth_authtoken_enabled',
440 ('auth_authtoken_enabled',
441 'True',
441 'True',
442 'bool'),
442 'bool'),
443
443
444 ('auth_rhodecode_enabled',
444 ('auth_rhodecode_enabled',
445 'True',
445 'True',
446 'bool'),
446 'bool'),
447 ]
447 ]
448 for k, v, t in defaults:
448 for k, v, t in defaults:
449 if (skip_existing and
449 if (skip_existing and
450 SettingsModel().get_setting_by_name(k) is not None):
450 SettingsModel().get_setting_by_name(k) is not None):
451 log.debug('Skipping option %s', k)
451 log.debug('Skipping option %s', k)
452 continue
452 continue
453 setting = RhodeCodeSetting(k, v, t)
453 setting = RhodeCodeSetting(k, v, t)
454 self.sa.add(setting)
454 self.sa.add(setting)
455
455
456 def create_default_options(self, skip_existing=False):
456 def create_default_options(self, skip_existing=False):
457 """Creates default settings"""
457 """Creates default settings"""
458
458
459 for k, v, t in [
459 for k, v, t in [
460 ('default_repo_enable_locking', False, 'bool'),
460 ('default_repo_enable_locking', False, 'bool'),
461 ('default_repo_enable_downloads', False, 'bool'),
461 ('default_repo_enable_downloads', False, 'bool'),
462 ('default_repo_enable_statistics', False, 'bool'),
462 ('default_repo_enable_statistics', False, 'bool'),
463 ('default_repo_private', False, 'bool'),
463 ('default_repo_private', False, 'bool'),
464 ('default_repo_type', 'hg', 'unicode')]:
464 ('default_repo_type', 'hg', 'unicode')]:
465
465
466 if (skip_existing and
466 if (skip_existing and
467 SettingsModel().get_setting_by_name(k) is not None):
467 SettingsModel().get_setting_by_name(k) is not None):
468 log.debug('Skipping option %s', k)
468 log.debug('Skipping option %s', k)
469 continue
469 continue
470 setting = RhodeCodeSetting(k, v, t)
470 setting = RhodeCodeSetting(k, v, t)
471 self.sa.add(setting)
471 self.sa.add(setting)
472
472
473 def fixup_groups(self):
473 def fixup_groups(self):
474 def_usr = User.get_default_user()
474 def_usr = User.get_default_user()
475 for g in RepoGroup.query().all():
475 for g in RepoGroup.query().all():
476 g.group_name = g.get_new_name(g.name)
476 g.group_name = g.get_new_name(g.name)
477 self.sa.add(g)
477 self.sa.add(g)
478 # get default perm
478 # get default perm
479 default = UserRepoGroupToPerm.query()\
479 default = UserRepoGroupToPerm.query()\
480 .filter(UserRepoGroupToPerm.group == g)\
480 .filter(UserRepoGroupToPerm.group == g)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
482 .scalar()
482 .scalar()
483
483
484 if default is None:
484 if default is None:
485 log.debug('missing default permission for group %s adding', g)
485 log.debug('missing default permission for group %s adding', g)
486 perm_obj = RepoGroupModel()._create_default_perms(g)
486 perm_obj = RepoGroupModel()._create_default_perms(g)
487 self.sa.add(perm_obj)
487 self.sa.add(perm_obj)
488
488
489 def reset_permissions(self, username):
489 def reset_permissions(self, username):
490 """
490 """
491 Resets permissions to default state, useful when old systems had
491 Resets permissions to default state, useful when old systems had
492 bad permissions, we must clean them up
492 bad permissions, we must clean them up
493
493
494 :param username:
494 :param username:
495 """
495 """
496 default_user = User.get_by_username(username)
496 default_user = User.get_by_username(username)
497 if not default_user:
497 if not default_user:
498 return
498 return
499
499
500 u2p = UserToPerm.query()\
500 u2p = UserToPerm.query()\
501 .filter(UserToPerm.user == default_user).all()
501 .filter(UserToPerm.user == default_user).all()
502 fixed = False
502 fixed = False
503 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
503 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
504 for p in u2p:
504 for p in u2p:
505 Session().delete(p)
505 Session().delete(p)
506 fixed = True
506 fixed = True
507 self.populate_default_permissions()
507 self.populate_default_permissions()
508 return fixed
508 return fixed
509
509
510 def config_prompt(self, test_repo_path='', retries=3):
510 def config_prompt(self, test_repo_path='', retries=3):
511 defaults = self.cli_args
511 defaults = self.cli_args
512 _path = defaults.get('repos_location')
512 _path = defaults.get('repos_location')
513 if retries == 3:
513 if retries == 3:
514 log.info('Setting up repositories config')
514 log.info('Setting up repositories config')
515
515
516 if _path is not None:
516 if _path is not None:
517 path = _path
517 path = _path
518 elif not self.tests and not test_repo_path:
518 elif not self.tests and not test_repo_path:
519 path = input(
519 path = input(
520 'Enter a valid absolute path to store repositories. '
520 'Enter a valid absolute path to store repositories. '
521 'All repositories in that path will be added automatically:'
521 'All repositories in that path will be added automatically:'
522 )
522 )
523 else:
523 else:
524 path = test_repo_path
524 path = test_repo_path
525 path_ok = True
525 path_ok = True
526
526
527 # check proper dir
527 # check proper dir
528 if not os.path.isdir(path):
528 if not os.path.isdir(path):
529 path_ok = False
529 path_ok = False
530 log.error('Given path %s is not a valid directory', path)
530 log.error('Given path %s is not a valid directory', path)
531
531
532 elif not os.path.isabs(path):
532 elif not os.path.isabs(path):
533 path_ok = False
533 path_ok = False
534 log.error('Given path %s is not an absolute path', path)
534 log.error('Given path %s is not an absolute path', path)
535
535
536 # check if path is at least readable.
536 # check if path is at least readable.
537 if not os.access(path, os.R_OK):
537 if not os.access(path, os.R_OK):
538 path_ok = False
538 path_ok = False
539 log.error('Given path %s is not readable', path)
539 log.error('Given path %s is not readable', path)
540
540
541 # check write access, warn user about non writeable paths
541 # check write access, warn user about non writeable paths
542 elif not os.access(path, os.W_OK) and path_ok:
542 elif not os.access(path, os.W_OK) and path_ok:
543 log.warning('No write permission to given path %s', path)
543 log.warning('No write permission to given path %s', path)
544
544
545 q = (f'Given path {path} is not writeable, do you want to '
545 q = (f'Given path {path} is not writeable, do you want to '
546 f'continue with read only mode ? [y/n]')
546 f'continue with read only mode ? [y/n]')
547 if not self.ask_ok(q):
547 if not self.ask_ok(q):
548 log.error('Canceled by user')
548 log.error('Canceled by user')
549 sys.exit(-1)
549 sys.exit(-1)
550
550
551 if retries == 0:
551 if retries == 0:
552 sys.exit('max retries reached')
552 sys.exit('max retries reached')
553 if not path_ok:
553 if not path_ok:
554 retries -= 1
554 retries -= 1
555 return self.config_prompt(test_repo_path, retries)
555 return self.config_prompt(test_repo_path, retries)
556
556
557 real_path = os.path.normpath(os.path.realpath(path))
557 real_path = os.path.normpath(os.path.realpath(path))
558
558
559 if real_path != os.path.normpath(path):
559 if real_path != os.path.normpath(path):
560 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
560 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
561 f'given path as {real_path} ? [y/n]')
561 f'given path as {real_path} ? [y/n]')
562 if not self.ask_ok(q):
562 if not self.ask_ok(q):
563 log.error('Canceled by user')
563 log.error('Canceled by user')
564 sys.exit(-1)
564 sys.exit(-1)
565
565
566 return real_path
566 return real_path
567
567
568 def create_settings(self, path):
568 def create_settings(self, path):
569
569
570 self.create_ui_settings(path)
570 self.create_ui_settings(path)
571
571
572 ui_config = [
572 ui_config = [
573 ('web', 'push_ssl', 'False'),
574 ('web', 'allow_archive', 'gz zip bz2'),
573 ('web', 'allow_archive', 'gz zip bz2'),
575 ('web', 'allow_push', '*'),
574 ('web', 'allow_push', '*'),
576 ('web', 'baseurl', '/'),
575 ('web', 'baseurl', '/'),
577 ('paths', '/', path),
576 ('paths', '/', path),
578 ('phases', 'publish', 'True')
577 ('phases', 'publish', 'True')
579 ]
578 ]
580 for section, key, value in ui_config:
579 for section, key, value in ui_config:
581 ui_conf = RhodeCodeUi()
580 ui_conf = RhodeCodeUi()
582 setattr(ui_conf, 'ui_section', section)
581 setattr(ui_conf, 'ui_section', section)
583 setattr(ui_conf, 'ui_key', key)
582 setattr(ui_conf, 'ui_key', key)
584 setattr(ui_conf, 'ui_value', value)
583 setattr(ui_conf, 'ui_value', value)
585 self.sa.add(ui_conf)
584 self.sa.add(ui_conf)
586
585
587 # rhodecode app settings
586 # rhodecode app settings
588 settings = [
587 settings = [
589 ('realm', 'RhodeCode', 'unicode'),
588 ('realm', 'RhodeCode', 'unicode'),
590 ('title', '', 'unicode'),
589 ('title', '', 'unicode'),
591 ('pre_code', '', 'unicode'),
590 ('pre_code', '', 'unicode'),
592 ('post_code', '', 'unicode'),
591 ('post_code', '', 'unicode'),
593
592
594 # Visual
593 # Visual
595 ('show_public_icon', True, 'bool'),
594 ('show_public_icon', True, 'bool'),
596 ('show_private_icon', True, 'bool'),
595 ('show_private_icon', True, 'bool'),
597 ('stylify_metatags', True, 'bool'),
596 ('stylify_metatags', True, 'bool'),
598 ('dashboard_items', 100, 'int'),
597 ('dashboard_items', 100, 'int'),
599 ('admin_grid_items', 25, 'int'),
598 ('admin_grid_items', 25, 'int'),
600
599
601 ('markup_renderer', 'markdown', 'unicode'),
600 ('markup_renderer', 'markdown', 'unicode'),
602
601
603 ('repository_fields', True, 'bool'),
602 ('repository_fields', True, 'bool'),
604 ('show_version', True, 'bool'),
603 ('show_version', True, 'bool'),
605 ('show_revision_number', True, 'bool'),
604 ('show_revision_number', True, 'bool'),
606 ('show_sha_length', 12, 'int'),
605 ('show_sha_length', 12, 'int'),
607
606
608 ('use_gravatar', False, 'bool'),
607 ('use_gravatar', False, 'bool'),
609 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
608 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
610
609
611 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
610 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
612 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
611 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
613 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
612 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
614 ('support_url', '', 'unicode'),
613 ('support_url', '', 'unicode'),
615 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
614 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
616
615
617 # VCS Settings
616 # VCS Settings
618 ('pr_merge_enabled', True, 'bool'),
617 ('pr_merge_enabled', True, 'bool'),
619 ('use_outdated_comments', True, 'bool'),
618 ('use_outdated_comments', True, 'bool'),
620 ('diff_cache', True, 'bool'),
619 ('diff_cache', True, 'bool'),
621 ]
620 ]
622
621
623 for key, val, type_ in settings:
622 for key, val, type_ in settings:
624 sett = RhodeCodeSetting(key, val, type_)
623 sett = RhodeCodeSetting(key, val, type_)
625 self.sa.add(sett)
624 self.sa.add(sett)
626
625
627 self.create_auth_plugin_options()
626 self.create_auth_plugin_options()
628 self.create_default_options()
627 self.create_default_options()
629
628
630 log.info('created ui config')
629 log.info('created ui config')
631
630
632 def create_user(self, username, password, email='', admin=False,
631 def create_user(self, username, password, email='', admin=False,
633 strict_creation_check=True, api_key=None):
632 strict_creation_check=True, api_key=None):
634 log.info('creating user `%s`', username)
633 log.info('creating user `%s`', username)
635 user = UserModel().create_or_update(
634 user = UserModel().create_or_update(
636 username, password, email, firstname='RhodeCode', lastname='Admin',
635 username, password, email, firstname='RhodeCode', lastname='Admin',
637 active=True, admin=admin, extern_type="rhodecode",
636 active=True, admin=admin, extern_type="rhodecode",
638 strict_creation_check=strict_creation_check)
637 strict_creation_check=strict_creation_check)
639
638
640 if api_key:
639 if api_key:
641 log.info('setting a new default auth token for user `%s`', username)
640 log.info('setting a new default auth token for user `%s`', username)
642 UserModel().add_auth_token(
641 UserModel().add_auth_token(
643 user=user, lifetime_minutes=-1,
642 user=user, lifetime_minutes=-1,
644 role=UserModel.auth_token_role.ROLE_ALL,
643 role=UserModel.auth_token_role.ROLE_ALL,
645 description='BUILTIN TOKEN')
644 description='BUILTIN TOKEN')
646
645
647 def create_default_user(self):
646 def create_default_user(self):
648 log.info('creating default user')
647 log.info('creating default user')
649 # create default user for handling default permissions.
648 # create default user for handling default permissions.
650 user = UserModel().create_or_update(username=User.DEFAULT_USER,
649 user = UserModel().create_or_update(username=User.DEFAULT_USER,
651 password=str(uuid.uuid1())[:20],
650 password=str(uuid.uuid1())[:20],
652 email=User.DEFAULT_USER_EMAIL,
651 email=User.DEFAULT_USER_EMAIL,
653 firstname='Anonymous',
652 firstname='Anonymous',
654 lastname='User',
653 lastname='User',
655 strict_creation_check=False)
654 strict_creation_check=False)
656 # based on configuration options activate/de-activate this user which
655 # based on configuration options activate/de-activate this user which
657 # controls anonymous access
656 # controls anonymous access
658 if self.cli_args.get('public_access') is False:
657 if self.cli_args.get('public_access') is False:
659 log.info('Public access disabled')
658 log.info('Public access disabled')
660 user.active = False
659 user.active = False
661 Session().add(user)
660 Session().add(user)
662 Session().commit()
661 Session().commit()
663
662
664 def create_permissions(self):
663 def create_permissions(self):
665 """
664 """
666 Creates all permissions defined in the system
665 Creates all permissions defined in the system
667 """
666 """
668 # module.(access|create|change|delete)_[name]
667 # module.(access|create|change|delete)_[name]
669 # module.(none|read|write|admin)
668 # module.(none|read|write|admin)
670 log.info('creating permissions')
669 log.info('creating permissions')
671 PermissionModel(self.sa).create_permissions()
670 PermissionModel(self.sa).create_permissions()
672
671
673 def populate_default_permissions(self):
672 def populate_default_permissions(self):
674 """
673 """
675 Populate default permissions. It will create only the default
674 Populate default permissions. It will create only the default
676 permissions that are missing, and not alter already defined ones
675 permissions that are missing, and not alter already defined ones
677 """
676 """
678 log.info('creating default user permissions')
677 log.info('creating default user permissions')
679 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
678 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,683 +1,662 b''
1
1
2
2
3 # Copyright (C) 2014-2023 RhodeCode GmbH
3 # Copyright (C) 2014-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import importlib
29 import importlib
30 from functools import wraps
30 from functools import wraps
31
31
32 import time
32 import time
33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34
34
35 from pyramid.httpexceptions import (
35 from pyramid.httpexceptions import (
36 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
36 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
37 from zope.cachedescriptors.property import Lazy as LazyProperty
37 from zope.cachedescriptors.property import Lazy as LazyProperty
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
40 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
41 from rhodecode.lib import rc_cache
41 from rhodecode.lib import rc_cache
42 from rhodecode.lib.svn_txn_utils import store_txn_id_data
42 from rhodecode.lib.svn_txn_utils import store_txn_id_data
43 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
43 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.base import (
44 from rhodecode.lib.base import (
45 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
45 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
46 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
47 from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
48 from rhodecode.lib.middleware import appenlight
48 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware.utils import scm_app_http
49 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.str_utils import safe_bytes, safe_int
50 from rhodecode.lib.str_utils import safe_bytes, safe_int
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
54 from rhodecode.lib.vcs.backends import base
54 from rhodecode.lib.vcs.backends import base
55
55
56 from rhodecode.model import meta
56 from rhodecode.model import meta
57 from rhodecode.model.db import User, Repository, PullRequest
57 from rhodecode.model.db import User, Repository, PullRequest
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def initialize_generator(factory):
65 def initialize_generator(factory):
66 """
66 """
67 Initializes the returned generator by draining its first element.
67 Initializes the returned generator by draining its first element.
68
68
69 This can be used to give a generator an initializer, which is the code
69 This can be used to give a generator an initializer, which is the code
70 up to the first yield statement. This decorator enforces that the first
70 up to the first yield statement. This decorator enforces that the first
71 produced element has the value ``"__init__"`` to make its special
71 produced element has the value ``"__init__"`` to make its special
72 purpose very explicit in the using code.
72 purpose very explicit in the using code.
73 """
73 """
74
74
75 @wraps(factory)
75 @wraps(factory)
76 def wrapper(*args, **kwargs):
76 def wrapper(*args, **kwargs):
77 gen = factory(*args, **kwargs)
77 gen = factory(*args, **kwargs)
78 try:
78 try:
79 init = next(gen)
79 init = next(gen)
80 except StopIteration:
80 except StopIteration:
81 raise ValueError('Generator must yield at least one element.')
81 raise ValueError('Generator must yield at least one element.')
82 if init != "__init__":
82 if init != "__init__":
83 raise ValueError('First yielded element must be "__init__".')
83 raise ValueError('First yielded element must be "__init__".')
84 return gen
84 return gen
85 return wrapper
85 return wrapper
86
86
87
87
88 class SimpleVCS(object):
88 class SimpleVCS(object):
89 """Common functionality for SCM HTTP handlers."""
89 """Common functionality for SCM HTTP handlers."""
90
90
91 SCM = 'unknown'
91 SCM = 'unknown'
92
92
93 acl_repo_name = None
93 acl_repo_name = None
94 url_repo_name = None
94 url_repo_name = None
95 vcs_repo_name = None
95 vcs_repo_name = None
96 rc_extras = {}
96 rc_extras = {}
97
97
98 # We have to handle requests to shadow repositories different than requests
98 # We have to handle requests to shadow repositories different than requests
99 # to normal repositories. Therefore we have to distinguish them. To do this
99 # to normal repositories. Therefore we have to distinguish them. To do this
100 # we use this regex which will match only on URLs pointing to shadow
100 # we use this regex which will match only on URLs pointing to shadow
101 # repositories.
101 # repositories.
102 shadow_repo_re = re.compile(
102 shadow_repo_re = re.compile(
103 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
103 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
104 '(?P<target>{slug_pat})/' # target repo
104 '(?P<target>{slug_pat})/' # target repo
105 'pull-request/(?P<pr_id>\\d+)/' # pull request
105 'pull-request/(?P<pr_id>\\d+)/' # pull request
106 'repository$' # shadow repo
106 'repository$' # shadow repo
107 .format(slug_pat=SLUG_RE.pattern))
107 .format(slug_pat=SLUG_RE.pattern))
108
108
109 def __init__(self, config, registry):
109 def __init__(self, config, registry):
110 self.registry = registry
110 self.registry = registry
111 self.config = config
111 self.config = config
112 # re-populated by specialized middleware
112 # re-populated by specialized middleware
113 self.repo_vcs_config = base.Config()
113 self.repo_vcs_config = base.Config()
114
114
115 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
115 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
116 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
116 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
117
117
118 # authenticate this VCS request using authfunc
118 # authenticate this VCS request using authfunc
119 auth_ret_code_detection = \
119 auth_ret_code_detection = \
120 str2bool(self.config.get('auth_ret_code_detection', False))
120 str2bool(self.config.get('auth_ret_code_detection', False))
121 self.authenticate = BasicAuth(
121 self.authenticate = BasicAuth(
122 '', authenticate, registry, config.get('auth_ret_code'),
122 '', authenticate, registry, config.get('auth_ret_code'),
123 auth_ret_code_detection, rc_realm=realm)
123 auth_ret_code_detection, rc_realm=realm)
124 self.ip_addr = '0.0.0.0'
124 self.ip_addr = '0.0.0.0'
125
125
126 @LazyProperty
126 @LazyProperty
127 def global_vcs_config(self):
127 def global_vcs_config(self):
128 try:
128 try:
129 return VcsSettingsModel().get_ui_settings_as_config_obj()
129 return VcsSettingsModel().get_ui_settings_as_config_obj()
130 except Exception:
130 except Exception:
131 return base.Config()
131 return base.Config()
132
132
133 @property
133 @property
134 def base_path(self):
134 def base_path(self):
135 settings_path = self.config.get('repo_store.path')
135 settings_path = self.config.get('repo_store.path')
136
136
137 if not settings_path:
137 if not settings_path:
138 raise ValueError('FATAL: repo_store.path is empty')
138 raise ValueError('FATAL: repo_store.path is empty')
139 return settings_path
139 return settings_path
140
140
141 def set_repo_names(self, environ):
141 def set_repo_names(self, environ):
142 """
142 """
143 This will populate the attributes acl_repo_name, url_repo_name,
143 This will populate the attributes acl_repo_name, url_repo_name,
144 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
144 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
145 shadow) repositories all names are equal. In case of requests to a
145 shadow) repositories all names are equal. In case of requests to a
146 shadow repository the acl-name points to the target repo of the pull
146 shadow repository the acl-name points to the target repo of the pull
147 request and the vcs-name points to the shadow repo file system path.
147 request and the vcs-name points to the shadow repo file system path.
148 The url-name is always the URL used by the vcs client program.
148 The url-name is always the URL used by the vcs client program.
149
149
150 Example in case of a shadow repo:
150 Example in case of a shadow repo:
151 acl_repo_name = RepoGroup/MyRepo
151 acl_repo_name = RepoGroup/MyRepo
152 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
152 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
153 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
153 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
154 """
154 """
155 # First we set the repo name from URL for all attributes. This is the
155 # First we set the repo name from URL for all attributes. This is the
156 # default if handling normal (non shadow) repo requests.
156 # default if handling normal (non shadow) repo requests.
157 self.url_repo_name = self._get_repository_name(environ)
157 self.url_repo_name = self._get_repository_name(environ)
158 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
158 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
159 self.is_shadow_repo = False
159 self.is_shadow_repo = False
160
160
161 # Check if this is a request to a shadow repository.
161 # Check if this is a request to a shadow repository.
162 match = self.shadow_repo_re.match(self.url_repo_name)
162 match = self.shadow_repo_re.match(self.url_repo_name)
163 if match:
163 if match:
164 match_dict = match.groupdict()
164 match_dict = match.groupdict()
165
165
166 # Build acl repo name from regex match.
166 # Build acl repo name from regex match.
167 acl_repo_name = safe_str('{groups}{target}'.format(
167 acl_repo_name = safe_str('{groups}{target}'.format(
168 groups=match_dict['groups'] or '',
168 groups=match_dict['groups'] or '',
169 target=match_dict['target']))
169 target=match_dict['target']))
170
170
171 # Retrieve pull request instance by ID from regex match.
171 # Retrieve pull request instance by ID from regex match.
172 pull_request = PullRequest.get(match_dict['pr_id'])
172 pull_request = PullRequest.get(match_dict['pr_id'])
173
173
174 # Only proceed if we got a pull request and if acl repo name from
174 # Only proceed if we got a pull request and if acl repo name from
175 # URL equals the target repo name of the pull request.
175 # URL equals the target repo name of the pull request.
176 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
176 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
177
177
178 # Get file system path to shadow repository.
178 # Get file system path to shadow repository.
179 workspace_id = PullRequestModel()._workspace_id(pull_request)
179 workspace_id = PullRequestModel()._workspace_id(pull_request)
180 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
180 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
181
181
182 # Store names for later usage.
182 # Store names for later usage.
183 self.vcs_repo_name = vcs_repo_name
183 self.vcs_repo_name = vcs_repo_name
184 self.acl_repo_name = acl_repo_name
184 self.acl_repo_name = acl_repo_name
185 self.is_shadow_repo = True
185 self.is_shadow_repo = True
186
186
187 log.debug('Setting all VCS repository names: %s', {
187 log.debug('Setting all VCS repository names: %s', {
188 'acl_repo_name': self.acl_repo_name,
188 'acl_repo_name': self.acl_repo_name,
189 'url_repo_name': self.url_repo_name,
189 'url_repo_name': self.url_repo_name,
190 'vcs_repo_name': self.vcs_repo_name,
190 'vcs_repo_name': self.vcs_repo_name,
191 })
191 })
192
192
193 @property
193 @property
194 def scm_app(self):
194 def scm_app(self):
195 custom_implementation = self.config['vcs.scm_app_implementation']
195 custom_implementation = self.config['vcs.scm_app_implementation']
196 if custom_implementation == 'http':
196 if custom_implementation == 'http':
197 log.debug('Using HTTP implementation of scm app.')
197 log.debug('Using HTTP implementation of scm app.')
198 scm_app_impl = scm_app_http
198 scm_app_impl = scm_app_http
199 else:
199 else:
200 log.debug('Using custom implementation of scm_app: "{}"'.format(
200 log.debug('Using custom implementation of scm_app: "{}"'.format(
201 custom_implementation))
201 custom_implementation))
202 scm_app_impl = importlib.import_module(custom_implementation)
202 scm_app_impl = importlib.import_module(custom_implementation)
203 return scm_app_impl
203 return scm_app_impl
204
204
205 def _get_by_id(self, repo_name):
205 def _get_by_id(self, repo_name):
206 """
206 """
207 Gets a special pattern _<ID> from clone url and tries to replace it
207 Gets a special pattern _<ID> from clone url and tries to replace it
208 with a repository_name for support of _<ID> non changeable urls
208 with a repository_name for support of _<ID> non changeable urls
209 """
209 """
210
210
211 data = repo_name.split('/')
211 data = repo_name.split('/')
212 if len(data) >= 2:
212 if len(data) >= 2:
213 from rhodecode.model.repo import RepoModel
213 from rhodecode.model.repo import RepoModel
214 by_id_match = RepoModel().get_repo_by_id(repo_name)
214 by_id_match = RepoModel().get_repo_by_id(repo_name)
215 if by_id_match:
215 if by_id_match:
216 data[1] = by_id_match.repo_name
216 data[1] = by_id_match.repo_name
217
217
218 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
218 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
219 # and we use this data
219 # and we use this data
220 maybe_new_path = '/'.join(data)
220 maybe_new_path = '/'.join(data)
221 return safe_bytes(maybe_new_path).decode('latin1')
221 return safe_bytes(maybe_new_path).decode('latin1')
222
222
223 def _invalidate_cache(self, repo_name):
223 def _invalidate_cache(self, repo_name):
224 """
224 """
225 Set's cache for this repository for invalidation on next access
225 Set's cache for this repository for invalidation on next access
226
226
227 :param repo_name: full repo name, also a cache key
227 :param repo_name: full repo name, also a cache key
228 """
228 """
229 ScmModel().mark_for_invalidation(repo_name)
229 ScmModel().mark_for_invalidation(repo_name)
230
230
231 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
231 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
232 db_repo = Repository.get_by_repo_name(repo_name)
232 db_repo = Repository.get_by_repo_name(repo_name)
233 if not db_repo:
233 if not db_repo:
234 log.debug('Repository `%s` not found inside the database.',
234 log.debug('Repository `%s` not found inside the database.',
235 repo_name)
235 repo_name)
236 return False
236 return False
237
237
238 if db_repo.repo_type != scm_type:
238 if db_repo.repo_type != scm_type:
239 log.warning(
239 log.warning(
240 'Repository `%s` have incorrect scm_type, expected %s got %s',
240 'Repository `%s` have incorrect scm_type, expected %s got %s',
241 repo_name, db_repo.repo_type, scm_type)
241 repo_name, db_repo.repo_type, scm_type)
242 return False
242 return False
243
243
244 config = db_repo._config
244 config = db_repo._config
245 config.set('extensions', 'largefiles', '')
245 config.set('extensions', 'largefiles', '')
246 return is_valid_repo(
246 return is_valid_repo(
247 repo_name, base_path,
247 repo_name, base_path,
248 explicit_scm=scm_type, expect_scm=scm_type, config=config)
248 explicit_scm=scm_type, expect_scm=scm_type, config=config)
249
249
250 def valid_and_active_user(self, user):
250 def valid_and_active_user(self, user):
251 """
251 """
252 Checks if that user is not empty, and if it's actually object it checks
252 Checks if that user is not empty, and if it's actually object it checks
253 if he's active.
253 if he's active.
254
254
255 :param user: user object or None
255 :param user: user object or None
256 :return: boolean
256 :return: boolean
257 """
257 """
258 if user is None:
258 if user is None:
259 return False
259 return False
260
260
261 elif user.active:
261 elif user.active:
262 return True
262 return True
263
263
264 return False
264 return False
265
265
266 @property
266 @property
267 def is_shadow_repo_dir(self):
267 def is_shadow_repo_dir(self):
268 return os.path.isdir(self.vcs_repo_name)
268 return os.path.isdir(self.vcs_repo_name)
269
269
270 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
270 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
271 plugin_id='', plugin_cache_active=False, cache_ttl=0):
271 plugin_id='', plugin_cache_active=False, cache_ttl=0):
272 """
272 """
273 Checks permissions using action (push/pull) user and repository
273 Checks permissions using action (push/pull) user and repository
274 name. If plugin_cache and ttl is set it will use the plugin which
274 name. If plugin_cache and ttl is set it will use the plugin which
275 authenticated the user to store the cached permissions result for N
275 authenticated the user to store the cached permissions result for N
276 amount of seconds as in cache_ttl
276 amount of seconds as in cache_ttl
277
277
278 :param action: push or pull action
278 :param action: push or pull action
279 :param user: user instance
279 :param user: user instance
280 :param repo_name: repository name
280 :param repo_name: repository name
281 """
281 """
282
282
283 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
283 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
284 plugin_id, plugin_cache_active, cache_ttl)
284 plugin_id, plugin_cache_active, cache_ttl)
285
285
286 user_id = user.user_id
286 user_id = user.user_id
287 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
287 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
288 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
288 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
289
289
290 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
290 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
291 expiration_time=cache_ttl,
291 expiration_time=cache_ttl,
292 condition=plugin_cache_active)
292 condition=plugin_cache_active)
293 def compute_perm_vcs(
293 def compute_perm_vcs(
294 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
294 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
295
295
296 log.debug('auth: calculating permission access now...')
296 log.debug('auth: calculating permission access now...')
297 # check IP
297 # check IP
298 inherit = user.inherit_default_permissions
298 inherit = user.inherit_default_permissions
299 ip_allowed = AuthUser.check_ip_allowed(
299 ip_allowed = AuthUser.check_ip_allowed(
300 user_id, ip_addr, inherit_from_default=inherit)
300 user_id, ip_addr, inherit_from_default=inherit)
301 if ip_allowed:
301 if ip_allowed:
302 log.info('Access for IP:%s allowed', ip_addr)
302 log.info('Access for IP:%s allowed', ip_addr)
303 else:
303 else:
304 return False
304 return False
305
305
306 if action == 'push':
306 if action == 'push':
307 perms = ('repository.write', 'repository.admin')
307 perms = ('repository.write', 'repository.admin')
308 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
308 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
309 return False
309 return False
310
310
311 else:
311 else:
312 # any other action need at least read permission
312 # any other action need at least read permission
313 perms = (
313 perms = (
314 'repository.read', 'repository.write', 'repository.admin')
314 'repository.read', 'repository.write', 'repository.admin')
315 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
315 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
316 return False
316 return False
317
317
318 return True
318 return True
319
319
320 start = time.time()
320 start = time.time()
321 log.debug('Running plugin `%s` permissions check', plugin_id)
321 log.debug('Running plugin `%s` permissions check', plugin_id)
322
322
323 # for environ based auth, password can be empty, but then the validation is
323 # for environ based auth, password can be empty, but then the validation is
324 # on the server that fills in the env data needed for authentication
324 # on the server that fills in the env data needed for authentication
325 perm_result = compute_perm_vcs(
325 perm_result = compute_perm_vcs(
326 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
326 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
327
327
328 auth_time = time.time() - start
328 auth_time = time.time() - start
329 log.debug('Permissions for plugin `%s` completed in %.4fs, '
329 log.debug('Permissions for plugin `%s` completed in %.4fs, '
330 'expiration time of fetched cache %.1fs.',
330 'expiration time of fetched cache %.1fs.',
331 plugin_id, auth_time, cache_ttl)
331 plugin_id, auth_time, cache_ttl)
332
332
333 return perm_result
333 return perm_result
334
334
335 def _get_http_scheme(self, environ):
335 def _get_http_scheme(self, environ):
336 try:
336 try:
337 return environ['wsgi.url_scheme']
337 return environ['wsgi.url_scheme']
338 except Exception:
338 except Exception:
339 log.exception('Failed to read http scheme')
339 log.exception('Failed to read http scheme')
340 return 'http'
340 return 'http'
341
341
342 def _check_ssl(self, environ, start_response):
343 """
344 Checks the SSL check flag and returns False if SSL is not present
345 and required True otherwise
346 """
347 org_proto = environ['wsgi._org_proto']
348 # check if we have SSL required ! if not it's a bad request !
349 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
350 if require_ssl and org_proto == 'http':
351 log.debug(
352 'Bad request: detected protocol is `%s` and '
353 'SSL/HTTPS is required.', org_proto)
354 return False
355 return True
356
357 def _get_default_cache_ttl(self):
342 def _get_default_cache_ttl(self):
358 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
343 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
359 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
344 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
360 plugin_settings = plugin.get_settings()
345 plugin_settings = plugin.get_settings()
361 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
346 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
362 plugin_settings) or (False, 0)
347 plugin_settings) or (False, 0)
363 return plugin_cache_active, cache_ttl
348 return plugin_cache_active, cache_ttl
364
349
365 def __call__(self, environ, start_response):
350 def __call__(self, environ, start_response):
366 try:
351 try:
367 return self._handle_request(environ, start_response)
352 return self._handle_request(environ, start_response)
368 except Exception:
353 except Exception:
369 log.exception("Exception while handling request")
354 log.exception("Exception while handling request")
370 appenlight.track_exception(environ)
355 appenlight.track_exception(environ)
371 return HTTPInternalServerError()(environ, start_response)
356 return HTTPInternalServerError()(environ, start_response)
372 finally:
357 finally:
373 meta.Session.remove()
358 meta.Session.remove()
374
359
375 def _handle_request(self, environ, start_response):
360 def _handle_request(self, environ, start_response):
376 if not self._check_ssl(environ, start_response):
377 reason = ('SSL required, while RhodeCode was unable '
378 'to detect this as SSL request')
379 log.debug('User not allowed to proceed, %s', reason)
380 return HTTPNotAcceptable(reason)(environ, start_response)
381
382 if not self.url_repo_name:
361 if not self.url_repo_name:
383 log.warning('Repository name is empty: %s', self.url_repo_name)
362 log.warning('Repository name is empty: %s', self.url_repo_name)
384 # failed to get repo name, we fail now
363 # failed to get repo name, we fail now
385 return HTTPNotFound()(environ, start_response)
364 return HTTPNotFound()(environ, start_response)
386 log.debug('Extracted repo name is %s', self.url_repo_name)
365 log.debug('Extracted repo name is %s', self.url_repo_name)
387
366
388 ip_addr = get_ip_addr(environ)
367 ip_addr = get_ip_addr(environ)
389 user_agent = get_user_agent(environ)
368 user_agent = get_user_agent(environ)
390 username = None
369 username = None
391
370
392 # skip passing error to error controller
371 # skip passing error to error controller
393 environ['pylons.status_code_redirect'] = True
372 environ['pylons.status_code_redirect'] = True
394
373
395 # ======================================================================
374 # ======================================================================
396 # GET ACTION PULL or PUSH
375 # GET ACTION PULL or PUSH
397 # ======================================================================
376 # ======================================================================
398 action = self._get_action(environ)
377 action = self._get_action(environ)
399
378
400 # ======================================================================
379 # ======================================================================
401 # Check if this is a request to a shadow repository of a pull request.
380 # Check if this is a request to a shadow repository of a pull request.
402 # In this case only pull action is allowed.
381 # In this case only pull action is allowed.
403 # ======================================================================
382 # ======================================================================
404 if self.is_shadow_repo and action != 'pull':
383 if self.is_shadow_repo and action != 'pull':
405 reason = 'Only pull action is allowed for shadow repositories.'
384 reason = 'Only pull action is allowed for shadow repositories.'
406 log.debug('User not allowed to proceed, %s', reason)
385 log.debug('User not allowed to proceed, %s', reason)
407 return HTTPNotAcceptable(reason)(environ, start_response)
386 return HTTPNotAcceptable(reason)(environ, start_response)
408
387
409 # Check if the shadow repo actually exists, in case someone refers
388 # Check if the shadow repo actually exists, in case someone refers
410 # to it, and it has been deleted because of successful merge.
389 # to it, and it has been deleted because of successful merge.
411 if self.is_shadow_repo and not self.is_shadow_repo_dir:
390 if self.is_shadow_repo and not self.is_shadow_repo_dir:
412 log.debug(
391 log.debug(
413 'Shadow repo detected, and shadow repo dir `%s` is missing',
392 'Shadow repo detected, and shadow repo dir `%s` is missing',
414 self.is_shadow_repo_dir)
393 self.is_shadow_repo_dir)
415 return HTTPNotFound()(environ, start_response)
394 return HTTPNotFound()(environ, start_response)
416
395
417 # ======================================================================
396 # ======================================================================
418 # CHECK ANONYMOUS PERMISSION
397 # CHECK ANONYMOUS PERMISSION
419 # ======================================================================
398 # ======================================================================
420 detect_force_push = False
399 detect_force_push = False
421 check_branch_perms = False
400 check_branch_perms = False
422 if action in ['pull', 'push']:
401 if action in ['pull', 'push']:
423 user_obj = anonymous_user = User.get_default_user()
402 user_obj = anonymous_user = User.get_default_user()
424 auth_user = user_obj.AuthUser()
403 auth_user = user_obj.AuthUser()
425 username = anonymous_user.username
404 username = anonymous_user.username
426 if anonymous_user.active:
405 if anonymous_user.active:
427 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
406 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
428 # ONLY check permissions if the user is activated
407 # ONLY check permissions if the user is activated
429 anonymous_perm = self._check_permission(
408 anonymous_perm = self._check_permission(
430 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
409 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
431 plugin_id='anonymous_access',
410 plugin_id='anonymous_access',
432 plugin_cache_active=plugin_cache_active,
411 plugin_cache_active=plugin_cache_active,
433 cache_ttl=cache_ttl,
412 cache_ttl=cache_ttl,
434 )
413 )
435 else:
414 else:
436 anonymous_perm = False
415 anonymous_perm = False
437
416
438 if not anonymous_user.active or not anonymous_perm:
417 if not anonymous_user.active or not anonymous_perm:
439 if not anonymous_user.active:
418 if not anonymous_user.active:
440 log.debug('Anonymous access is disabled, running '
419 log.debug('Anonymous access is disabled, running '
441 'authentication')
420 'authentication')
442
421
443 if not anonymous_perm:
422 if not anonymous_perm:
444 log.debug('Not enough credentials to access repo: `%s` '
423 log.debug('Not enough credentials to access repo: `%s` '
445 'repository as anonymous user', self.acl_repo_name)
424 'repository as anonymous user', self.acl_repo_name)
446
425
447 username = None
426 username = None
448 # ==============================================================
427 # ==============================================================
449 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
428 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
450 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
429 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
451 # ==============================================================
430 # ==============================================================
452
431
453 # try to auth based on environ, container auth methods
432 # try to auth based on environ, container auth methods
454 log.debug('Running PRE-AUTH for container|headers based authentication')
433 log.debug('Running PRE-AUTH for container|headers based authentication')
455
434
456 # headers auth, by just reading special headers and bypass the auth with user/passwd
435 # headers auth, by just reading special headers and bypass the auth with user/passwd
457 pre_auth = authenticate(
436 pre_auth = authenticate(
458 '', '', environ, VCS_TYPE, registry=self.registry,
437 '', '', environ, VCS_TYPE, registry=self.registry,
459 acl_repo_name=self.acl_repo_name)
438 acl_repo_name=self.acl_repo_name)
460
439
461 if pre_auth and pre_auth.get('username'):
440 if pre_auth and pre_auth.get('username'):
462 username = pre_auth['username']
441 username = pre_auth['username']
463 log.debug('PRE-AUTH got `%s` as username', username)
442 log.debug('PRE-AUTH got `%s` as username', username)
464 if pre_auth:
443 if pre_auth:
465 log.debug('PRE-AUTH successful from %s',
444 log.debug('PRE-AUTH successful from %s',
466 pre_auth.get('auth_data', {}).get('_plugin'))
445 pre_auth.get('auth_data', {}).get('_plugin'))
467
446
468 # If not authenticated by the container, running basic auth
447 # If not authenticated by the container, running basic auth
469 # before inject the calling repo_name for special scope checks
448 # before inject the calling repo_name for special scope checks
470 self.authenticate.acl_repo_name = self.acl_repo_name
449 self.authenticate.acl_repo_name = self.acl_repo_name
471
450
472 plugin_cache_active, cache_ttl = False, 0
451 plugin_cache_active, cache_ttl = False, 0
473 plugin = None
452 plugin = None
474
453
475 # regular auth chain
454 # regular auth chain
476 if not username:
455 if not username:
477 self.authenticate.realm = self.authenticate.get_rc_realm()
456 self.authenticate.realm = self.authenticate.get_rc_realm()
478
457
479 try:
458 try:
480 auth_result = self.authenticate(environ)
459 auth_result = self.authenticate(environ)
481 except (UserCreationError, NotAllowedToCreateUserError) as e:
460 except (UserCreationError, NotAllowedToCreateUserError) as e:
482 log.error(e)
461 log.error(e)
483 reason = safe_str(e)
462 reason = safe_str(e)
484 return HTTPNotAcceptable(reason)(environ, start_response)
463 return HTTPNotAcceptable(reason)(environ, start_response)
485
464
486 if isinstance(auth_result, dict):
465 if isinstance(auth_result, dict):
487 AUTH_TYPE.update(environ, 'basic')
466 AUTH_TYPE.update(environ, 'basic')
488 REMOTE_USER.update(environ, auth_result['username'])
467 REMOTE_USER.update(environ, auth_result['username'])
489 username = auth_result['username']
468 username = auth_result['username']
490 plugin = auth_result.get('auth_data', {}).get('_plugin')
469 plugin = auth_result.get('auth_data', {}).get('_plugin')
491 log.info(
470 log.info(
492 'MAIN-AUTH successful for user `%s` from %s plugin',
471 'MAIN-AUTH successful for user `%s` from %s plugin',
493 username, plugin)
472 username, plugin)
494
473
495 plugin_cache_active, cache_ttl = auth_result.get(
474 plugin_cache_active, cache_ttl = auth_result.get(
496 'auth_data', {}).get('_ttl_cache') or (False, 0)
475 'auth_data', {}).get('_ttl_cache') or (False, 0)
497 else:
476 else:
498 return auth_result.wsgi_application(environ, start_response)
477 return auth_result.wsgi_application(environ, start_response)
499
478
500 # ==============================================================
479 # ==============================================================
501 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
480 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
502 # ==============================================================
481 # ==============================================================
503 user = User.get_by_username(username)
482 user = User.get_by_username(username)
504 if not self.valid_and_active_user(user):
483 if not self.valid_and_active_user(user):
505 return HTTPForbidden()(environ, start_response)
484 return HTTPForbidden()(environ, start_response)
506 username = user.username
485 username = user.username
507 user_id = user.user_id
486 user_id = user.user_id
508
487
509 # check user attributes for password change flag
488 # check user attributes for password change flag
510 user_obj = user
489 user_obj = user
511 auth_user = user_obj.AuthUser()
490 auth_user = user_obj.AuthUser()
512 if user_obj and user_obj.username != User.DEFAULT_USER and \
491 if user_obj and user_obj.username != User.DEFAULT_USER and \
513 user_obj.user_data.get('force_password_change'):
492 user_obj.user_data.get('force_password_change'):
514 reason = 'password change required'
493 reason = 'password change required'
515 log.debug('User not allowed to authenticate, %s', reason)
494 log.debug('User not allowed to authenticate, %s', reason)
516 return HTTPNotAcceptable(reason)(environ, start_response)
495 return HTTPNotAcceptable(reason)(environ, start_response)
517
496
518 # check permissions for this repository
497 # check permissions for this repository
519 perm = self._check_permission(
498 perm = self._check_permission(
520 action, user, auth_user, self.acl_repo_name, ip_addr,
499 action, user, auth_user, self.acl_repo_name, ip_addr,
521 plugin, plugin_cache_active, cache_ttl)
500 plugin, plugin_cache_active, cache_ttl)
522 if not perm:
501 if not perm:
523 return HTTPForbidden()(environ, start_response)
502 return HTTPForbidden()(environ, start_response)
524 environ['rc_auth_user_id'] = str(user_id)
503 environ['rc_auth_user_id'] = str(user_id)
525
504
526 if action == 'push':
505 if action == 'push':
527 perms = auth_user.get_branch_permissions(self.acl_repo_name)
506 perms = auth_user.get_branch_permissions(self.acl_repo_name)
528 if perms:
507 if perms:
529 check_branch_perms = True
508 check_branch_perms = True
530 detect_force_push = True
509 detect_force_push = True
531
510
532 # extras are injected into UI object and later available
511 # extras are injected into UI object and later available
533 # in hooks executed by RhodeCode
512 # in hooks executed by RhodeCode
534 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
513 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
535
514
536 extras = vcs_operation_context(
515 extras = vcs_operation_context(
537 environ, repo_name=self.acl_repo_name, username=username,
516 environ, repo_name=self.acl_repo_name, username=username,
538 action=action, scm=self.SCM, check_locking=check_locking,
517 action=action, scm=self.SCM, check_locking=check_locking,
539 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
518 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
540 detect_force_push=detect_force_push
519 detect_force_push=detect_force_push
541 )
520 )
542
521
543 # ======================================================================
522 # ======================================================================
544 # REQUEST HANDLING
523 # REQUEST HANDLING
545 # ======================================================================
524 # ======================================================================
546 repo_path = os.path.join(
525 repo_path = os.path.join(
547 safe_str(self.base_path), safe_str(self.vcs_repo_name))
526 safe_str(self.base_path), safe_str(self.vcs_repo_name))
548 log.debug('Repository path is %s', repo_path)
527 log.debug('Repository path is %s', repo_path)
549
528
550 fix_PATH()
529 fix_PATH()
551
530
552 log.info(
531 log.info(
553 '%s action on %s repo "%s" by "%s" from %s %s',
532 '%s action on %s repo "%s" by "%s" from %s %s',
554 action, self.SCM, safe_str(self.url_repo_name),
533 action, self.SCM, safe_str(self.url_repo_name),
555 safe_str(username), ip_addr, user_agent)
534 safe_str(username), ip_addr, user_agent)
556
535
557 return self._generate_vcs_response(
536 return self._generate_vcs_response(
558 environ, start_response, repo_path, extras, action)
537 environ, start_response, repo_path, extras, action)
559
538
560 def _get_txn_id(self, environ):
539 def _get_txn_id(self, environ):
561
540
562 for k in ['RAW_URI', 'HTTP_DESTINATION']:
541 for k in ['RAW_URI', 'HTTP_DESTINATION']:
563 url = environ.get(k)
542 url = environ.get(k)
564 if not url:
543 if not url:
565 continue
544 continue
566
545
567 # regex to search for svn-txn-id
546 # regex to search for svn-txn-id
568 pattern = r'/!svn/txr/([^/]+)/'
547 pattern = r'/!svn/txr/([^/]+)/'
569
548
570 # Search for the pattern in the URL
549 # Search for the pattern in the URL
571 match = re.search(pattern, url)
550 match = re.search(pattern, url)
572
551
573 # Check if a match is found and extract the captured group
552 # Check if a match is found and extract the captured group
574 if match:
553 if match:
575 txn_id = match.group(1)
554 txn_id = match.group(1)
576 return txn_id
555 return txn_id
577
556
578 @initialize_generator
557 @initialize_generator
579 def _generate_vcs_response(
558 def _generate_vcs_response(
580 self, environ, start_response, repo_path, extras, action):
559 self, environ, start_response, repo_path, extras, action):
581 """
560 """
582 Returns a generator for the response content.
561 Returns a generator for the response content.
583
562
584 This method is implemented as a generator, so that it can trigger
563 This method is implemented as a generator, so that it can trigger
585 the cache validation after all content sent back to the client. It
564 the cache validation after all content sent back to the client. It
586 also handles the locking exceptions which will be triggered when
565 also handles the locking exceptions which will be triggered when
587 the first chunk is produced by the underlying WSGI application.
566 the first chunk is produced by the underlying WSGI application.
588 """
567 """
589 svn_txn_id = ''
568 svn_txn_id = ''
590 if action == 'push':
569 if action == 'push':
591 svn_txn_id = self._get_txn_id(environ)
570 svn_txn_id = self._get_txn_id(environ)
592
571
593 callback_daemon, extras = self._prepare_callback_daemon(
572 callback_daemon, extras = self._prepare_callback_daemon(
594 extras, environ, action, txn_id=svn_txn_id)
573 extras, environ, action, txn_id=svn_txn_id)
595
574
596 if svn_txn_id:
575 if svn_txn_id:
597
576
598 port = safe_int(extras['hooks_uri'].split(':')[-1])
577 port = safe_int(extras['hooks_uri'].split(':')[-1])
599 txn_id_data = extras.copy()
578 txn_id_data = extras.copy()
600 txn_id_data.update({'port': port})
579 txn_id_data.update({'port': port})
601 txn_id_data.update({'req_method': environ['REQUEST_METHOD']})
580 txn_id_data.update({'req_method': environ['REQUEST_METHOD']})
602
581
603 full_repo_path = repo_path
582 full_repo_path = repo_path
604 store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data)
583 store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data)
605
584
606 log.debug('HOOKS extras is %s', extras)
585 log.debug('HOOKS extras is %s', extras)
607
586
608 http_scheme = self._get_http_scheme(environ)
587 http_scheme = self._get_http_scheme(environ)
609
588
610 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
589 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
611 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
590 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
612 with callback_daemon:
591 with callback_daemon:
613 app.rc_extras = extras
592 app.rc_extras = extras
614
593
615 try:
594 try:
616 response = app(environ, start_response)
595 response = app(environ, start_response)
617 finally:
596 finally:
618 # This statement works together with the decorator
597 # This statement works together with the decorator
619 # "initialize_generator" above. The decorator ensures that
598 # "initialize_generator" above. The decorator ensures that
620 # we hit the first yield statement before the generator is
599 # we hit the first yield statement before the generator is
621 # returned back to the WSGI server. This is needed to
600 # returned back to the WSGI server. This is needed to
622 # ensure that the call to "app" above triggers the
601 # ensure that the call to "app" above triggers the
623 # needed callback to "start_response" before the
602 # needed callback to "start_response" before the
624 # generator is actually used.
603 # generator is actually used.
625 yield "__init__"
604 yield "__init__"
626
605
627 # iter content
606 # iter content
628 for chunk in response:
607 for chunk in response:
629 yield chunk
608 yield chunk
630
609
631 try:
610 try:
632 # invalidate cache on push
611 # invalidate cache on push
633 if action == 'push':
612 if action == 'push':
634 self._invalidate_cache(self.url_repo_name)
613 self._invalidate_cache(self.url_repo_name)
635 finally:
614 finally:
636 meta.Session.remove()
615 meta.Session.remove()
637
616
638 def _get_repository_name(self, environ):
617 def _get_repository_name(self, environ):
639 """Get repository name out of the environmnent
618 """Get repository name out of the environmnent
640
619
641 :param environ: WSGI environment
620 :param environ: WSGI environment
642 """
621 """
643 raise NotImplementedError()
622 raise NotImplementedError()
644
623
645 def _get_action(self, environ):
624 def _get_action(self, environ):
646 """Map request commands into a pull or push command.
625 """Map request commands into a pull or push command.
647
626
648 :param environ: WSGI environment
627 :param environ: WSGI environment
649 """
628 """
650 raise NotImplementedError()
629 raise NotImplementedError()
651
630
652 def _create_wsgi_app(self, repo_path, repo_name, config):
631 def _create_wsgi_app(self, repo_path, repo_name, config):
653 """Return the WSGI app that will finally handle the request."""
632 """Return the WSGI app that will finally handle the request."""
654 raise NotImplementedError()
633 raise NotImplementedError()
655
634
656 def _create_config(self, extras, repo_name, scheme='http'):
635 def _create_config(self, extras, repo_name, scheme='http'):
657 """Create a safe config representation."""
636 """Create a safe config representation."""
658 raise NotImplementedError()
637 raise NotImplementedError()
659
638
660 def _should_use_callback_daemon(self, extras, environ, action):
639 def _should_use_callback_daemon(self, extras, environ, action):
661 if extras.get('is_shadow_repo'):
640 if extras.get('is_shadow_repo'):
662 # we don't want to execute hooks, and callback daemon for shadow repos
641 # we don't want to execute hooks, and callback daemon for shadow repos
663 return False
642 return False
664 return True
643 return True
665
644
666 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
645 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
667 protocol = vcs_settings.HOOKS_PROTOCOL
646 protocol = vcs_settings.HOOKS_PROTOCOL
668
647
669 if not self._should_use_callback_daemon(extras, environ, action):
648 if not self._should_use_callback_daemon(extras, environ, action):
670 # disable callback daemon for actions that don't require it
649 # disable callback daemon for actions that don't require it
671 protocol = 'local'
650 protocol = 'local'
672
651
673 return prepare_callback_daemon(
652 return prepare_callback_daemon(
674 extras, protocol=protocol,
653 extras, protocol=protocol,
675 host=vcs_settings.HOOKS_HOST, txn_id=txn_id)
654 host=vcs_settings.HOOKS_HOST, txn_id=txn_id)
676
655
677
656
678 def _should_check_locking(query_string):
657 def _should_check_locking(query_string):
679 # this is kind of hacky, but due to how mercurial handles client-server
658 # this is kind of hacky, but due to how mercurial handles client-server
680 # server see all operation on commit; bookmarks, phases and
659 # server see all operation on commit; bookmarks, phases and
681 # obsolescence marker in different transaction, we don't want to check
660 # obsolescence marker in different transaction, we don't want to check
682 # locking on those
661 # locking on those
683 return query_string not in ['cmd=listkeys']
662 return query_string not in ['cmd=listkeys']
@@ -1,308 +1,312 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 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 gzip
20 import gzip
21 import shutil
21 import shutil
22 import logging
22 import logging
23 import tempfile
23 import tempfile
24 import urllib.parse
24 import urllib.parse
25
25
26 from webob.exc import HTTPNotFound
26 from webob.exc import HTTPNotFound
27
27
28 import rhodecode
28 import rhodecode
29 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
30 from rhodecode.lib.middleware.utils import get_path_info
30 from rhodecode.lib.middleware.utils import get_path_info
31 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
32 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
33 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplehg import SimpleHg
34 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.lib.middleware.simplesvn import SimpleSvn
35 from rhodecode.lib.str_utils import safe_str
35 from rhodecode.lib.str_utils import safe_str
36 from rhodecode.model.settings import VcsSettingsModel
36 from rhodecode.model.settings import VcsSettingsModel
37
37
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41 VCS_TYPE_KEY = '_rc_vcs_type'
41 VCS_TYPE_KEY = '_rc_vcs_type'
42 VCS_TYPE_SKIP = '_rc_vcs_skip'
42 VCS_TYPE_SKIP = '_rc_vcs_skip'
43
43
44
44
45 def is_git(environ):
45 def is_git(environ):
46 """
46 """
47 Returns True if requests should be handled by GIT wsgi middleware
47 Returns True if requests should be handled by GIT wsgi middleware
48 """
48 """
49 path_info = get_path_info(environ)
49 path_info = get_path_info(environ)
50 is_git_path = GIT_PROTO_PAT.match(path_info)
50 is_git_path = GIT_PROTO_PAT.match(path_info)
51 log.debug(
51 log.debug(
52 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
52 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
53 is_git_path is not None)
53 is_git_path is not None)
54
54
55 return is_git_path
55 return is_git_path
56
56
57
57
58 def is_hg(environ):
58 def is_hg(environ):
59 """
59 """
60 Returns True if requests target is mercurial server - header
60 Returns True if requests target is mercurial server - header
61 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
61 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
62 """
62 """
63 is_hg_path = False
63 is_hg_path = False
64
64
65 http_accept = environ.get('HTTP_ACCEPT')
65 http_accept = environ.get('HTTP_ACCEPT')
66
66
67 if http_accept and http_accept.startswith('application/mercurial'):
67 if http_accept and http_accept.startswith('application/mercurial'):
68 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
68 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
69 if 'cmd' in query:
69 if 'cmd' in query:
70 is_hg_path = True
70 is_hg_path = True
71
71
72 path_info = get_path_info(environ)
72 path_info = get_path_info(environ)
73 log.debug(
73 log.debug(
74 'request path: `%s` detected as HG PROTOCOL %s', path_info,
74 'request path: `%s` detected as HG PROTOCOL %s', path_info,
75 is_hg_path)
75 is_hg_path)
76
76
77 return is_hg_path
77 return is_hg_path
78
78
79
79
80 def is_svn(environ):
80 def is_svn(environ):
81 """
81 """
82 Returns True if requests target is Subversion server
82 Returns True if requests target is Subversion server
83 """
83 """
84
84
85 http_dav = environ.get('HTTP_DAV', '')
85 http_dav = environ.get('HTTP_DAV', '')
86 magic_path_segment = rhodecode.CONFIG.get(
86 magic_path_segment = rhodecode.CONFIG.get(
87 'rhodecode_subversion_magic_path', '/!svn')
87 'rhodecode_subversion_magic_path', '/!svn')
88 path_info = get_path_info(environ)
88 path_info = get_path_info(environ)
89 req_method = environ['REQUEST_METHOD']
89 req_method = environ['REQUEST_METHOD']
90
90
91 is_svn_path = (
91 is_svn_path = (
92 'subversion' in http_dav or
92 'subversion' in http_dav or
93 magic_path_segment in path_info
93 magic_path_segment in path_info
94 or req_method in ['PROPFIND', 'PROPPATCH', 'HEAD']
94 or req_method in ['PROPFIND', 'PROPPATCH', 'HEAD']
95 )
95 )
96 log.debug(
96 log.debug(
97 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
97 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
98 is_svn_path)
98 is_svn_path)
99
99
100 return is_svn_path
100 return is_svn_path
101
101
102
102
103 class GunzipMiddleware(object):
103 class GunzipMiddleware(object):
104 """
104 """
105 WSGI middleware that unzips gzip-encoded requests before
105 WSGI middleware that unzips gzip-encoded requests before
106 passing on to the underlying application.
106 passing on to the underlying application.
107 """
107 """
108
108
109 def __init__(self, application):
109 def __init__(self, application):
110 self.app = application
110 self.app = application
111
111
112 def __call__(self, environ, start_response):
112 def __call__(self, environ, start_response):
113 accepts_encoding_header = safe_str(environ.get('HTTP_CONTENT_ENCODING', ''))
113 accepts_encoding_header = safe_str(environ.get('HTTP_CONTENT_ENCODING', ''))
114
114
115 if 'gzip' in accepts_encoding_header:
115 if 'gzip' in accepts_encoding_header:
116 log.debug('gzip detected, now running gunzip wrapper')
116 log.debug('gzip detected, now running gunzip wrapper')
117 wsgi_input = environ['wsgi.input']
117 wsgi_input = environ['wsgi.input']
118
118
119 if not hasattr(environ['wsgi.input'], 'seek'):
119 if not hasattr(environ['wsgi.input'], 'seek'):
120 # The gzip implementation in the standard library of Python 2.x
120 # The gzip implementation in the standard library of Python 2.x
121 # requires the '.seek()' and '.tell()' methods to be available
121 # requires the '.seek()' and '.tell()' methods to be available
122 # on the input stream. Read the data into a temporary file to
122 # on the input stream. Read the data into a temporary file to
123 # work around this limitation.
123 # work around this limitation.
124
124
125 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
125 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
126 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
126 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
127 wsgi_input.seek(0)
127 wsgi_input.seek(0)
128
128
129 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
129 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
130 # since we "Ungzipped" the content we say now it's no longer gzip
130 # since we "Ungzipped" the content we say now it's no longer gzip
131 # content encoding
131 # content encoding
132 del environ['HTTP_CONTENT_ENCODING']
132 del environ['HTTP_CONTENT_ENCODING']
133
133
134 # content length has changes ? or i'm not sure
134 # content length has changes ? or i'm not sure
135 if 'CONTENT_LENGTH' in environ:
135 if 'CONTENT_LENGTH' in environ:
136 del environ['CONTENT_LENGTH']
136 del environ['CONTENT_LENGTH']
137 else:
137 else:
138 log.debug('content not gzipped, gzipMiddleware passing '
138 log.debug('content not gzipped, gzipMiddleware passing '
139 'request further')
139 'request further')
140 return self.app(environ, start_response)
140 return self.app(environ, start_response)
141
141
142
142
143 def is_vcs_call(environ):
143 def is_vcs_call(environ):
144 if VCS_TYPE_KEY in environ:
144 if VCS_TYPE_KEY in environ:
145 raw_type = environ[VCS_TYPE_KEY]
145 raw_type = environ[VCS_TYPE_KEY]
146 return raw_type and raw_type != VCS_TYPE_SKIP
146 return raw_type and raw_type != VCS_TYPE_SKIP
147 return False
147 return False
148
148
149
149
150 def detect_vcs_request(environ, backends):
150 def detect_vcs_request(environ, backends):
151 checks = {
151 checks = {
152 'hg': (is_hg, SimpleHg),
152 'hg': (is_hg, SimpleHg),
153 'git': (is_git, SimpleGit),
153 'git': (is_git, SimpleGit),
154 'svn': (is_svn, SimpleSvn),
154 'svn': (is_svn, SimpleSvn),
155 }
155 }
156 handler = None
156 handler = None
157 # List of path views first chunk we don't do any checks
157 # List of path views first chunk we don't do any checks
158 white_list = [
158 white_list = [
159 # favicon often requested by browsers
159 # favicon often requested by browsers
160 'favicon.ico',
160 'favicon.ico',
161
161
162 # static files no detection
163 '_static++',
164
165 # debug-toolbar
166 '_debug_toolbar++',
167
162 # e.g /_file_store/download
168 # e.g /_file_store/download
163 '_file_store++',
169 '_file_store++',
164
170
165 # login
171 # login
166 "_admin/login",
172 f"{ADMIN_PREFIX}/login",
173 f"{ADMIN_PREFIX}/logout",
167
174
168 # 2fa
175 # 2fa
169 f"{ADMIN_PREFIX}/check_2fa",
176 f"{ADMIN_PREFIX}/check_2fa",
170 f"{ADMIN_PREFIX}/setup_2fa",
177 f"{ADMIN_PREFIX}/setup_2fa",
171
178
172 # _admin/api is safe too
179 # _admin/api is safe too
173 f'{ADMIN_PREFIX}/api',
180 f'{ADMIN_PREFIX}/api',
174
181
175 # _admin/gist is safe too
182 # _admin/gist is safe too
176 f'{ADMIN_PREFIX}/gists++',
183 f'{ADMIN_PREFIX}/gists++',
177
184
178 # _admin/my_account is safe too
185 # _admin/my_account is safe too
179 f'{ADMIN_PREFIX}/my_account++',
186 f'{ADMIN_PREFIX}/my_account++',
180
187
181 # static files no detection
182 '_static++',
183
184 # debug-toolbar
185 '_debug_toolbar++',
186
187 # skip ops ping, status
188 # skip ops ping, status
188 f'{ADMIN_PREFIX}/ops/ping',
189 f'{ADMIN_PREFIX}/ops/ping',
189 f'{ADMIN_PREFIX}/ops/status',
190 f'{ADMIN_PREFIX}/ops/status',
190
191
191 # full channelstream connect should be VCS skipped
192 # full channelstream connect should be VCS skipped
192 f'{ADMIN_PREFIX}/channelstream/connect',
193 f'{ADMIN_PREFIX}/channelstream/connect',
193
194
194 '++/repo_creating_check'
195 '++/repo_creating_check'
195 ]
196 ]
197
196 path_info = get_path_info(environ)
198 path_info = get_path_info(environ)
197 path_url = path_info.lstrip('/')
199 path_url = path_info.lstrip('/')
198 req_method = environ.get('REQUEST_METHOD')
200 req_method = environ.get('REQUEST_METHOD')
199
201
200 for item in white_list:
202 for item in white_list:
203 item = item.lstrip('/')
204
201 if item.endswith('++') and path_url.startswith(item[:-2]):
205 if item.endswith('++') and path_url.startswith(item[:-2]):
202 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
206 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
203 return handler
207 return handler
204 if item.startswith('++') and path_url.endswith(item[2:]):
208 if item.startswith('++') and path_url.endswith(item[2:]):
205 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
209 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
206 return handler
210 return handler
207 if item == path_url:
211 if item == path_url:
208 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
212 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
209 return handler
213 return handler
210
214
211 if VCS_TYPE_KEY in environ:
215 if VCS_TYPE_KEY in environ:
212 raw_type = environ[VCS_TYPE_KEY]
216 raw_type = environ[VCS_TYPE_KEY]
213 if raw_type == VCS_TYPE_SKIP:
217 if raw_type == VCS_TYPE_SKIP:
214 log.debug('got `skip` marker for vcs detection, skipping...')
218 log.debug('got `skip` marker for vcs detection, skipping...')
215 return handler
219 return handler
216
220
217 _check, handler = checks.get(raw_type) or [None, None]
221 _check, handler = checks.get(raw_type) or [None, None]
218 if handler:
222 if handler:
219 log.debug('got handler:%s from environ', handler)
223 log.debug('got handler:%s from environ', handler)
220
224
221 if not handler:
225 if not handler:
222 log.debug('request start: checking if request for `%s:%s` is of VCS type in order: %s',
226 log.debug('request start: checking if request for `%s:%s` is of VCS type in order: %s',
223 req_method, path_url, backends)
227 req_method, path_url, backends)
224 for vcs_type in backends:
228 for vcs_type in backends:
225 vcs_check, _handler = checks[vcs_type]
229 vcs_check, _handler = checks[vcs_type]
226 if vcs_check(environ):
230 if vcs_check(environ):
227 log.debug('vcs handler found %s', _handler)
231 log.debug('vcs handler found %s', _handler)
228 handler = _handler
232 handler = _handler
229 break
233 break
230
234
231 return handler
235 return handler
232
236
233
237
234 class VCSMiddleware(object):
238 class VCSMiddleware(object):
235
239
236 def __init__(self, app, registry, config, appenlight_client):
240 def __init__(self, app, registry, config, appenlight_client):
237 self.application = app
241 self.application = app
238 self.registry = registry
242 self.registry = registry
239 self.config = config
243 self.config = config
240 self.appenlight_client = appenlight_client
244 self.appenlight_client = appenlight_client
241 self.use_gzip = True
245 self.use_gzip = True
242 # order in which we check the middlewares, based on vcs.backends config
246 # order in which we check the middlewares, based on vcs.backends config
243 self.check_middlewares = config['vcs.backends']
247 self.check_middlewares = config['vcs.backends']
244
248
245 def vcs_config(self, repo_name=None):
249 def vcs_config(self, repo_name=None):
246 """
250 """
247 returns serialized VcsSettings
251 returns serialized VcsSettings
248 """
252 """
249 try:
253 try:
250 return VcsSettingsModel(
254 return VcsSettingsModel(
251 repo=repo_name).get_ui_settings_as_config_obj()
255 repo=repo_name).get_ui_settings_as_config_obj()
252 except Exception:
256 except Exception:
253 pass
257 pass
254
258
255 def wrap_in_gzip_if_enabled(self, app, config):
259 def wrap_in_gzip_if_enabled(self, app, config):
256 if self.use_gzip:
260 if self.use_gzip:
257 app = GunzipMiddleware(app)
261 app = GunzipMiddleware(app)
258 return app
262 return app
259
263
260 def _get_handler_app(self, environ):
264 def _get_handler_app(self, environ):
261 app = None
265 app = None
262 log.debug('VCSMiddleware: detecting vcs type.')
266 log.debug('VCSMiddleware: detecting vcs type.')
263 handler = detect_vcs_request(environ, self.check_middlewares)
267 handler = detect_vcs_request(environ, self.check_middlewares)
264 if handler:
268 if handler:
265 app = handler(self.config, self.registry)
269 app = handler(self.config, self.registry)
266
270
267 return app
271 return app
268
272
269 def __call__(self, environ, start_response):
273 def __call__(self, environ, start_response):
270 # check if we handle one of interesting protocols, optionally extract
274 # check if we handle one of interesting protocols, optionally extract
271 # specific vcsSettings and allow changes of how things are wrapped
275 # specific vcsSettings and allow changes of how things are wrapped
272 vcs_handler = self._get_handler_app(environ)
276 vcs_handler = self._get_handler_app(environ)
273 if vcs_handler:
277 if vcs_handler:
274 # translate the _REPO_ID into real repo NAME for usage
278 # translate the _REPO_ID into real repo NAME for usage
275 # in middleware
279 # in middleware
276
280
277 path_info = get_path_info(environ)
281 path_info = get_path_info(environ)
278 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
282 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
279
283
280 # Set acl, url and vcs repo names.
284 # Set acl, url and vcs repo names.
281 vcs_handler.set_repo_names(environ)
285 vcs_handler.set_repo_names(environ)
282
286
283 # register repo config back to the handler
287 # register repo config back to the handler
284 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
288 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
285 # maybe damaged/non existent settings. We still want to
289 # maybe damaged/non existent settings. We still want to
286 # pass that point to validate on is_valid_and_existing_repo
290 # pass that point to validate on is_valid_and_existing_repo
287 # and return proper HTTP Code back to client
291 # and return proper HTTP Code back to client
288 if vcs_conf:
292 if vcs_conf:
289 vcs_handler.repo_vcs_config = vcs_conf
293 vcs_handler.repo_vcs_config = vcs_conf
290
294
291 # check for type, presence in database and on filesystem
295 # check for type, presence in database and on filesystem
292 if not vcs_handler.is_valid_and_existing_repo(
296 if not vcs_handler.is_valid_and_existing_repo(
293 vcs_handler.acl_repo_name,
297 vcs_handler.acl_repo_name,
294 vcs_handler.base_path,
298 vcs_handler.base_path,
295 vcs_handler.SCM):
299 vcs_handler.SCM):
296 return HTTPNotFound()(environ, start_response)
300 return HTTPNotFound()(environ, start_response)
297
301
298 environ['REPO_NAME'] = vcs_handler.url_repo_name
302 environ['REPO_NAME'] = vcs_handler.url_repo_name
299
303
300 # Wrap handler in middlewares if they are enabled.
304 # Wrap handler in middlewares if they are enabled.
301 vcs_handler = self.wrap_in_gzip_if_enabled(
305 vcs_handler = self.wrap_in_gzip_if_enabled(
302 vcs_handler, self.config)
306 vcs_handler, self.config)
303 vcs_handler, _ = wrap_in_appenlight_if_enabled(
307 vcs_handler, _ = wrap_in_appenlight_if_enabled(
304 vcs_handler, self.config, self.appenlight_client)
308 vcs_handler, self.config, self.appenlight_client)
305
309
306 return vcs_handler(environ, start_response)
310 return vcs_handler(environ, start_response)
307
311
308 return self.application(environ, start_response)
312 return self.application(environ, start_response)
@@ -1,124 +1,123 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 import logging
18 import logging
19
19
20 import click
20 import click
21 import pyramid.paster
21 import pyramid.paster
22
22
23 from rhodecode.lib.pyramid_utils import bootstrap
23 from rhodecode.lib.pyramid_utils import bootstrap
24 from rhodecode.lib.config_utils import get_app_config
24 from rhodecode.lib.config_utils import get_app_config
25 from rhodecode.lib.db_manage import DbManage
25 from rhodecode.lib.db_manage import DbManage
26 from rhodecode.lib.utils2 import get_encryption_key
26 from rhodecode.lib.utils2 import get_encryption_key
27 from rhodecode.model.db import Session
27 from rhodecode.model.db import Session
28
28
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 @click.command()
33 @click.command()
34 @click.argument('ini_path', type=click.Path(exists=True))
34 @click.argument('ini_path', type=click.Path(exists=True))
35 @click.option(
35 @click.option(
36 '--force-yes/--force-no', default=None,
36 '--force-yes/--force-no', default=None,
37 help="Force yes/no to every question")
37 help="Force yes/no to every question")
38 @click.option(
38 @click.option(
39 '--user',
39 '--user',
40 default=None,
40 default=None,
41 help='Initial super-admin username')
41 help='Initial super-admin username')
42 @click.option(
42 @click.option(
43 '--email',
43 '--email',
44 default=None,
44 default=None,
45 help='Initial super-admin email address.')
45 help='Initial super-admin email address.')
46 @click.option(
46 @click.option(
47 '--password',
47 '--password',
48 default=None,
48 default=None,
49 help='Initial super-admin password. Minimum 6 chars.')
49 help='Initial super-admin password. Minimum 6 chars.')
50 @click.option(
50 @click.option(
51 '--api-key',
51 '--api-key',
52 help='Initial API key for the admin user')
52 help='Initial API key for the admin user')
53 @click.option(
53 @click.option(
54 '--repos',
54 '--repos',
55 default=None,
55 default=None,
56 help='Absolute path to storage location. This is storage for all '
56 help='Absolute path to storage location. This is storage for all '
57 'existing and future repositories, and repository groups.')
57 'existing and future repositories, and repository groups.')
58 @click.option(
58 @click.option(
59 '--public-access/--no-public-access',
59 '--public-access/--no-public-access',
60 default=None,
60 default=None,
61 help='Enable public access on this installation. '
61 help='Enable public access on this installation. '
62 'Default is public access enabled.')
62 'Default is public access enabled.')
63 @click.option(
63 @click.option(
64 '--skip-existing-db',
64 '--skip-existing-db',
65 default=False,
65 default=False,
66 is_flag=True,
66 is_flag=True,
67 help='Do not destroy and re-initialize the database if it already exist.')
67 help='Do not destroy and re-initialize the database if it already exist.')
68 @click.option(
68 @click.option(
69 '--apply-license-key',
69 '--apply-license-key',
70 default=False,
70 default=False,
71 is_flag=True,
71 is_flag=True,
72 help='Get the license key from a license file or ENV and apply during DB creation.')
72 help='Get the license key from a license file or ENV and apply during DB creation.')
73 def main(ini_path, force_yes, user, email, password, api_key, repos,
73 def main(ini_path, force_yes, user, email, password, api_key, repos,
74 public_access, skip_existing_db, apply_license_key):
74 public_access, skip_existing_db, apply_license_key):
75 return command(ini_path, force_yes, user, email, password, api_key,
75 return command(ini_path, force_yes, user, email, password, api_key,
76 repos, public_access, skip_existing_db, apply_license_key)
76 repos, public_access, skip_existing_db, apply_license_key)
77
77
78
78
79 def command(ini_path, force_yes, user, email, password, api_key, repos,
79 def command(ini_path, force_yes, user, email, password, api_key, repos,
80 public_access, skip_existing_db, apply_license_key):
80 public_access, skip_existing_db, apply_license_key):
81 # mapping of old parameters to new CLI from click
81 # mapping of old parameters to new CLI from click
82 options = dict(
82 options = dict(
83 username=user,
83 username=user,
84 email=email,
84 email=email,
85 password=password,
85 password=password,
86 api_key=api_key,
86 api_key=api_key,
87 repos_location=repos,
87 repos_location=repos,
88 force_ask=force_yes,
88 force_ask=force_yes,
89 public_access=public_access
89 public_access=public_access
90 )
90 )
91 pyramid.paster.setup_logging(ini_path)
91 pyramid.paster.setup_logging(ini_path)
92
92
93 config = get_app_config(ini_path)
93 config = get_app_config(ini_path)
94
94
95 db_uri = config['sqlalchemy.db1.url']
95 db_uri = config['sqlalchemy.db1.url']
96 enc_key = get_encryption_key(config)
96 enc_key = get_encryption_key(config)
97 dbmanage = DbManage(log_sql=True, dbconf=db_uri, root='.',
97 dbmanage = DbManage(log_sql=True, dbconf=db_uri, root='.',
98 tests=False, cli_args=options, enc_key=enc_key)
98 tests=False, cli_args=options, enc_key=enc_key)
99 if skip_existing_db and dbmanage.db_exists():
99 if skip_existing_db and dbmanage.db_exists():
100 return
100 return
101
101
102 dbmanage.create_tables(override=True)
102 dbmanage.create_tables(override=True)
103 dbmanage.set_db_version()
103 dbmanage.set_db_version()
104 opts = dbmanage.config_prompt(None)
104 opts = dbmanage.config_prompt(None)
105 dbmanage.create_settings(opts)
105 dbmanage.create_settings(opts)
106 dbmanage.create_default_user()
106 dbmanage.create_default_user()
107 dbmanage.create_admin_and_prompt()
107 dbmanage.create_admin_and_prompt()
108 dbmanage.create_permissions()
108 dbmanage.create_permissions()
109 dbmanage.populate_default_permissions()
109 dbmanage.populate_default_permissions()
110 if apply_license_key:
110 if apply_license_key:
111 try:
111 from rhodecode.model.license import apply_license_from_file
112 from rc_license.models import apply_trial_license_if_missing
112 license_file_path = config.get('license.import_path')
113 apply_trial_license_if_missing(force=True)
113 if license_file_path:
114 except ImportError:
114 apply_license_from_file(license_file_path, force=True)
115 pass
116
115
117 Session().commit()
116 Session().commit()
118
117
119 with bootstrap(ini_path, env={'RC_CMD_SETUP_RC': '1'}) as env:
118 with bootstrap(ini_path, env={'RC_CMD_SETUP_RC': '1'}) as env:
120 msg = 'Successfully initialized database, schema and default data.'
119 msg = 'Successfully initialized database, schema and default data.'
121 print()
120 print()
122 print('*' * len(msg))
121 print('*' * len(msg))
123 print(msg.upper())
122 print(msg.upper())
124 print('*' * len(msg))
123 print('*' * len(msg))
@@ -1,835 +1,834 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Utilities library for RhodeCode
20 Utilities library for RhodeCode
21 """
21 """
22
22
23 import datetime
23 import datetime
24
24
25 import decorator
25 import decorator
26 import logging
26 import logging
27 import os
27 import os
28 import re
28 import re
29 import sys
29 import sys
30 import shutil
30 import shutil
31 import socket
31 import socket
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35
35
36 from functools import wraps
36 from functools import wraps
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
41 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
42
42
43 from mako import exceptions
43 from mako import exceptions
44
44
45 from rhodecode import ConfigGet
45 from rhodecode import ConfigGet
46 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
46 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
47 from rhodecode.lib.type_utils import AttributeDict
47 from rhodecode.lib.type_utils import AttributeDict
48 from rhodecode.lib.str_utils import safe_bytes, safe_str
48 from rhodecode.lib.str_utils import safe_bytes, safe_str
49 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.backends.base import Config
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
52 from rhodecode.lib.ext_json import sjson as json
52 from rhodecode.lib.ext_json import sjson as json
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def adopt_for_celery(func):
80 def adopt_for_celery(func):
81 """
81 """
82 Decorator designed to adopt hooks (from rhodecode.lib.hooks_base)
82 Decorator designed to adopt hooks (from rhodecode.lib.hooks_base)
83 for further usage as a celery tasks.
83 for further usage as a celery tasks.
84 """
84 """
85 @wraps(func)
85 @wraps(func)
86 def wrapper(extras):
86 def wrapper(extras):
87 extras = AttributeDict(extras)
87 extras = AttributeDict(extras)
88 try:
88 try:
89 # HooksResponse implements to_json method which must be used there.
89 # HooksResponse implements to_json method which must be used there.
90 return func(extras).to_json()
90 return func(extras).to_json()
91 except Exception as e:
91 except Exception as e:
92 return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args}
92 return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args}
93 return wrapper
93 return wrapper
94
94
95
95
96 def repo_name_slug(value):
96 def repo_name_slug(value):
97 """
97 """
98 Return slug of name of repository
98 Return slug of name of repository
99 This function is called on each creation/modification
99 This function is called on each creation/modification
100 of repository to prevent bad names in repo
100 of repository to prevent bad names in repo
101 """
101 """
102
102
103 replacement_char = '-'
103 replacement_char = '-'
104
104
105 slug = strip_tags(value)
105 slug = strip_tags(value)
106 slug = convert_accented_entities(slug)
106 slug = convert_accented_entities(slug)
107 slug = convert_misc_entities(slug)
107 slug = convert_misc_entities(slug)
108
108
109 slug = SLUG_BAD_CHAR_RE.sub('', slug)
109 slug = SLUG_BAD_CHAR_RE.sub('', slug)
110 slug = re.sub(r'[\s]+', '-', slug)
110 slug = re.sub(r'[\s]+', '-', slug)
111 slug = collapse(slug, replacement_char)
111 slug = collapse(slug, replacement_char)
112
112
113 return slug
113 return slug
114
114
115
115
116 #==============================================================================
116 #==============================================================================
117 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
117 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
118 #==============================================================================
118 #==============================================================================
119 def get_repo_slug(request):
119 def get_repo_slug(request):
120 _repo = ''
120 _repo = ''
121
121
122 if hasattr(request, 'db_repo_name'):
122 if hasattr(request, 'db_repo_name'):
123 # if our requests has set db reference use it for name, this
123 # if our requests has set db reference use it for name, this
124 # translates the example.com/_<id> into proper repo names
124 # translates the example.com/_<id> into proper repo names
125 _repo = request.db_repo_name
125 _repo = request.db_repo_name
126 elif getattr(request, 'matchdict', None):
126 elif getattr(request, 'matchdict', None):
127 # pyramid
127 # pyramid
128 _repo = request.matchdict.get('repo_name')
128 _repo = request.matchdict.get('repo_name')
129
129
130 if _repo:
130 if _repo:
131 _repo = _repo.rstrip('/')
131 _repo = _repo.rstrip('/')
132 return _repo
132 return _repo
133
133
134
134
135 def get_repo_group_slug(request):
135 def get_repo_group_slug(request):
136 _group = ''
136 _group = ''
137 if hasattr(request, 'db_repo_group'):
137 if hasattr(request, 'db_repo_group'):
138 # if our requests has set db reference use it for name, this
138 # if our requests has set db reference use it for name, this
139 # translates the example.com/_<id> into proper repo group names
139 # translates the example.com/_<id> into proper repo group names
140 _group = request.db_repo_group.group_name
140 _group = request.db_repo_group.group_name
141 elif getattr(request, 'matchdict', None):
141 elif getattr(request, 'matchdict', None):
142 # pyramid
142 # pyramid
143 _group = request.matchdict.get('repo_group_name')
143 _group = request.matchdict.get('repo_group_name')
144
144
145 if _group:
145 if _group:
146 _group = _group.rstrip('/')
146 _group = _group.rstrip('/')
147 return _group
147 return _group
148
148
149
149
150 def get_user_group_slug(request):
150 def get_user_group_slug(request):
151 _user_group = ''
151 _user_group = ''
152
152
153 if hasattr(request, 'db_user_group'):
153 if hasattr(request, 'db_user_group'):
154 _user_group = request.db_user_group.users_group_name
154 _user_group = request.db_user_group.users_group_name
155 elif getattr(request, 'matchdict', None):
155 elif getattr(request, 'matchdict', None):
156 # pyramid
156 # pyramid
157 _user_group = request.matchdict.get('user_group_id')
157 _user_group = request.matchdict.get('user_group_id')
158 _user_group_name = request.matchdict.get('user_group_name')
158 _user_group_name = request.matchdict.get('user_group_name')
159 try:
159 try:
160 if _user_group:
160 if _user_group:
161 _user_group = UserGroup.get(_user_group)
161 _user_group = UserGroup.get(_user_group)
162 elif _user_group_name:
162 elif _user_group_name:
163 _user_group = UserGroup.get_by_group_name(_user_group_name)
163 _user_group = UserGroup.get_by_group_name(_user_group_name)
164
164
165 if _user_group:
165 if _user_group:
166 _user_group = _user_group.users_group_name
166 _user_group = _user_group.users_group_name
167 except Exception:
167 except Exception:
168 log.exception('Failed to get user group by id and name')
168 log.exception('Failed to get user group by id and name')
169 # catch all failures here
169 # catch all failures here
170 return None
170 return None
171
171
172 return _user_group
172 return _user_group
173
173
174
174
175 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
176 """
176 """
177 Scans given path for repos and return (name,(type,path)) tuple
177 Scans given path for repos and return (name,(type,path)) tuple
178
178
179 :param path: path to scan for repositories
179 :param path: path to scan for repositories
180 :param recursive: recursive search and return names with subdirs in front
180 :param recursive: recursive search and return names with subdirs in front
181 """
181 """
182
182
183 # remove ending slash for better results
183 # remove ending slash for better results
184 path = path.rstrip(os.sep)
184 path = path.rstrip(os.sep)
185 log.debug('now scanning in %s location recursive:%s...', path, recursive)
185 log.debug('now scanning in %s location recursive:%s...', path, recursive)
186
186
187 def _get_repos(p):
187 def _get_repos(p):
188 dirpaths = get_dirpaths(p)
188 dirpaths = get_dirpaths(p)
189 if not _is_dir_writable(p):
189 if not _is_dir_writable(p):
190 log.warning('repo path without write access: %s', p)
190 log.warning('repo path without write access: %s', p)
191
191
192 for dirpath in dirpaths:
192 for dirpath in dirpaths:
193 if os.path.isfile(os.path.join(p, dirpath)):
193 if os.path.isfile(os.path.join(p, dirpath)):
194 continue
194 continue
195 cur_path = os.path.join(p, dirpath)
195 cur_path = os.path.join(p, dirpath)
196
196
197 # skip removed repos
197 # skip removed repos
198 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
198 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
199 continue
199 continue
200
200
201 #skip .<somethin> dirs
201 #skip .<somethin> dirs
202 if dirpath.startswith('.'):
202 if dirpath.startswith('.'):
203 continue
203 continue
204
204
205 try:
205 try:
206 scm_info = get_scm(cur_path)
206 scm_info = get_scm(cur_path)
207 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
207 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
208 except VCSError:
208 except VCSError:
209 if not recursive:
209 if not recursive:
210 continue
210 continue
211 #check if this dir containts other repos for recursive scan
211 #check if this dir containts other repos for recursive scan
212 rec_path = os.path.join(p, dirpath)
212 rec_path = os.path.join(p, dirpath)
213 if os.path.isdir(rec_path):
213 if os.path.isdir(rec_path):
214 yield from _get_repos(rec_path)
214 yield from _get_repos(rec_path)
215
215
216 return _get_repos(path)
216 return _get_repos(path)
217
217
218
218
219 def get_dirpaths(p: str) -> list:
219 def get_dirpaths(p: str) -> list:
220 try:
220 try:
221 # OS-independable way of checking if we have at least read-only
221 # OS-independable way of checking if we have at least read-only
222 # access or not.
222 # access or not.
223 dirpaths = os.listdir(p)
223 dirpaths = os.listdir(p)
224 except OSError:
224 except OSError:
225 log.warning('ignoring repo path without read access: %s', p)
225 log.warning('ignoring repo path without read access: %s', p)
226 return []
226 return []
227
227
228 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
228 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
229 # decode paths and suddenly returns unicode objects itself. The items it
229 # decode paths and suddenly returns unicode objects itself. The items it
230 # cannot decode are returned as strings and cause issues.
230 # cannot decode are returned as strings and cause issues.
231 #
231 #
232 # Those paths are ignored here until a solid solution for path handling has
232 # Those paths are ignored here until a solid solution for path handling has
233 # been built.
233 # been built.
234 expected_type = type(p)
234 expected_type = type(p)
235
235
236 def _has_correct_type(item):
236 def _has_correct_type(item):
237 if type(item) is not expected_type:
237 if type(item) is not expected_type:
238 log.error(
238 log.error(
239 "Ignoring path %s since it cannot be decoded into str.",
239 "Ignoring path %s since it cannot be decoded into str.",
240 # Using "repr" to make sure that we see the byte value in case
240 # Using "repr" to make sure that we see the byte value in case
241 # of support.
241 # of support.
242 repr(item))
242 repr(item))
243 return False
243 return False
244 return True
244 return True
245
245
246 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
246 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
247
247
248 return dirpaths
248 return dirpaths
249
249
250
250
251 def _is_dir_writable(path):
251 def _is_dir_writable(path):
252 """
252 """
253 Probe if `path` is writable.
253 Probe if `path` is writable.
254
254
255 Due to trouble on Cygwin / Windows, this is actually probing if it is
255 Due to trouble on Cygwin / Windows, this is actually probing if it is
256 possible to create a file inside of `path`, stat does not produce reliable
256 possible to create a file inside of `path`, stat does not produce reliable
257 results in this case.
257 results in this case.
258 """
258 """
259 try:
259 try:
260 with tempfile.TemporaryFile(dir=path):
260 with tempfile.TemporaryFile(dir=path):
261 pass
261 pass
262 except OSError:
262 except OSError:
263 return False
263 return False
264 return True
264 return True
265
265
266
266
267 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
267 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
268 """
268 """
269 Returns True if given path is a valid repository False otherwise.
269 Returns True if given path is a valid repository False otherwise.
270 If expect_scm param is given also, compare if given scm is the same
270 If expect_scm param is given also, compare if given scm is the same
271 as expected from scm parameter. If explicit_scm is given don't try to
271 as expected from scm parameter. If explicit_scm is given don't try to
272 detect the scm, just use the given one to check if repo is valid
272 detect the scm, just use the given one to check if repo is valid
273
273
274 :param repo_name:
274 :param repo_name:
275 :param base_path:
275 :param base_path:
276 :param expect_scm:
276 :param expect_scm:
277 :param explicit_scm:
277 :param explicit_scm:
278 :param config:
278 :param config:
279
279
280 :return True: if given path is a valid repository
280 :return True: if given path is a valid repository
281 """
281 """
282 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
282 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
283 log.debug('Checking if `%s` is a valid path for repository. '
283 log.debug('Checking if `%s` is a valid path for repository. '
284 'Explicit type: %s', repo_name, explicit_scm)
284 'Explicit type: %s', repo_name, explicit_scm)
285
285
286 try:
286 try:
287 if explicit_scm:
287 if explicit_scm:
288 detected_scms = [get_scm_backend(explicit_scm)(
288 detected_scms = [get_scm_backend(explicit_scm)(
289 full_path, config=config).alias]
289 full_path, config=config).alias]
290 else:
290 else:
291 detected_scms = get_scm(full_path)
291 detected_scms = get_scm(full_path)
292
292
293 if expect_scm:
293 if expect_scm:
294 return detected_scms[0] == expect_scm
294 return detected_scms[0] == expect_scm
295 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
295 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
296 return True
296 return True
297 except VCSError:
297 except VCSError:
298 log.debug('path: %s is not a valid repo !', full_path)
298 log.debug('path: %s is not a valid repo !', full_path)
299 return False
299 return False
300
300
301
301
302 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
302 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
303 """
303 """
304 Returns True if a given path is a repository group, False otherwise
304 Returns True if a given path is a repository group, False otherwise
305
305
306 :param repo_group_name:
306 :param repo_group_name:
307 :param base_path:
307 :param base_path:
308 """
308 """
309 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
309 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
310 log.debug('Checking if `%s` is a valid path for repository group',
310 log.debug('Checking if `%s` is a valid path for repository group',
311 repo_group_name)
311 repo_group_name)
312
312
313 # check if it's not a repo
313 # check if it's not a repo
314 if is_valid_repo(repo_group_name, base_path):
314 if is_valid_repo(repo_group_name, base_path):
315 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
315 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
316 return False
316 return False
317
317
318 try:
318 try:
319 # we need to check bare git repos at higher level
319 # we need to check bare git repos at higher level
320 # since we might match branches/hooks/info/objects or possible
320 # since we might match branches/hooks/info/objects or possible
321 # other things inside bare git repo
321 # other things inside bare git repo
322 maybe_repo = os.path.dirname(full_path)
322 maybe_repo = os.path.dirname(full_path)
323 if maybe_repo == base_path:
323 if maybe_repo == base_path:
324 # skip root level repo check; we know root location CANNOT BE a repo group
324 # skip root level repo check; we know root location CANNOT BE a repo group
325 return False
325 return False
326
326
327 scm_ = get_scm(maybe_repo)
327 scm_ = get_scm(maybe_repo)
328 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
328 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
329 return False
329 return False
330 except VCSError:
330 except VCSError:
331 pass
331 pass
332
332
333 # check if it's a valid path
333 # check if it's a valid path
334 if skip_path_check or os.path.isdir(full_path):
334 if skip_path_check or os.path.isdir(full_path):
335 log.debug('path: %s is a valid repo group !', full_path)
335 log.debug('path: %s is a valid repo group !', full_path)
336 return True
336 return True
337
337
338 log.debug('path: %s is not a valid repo group !', full_path)
338 log.debug('path: %s is not a valid repo group !', full_path)
339 return False
339 return False
340
340
341
341
342 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
342 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
343 while True:
343 while True:
344 ok = input(prompt)
344 ok = input(prompt)
345 if ok.lower() in ('y', 'ye', 'yes'):
345 if ok.lower() in ('y', 'ye', 'yes'):
346 return True
346 return True
347 if ok.lower() in ('n', 'no', 'nop', 'nope'):
347 if ok.lower() in ('n', 'no', 'nop', 'nope'):
348 return False
348 return False
349 retries = retries - 1
349 retries = retries - 1
350 if retries < 0:
350 if retries < 0:
351 raise OSError
351 raise OSError
352 print(complaint)
352 print(complaint)
353
353
354 # propagated from mercurial documentation
354 # propagated from mercurial documentation
355 ui_sections = [
355 ui_sections = [
356 'alias', 'auth',
356 'alias', 'auth',
357 'decode/encode', 'defaults',
357 'decode/encode', 'defaults',
358 'diff', 'email',
358 'diff', 'email',
359 'extensions', 'format',
359 'extensions', 'format',
360 'merge-patterns', 'merge-tools',
360 'merge-patterns', 'merge-tools',
361 'hooks', 'http_proxy',
361 'hooks', 'http_proxy',
362 'smtp', 'patch',
362 'smtp', 'patch',
363 'paths', 'profiling',
363 'paths', 'profiling',
364 'server', 'trusted',
364 'server', 'trusted',
365 'ui', 'web', ]
365 'ui', 'web', ]
366
366
367
367
368 def prepare_config_data(clear_session=True, repo=None):
368 def prepare_config_data(clear_session=True, repo=None):
369 """
369 """
370 Read the configuration data from the database, *.ini files and return configuration
370 Read the configuration data from the database, *.ini files and return configuration
371 tuples.
371 tuples.
372 """
372 """
373 from rhodecode.model.settings import VcsSettingsModel
373 from rhodecode.model.settings import VcsSettingsModel
374
374
375 config = []
375 config = []
376
376
377 sa = meta.Session()
377 sa = meta.Session()
378 settings_model = VcsSettingsModel(repo=repo, sa=sa)
378 settings_model = VcsSettingsModel(repo=repo, sa=sa)
379
379
380 ui_settings = settings_model.get_ui_settings()
380 ui_settings = settings_model.get_ui_settings()
381
381
382 ui_data = []
382 ui_data = []
383 for setting in ui_settings:
383 for setting in ui_settings:
384 # Todo: remove this section once transition to *.ini files will be completed
384 # Todo: remove this section once transition to *.ini files will be completed
385 if setting.section in ('largefiles', 'vcs_git_lfs'):
385 if setting.section in ('largefiles', 'vcs_git_lfs'):
386 if setting.key != 'enabled':
386 if setting.key != 'enabled':
387 continue
387 continue
388 if setting.active:
388 if setting.active:
389 ui_data.append((setting.section, setting.key, setting.value))
389 ui_data.append((setting.section, setting.key, setting.value))
390 config.append((
390 config.append((
391 safe_str(setting.section), safe_str(setting.key),
391 safe_str(setting.section), safe_str(setting.key),
392 safe_str(setting.value)))
392 safe_str(setting.value)))
393 if setting.key == 'push_ssl':
393 if setting.key == 'push_ssl':
394 # force set push_ssl requirement to False, rhodecode
394 # force set push_ssl requirement to False this is deprecated, and we must force it to False
395 # handles that
396 config.append((
395 config.append((
397 safe_str(setting.section), safe_str(setting.key), False))
396 safe_str(setting.section), safe_str(setting.key), False))
398 config_getter = ConfigGet()
397 config_getter = ConfigGet()
399 config.append(('vcs_git_lfs', 'store_location', config_getter.get_str('vcs.git.lfs.storage_location')))
398 config.append(('vcs_git_lfs', 'store_location', config_getter.get_str('vcs.git.lfs.storage_location')))
400 config.append(('largefiles', 'usercache', config_getter.get_str('vcs.hg.largefiles.storage_location')))
399 config.append(('largefiles', 'usercache', config_getter.get_str('vcs.hg.largefiles.storage_location')))
401 log.debug(
400 log.debug(
402 'settings ui from db@repo[%s]: %s',
401 'settings ui from db@repo[%s]: %s',
403 repo,
402 repo,
404 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
403 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
405 if clear_session:
404 if clear_session:
406 meta.Session.remove()
405 meta.Session.remove()
407
406
408 # TODO: mikhail: probably it makes no sense to re-read hooks information.
407 # TODO: mikhail: probably it makes no sense to re-read hooks information.
409 # It's already there and activated/deactivated
408 # It's already there and activated/deactivated
410 skip_entries = []
409 skip_entries = []
411 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
410 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
412 if 'pull' not in enabled_hook_classes:
411 if 'pull' not in enabled_hook_classes:
413 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
412 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
414 if 'push' not in enabled_hook_classes:
413 if 'push' not in enabled_hook_classes:
415 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
414 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
415 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
417 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
418
417
419 config = [entry for entry in config if entry[:2] not in skip_entries]
418 config = [entry for entry in config if entry[:2] not in skip_entries]
420
419
421 return config
420 return config
422
421
423
422
424 def make_db_config(clear_session=True, repo=None):
423 def make_db_config(clear_session=True, repo=None):
425 """
424 """
426 Create a :class:`Config` instance based on the values in the database.
425 Create a :class:`Config` instance based on the values in the database.
427 """
426 """
428 config = Config()
427 config = Config()
429 config_data = prepare_config_data(clear_session=clear_session, repo=repo)
428 config_data = prepare_config_data(clear_session=clear_session, repo=repo)
430 for section, option, value in config_data:
429 for section, option, value in config_data:
431 config.set(section, option, value)
430 config.set(section, option, value)
432 return config
431 return config
433
432
434
433
435 def get_enabled_hook_classes(ui_settings):
434 def get_enabled_hook_classes(ui_settings):
436 """
435 """
437 Return the enabled hook classes.
436 Return the enabled hook classes.
438
437
439 :param ui_settings: List of ui_settings as returned
438 :param ui_settings: List of ui_settings as returned
440 by :meth:`VcsSettingsModel.get_ui_settings`
439 by :meth:`VcsSettingsModel.get_ui_settings`
441
440
442 :return: a list with the enabled hook classes. The order is not guaranteed.
441 :return: a list with the enabled hook classes. The order is not guaranteed.
443 :rtype: list
442 :rtype: list
444 """
443 """
445 enabled_hooks = []
444 enabled_hooks = []
446 active_hook_keys = [
445 active_hook_keys = [
447 key for section, key, value, active in ui_settings
446 key for section, key, value, active in ui_settings
448 if section == 'hooks' and active]
447 if section == 'hooks' and active]
449
448
450 hook_names = {
449 hook_names = {
451 RhodeCodeUi.HOOK_PUSH: 'push',
450 RhodeCodeUi.HOOK_PUSH: 'push',
452 RhodeCodeUi.HOOK_PULL: 'pull',
451 RhodeCodeUi.HOOK_PULL: 'pull',
453 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
452 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
454 }
453 }
455
454
456 for key in active_hook_keys:
455 for key in active_hook_keys:
457 hook = hook_names.get(key)
456 hook = hook_names.get(key)
458 if hook:
457 if hook:
459 enabled_hooks.append(hook)
458 enabled_hooks.append(hook)
460
459
461 return enabled_hooks
460 return enabled_hooks
462
461
463
462
464 def set_rhodecode_config(config):
463 def set_rhodecode_config(config):
465 """
464 """
466 Updates pyramid config with new settings from database
465 Updates pyramid config with new settings from database
467
466
468 :param config:
467 :param config:
469 """
468 """
470 from rhodecode.model.settings import SettingsModel
469 from rhodecode.model.settings import SettingsModel
471 app_settings = SettingsModel().get_all_settings()
470 app_settings = SettingsModel().get_all_settings()
472
471
473 for k, v in list(app_settings.items()):
472 for k, v in list(app_settings.items()):
474 config[k] = v
473 config[k] = v
475
474
476
475
477 def get_rhodecode_realm():
476 def get_rhodecode_realm():
478 """
477 """
479 Return the rhodecode realm from database.
478 Return the rhodecode realm from database.
480 """
479 """
481 from rhodecode.model.settings import SettingsModel
480 from rhodecode.model.settings import SettingsModel
482 realm = SettingsModel().get_setting_by_name('realm')
481 realm = SettingsModel().get_setting_by_name('realm')
483 return safe_str(realm.app_settings_value)
482 return safe_str(realm.app_settings_value)
484
483
485
484
486 def get_rhodecode_repo_store_path():
485 def get_rhodecode_repo_store_path():
487 """
486 """
488 Returns the base path. The base path is the filesystem path which points
487 Returns the base path. The base path is the filesystem path which points
489 to the repository store.
488 to the repository store.
490 """
489 """
491
490
492 import rhodecode
491 import rhodecode
493 return rhodecode.CONFIG['repo_store.path']
492 return rhodecode.CONFIG['repo_store.path']
494
493
495
494
496 def map_groups(path):
495 def map_groups(path):
497 """
496 """
498 Given a full path to a repository, create all nested groups that this
497 Given a full path to a repository, create all nested groups that this
499 repo is inside. This function creates parent-child relationships between
498 repo is inside. This function creates parent-child relationships between
500 groups and creates default perms for all new groups.
499 groups and creates default perms for all new groups.
501
500
502 :param paths: full path to repository
501 :param paths: full path to repository
503 """
502 """
504 from rhodecode.model.repo_group import RepoGroupModel
503 from rhodecode.model.repo_group import RepoGroupModel
505 sa = meta.Session()
504 sa = meta.Session()
506 groups = path.split(Repository.NAME_SEP)
505 groups = path.split(Repository.NAME_SEP)
507 parent = None
506 parent = None
508 group = None
507 group = None
509
508
510 # last element is repo in nested groups structure
509 # last element is repo in nested groups structure
511 groups = groups[:-1]
510 groups = groups[:-1]
512 rgm = RepoGroupModel(sa)
511 rgm = RepoGroupModel(sa)
513 owner = User.get_first_super_admin()
512 owner = User.get_first_super_admin()
514 for lvl, group_name in enumerate(groups):
513 for lvl, group_name in enumerate(groups):
515 group_name = '/'.join(groups[:lvl] + [group_name])
514 group_name = '/'.join(groups[:lvl] + [group_name])
516 group = RepoGroup.get_by_group_name(group_name)
515 group = RepoGroup.get_by_group_name(group_name)
517 desc = '%s group' % group_name
516 desc = '%s group' % group_name
518
517
519 # skip folders that are now removed repos
518 # skip folders that are now removed repos
520 if REMOVED_REPO_PAT.match(group_name):
519 if REMOVED_REPO_PAT.match(group_name):
521 break
520 break
522
521
523 if group is None:
522 if group is None:
524 log.debug('creating group level: %s group_name: %s',
523 log.debug('creating group level: %s group_name: %s',
525 lvl, group_name)
524 lvl, group_name)
526 group = RepoGroup(group_name, parent)
525 group = RepoGroup(group_name, parent)
527 group.group_description = desc
526 group.group_description = desc
528 group.user = owner
527 group.user = owner
529 sa.add(group)
528 sa.add(group)
530 perm_obj = rgm._create_default_perms(group)
529 perm_obj = rgm._create_default_perms(group)
531 sa.add(perm_obj)
530 sa.add(perm_obj)
532 sa.flush()
531 sa.flush()
533
532
534 parent = group
533 parent = group
535 return group
534 return group
536
535
537
536
538 def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
537 def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
539 """
538 """
540 maps all repos given in initial_repo_list, non existing repositories
539 maps all repos given in initial_repo_list, non existing repositories
541 are created, if remove_obsolete is True it also checks for db entries
540 are created, if remove_obsolete is True it also checks for db entries
542 that are not in initial_repo_list and removes them.
541 that are not in initial_repo_list and removes them.
543
542
544 :param initial_repo_list: list of repositories found by scanning methods
543 :param initial_repo_list: list of repositories found by scanning methods
545 :param remove_obsolete: check for obsolete entries in database
544 :param remove_obsolete: check for obsolete entries in database
546 """
545 """
547 from rhodecode.model.repo import RepoModel
546 from rhodecode.model.repo import RepoModel
548 from rhodecode.model.repo_group import RepoGroupModel
547 from rhodecode.model.repo_group import RepoGroupModel
549 from rhodecode.model.settings import SettingsModel
548 from rhodecode.model.settings import SettingsModel
550
549
551 sa = meta.Session()
550 sa = meta.Session()
552 repo_model = RepoModel()
551 repo_model = RepoModel()
553 user = User.get_first_super_admin()
552 user = User.get_first_super_admin()
554 added = []
553 added = []
555
554
556 # creation defaults
555 # creation defaults
557 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
556 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
558 enable_statistics = defs.get('repo_enable_statistics')
557 enable_statistics = defs.get('repo_enable_statistics')
559 enable_locking = defs.get('repo_enable_locking')
558 enable_locking = defs.get('repo_enable_locking')
560 enable_downloads = defs.get('repo_enable_downloads')
559 enable_downloads = defs.get('repo_enable_downloads')
561 private = defs.get('repo_private')
560 private = defs.get('repo_private')
562
561
563 for name, repo in list(initial_repo_list.items()):
562 for name, repo in list(initial_repo_list.items()):
564 group = map_groups(name)
563 group = map_groups(name)
565 str_name = safe_str(name)
564 str_name = safe_str(name)
566 db_repo = repo_model.get_by_repo_name(str_name)
565 db_repo = repo_model.get_by_repo_name(str_name)
567
566
568 # found repo that is on filesystem not in RhodeCode database
567 # found repo that is on filesystem not in RhodeCode database
569 if not db_repo:
568 if not db_repo:
570 log.info('repository `%s` not found in the database, creating now', name)
569 log.info('repository `%s` not found in the database, creating now', name)
571 added.append(name)
570 added.append(name)
572 desc = (repo.description
571 desc = (repo.description
573 if repo.description != 'unknown'
572 if repo.description != 'unknown'
574 else '%s repository' % name)
573 else '%s repository' % name)
575
574
576 db_repo = repo_model._create_repo(
575 db_repo = repo_model._create_repo(
577 repo_name=name,
576 repo_name=name,
578 repo_type=repo.alias,
577 repo_type=repo.alias,
579 description=desc,
578 description=desc,
580 repo_group=getattr(group, 'group_id', None),
579 repo_group=getattr(group, 'group_id', None),
581 owner=user,
580 owner=user,
582 enable_locking=enable_locking,
581 enable_locking=enable_locking,
583 enable_downloads=enable_downloads,
582 enable_downloads=enable_downloads,
584 enable_statistics=enable_statistics,
583 enable_statistics=enable_statistics,
585 private=private,
584 private=private,
586 state=Repository.STATE_CREATED
585 state=Repository.STATE_CREATED
587 )
586 )
588 sa.commit()
587 sa.commit()
589 # we added that repo just now, and make sure we updated server info
588 # we added that repo just now, and make sure we updated server info
590 if db_repo.repo_type == 'git':
589 if db_repo.repo_type == 'git':
591 git_repo = db_repo.scm_instance()
590 git_repo = db_repo.scm_instance()
592 # update repository server-info
591 # update repository server-info
593 log.debug('Running update server info')
592 log.debug('Running update server info')
594 git_repo._update_server_info(force=True)
593 git_repo._update_server_info(force=True)
595
594
596 db_repo.update_commit_cache(recursive=False)
595 db_repo.update_commit_cache(recursive=False)
597
596
598 config = db_repo._config
597 config = db_repo._config
599 config.set('extensions', 'largefiles', '')
598 config.set('extensions', 'largefiles', '')
600 repo = db_repo.scm_instance(config=config)
599 repo = db_repo.scm_instance(config=config)
601 repo.install_hooks(force=force_hooks_rebuild)
600 repo.install_hooks(force=force_hooks_rebuild)
602
601
603 removed = []
602 removed = []
604 if remove_obsolete:
603 if remove_obsolete:
605 # remove from database those repositories that are not in the filesystem
604 # remove from database those repositories that are not in the filesystem
606 for repo in sa.query(Repository).all():
605 for repo in sa.query(Repository).all():
607 if repo.repo_name not in list(initial_repo_list.keys()):
606 if repo.repo_name not in list(initial_repo_list.keys()):
608 log.debug("Removing non-existing repository found in db `%s`",
607 log.debug("Removing non-existing repository found in db `%s`",
609 repo.repo_name)
608 repo.repo_name)
610 try:
609 try:
611 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
610 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
612 sa.commit()
611 sa.commit()
613 removed.append(repo.repo_name)
612 removed.append(repo.repo_name)
614 except Exception:
613 except Exception:
615 # don't hold further removals on error
614 # don't hold further removals on error
616 log.error(traceback.format_exc())
615 log.error(traceback.format_exc())
617 sa.rollback()
616 sa.rollback()
618
617
619 def splitter(full_repo_name):
618 def splitter(full_repo_name):
620 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
619 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
621 gr_name = None
620 gr_name = None
622 if len(_parts) == 2:
621 if len(_parts) == 2:
623 gr_name = _parts[0]
622 gr_name = _parts[0]
624 return gr_name
623 return gr_name
625
624
626 initial_repo_group_list = [splitter(x) for x in
625 initial_repo_group_list = [splitter(x) for x in
627 list(initial_repo_list.keys()) if splitter(x)]
626 list(initial_repo_list.keys()) if splitter(x)]
628
627
629 # remove from database those repository groups that are not in the
628 # remove from database those repository groups that are not in the
630 # filesystem due to parent child relationships we need to delete them
629 # filesystem due to parent child relationships we need to delete them
631 # in a specific order of most nested first
630 # in a specific order of most nested first
632 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
631 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
633 def nested_sort(gr):
632 def nested_sort(gr):
634 return len(gr.split('/'))
633 return len(gr.split('/'))
635 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
634 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
636 if group_name not in initial_repo_group_list:
635 if group_name not in initial_repo_group_list:
637 repo_group = RepoGroup.get_by_group_name(group_name)
636 repo_group = RepoGroup.get_by_group_name(group_name)
638 if (repo_group.children.all() or
637 if (repo_group.children.all() or
639 not RepoGroupModel().check_exist_filesystem(
638 not RepoGroupModel().check_exist_filesystem(
640 group_name=group_name, exc_on_failure=False)):
639 group_name=group_name, exc_on_failure=False)):
641 continue
640 continue
642
641
643 log.info(
642 log.info(
644 'Removing non-existing repository group found in db `%s`',
643 'Removing non-existing repository group found in db `%s`',
645 group_name)
644 group_name)
646 try:
645 try:
647 RepoGroupModel(sa).delete(group_name, fs_remove=False)
646 RepoGroupModel(sa).delete(group_name, fs_remove=False)
648 sa.commit()
647 sa.commit()
649 removed.append(group_name)
648 removed.append(group_name)
650 except Exception:
649 except Exception:
651 # don't hold further removals on error
650 # don't hold further removals on error
652 log.exception(
651 log.exception(
653 'Unable to remove repository group `%s`',
652 'Unable to remove repository group `%s`',
654 group_name)
653 group_name)
655 sa.rollback()
654 sa.rollback()
656 raise
655 raise
657
656
658 return added, removed
657 return added, removed
659
658
660
659
661 def load_rcextensions(root_path):
660 def load_rcextensions(root_path):
662 import rhodecode
661 import rhodecode
663 from rhodecode.config import conf
662 from rhodecode.config import conf
664
663
665 path = os.path.join(root_path)
664 path = os.path.join(root_path)
666 sys.path.append(path)
665 sys.path.append(path)
667
666
668 try:
667 try:
669 rcextensions = __import__('rcextensions')
668 rcextensions = __import__('rcextensions')
670 except ImportError:
669 except ImportError:
671 if os.path.isdir(os.path.join(path, 'rcextensions')):
670 if os.path.isdir(os.path.join(path, 'rcextensions')):
672 log.warning('Unable to load rcextensions from %s', path)
671 log.warning('Unable to load rcextensions from %s', path)
673 rcextensions = None
672 rcextensions = None
674
673
675 if rcextensions:
674 if rcextensions:
676 log.info('Loaded rcextensions from %s...', rcextensions)
675 log.info('Loaded rcextensions from %s...', rcextensions)
677 rhodecode.EXTENSIONS = rcextensions
676 rhodecode.EXTENSIONS = rcextensions
678
677
679 # Additional mappings that are not present in the pygments lexers
678 # Additional mappings that are not present in the pygments lexers
680 conf.LANGUAGES_EXTENSIONS_MAP.update(
679 conf.LANGUAGES_EXTENSIONS_MAP.update(
681 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
680 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
682
681
683
682
684 def get_custom_lexer(extension):
683 def get_custom_lexer(extension):
685 """
684 """
686 returns a custom lexer if it is defined in rcextensions module, or None
685 returns a custom lexer if it is defined in rcextensions module, or None
687 if there's no custom lexer defined
686 if there's no custom lexer defined
688 """
687 """
689 import rhodecode
688 import rhodecode
690 from pygments import lexers
689 from pygments import lexers
691
690
692 # custom override made by RhodeCode
691 # custom override made by RhodeCode
693 if extension in ['mako']:
692 if extension in ['mako']:
694 return lexers.get_lexer_by_name('html+mako')
693 return lexers.get_lexer_by_name('html+mako')
695
694
696 # check if we didn't define this extension as other lexer
695 # check if we didn't define this extension as other lexer
697 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
696 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
698 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
697 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
699 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
698 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
700 return lexers.get_lexer_by_name(_lexer_name)
699 return lexers.get_lexer_by_name(_lexer_name)
701
700
702
701
703 #==============================================================================
702 #==============================================================================
704 # TEST FUNCTIONS AND CREATORS
703 # TEST FUNCTIONS AND CREATORS
705 #==============================================================================
704 #==============================================================================
706 def create_test_index(repo_location, config):
705 def create_test_index(repo_location, config):
707 """
706 """
708 Makes default test index.
707 Makes default test index.
709 """
708 """
710 try:
709 try:
711 import rc_testdata
710 import rc_testdata
712 except ImportError:
711 except ImportError:
713 raise ImportError('Failed to import rc_testdata, '
712 raise ImportError('Failed to import rc_testdata, '
714 'please make sure this package is installed from requirements_test.txt')
713 'please make sure this package is installed from requirements_test.txt')
715 rc_testdata.extract_search_index(
714 rc_testdata.extract_search_index(
716 'vcs_search_index', os.path.dirname(config['search.location']))
715 'vcs_search_index', os.path.dirname(config['search.location']))
717
716
718
717
719 def create_test_directory(test_path):
718 def create_test_directory(test_path):
720 """
719 """
721 Create test directory if it doesn't exist.
720 Create test directory if it doesn't exist.
722 """
721 """
723 if not os.path.isdir(test_path):
722 if not os.path.isdir(test_path):
724 log.debug('Creating testdir %s', test_path)
723 log.debug('Creating testdir %s', test_path)
725 os.makedirs(test_path)
724 os.makedirs(test_path)
726
725
727
726
728 def create_test_database(test_path, config):
727 def create_test_database(test_path, config):
729 """
728 """
730 Makes a fresh database.
729 Makes a fresh database.
731 """
730 """
732 from rhodecode.lib.db_manage import DbManage
731 from rhodecode.lib.db_manage import DbManage
733 from rhodecode.lib.utils2 import get_encryption_key
732 from rhodecode.lib.utils2 import get_encryption_key
734
733
735 # PART ONE create db
734 # PART ONE create db
736 dbconf = config['sqlalchemy.db1.url']
735 dbconf = config['sqlalchemy.db1.url']
737 enc_key = get_encryption_key(config)
736 enc_key = get_encryption_key(config)
738
737
739 log.debug('making test db %s', dbconf)
738 log.debug('making test db %s', dbconf)
740
739
741 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
740 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
742 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
741 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
743 dbmanage.create_tables(override=True)
742 dbmanage.create_tables(override=True)
744 dbmanage.set_db_version()
743 dbmanage.set_db_version()
745 # for tests dynamically set new root paths based on generated content
744 # for tests dynamically set new root paths based on generated content
746 dbmanage.create_settings(dbmanage.config_prompt(test_path))
745 dbmanage.create_settings(dbmanage.config_prompt(test_path))
747 dbmanage.create_default_user()
746 dbmanage.create_default_user()
748 dbmanage.create_test_admin_and_users()
747 dbmanage.create_test_admin_and_users()
749 dbmanage.create_permissions()
748 dbmanage.create_permissions()
750 dbmanage.populate_default_permissions()
749 dbmanage.populate_default_permissions()
751 Session().commit()
750 Session().commit()
752
751
753
752
754 def create_test_repositories(test_path, config):
753 def create_test_repositories(test_path, config):
755 """
754 """
756 Creates test repositories in the temporary directory. Repositories are
755 Creates test repositories in the temporary directory. Repositories are
757 extracted from archives within the rc_testdata package.
756 extracted from archives within the rc_testdata package.
758 """
757 """
759 import rc_testdata
758 import rc_testdata
760 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
759 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
761
760
762 log.debug('making test vcs repositories')
761 log.debug('making test vcs repositories')
763
762
764 idx_path = config['search.location']
763 idx_path = config['search.location']
765 data_path = config['cache_dir']
764 data_path = config['cache_dir']
766
765
767 # clean index and data
766 # clean index and data
768 if idx_path and os.path.exists(idx_path):
767 if idx_path and os.path.exists(idx_path):
769 log.debug('remove %s', idx_path)
768 log.debug('remove %s', idx_path)
770 shutil.rmtree(idx_path)
769 shutil.rmtree(idx_path)
771
770
772 if data_path and os.path.exists(data_path):
771 if data_path and os.path.exists(data_path):
773 log.debug('remove %s', data_path)
772 log.debug('remove %s', data_path)
774 shutil.rmtree(data_path)
773 shutil.rmtree(data_path)
775
774
776 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
775 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
777 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
776 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
778
777
779 # Note: Subversion is in the process of being integrated with the system,
778 # Note: Subversion is in the process of being integrated with the system,
780 # until we have a properly packed version of the test svn repository, this
779 # until we have a properly packed version of the test svn repository, this
781 # tries to copy over the repo from a package "rc_testdata"
780 # tries to copy over the repo from a package "rc_testdata"
782 svn_repo_path = rc_testdata.get_svn_repo_archive()
781 svn_repo_path = rc_testdata.get_svn_repo_archive()
783 with tarfile.open(svn_repo_path) as tar:
782 with tarfile.open(svn_repo_path) as tar:
784 tar.extractall(jn(test_path, SVN_REPO))
783 tar.extractall(jn(test_path, SVN_REPO))
785
784
786
785
787 def password_changed(auth_user, session):
786 def password_changed(auth_user, session):
788 # Never report password change in case of default user or anonymous user.
787 # Never report password change in case of default user or anonymous user.
789 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
788 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
790 return False
789 return False
791
790
792 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
791 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
793 rhodecode_user = session.get('rhodecode_user', {})
792 rhodecode_user = session.get('rhodecode_user', {})
794 session_password_hash = rhodecode_user.get('password', '')
793 session_password_hash = rhodecode_user.get('password', '')
795 return password_hash != session_password_hash
794 return password_hash != session_password_hash
796
795
797
796
798 def read_opensource_licenses():
797 def read_opensource_licenses():
799 global _license_cache
798 global _license_cache
800
799
801 if not _license_cache:
800 if not _license_cache:
802 licenses = pkg_resources.resource_string(
801 licenses = pkg_resources.resource_string(
803 'rhodecode', 'config/licenses.json')
802 'rhodecode', 'config/licenses.json')
804 _license_cache = json.loads(licenses)
803 _license_cache = json.loads(licenses)
805
804
806 return _license_cache
805 return _license_cache
807
806
808
807
809 def generate_platform_uuid():
808 def generate_platform_uuid():
810 """
809 """
811 Generates platform UUID based on it's name
810 Generates platform UUID based on it's name
812 """
811 """
813 import platform
812 import platform
814
813
815 try:
814 try:
816 uuid_list = [platform.platform()]
815 uuid_list = [platform.platform()]
817 return sha256_safe(':'.join(uuid_list))
816 return sha256_safe(':'.join(uuid_list))
818 except Exception as e:
817 except Exception as e:
819 log.error('Failed to generate host uuid: %s', e)
818 log.error('Failed to generate host uuid: %s', e)
820 return 'UNDEFINED'
819 return 'UNDEFINED'
821
820
822
821
823 def send_test_email(recipients, email_body='TEST EMAIL'):
822 def send_test_email(recipients, email_body='TEST EMAIL'):
824 """
823 """
825 Simple code for generating test emails.
824 Simple code for generating test emails.
826 Usage::
825 Usage::
827
826
828 from rhodecode.lib import utils
827 from rhodecode.lib import utils
829 utils.send_test_email()
828 utils.send_test_email()
830 """
829 """
831 from rhodecode.lib.celerylib import tasks, run_task
830 from rhodecode.lib.celerylib import tasks, run_task
832
831
833 email_body = email_body_plaintext = email_body
832 email_body = email_body_plaintext = email_body
834 subject = f'SUBJECT FROM: {socket.gethostname()}'
833 subject = f'SUBJECT FROM: {socket.gethostname()}'
835 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
834 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
@@ -1,663 +1,662 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 this is forms validation classes
20 this is forms validation classes
21 http://formencode.org/module-formencode.validators.html
21 http://formencode.org/module-formencode.validators.html
22 for list off all availible validators
22 for list off all availible validators
23
23
24 we can create our own validators
24 we can create our own validators
25
25
26 The table below outlines the options which can be used in a schema in addition to the validators themselves
26 The table below outlines the options which can be used in a schema in addition to the validators themselves
27 pre_validators [] These validators will be applied before the schema
27 pre_validators [] These validators will be applied before the schema
28 chained_validators [] These validators will be applied after the schema
28 chained_validators [] These validators will be applied after the schema
29 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
29 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
30 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
30 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
31 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
31 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
32 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
32 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
33
33
34
34
35 <name> = formencode.validators.<name of validator>
35 <name> = formencode.validators.<name of validator>
36 <name> must equal form name
36 <name> must equal form name
37 list=[1,2,3,4,5]
37 list=[1,2,3,4,5]
38 for SELECT use formencode.All(OneOf(list), Int())
38 for SELECT use formencode.All(OneOf(list), Int())
39
39
40 """
40 """
41
41
42 import deform
42 import deform
43 import logging
43 import logging
44 import formencode
44 import formencode
45
45
46 from pkg_resources import resource_filename
46 from pkg_resources import resource_filename
47 from formencode import All, Pipe
47 from formencode import All, Pipe
48
48
49 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
50
50
51 from rhodecode import BACKENDS
51 from rhodecode import BACKENDS
52 from rhodecode.lib import helpers
52 from rhodecode.lib import helpers
53 from rhodecode.model import validators as v
53 from rhodecode.model import validators as v
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 deform_templates = resource_filename('deform', 'templates')
58 deform_templates = resource_filename('deform', 'templates')
59 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
59 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
60 search_path = (rhodecode_templates, deform_templates)
60 search_path = (rhodecode_templates, deform_templates)
61
61
62
62
63 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
63 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
64 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
64 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
65 def __call__(self, template_name, **kw):
65 def __call__(self, template_name, **kw):
66 kw['h'] = helpers
66 kw['h'] = helpers
67 kw['request'] = get_current_request()
67 kw['request'] = get_current_request()
68 return self.load(template_name)(**kw)
68 return self.load(template_name)(**kw)
69
69
70
70
71 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
71 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 deform.Form.set_default_renderer(form_renderer)
72 deform.Form.set_default_renderer(form_renderer)
73
73
74
74
75 def LoginForm(localizer):
75 def LoginForm(localizer):
76 _ = localizer
76 _ = localizer
77
77
78 class _LoginForm(formencode.Schema):
78 class _LoginForm(formencode.Schema):
79 allow_extra_fields = True
79 allow_extra_fields = True
80 filter_extra_fields = True
80 filter_extra_fields = True
81 username = v.UnicodeString(
81 username = v.UnicodeString(
82 strip=True,
82 strip=True,
83 min=1,
83 min=1,
84 not_empty=True,
84 not_empty=True,
85 messages={
85 messages={
86 'empty': _('Please enter a login'),
86 'empty': _('Please enter a login'),
87 'tooShort': _('Enter a value %(min)i characters long or more')
87 'tooShort': _('Enter a value %(min)i characters long or more')
88 }
88 }
89 )
89 )
90
90
91 password = v.UnicodeString(
91 password = v.UnicodeString(
92 strip=False,
92 strip=False,
93 min=3,
93 min=3,
94 max=72,
94 max=72,
95 not_empty=True,
95 not_empty=True,
96 messages={
96 messages={
97 'empty': _('Please enter a password'),
97 'empty': _('Please enter a password'),
98 'tooShort': _('Enter %(min)i characters or more')}
98 'tooShort': _('Enter %(min)i characters or more')}
99 )
99 )
100
100
101 remember = v.StringBoolean(if_missing=False)
101 remember = v.StringBoolean(if_missing=False)
102
102
103 chained_validators = [v.ValidAuth(localizer)]
103 chained_validators = [v.ValidAuth(localizer)]
104 return _LoginForm
104 return _LoginForm
105
105
106
106
107 def TOTPForm(localizer, user, allow_recovery_code_use=False):
107 def TOTPForm(localizer, user, allow_recovery_code_use=False):
108 _ = localizer
108 _ = localizer
109
109
110 class _TOTPForm(formencode.Schema):
110 class _TOTPForm(formencode.Schema):
111 allow_extra_fields = True
111 allow_extra_fields = True
112 filter_extra_fields = False
112 filter_extra_fields = False
113 totp = v.Regex(r'^(?:\d{6}|[A-Z0-9]{32})$')
113 totp = v.Regex(r'^(?:\d{6}|[A-Z0-9]{32})$')
114 secret_totp = v.String()
114 secret_totp = v.String()
115
115
116 def to_python(self, value, state=None):
116 def to_python(self, value, state=None):
117 validation_checks = [user.is_totp_valid]
117 validation_checks = [user.is_totp_valid]
118 if allow_recovery_code_use:
118 if allow_recovery_code_use:
119 validation_checks.append(user.is_2fa_recovery_code_valid)
119 validation_checks.append(user.is_2fa_recovery_code_valid)
120 form_data = super().to_python(value, state)
120 form_data = super().to_python(value, state)
121 received_code = form_data['totp']
121 received_code = form_data['totp']
122 secret = form_data.get('secret_totp')
122 secret = form_data.get('secret_totp')
123
123
124 if not any(map(lambda func: func(received_code, secret), validation_checks)):
124 if not any(map(lambda func: func(received_code, secret), validation_checks)):
125 error_msg = _('Code is invalid. Try again!')
125 error_msg = _('Code is invalid. Try again!')
126 raise formencode.Invalid(error_msg, v, state, error_dict={'totp': error_msg})
126 raise formencode.Invalid(error_msg, v, state, error_dict={'totp': error_msg})
127 return form_data
127 return form_data
128
128
129 return _TOTPForm
129 return _TOTPForm
130
130
131
131
132 def WhitelistedVcsClientsForm(localizer):
132 def WhitelistedVcsClientsForm(localizer):
133 _ = localizer
133 _ = localizer
134
134
135 class _WhitelistedVcsClientsForm(formencode.Schema):
135 class _WhitelistedVcsClientsForm(formencode.Schema):
136 regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$'
136 regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$'
137 allow_extra_fields = True
137 allow_extra_fields = True
138 filter_extra_fields = True
138 filter_extra_fields = True
139 git = v.Regex(regexp)
139 git = v.Regex(regexp)
140 hg = v.Regex(regexp)
140 hg = v.Regex(regexp)
141 svn = v.Regex(regexp)
141 svn = v.Regex(regexp)
142
142
143 return _WhitelistedVcsClientsForm
143 return _WhitelistedVcsClientsForm
144
144
145
145
146 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
146 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
147 old_data = old_data or {}
147 old_data = old_data or {}
148 available_languages = available_languages or []
148 available_languages = available_languages or []
149 _ = localizer
149 _ = localizer
150
150
151 class _UserForm(formencode.Schema):
151 class _UserForm(formencode.Schema):
152 allow_extra_fields = True
152 allow_extra_fields = True
153 filter_extra_fields = True
153 filter_extra_fields = True
154 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
154 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
155 v.ValidUsername(localizer, edit, old_data))
155 v.ValidUsername(localizer, edit, old_data))
156 if edit:
156 if edit:
157 new_password = All(
157 new_password = All(
158 v.ValidPassword(localizer),
158 v.ValidPassword(localizer),
159 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
159 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
160 )
160 )
161 password_confirmation = All(
161 password_confirmation = All(
162 v.ValidPassword(localizer),
162 v.ValidPassword(localizer),
163 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
163 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
164 )
164 )
165 admin = v.StringBoolean(if_missing=False)
165 admin = v.StringBoolean(if_missing=False)
166 else:
166 else:
167 password = All(
167 password = All(
168 v.ValidPassword(localizer),
168 v.ValidPassword(localizer),
169 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
169 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
170 )
170 )
171 password_confirmation = All(
171 password_confirmation = All(
172 v.ValidPassword(localizer),
172 v.ValidPassword(localizer),
173 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
173 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
174 )
174 )
175
175
176 password_change = v.StringBoolean(if_missing=False)
176 password_change = v.StringBoolean(if_missing=False)
177 create_repo_group = v.StringBoolean(if_missing=False)
177 create_repo_group = v.StringBoolean(if_missing=False)
178
178
179 active = v.StringBoolean(if_missing=False)
179 active = v.StringBoolean(if_missing=False)
180 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
180 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
181 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
181 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
182 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
182 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
183 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
183 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
184 if_missing='')
184 if_missing='')
185 extern_name = v.UnicodeString(strip=True)
185 extern_name = v.UnicodeString(strip=True)
186 extern_type = v.UnicodeString(strip=True)
186 extern_type = v.UnicodeString(strip=True)
187 language = v.OneOf(available_languages, hideList=False,
187 language = v.OneOf(available_languages, hideList=False,
188 testValueList=True, if_missing=None)
188 testValueList=True, if_missing=None)
189 chained_validators = [v.ValidPasswordsMatch(localizer)]
189 chained_validators = [v.ValidPasswordsMatch(localizer)]
190 return _UserForm
190 return _UserForm
191
191
192
192
193 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
193 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
194 old_data = old_data or {}
194 old_data = old_data or {}
195 _ = localizer
195 _ = localizer
196
196
197 class _UserGroupForm(formencode.Schema):
197 class _UserGroupForm(formencode.Schema):
198 allow_extra_fields = True
198 allow_extra_fields = True
199 filter_extra_fields = True
199 filter_extra_fields = True
200
200
201 users_group_name = All(
201 users_group_name = All(
202 v.UnicodeString(strip=True, min=1, not_empty=True),
202 v.UnicodeString(strip=True, min=1, not_empty=True),
203 v.ValidUserGroup(localizer, edit, old_data)
203 v.ValidUserGroup(localizer, edit, old_data)
204 )
204 )
205 user_group_description = v.UnicodeString(strip=True, min=1,
205 user_group_description = v.UnicodeString(strip=True, min=1,
206 not_empty=False)
206 not_empty=False)
207
207
208 users_group_active = v.StringBoolean(if_missing=False)
208 users_group_active = v.StringBoolean(if_missing=False)
209
209
210 if edit:
210 if edit:
211 # this is user group owner
211 # this is user group owner
212 user = All(
212 user = All(
213 v.UnicodeString(not_empty=True),
213 v.UnicodeString(not_empty=True),
214 v.ValidRepoUser(localizer, allow_disabled))
214 v.ValidRepoUser(localizer, allow_disabled))
215 return _UserGroupForm
215 return _UserGroupForm
216
216
217
217
218 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
218 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
219 can_create_in_root=False, allow_disabled=False):
219 can_create_in_root=False, allow_disabled=False):
220 _ = localizer
220 _ = localizer
221 old_data = old_data or {}
221 old_data = old_data or {}
222 available_groups = available_groups or []
222 available_groups = available_groups or []
223
223
224 class _RepoGroupForm(formencode.Schema):
224 class _RepoGroupForm(formencode.Schema):
225 allow_extra_fields = True
225 allow_extra_fields = True
226 filter_extra_fields = False
226 filter_extra_fields = False
227
227
228 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
228 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 v.SlugifyName(localizer),)
229 v.SlugifyName(localizer),)
230 group_description = v.UnicodeString(strip=True, min=1,
230 group_description = v.UnicodeString(strip=True, min=1,
231 not_empty=False)
231 not_empty=False)
232 group_copy_permissions = v.StringBoolean(if_missing=False)
232 group_copy_permissions = v.StringBoolean(if_missing=False)
233
233
234 group_parent_id = v.OneOf(available_groups, hideList=False,
234 group_parent_id = v.OneOf(available_groups, hideList=False,
235 testValueList=True, not_empty=True)
235 testValueList=True, not_empty=True)
236 enable_locking = v.StringBoolean(if_missing=False)
236 enable_locking = v.StringBoolean(if_missing=False)
237 chained_validators = [
237 chained_validators = [
238 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
238 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
239
239
240 if edit:
240 if edit:
241 # this is repo group owner
241 # this is repo group owner
242 user = All(
242 user = All(
243 v.UnicodeString(not_empty=True),
243 v.UnicodeString(not_empty=True),
244 v.ValidRepoUser(localizer, allow_disabled))
244 v.ValidRepoUser(localizer, allow_disabled))
245 return _RepoGroupForm
245 return _RepoGroupForm
246
246
247
247
248 def RegisterForm(localizer, edit=False, old_data=None):
248 def RegisterForm(localizer, edit=False, old_data=None):
249 _ = localizer
249 _ = localizer
250 old_data = old_data or {}
250 old_data = old_data or {}
251
251
252 class _RegisterForm(formencode.Schema):
252 class _RegisterForm(formencode.Schema):
253 allow_extra_fields = True
253 allow_extra_fields = True
254 filter_extra_fields = True
254 filter_extra_fields = True
255 username = All(
255 username = All(
256 v.ValidUsername(localizer, edit, old_data),
256 v.ValidUsername(localizer, edit, old_data),
257 v.UnicodeString(strip=True, min=1, not_empty=True)
257 v.UnicodeString(strip=True, min=1, not_empty=True)
258 )
258 )
259 password = All(
259 password = All(
260 v.ValidPassword(localizer),
260 v.ValidPassword(localizer),
261 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
261 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
262 )
262 )
263 password_confirmation = All(
263 password_confirmation = All(
264 v.ValidPassword(localizer),
264 v.ValidPassword(localizer),
265 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
265 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
266 )
266 )
267 active = v.StringBoolean(if_missing=False)
267 active = v.StringBoolean(if_missing=False)
268 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
268 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
269 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
269 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
270 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
270 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
271
271
272 chained_validators = [v.ValidPasswordsMatch(localizer)]
272 chained_validators = [v.ValidPasswordsMatch(localizer)]
273 return _RegisterForm
273 return _RegisterForm
274
274
275
275
276 def PasswordResetForm(localizer):
276 def PasswordResetForm(localizer):
277 _ = localizer
277 _ = localizer
278
278
279 class _PasswordResetForm(formencode.Schema):
279 class _PasswordResetForm(formencode.Schema):
280 allow_extra_fields = True
280 allow_extra_fields = True
281 filter_extra_fields = True
281 filter_extra_fields = True
282 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
282 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
283 return _PasswordResetForm
283 return _PasswordResetForm
284
284
285
285
286 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
286 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
287 _ = localizer
287 _ = localizer
288 old_data = old_data or {}
288 old_data = old_data or {}
289 repo_groups = repo_groups or []
289 repo_groups = repo_groups or []
290 supported_backends = BACKENDS.keys()
290 supported_backends = BACKENDS.keys()
291
291
292 class _RepoForm(formencode.Schema):
292 class _RepoForm(formencode.Schema):
293 allow_extra_fields = True
293 allow_extra_fields = True
294 filter_extra_fields = False
294 filter_extra_fields = False
295 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
295 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
296 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
296 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
297 repo_group = All(v.CanWriteGroup(localizer, old_data),
297 repo_group = All(v.CanWriteGroup(localizer, old_data),
298 v.OneOf(repo_groups, hideList=True))
298 v.OneOf(repo_groups, hideList=True))
299 repo_type = v.OneOf(supported_backends, required=False,
299 repo_type = v.OneOf(supported_backends, required=False,
300 if_missing=old_data.get('repo_type'))
300 if_missing=old_data.get('repo_type'))
301 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
301 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
302 repo_private = v.StringBoolean(if_missing=False)
302 repo_private = v.StringBoolean(if_missing=False)
303 repo_copy_permissions = v.StringBoolean(if_missing=False)
303 repo_copy_permissions = v.StringBoolean(if_missing=False)
304 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
304 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
305
305
306 repo_enable_statistics = v.StringBoolean(if_missing=False)
306 repo_enable_statistics = v.StringBoolean(if_missing=False)
307 repo_enable_downloads = v.StringBoolean(if_missing=False)
307 repo_enable_downloads = v.StringBoolean(if_missing=False)
308 repo_enable_locking = v.StringBoolean(if_missing=False)
308 repo_enable_locking = v.StringBoolean(if_missing=False)
309
309
310 if edit:
310 if edit:
311 # this is repo owner
311 # this is repo owner
312 user = All(
312 user = All(
313 v.UnicodeString(not_empty=True),
313 v.UnicodeString(not_empty=True),
314 v.ValidRepoUser(localizer, allow_disabled))
314 v.ValidRepoUser(localizer, allow_disabled))
315 clone_uri_change = v.UnicodeString(
315 clone_uri_change = v.UnicodeString(
316 not_empty=False, if_missing=v.Missing)
316 not_empty=False, if_missing=v.Missing)
317
317
318 chained_validators = [v.ValidCloneUri(localizer),
318 chained_validators = [v.ValidCloneUri(localizer),
319 v.ValidRepoName(localizer, edit, old_data)]
319 v.ValidRepoName(localizer, edit, old_data)]
320 return _RepoForm
320 return _RepoForm
321
321
322
322
323 def RepoPermsForm(localizer):
323 def RepoPermsForm(localizer):
324 _ = localizer
324 _ = localizer
325
325
326 class _RepoPermsForm(formencode.Schema):
326 class _RepoPermsForm(formencode.Schema):
327 allow_extra_fields = True
327 allow_extra_fields = True
328 filter_extra_fields = False
328 filter_extra_fields = False
329 chained_validators = [v.ValidPerms(localizer, type_='repo')]
329 chained_validators = [v.ValidPerms(localizer, type_='repo')]
330 return _RepoPermsForm
330 return _RepoPermsForm
331
331
332
332
333 def RepoGroupPermsForm(localizer, valid_recursive_choices):
333 def RepoGroupPermsForm(localizer, valid_recursive_choices):
334 _ = localizer
334 _ = localizer
335
335
336 class _RepoGroupPermsForm(formencode.Schema):
336 class _RepoGroupPermsForm(formencode.Schema):
337 allow_extra_fields = True
337 allow_extra_fields = True
338 filter_extra_fields = False
338 filter_extra_fields = False
339 recursive = v.OneOf(valid_recursive_choices)
339 recursive = v.OneOf(valid_recursive_choices)
340 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
340 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
341 return _RepoGroupPermsForm
341 return _RepoGroupPermsForm
342
342
343
343
344 def UserGroupPermsForm(localizer):
344 def UserGroupPermsForm(localizer):
345 _ = localizer
345 _ = localizer
346
346
347 class _UserPermsForm(formencode.Schema):
347 class _UserPermsForm(formencode.Schema):
348 allow_extra_fields = True
348 allow_extra_fields = True
349 filter_extra_fields = False
349 filter_extra_fields = False
350 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
350 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
351 return _UserPermsForm
351 return _UserPermsForm
352
352
353
353
354 def RepoFieldForm(localizer):
354 def RepoFieldForm(localizer):
355 _ = localizer
355 _ = localizer
356
356
357 class _RepoFieldForm(formencode.Schema):
357 class _RepoFieldForm(formencode.Schema):
358 filter_extra_fields = True
358 filter_extra_fields = True
359 allow_extra_fields = True
359 allow_extra_fields = True
360
360
361 new_field_key = All(v.FieldKey(localizer),
361 new_field_key = All(v.FieldKey(localizer),
362 v.UnicodeString(strip=True, min=3, not_empty=True))
362 v.UnicodeString(strip=True, min=3, not_empty=True))
363 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
363 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
364 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
364 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
365 if_missing='str')
365 if_missing='str')
366 new_field_label = v.UnicodeString(not_empty=False)
366 new_field_label = v.UnicodeString(not_empty=False)
367 new_field_desc = v.UnicodeString(not_empty=False)
367 new_field_desc = v.UnicodeString(not_empty=False)
368 return _RepoFieldForm
368 return _RepoFieldForm
369
369
370
370
371 def RepoForkForm(localizer, edit=False, old_data=None,
371 def RepoForkForm(localizer, edit=False, old_data=None,
372 supported_backends=BACKENDS.keys(), repo_groups=None):
372 supported_backends=BACKENDS.keys(), repo_groups=None):
373 _ = localizer
373 _ = localizer
374 old_data = old_data or {}
374 old_data = old_data or {}
375 repo_groups = repo_groups or []
375 repo_groups = repo_groups or []
376
376
377 class _RepoForkForm(formencode.Schema):
377 class _RepoForkForm(formencode.Schema):
378 allow_extra_fields = True
378 allow_extra_fields = True
379 filter_extra_fields = False
379 filter_extra_fields = False
380 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
380 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
381 v.SlugifyName(localizer))
381 v.SlugifyName(localizer))
382 repo_group = All(v.CanWriteGroup(localizer, ),
382 repo_group = All(v.CanWriteGroup(localizer, ),
383 v.OneOf(repo_groups, hideList=True))
383 v.OneOf(repo_groups, hideList=True))
384 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
384 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
385 description = v.UnicodeString(strip=True, min=1, not_empty=True)
385 description = v.UnicodeString(strip=True, min=1, not_empty=True)
386 private = v.StringBoolean(if_missing=False)
386 private = v.StringBoolean(if_missing=False)
387 copy_permissions = v.StringBoolean(if_missing=False)
387 copy_permissions = v.StringBoolean(if_missing=False)
388 fork_parent_id = v.UnicodeString()
388 fork_parent_id = v.UnicodeString()
389 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
389 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
390 return _RepoForkForm
390 return _RepoForkForm
391
391
392
392
393 def ApplicationSettingsForm(localizer):
393 def ApplicationSettingsForm(localizer):
394 _ = localizer
394 _ = localizer
395
395
396 class _ApplicationSettingsForm(formencode.Schema):
396 class _ApplicationSettingsForm(formencode.Schema):
397 allow_extra_fields = True
397 allow_extra_fields = True
398 filter_extra_fields = False
398 filter_extra_fields = False
399 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
399 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
400 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
400 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
401 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
401 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
402 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
402 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
403 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
403 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
404 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
404 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
405 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
405 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
406 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
406 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
407 return _ApplicationSettingsForm
407 return _ApplicationSettingsForm
408
408
409
409
410 def ApplicationVisualisationForm(localizer):
410 def ApplicationVisualisationForm(localizer):
411 from rhodecode.model.db import Repository
411 from rhodecode.model.db import Repository
412 _ = localizer
412 _ = localizer
413
413
414 class _ApplicationVisualisationForm(formencode.Schema):
414 class _ApplicationVisualisationForm(formencode.Schema):
415 allow_extra_fields = True
415 allow_extra_fields = True
416 filter_extra_fields = False
416 filter_extra_fields = False
417 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
417 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
418 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
418 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
419 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
419 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
420
420
421 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
421 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
422 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
422 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
423 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
423 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
424 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
424 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
425 rhodecode_show_version = v.StringBoolean(if_missing=False)
425 rhodecode_show_version = v.StringBoolean(if_missing=False)
426 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
426 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
427 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
427 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
428 rhodecode_gravatar_url = v.UnicodeString(min=3)
428 rhodecode_gravatar_url = v.UnicodeString(min=3)
429 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
429 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
430 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
430 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
431 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
431 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
432 rhodecode_support_url = v.UnicodeString()
432 rhodecode_support_url = v.UnicodeString()
433 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
433 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
434 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
434 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
435 return _ApplicationVisualisationForm
435 return _ApplicationVisualisationForm
436
436
437
437
438 class _BaseVcsSettingsForm(formencode.Schema):
438 class _BaseVcsSettingsForm(formencode.Schema):
439
439
440 allow_extra_fields = True
440 allow_extra_fields = True
441 filter_extra_fields = False
441 filter_extra_fields = False
442 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
442 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
443 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
443 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
444 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
444 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
445
445
446 # PR/Code-review
446 # PR/Code-review
447 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
447 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
448 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
448 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
449
449
450 # hg
450 # hg
451 extensions_largefiles = v.StringBoolean(if_missing=False)
451 extensions_largefiles = v.StringBoolean(if_missing=False)
452 extensions_evolve = v.StringBoolean(if_missing=False)
452 extensions_evolve = v.StringBoolean(if_missing=False)
453 phases_publish = v.StringBoolean(if_missing=False)
453 phases_publish = v.StringBoolean(if_missing=False)
454
454
455 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
455 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
456 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
456 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
457
457
458 # git
458 # git
459 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
459 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
460 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
460 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
461 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
461 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
462
462
463 # cache
463 # cache
464 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
464 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
465
465
466
466
467 def ApplicationUiSettingsForm(localizer):
467 def ApplicationUiSettingsForm(localizer):
468 _ = localizer
468 _ = localizer
469
469
470 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
470 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
471 web_push_ssl = v.StringBoolean(if_missing=False)
472 extensions_hggit = v.StringBoolean(if_missing=False)
471 extensions_hggit = v.StringBoolean(if_missing=False)
473 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
472 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
474 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
473 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
475 return _ApplicationUiSettingsForm
474 return _ApplicationUiSettingsForm
476
475
477
476
478 def RepoVcsSettingsForm(localizer, repo_name):
477 def RepoVcsSettingsForm(localizer, repo_name):
479 _ = localizer
478 _ = localizer
480
479
481 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
480 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
482 inherit_global_settings = v.StringBoolean(if_missing=False)
481 inherit_global_settings = v.StringBoolean(if_missing=False)
483 new_svn_branch = v.ValidSvnPattern(localizer,
482 new_svn_branch = v.ValidSvnPattern(localizer,
484 section='vcs_svn_branch', repo_name=repo_name)
483 section='vcs_svn_branch', repo_name=repo_name)
485 new_svn_tag = v.ValidSvnPattern(localizer,
484 new_svn_tag = v.ValidSvnPattern(localizer,
486 section='vcs_svn_tag', repo_name=repo_name)
485 section='vcs_svn_tag', repo_name=repo_name)
487 return _RepoVcsSettingsForm
486 return _RepoVcsSettingsForm
488
487
489
488
490 def LabsSettingsForm(localizer):
489 def LabsSettingsForm(localizer):
491 _ = localizer
490 _ = localizer
492
491
493 class _LabSettingsForm(formencode.Schema):
492 class _LabSettingsForm(formencode.Schema):
494 allow_extra_fields = True
493 allow_extra_fields = True
495 filter_extra_fields = False
494 filter_extra_fields = False
496 return _LabSettingsForm
495 return _LabSettingsForm
497
496
498
497
499 def ApplicationPermissionsForm(
498 def ApplicationPermissionsForm(
500 localizer, register_choices, password_reset_choices,
499 localizer, register_choices, password_reset_choices,
501 extern_activate_choices):
500 extern_activate_choices):
502 _ = localizer
501 _ = localizer
503
502
504 class _DefaultPermissionsForm(formencode.Schema):
503 class _DefaultPermissionsForm(formencode.Schema):
505 allow_extra_fields = True
504 allow_extra_fields = True
506 filter_extra_fields = True
505 filter_extra_fields = True
507
506
508 anonymous = v.StringBoolean(if_missing=False)
507 anonymous = v.StringBoolean(if_missing=False)
509 default_register = v.OneOf(register_choices)
508 default_register = v.OneOf(register_choices)
510 default_register_message = v.UnicodeString()
509 default_register_message = v.UnicodeString()
511 default_password_reset = v.OneOf(password_reset_choices)
510 default_password_reset = v.OneOf(password_reset_choices)
512 default_extern_activate = v.OneOf(extern_activate_choices)
511 default_extern_activate = v.OneOf(extern_activate_choices)
513 return _DefaultPermissionsForm
512 return _DefaultPermissionsForm
514
513
515
514
516 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
515 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
517 user_group_perms_choices):
516 user_group_perms_choices):
518 _ = localizer
517 _ = localizer
519
518
520 class _ObjectPermissionsForm(formencode.Schema):
519 class _ObjectPermissionsForm(formencode.Schema):
521 allow_extra_fields = True
520 allow_extra_fields = True
522 filter_extra_fields = True
521 filter_extra_fields = True
523 overwrite_default_repo = v.StringBoolean(if_missing=False)
522 overwrite_default_repo = v.StringBoolean(if_missing=False)
524 overwrite_default_group = v.StringBoolean(if_missing=False)
523 overwrite_default_group = v.StringBoolean(if_missing=False)
525 overwrite_default_user_group = v.StringBoolean(if_missing=False)
524 overwrite_default_user_group = v.StringBoolean(if_missing=False)
526
525
527 default_repo_perm = v.OneOf(repo_perms_choices)
526 default_repo_perm = v.OneOf(repo_perms_choices)
528 default_group_perm = v.OneOf(group_perms_choices)
527 default_group_perm = v.OneOf(group_perms_choices)
529 default_user_group_perm = v.OneOf(user_group_perms_choices)
528 default_user_group_perm = v.OneOf(user_group_perms_choices)
530
529
531 return _ObjectPermissionsForm
530 return _ObjectPermissionsForm
532
531
533
532
534 def BranchPermissionsForm(localizer, branch_perms_choices):
533 def BranchPermissionsForm(localizer, branch_perms_choices):
535 _ = localizer
534 _ = localizer
536
535
537 class _BranchPermissionsForm(formencode.Schema):
536 class _BranchPermissionsForm(formencode.Schema):
538 allow_extra_fields = True
537 allow_extra_fields = True
539 filter_extra_fields = True
538 filter_extra_fields = True
540 overwrite_default_branch = v.StringBoolean(if_missing=False)
539 overwrite_default_branch = v.StringBoolean(if_missing=False)
541 default_branch_perm = v.OneOf(branch_perms_choices)
540 default_branch_perm = v.OneOf(branch_perms_choices)
542
541
543 return _BranchPermissionsForm
542 return _BranchPermissionsForm
544
543
545
544
546 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
545 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
547 repo_group_create_choices, user_group_create_choices,
546 repo_group_create_choices, user_group_create_choices,
548 fork_choices, inherit_default_permissions_choices):
547 fork_choices, inherit_default_permissions_choices):
549 _ = localizer
548 _ = localizer
550
549
551 class _DefaultPermissionsForm(formencode.Schema):
550 class _DefaultPermissionsForm(formencode.Schema):
552 allow_extra_fields = True
551 allow_extra_fields = True
553 filter_extra_fields = True
552 filter_extra_fields = True
554
553
555 anonymous = v.StringBoolean(if_missing=False)
554 anonymous = v.StringBoolean(if_missing=False)
556
555
557 default_repo_create = v.OneOf(create_choices)
556 default_repo_create = v.OneOf(create_choices)
558 default_repo_create_on_write = v.OneOf(create_on_write_choices)
557 default_repo_create_on_write = v.OneOf(create_on_write_choices)
559 default_user_group_create = v.OneOf(user_group_create_choices)
558 default_user_group_create = v.OneOf(user_group_create_choices)
560 default_repo_group_create = v.OneOf(repo_group_create_choices)
559 default_repo_group_create = v.OneOf(repo_group_create_choices)
561 default_fork_create = v.OneOf(fork_choices)
560 default_fork_create = v.OneOf(fork_choices)
562 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
561 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
563 return _DefaultPermissionsForm
562 return _DefaultPermissionsForm
564
563
565
564
566 def UserIndividualPermissionsForm(localizer):
565 def UserIndividualPermissionsForm(localizer):
567 _ = localizer
566 _ = localizer
568
567
569 class _DefaultPermissionsForm(formencode.Schema):
568 class _DefaultPermissionsForm(formencode.Schema):
570 allow_extra_fields = True
569 allow_extra_fields = True
571 filter_extra_fields = True
570 filter_extra_fields = True
572
571
573 inherit_default_permissions = v.StringBoolean(if_missing=False)
572 inherit_default_permissions = v.StringBoolean(if_missing=False)
574 return _DefaultPermissionsForm
573 return _DefaultPermissionsForm
575
574
576
575
577 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
576 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
578 _ = localizer
577 _ = localizer
579 old_data = old_data or {}
578 old_data = old_data or {}
580
579
581 class _DefaultsForm(formencode.Schema):
580 class _DefaultsForm(formencode.Schema):
582 allow_extra_fields = True
581 allow_extra_fields = True
583 filter_extra_fields = True
582 filter_extra_fields = True
584 default_repo_type = v.OneOf(supported_backends)
583 default_repo_type = v.OneOf(supported_backends)
585 default_repo_private = v.StringBoolean(if_missing=False)
584 default_repo_private = v.StringBoolean(if_missing=False)
586 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
585 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
587 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
586 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
588 default_repo_enable_locking = v.StringBoolean(if_missing=False)
587 default_repo_enable_locking = v.StringBoolean(if_missing=False)
589 return _DefaultsForm
588 return _DefaultsForm
590
589
591
590
592 def AuthSettingsForm(localizer):
591 def AuthSettingsForm(localizer):
593 _ = localizer
592 _ = localizer
594
593
595 class _AuthSettingsForm(formencode.Schema):
594 class _AuthSettingsForm(formencode.Schema):
596 allow_extra_fields = True
595 allow_extra_fields = True
597 filter_extra_fields = True
596 filter_extra_fields = True
598 auth_plugins = All(v.ValidAuthPlugins(localizer),
597 auth_plugins = All(v.ValidAuthPlugins(localizer),
599 v.UniqueListFromString(localizer)(not_empty=True))
598 v.UniqueListFromString(localizer)(not_empty=True))
600 return _AuthSettingsForm
599 return _AuthSettingsForm
601
600
602
601
603 def UserExtraEmailForm(localizer):
602 def UserExtraEmailForm(localizer):
604 _ = localizer
603 _ = localizer
605
604
606 class _UserExtraEmailForm(formencode.Schema):
605 class _UserExtraEmailForm(formencode.Schema):
607 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
606 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
608 return _UserExtraEmailForm
607 return _UserExtraEmailForm
609
608
610
609
611 def UserExtraIpForm(localizer):
610 def UserExtraIpForm(localizer):
612 _ = localizer
611 _ = localizer
613
612
614 class _UserExtraIpForm(formencode.Schema):
613 class _UserExtraIpForm(formencode.Schema):
615 ip = v.ValidIp(localizer)(not_empty=True)
614 ip = v.ValidIp(localizer)(not_empty=True)
616 return _UserExtraIpForm
615 return _UserExtraIpForm
617
616
618
617
619 def PullRequestForm(localizer, repo_id):
618 def PullRequestForm(localizer, repo_id):
620 _ = localizer
619 _ = localizer
621
620
622 class ReviewerForm(formencode.Schema):
621 class ReviewerForm(formencode.Schema):
623 user_id = v.Int(not_empty=True)
622 user_id = v.Int(not_empty=True)
624 reasons = All()
623 reasons = All()
625 rules = All(v.UniqueList(localizer, convert=int)())
624 rules = All(v.UniqueList(localizer, convert=int)())
626 mandatory = v.StringBoolean()
625 mandatory = v.StringBoolean()
627 role = v.String(if_missing='reviewer')
626 role = v.String(if_missing='reviewer')
628
627
629 class ObserverForm(formencode.Schema):
628 class ObserverForm(formencode.Schema):
630 user_id = v.Int(not_empty=True)
629 user_id = v.Int(not_empty=True)
631 reasons = All()
630 reasons = All()
632 rules = All(v.UniqueList(localizer, convert=int)())
631 rules = All(v.UniqueList(localizer, convert=int)())
633 mandatory = v.StringBoolean()
632 mandatory = v.StringBoolean()
634 role = v.String(if_missing='observer')
633 role = v.String(if_missing='observer')
635
634
636 class _PullRequestForm(formencode.Schema):
635 class _PullRequestForm(formencode.Schema):
637 allow_extra_fields = True
636 allow_extra_fields = True
638 filter_extra_fields = True
637 filter_extra_fields = True
639
638
640 common_ancestor = v.UnicodeString(strip=True, required=True)
639 common_ancestor = v.UnicodeString(strip=True, required=True)
641 source_repo = v.UnicodeString(strip=True, required=True)
640 source_repo = v.UnicodeString(strip=True, required=True)
642 source_ref = v.UnicodeString(strip=True, required=True)
641 source_ref = v.UnicodeString(strip=True, required=True)
643 target_repo = v.UnicodeString(strip=True, required=True)
642 target_repo = v.UnicodeString(strip=True, required=True)
644 target_ref = v.UnicodeString(strip=True, required=True)
643 target_ref = v.UnicodeString(strip=True, required=True)
645 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
644 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
646 v.UniqueList(localizer)(not_empty=True))
645 v.UniqueList(localizer)(not_empty=True))
647 review_members = formencode.ForEach(ReviewerForm())
646 review_members = formencode.ForEach(ReviewerForm())
648 observer_members = formencode.ForEach(ObserverForm())
647 observer_members = formencode.ForEach(ObserverForm())
649 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
648 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
650 pullrequest_desc = v.UnicodeString(strip=True, required=False)
649 pullrequest_desc = v.UnicodeString(strip=True, required=False)
651 description_renderer = v.UnicodeString(strip=True, required=False)
650 description_renderer = v.UnicodeString(strip=True, required=False)
652
651
653 return _PullRequestForm
652 return _PullRequestForm
654
653
655
654
656 def IssueTrackerPatternsForm(localizer):
655 def IssueTrackerPatternsForm(localizer):
657 _ = localizer
656 _ = localizer
658
657
659 class _IssueTrackerPatternsForm(formencode.Schema):
658 class _IssueTrackerPatternsForm(formencode.Schema):
660 allow_extra_fields = True
659 allow_extra_fields = True
661 filter_extra_fields = False
660 filter_extra_fields = False
662 chained_validators = [v.ValidPattern(localizer)]
661 chained_validators = [v.ValidPattern(localizer)]
663 return _IssueTrackerPatternsForm
662 return _IssueTrackerPatternsForm
@@ -1,893 +1,888 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import re
20 import re
21 import logging
21 import logging
22 import time
22 import time
23 import functools
23 import functools
24 from collections import namedtuple
24 from collections import namedtuple
25
25
26 from pyramid.threadlocal import get_current_request
26 from pyramid.threadlocal import get_current_request
27
27
28 from rhodecode.lib import rc_cache
28 from rhodecode.lib import rc_cache
29 from rhodecode.lib.hash_utils import sha1_safe
29 from rhodecode.lib.hash_utils import sha1_safe
30 from rhodecode.lib.html_filters import sanitize_html
30 from rhodecode.lib.html_filters import sanitize_html
31 from rhodecode.lib.utils2 import (
31 from rhodecode.lib.utils2 import (
32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
33 from rhodecode.lib.vcs.backends import base
33 from rhodecode.lib.vcs.backends import base
34 from rhodecode.lib.statsd_client import StatsdClient
34 from rhodecode.lib.statsd_client import StatsdClient
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import (
36 from rhodecode.model.db import (
37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 UiSetting = namedtuple(
44 UiSetting = namedtuple(
45 'UiSetting', ['section', 'key', 'value', 'active'])
45 'UiSetting', ['section', 'key', 'value', 'active'])
46
46
47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
48
48
49
49
50 class SettingNotFound(Exception):
50 class SettingNotFound(Exception):
51 def __init__(self, setting_id):
51 def __init__(self, setting_id):
52 msg = f'Setting `{setting_id}` is not found'
52 msg = f'Setting `{setting_id}` is not found'
53 super().__init__(msg)
53 super().__init__(msg)
54
54
55
55
56 class SettingsModel(BaseModel):
56 class SettingsModel(BaseModel):
57 BUILTIN_HOOKS = (
57 BUILTIN_HOOKS = (
58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
61 RhodeCodeUi.HOOK_PUSH_KEY,)
61 RhodeCodeUi.HOOK_PUSH_KEY,)
62 HOOKS_SECTION = 'hooks'
62 HOOKS_SECTION = 'hooks'
63
63
64 def __init__(self, sa=None, repo=None):
64 def __init__(self, sa=None, repo=None):
65 self.repo = repo
65 self.repo = repo
66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
67 self.SettingsDbModel = (
67 self.SettingsDbModel = (
68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
69 super().__init__(sa)
69 super().__init__(sa)
70
70
71 def get_keyname(self, key_name, prefix='rhodecode_'):
71 def get_keyname(self, key_name, prefix='rhodecode_'):
72 return f'{prefix}{key_name}'
72 return f'{prefix}{key_name}'
73
73
74 def get_ui_by_key(self, key):
74 def get_ui_by_key(self, key):
75 q = self.UiDbModel.query()
75 q = self.UiDbModel.query()
76 q = q.filter(self.UiDbModel.ui_key == key)
76 q = q.filter(self.UiDbModel.ui_key == key)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 return q.scalar()
78 return q.scalar()
79
79
80 def get_ui_by_section(self, section):
80 def get_ui_by_section(self, section):
81 q = self.UiDbModel.query()
81 q = self.UiDbModel.query()
82 q = q.filter(self.UiDbModel.ui_section == section)
82 q = q.filter(self.UiDbModel.ui_section == section)
83 q = self._filter_by_repo(RepoRhodeCodeUi, q)
83 q = self._filter_by_repo(RepoRhodeCodeUi, q)
84 return q.all()
84 return q.all()
85
85
86 def get_ui_by_section_and_key(self, section, key):
86 def get_ui_by_section_and_key(self, section, key):
87 q = self.UiDbModel.query()
87 q = self.UiDbModel.query()
88 q = q.filter(self.UiDbModel.ui_section == section)
88 q = q.filter(self.UiDbModel.ui_section == section)
89 q = q.filter(self.UiDbModel.ui_key == key)
89 q = q.filter(self.UiDbModel.ui_key == key)
90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
91 return q.scalar()
91 return q.scalar()
92
92
93 def get_ui(self, section=None, key=None):
93 def get_ui(self, section=None, key=None):
94 q = self.UiDbModel.query()
94 q = self.UiDbModel.query()
95 q = self._filter_by_repo(RepoRhodeCodeUi, q)
95 q = self._filter_by_repo(RepoRhodeCodeUi, q)
96
96
97 if section:
97 if section:
98 q = q.filter(self.UiDbModel.ui_section == section)
98 q = q.filter(self.UiDbModel.ui_section == section)
99 if key:
99 if key:
100 q = q.filter(self.UiDbModel.ui_key == key)
100 q = q.filter(self.UiDbModel.ui_key == key)
101
101
102 # TODO: mikhail: add caching
102 # TODO: mikhail: add caching
103 result = [
103 result = [
104 UiSetting(
104 UiSetting(
105 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
105 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
106 value=safe_str(r.ui_value), active=r.ui_active
106 value=safe_str(r.ui_value), active=r.ui_active
107 )
107 )
108 for r in q.all()
108 for r in q.all()
109 ]
109 ]
110 return result
110 return result
111
111
112 def get_builtin_hooks(self):
112 def get_builtin_hooks(self):
113 q = self.UiDbModel.query()
113 q = self.UiDbModel.query()
114 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
115 return self._get_hooks(q)
115 return self._get_hooks(q)
116
116
117 def get_custom_hooks(self):
117 def get_custom_hooks(self):
118 q = self.UiDbModel.query()
118 q = self.UiDbModel.query()
119 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
119 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
120 return self._get_hooks(q)
120 return self._get_hooks(q)
121
121
122 def create_ui_section_value(self, section, val, key=None, active=True):
122 def create_ui_section_value(self, section, val, key=None, active=True):
123 new_ui = self.UiDbModel()
123 new_ui = self.UiDbModel()
124 new_ui.ui_section = section
124 new_ui.ui_section = section
125 new_ui.ui_value = val
125 new_ui.ui_value = val
126 new_ui.ui_active = active
126 new_ui.ui_active = active
127
127
128 repository_id = ''
128 repository_id = ''
129 if self.repo:
129 if self.repo:
130 repo = self._get_repo(self.repo)
130 repo = self._get_repo(self.repo)
131 repository_id = repo.repo_id
131 repository_id = repo.repo_id
132 new_ui.repository_id = repository_id
132 new_ui.repository_id = repository_id
133
133
134 if not key:
134 if not key:
135 # keys are unique so they need appended info
135 # keys are unique so they need appended info
136 if self.repo:
136 if self.repo:
137 key = sha1_safe(f'{section}{val}{repository_id}')
137 key = sha1_safe(f'{section}{val}{repository_id}')
138 else:
138 else:
139 key = sha1_safe(f'{section}{val}')
139 key = sha1_safe(f'{section}{val}')
140
140
141 new_ui.ui_key = key
141 new_ui.ui_key = key
142
142
143 Session().add(new_ui)
143 Session().add(new_ui)
144 return new_ui
144 return new_ui
145
145
146 def create_or_update_hook(self, key, value):
146 def create_or_update_hook(self, key, value):
147 ui = (
147 ui = (
148 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
148 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
149 self.UiDbModel())
149 self.UiDbModel())
150 ui.ui_section = self.HOOKS_SECTION
150 ui.ui_section = self.HOOKS_SECTION
151 ui.ui_active = True
151 ui.ui_active = True
152 ui.ui_key = key
152 ui.ui_key = key
153 ui.ui_value = value
153 ui.ui_value = value
154
154
155 if self.repo:
155 if self.repo:
156 repo = self._get_repo(self.repo)
156 repo = self._get_repo(self.repo)
157 repository_id = repo.repo_id
157 repository_id = repo.repo_id
158 ui.repository_id = repository_id
158 ui.repository_id = repository_id
159
159
160 Session().add(ui)
160 Session().add(ui)
161 return ui
161 return ui
162
162
163 def delete_ui(self, id_):
163 def delete_ui(self, id_):
164 ui = self.UiDbModel.get(id_)
164 ui = self.UiDbModel.get(id_)
165 if not ui:
165 if not ui:
166 raise SettingNotFound(id_)
166 raise SettingNotFound(id_)
167 Session().delete(ui)
167 Session().delete(ui)
168
168
169 def get_setting_by_name(self, name):
169 def get_setting_by_name(self, name):
170 q = self._get_settings_query()
170 q = self._get_settings_query()
171 q = q.filter(self.SettingsDbModel.app_settings_name == name)
171 q = q.filter(self.SettingsDbModel.app_settings_name == name)
172 return q.scalar()
172 return q.scalar()
173
173
174 def create_or_update_setting(
174 def create_or_update_setting(
175 self, name, val: Optional | str = Optional(''), type_: Optional | str = Optional('unicode')):
175 self, name, val: Optional | str = Optional(''), type_: Optional | str = Optional('unicode')):
176 """
176 """
177 Creates or updates RhodeCode setting. If updates are triggered, it will
177 Creates or updates RhodeCode setting. If updates are triggered, it will
178 only update parameters that are explicitly set Optional instance will
178 only update parameters that are explicitly set Optional instance will
179 be skipped
179 be skipped
180
180
181 :param name:
181 :param name:
182 :param val:
182 :param val:
183 :param type_:
183 :param type_:
184 :return:
184 :return:
185 """
185 """
186
186
187 res = self.get_setting_by_name(name)
187 res = self.get_setting_by_name(name)
188 repo = self._get_repo(self.repo) if self.repo else None
188 repo = self._get_repo(self.repo) if self.repo else None
189
189
190 if not res:
190 if not res:
191 val = Optional.extract(val)
191 val = Optional.extract(val)
192 type_ = Optional.extract(type_)
192 type_ = Optional.extract(type_)
193
193
194 args = (
194 args = (
195 (repo.repo_id, name, val, type_)
195 (repo.repo_id, name, val, type_)
196 if repo else (name, val, type_))
196 if repo else (name, val, type_))
197 res = self.SettingsDbModel(*args)
197 res = self.SettingsDbModel(*args)
198
198
199 else:
199 else:
200 if self.repo:
200 if self.repo:
201 res.repository_id = repo.repo_id
201 res.repository_id = repo.repo_id
202
202
203 res.app_settings_name = name
203 res.app_settings_name = name
204 if not isinstance(type_, Optional):
204 if not isinstance(type_, Optional):
205 # update if set
205 # update if set
206 res.app_settings_type = type_
206 res.app_settings_type = type_
207 if not isinstance(val, Optional):
207 if not isinstance(val, Optional):
208 # update if set
208 # update if set
209 res.app_settings_value = val
209 res.app_settings_value = val
210
210
211 Session().add(res)
211 Session().add(res)
212 return res
212 return res
213
213
214 def get_cache_region(self):
214 def get_cache_region(self):
215 repo = self._get_repo(self.repo) if self.repo else None
215 repo = self._get_repo(self.repo) if self.repo else None
216 cache_key = f"repo.v1.{repo.repo_id}" if repo else "repo.v1.ALL"
216 cache_key = f"repo.v1.{repo.repo_id}" if repo else "repo.v1.ALL"
217 cache_namespace_uid = f'cache_settings.{cache_key}'
217 cache_namespace_uid = f'cache_settings.{cache_key}'
218 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
218 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
219 return region, cache_namespace_uid
219 return region, cache_namespace_uid
220
220
221 def invalidate_settings_cache(self, hard=False):
221 def invalidate_settings_cache(self, hard=False):
222 region, namespace_key = self.get_cache_region()
222 region, namespace_key = self.get_cache_region()
223 log.debug('Invalidation cache [%s] region %s for cache_key: %s',
223 log.debug('Invalidation cache [%s] region %s for cache_key: %s',
224 'invalidate_settings_cache', region, namespace_key)
224 'invalidate_settings_cache', region, namespace_key)
225
225
226 # we use hard cleanup if invalidation is sent
226 # we use hard cleanup if invalidation is sent
227 rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)
227 rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)
228
228
229 def get_cache_call_method(self, cache=True):
229 def get_cache_call_method(self, cache=True):
230 region, cache_key = self.get_cache_region()
230 region, cache_key = self.get_cache_region()
231
231
232 @region.conditional_cache_on_arguments(condition=cache)
232 @region.conditional_cache_on_arguments(condition=cache)
233 def _get_all_settings(name, key):
233 def _get_all_settings(name, key):
234 q = self._get_settings_query()
234 q = self._get_settings_query()
235 if not q:
235 if not q:
236 raise Exception('Could not get application settings !')
236 raise Exception('Could not get application settings !')
237
237
238 settings = {
238 settings = {
239 self.get_keyname(res.app_settings_name): res.app_settings_value
239 self.get_keyname(res.app_settings_name): res.app_settings_value
240 for res in q
240 for res in q
241 }
241 }
242 return settings
242 return settings
243 return _get_all_settings
243 return _get_all_settings
244
244
245 def get_all_settings(self, cache=False, from_request=True):
245 def get_all_settings(self, cache=False, from_request=True):
246 # defines if we use GLOBAL, or PER_REPO
246 # defines if we use GLOBAL, or PER_REPO
247 repo = self._get_repo(self.repo) if self.repo else None
247 repo = self._get_repo(self.repo) if self.repo else None
248
248
249 # initially try the request context; this is the fastest
249 # initially try the request context; this is the fastest
250 # we only fetch global config, NOT for repo-specific
250 # we only fetch global config, NOT for repo-specific
251 if from_request and not repo:
251 if from_request and not repo:
252 request = get_current_request()
252 request = get_current_request()
253
253
254 if request and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
254 if request and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
255 rc_config = request.call_context.rc_config
255 rc_config = request.call_context.rc_config
256 if rc_config:
256 if rc_config:
257 return rc_config
257 return rc_config
258
258
259 _region, cache_key = self.get_cache_region()
259 _region, cache_key = self.get_cache_region()
260 _get_all_settings = self.get_cache_call_method(cache=cache)
260 _get_all_settings = self.get_cache_call_method(cache=cache)
261
261
262 start = time.time()
262 start = time.time()
263 result = _get_all_settings('rhodecode_settings', cache_key)
263 result = _get_all_settings('rhodecode_settings', cache_key)
264 compute_time = time.time() - start
264 compute_time = time.time() - start
265 log.debug('cached method:%s took %.4fs', _get_all_settings.__name__, compute_time)
265 log.debug('cached method:%s took %.4fs', _get_all_settings.__name__, compute_time)
266
266
267 statsd = StatsdClient.statsd
267 statsd = StatsdClient.statsd
268 if statsd:
268 if statsd:
269 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
269 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
270 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
270 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
271 use_decimals=False)
271 use_decimals=False)
272
272
273 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
273 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
274
274
275 return result
275 return result
276
276
277 def get_auth_settings(self):
277 def get_auth_settings(self):
278 q = self._get_settings_query()
278 q = self._get_settings_query()
279 q = q.filter(
279 q = q.filter(
280 self.SettingsDbModel.app_settings_name.startswith('auth_'))
280 self.SettingsDbModel.app_settings_name.startswith('auth_'))
281 rows = q.all()
281 rows = q.all()
282 auth_settings = {
282 auth_settings = {
283 row.app_settings_name: row.app_settings_value for row in rows}
283 row.app_settings_name: row.app_settings_value for row in rows}
284 return auth_settings
284 return auth_settings
285
285
286 def get_auth_plugins(self):
286 def get_auth_plugins(self):
287 auth_plugins = self.get_setting_by_name("auth_plugins")
287 auth_plugins = self.get_setting_by_name("auth_plugins")
288 return auth_plugins.app_settings_value
288 return auth_plugins.app_settings_value
289
289
290 def get_default_repo_settings(self, strip_prefix=False):
290 def get_default_repo_settings(self, strip_prefix=False):
291 q = self._get_settings_query()
291 q = self._get_settings_query()
292 q = q.filter(
292 q = q.filter(
293 self.SettingsDbModel.app_settings_name.startswith('default_'))
293 self.SettingsDbModel.app_settings_name.startswith('default_'))
294 rows = q.all()
294 rows = q.all()
295
295
296 result = {}
296 result = {}
297 for row in rows:
297 for row in rows:
298 key = row.app_settings_name
298 key = row.app_settings_name
299 if strip_prefix:
299 if strip_prefix:
300 key = remove_prefix(key, prefix='default_')
300 key = remove_prefix(key, prefix='default_')
301 result.update({key: row.app_settings_value})
301 result.update({key: row.app_settings_value})
302 return result
302 return result
303
303
304 def get_repo(self):
304 def get_repo(self):
305 repo = self._get_repo(self.repo)
305 repo = self._get_repo(self.repo)
306 if not repo:
306 if not repo:
307 raise Exception(
307 raise Exception(
308 f'Repository `{self.repo}` cannot be found inside the database')
308 f'Repository `{self.repo}` cannot be found inside the database')
309 return repo
309 return repo
310
310
311 def _filter_by_repo(self, model, query):
311 def _filter_by_repo(self, model, query):
312 if self.repo:
312 if self.repo:
313 repo = self.get_repo()
313 repo = self.get_repo()
314 query = query.filter(model.repository_id == repo.repo_id)
314 query = query.filter(model.repository_id == repo.repo_id)
315 return query
315 return query
316
316
317 def _get_hooks(self, query):
317 def _get_hooks(self, query):
318 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
318 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
319 query = self._filter_by_repo(RepoRhodeCodeUi, query)
319 query = self._filter_by_repo(RepoRhodeCodeUi, query)
320 return query.all()
320 return query.all()
321
321
322 def _get_settings_query(self):
322 def _get_settings_query(self):
323 q = self.SettingsDbModel.query()
323 q = self.SettingsDbModel.query()
324 return self._filter_by_repo(RepoRhodeCodeSetting, q)
324 return self._filter_by_repo(RepoRhodeCodeSetting, q)
325
325
326 def list_enabled_social_plugins(self, settings):
326 def list_enabled_social_plugins(self, settings):
327 enabled = []
327 enabled = []
328 for plug in SOCIAL_PLUGINS_LIST:
328 for plug in SOCIAL_PLUGINS_LIST:
329 if str2bool(settings.get(f'rhodecode_auth_{plug}_enabled')):
329 if str2bool(settings.get(f'rhodecode_auth_{plug}_enabled')):
330 enabled.append(plug)
330 enabled.append(plug)
331 return enabled
331 return enabled
332
332
333
333
334 def assert_repo_settings(func):
334 def assert_repo_settings(func):
335 @functools.wraps(func)
335 @functools.wraps(func)
336 def _wrapper(self, *args, **kwargs):
336 def _wrapper(self, *args, **kwargs):
337 if not self.repo_settings:
337 if not self.repo_settings:
338 raise Exception('Repository is not specified')
338 raise Exception('Repository is not specified')
339 return func(self, *args, **kwargs)
339 return func(self, *args, **kwargs)
340 return _wrapper
340 return _wrapper
341
341
342
342
343 class IssueTrackerSettingsModel(object):
343 class IssueTrackerSettingsModel(object):
344 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
344 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
345 SETTINGS_PREFIX = 'issuetracker_'
345 SETTINGS_PREFIX = 'issuetracker_'
346
346
347 def __init__(self, sa=None, repo=None):
347 def __init__(self, sa=None, repo=None):
348 self.global_settings = SettingsModel(sa=sa)
348 self.global_settings = SettingsModel(sa=sa)
349 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
349 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
350
350
351 @property
351 @property
352 def inherit_global_settings(self):
352 def inherit_global_settings(self):
353 if not self.repo_settings:
353 if not self.repo_settings:
354 return True
354 return True
355 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
355 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
356 return setting.app_settings_value if setting else True
356 return setting.app_settings_value if setting else True
357
357
358 @inherit_global_settings.setter
358 @inherit_global_settings.setter
359 def inherit_global_settings(self, value):
359 def inherit_global_settings(self, value):
360 if self.repo_settings:
360 if self.repo_settings:
361 settings = self.repo_settings.create_or_update_setting(
361 settings = self.repo_settings.create_or_update_setting(
362 self.INHERIT_SETTINGS, value, type_='bool')
362 self.INHERIT_SETTINGS, value, type_='bool')
363 Session().add(settings)
363 Session().add(settings)
364
364
365 def _get_keyname(self, key, uid, prefix='rhodecode_'):
365 def _get_keyname(self, key, uid, prefix='rhodecode_'):
366 return f'{prefix}{self.SETTINGS_PREFIX}{key}_{uid}'
366 return f'{prefix}{self.SETTINGS_PREFIX}{key}_{uid}'
367
367
368 def _make_dict_for_settings(self, qs):
368 def _make_dict_for_settings(self, qs):
369 prefix_match = self._get_keyname('pat', '',)
369 prefix_match = self._get_keyname('pat', '',)
370
370
371 issuetracker_entries = {}
371 issuetracker_entries = {}
372 # create keys
372 # create keys
373 for k, v in qs.items():
373 for k, v in qs.items():
374 if k.startswith(prefix_match):
374 if k.startswith(prefix_match):
375 uid = k[len(prefix_match):]
375 uid = k[len(prefix_match):]
376 issuetracker_entries[uid] = None
376 issuetracker_entries[uid] = None
377
377
378 def url_cleaner(input_str):
378 def url_cleaner(input_str):
379 input_str = input_str.replace('"', '').replace("'", '')
379 input_str = input_str.replace('"', '').replace("'", '')
380 input_str = sanitize_html(input_str, strip=True)
380 input_str = sanitize_html(input_str, strip=True)
381 return input_str
381 return input_str
382
382
383 # populate
383 # populate
384 for uid in issuetracker_entries:
384 for uid in issuetracker_entries:
385 url_data = qs.get(self._get_keyname('url', uid))
385 url_data = qs.get(self._get_keyname('url', uid))
386
386
387 pat = qs.get(self._get_keyname('pat', uid))
387 pat = qs.get(self._get_keyname('pat', uid))
388 try:
388 try:
389 pat_compiled = re.compile(r'%s' % pat)
389 pat_compiled = re.compile(r'%s' % pat)
390 except re.error:
390 except re.error:
391 pat_compiled = None
391 pat_compiled = None
392
392
393 issuetracker_entries[uid] = AttributeDict({
393 issuetracker_entries[uid] = AttributeDict({
394 'pat': pat,
394 'pat': pat,
395 'pat_compiled': pat_compiled,
395 'pat_compiled': pat_compiled,
396 'url': url_cleaner(
396 'url': url_cleaner(
397 qs.get(self._get_keyname('url', uid)) or ''),
397 qs.get(self._get_keyname('url', uid)) or ''),
398 'pref': sanitize_html(
398 'pref': sanitize_html(
399 qs.get(self._get_keyname('pref', uid)) or ''),
399 qs.get(self._get_keyname('pref', uid)) or ''),
400 'desc': qs.get(
400 'desc': qs.get(
401 self._get_keyname('desc', uid)),
401 self._get_keyname('desc', uid)),
402 })
402 })
403
403
404 return issuetracker_entries
404 return issuetracker_entries
405
405
406 def get_global_settings(self, cache=False):
406 def get_global_settings(self, cache=False):
407 """
407 """
408 Returns list of global issue tracker settings
408 Returns list of global issue tracker settings
409 """
409 """
410 defaults = self.global_settings.get_all_settings(cache=cache)
410 defaults = self.global_settings.get_all_settings(cache=cache)
411 settings = self._make_dict_for_settings(defaults)
411 settings = self._make_dict_for_settings(defaults)
412 return settings
412 return settings
413
413
414 def get_repo_settings(self, cache=False):
414 def get_repo_settings(self, cache=False):
415 """
415 """
416 Returns list of issue tracker settings per repository
416 Returns list of issue tracker settings per repository
417 """
417 """
418 if not self.repo_settings:
418 if not self.repo_settings:
419 raise Exception('Repository is not specified')
419 raise Exception('Repository is not specified')
420 all_settings = self.repo_settings.get_all_settings(cache=cache)
420 all_settings = self.repo_settings.get_all_settings(cache=cache)
421 settings = self._make_dict_for_settings(all_settings)
421 settings = self._make_dict_for_settings(all_settings)
422 return settings
422 return settings
423
423
424 def get_settings(self, cache=False):
424 def get_settings(self, cache=False):
425 if self.inherit_global_settings:
425 if self.inherit_global_settings:
426 return self.get_global_settings(cache=cache)
426 return self.get_global_settings(cache=cache)
427 else:
427 else:
428 return self.get_repo_settings(cache=cache)
428 return self.get_repo_settings(cache=cache)
429
429
430 def delete_entries(self, uid):
430 def delete_entries(self, uid):
431 if self.repo_settings:
431 if self.repo_settings:
432 all_patterns = self.get_repo_settings()
432 all_patterns = self.get_repo_settings()
433 settings_model = self.repo_settings
433 settings_model = self.repo_settings
434 else:
434 else:
435 all_patterns = self.get_global_settings()
435 all_patterns = self.get_global_settings()
436 settings_model = self.global_settings
436 settings_model = self.global_settings
437 entries = all_patterns.get(uid, [])
437 entries = all_patterns.get(uid, [])
438
438
439 for del_key in entries:
439 for del_key in entries:
440 setting_name = self._get_keyname(del_key, uid, prefix='')
440 setting_name = self._get_keyname(del_key, uid, prefix='')
441 entry = settings_model.get_setting_by_name(setting_name)
441 entry = settings_model.get_setting_by_name(setting_name)
442 if entry:
442 if entry:
443 Session().delete(entry)
443 Session().delete(entry)
444
444
445 Session().commit()
445 Session().commit()
446
446
447 def create_or_update_setting(
447 def create_or_update_setting(
448 self, name, val=Optional(''), type_=Optional('unicode')):
448 self, name, val=Optional(''), type_=Optional('unicode')):
449 if self.repo_settings:
449 if self.repo_settings:
450 setting = self.repo_settings.create_or_update_setting(
450 setting = self.repo_settings.create_or_update_setting(
451 name, val, type_)
451 name, val, type_)
452 else:
452 else:
453 setting = self.global_settings.create_or_update_setting(
453 setting = self.global_settings.create_or_update_setting(
454 name, val, type_)
454 name, val, type_)
455 return setting
455 return setting
456
456
457
457
458 class VcsSettingsModel(object):
458 class VcsSettingsModel(object):
459
459
460 INHERIT_SETTINGS = 'inherit_vcs_settings'
460 INHERIT_SETTINGS = 'inherit_vcs_settings'
461 GENERAL_SETTINGS = (
461 GENERAL_SETTINGS = (
462 'use_outdated_comments',
462 'use_outdated_comments',
463 'pr_merge_enabled',
463 'pr_merge_enabled',
464 'hg_use_rebase_for_merging',
464 'hg_use_rebase_for_merging',
465 'hg_close_branch_before_merging',
465 'hg_close_branch_before_merging',
466 'git_use_rebase_for_merging',
466 'git_use_rebase_for_merging',
467 'git_close_branch_before_merging',
467 'git_close_branch_before_merging',
468 'diff_cache',
468 'diff_cache',
469 )
469 )
470
470
471 HOOKS_SETTINGS = (
471 HOOKS_SETTINGS = (
472 ('hooks', 'changegroup.repo_size'),
472 ('hooks', 'changegroup.repo_size'),
473 ('hooks', 'changegroup.push_logger'),
473 ('hooks', 'changegroup.push_logger'),
474 ('hooks', 'outgoing.pull_logger'),
474 ('hooks', 'outgoing.pull_logger'),
475 )
475 )
476 HG_SETTINGS = (
476 HG_SETTINGS = (
477 ('extensions', 'largefiles'),
477 ('extensions', 'largefiles'),
478 ('phases', 'publish'),
478 ('phases', 'publish'),
479 ('extensions', 'evolve'),
479 ('extensions', 'evolve'),
480 ('extensions', 'topic'),
480 ('extensions', 'topic'),
481 ('experimental', 'evolution'),
481 ('experimental', 'evolution'),
482 ('experimental', 'evolution.exchange'),
482 ('experimental', 'evolution.exchange'),
483 )
483 )
484 GIT_SETTINGS = (
484 GIT_SETTINGS = (
485 ('vcs_git_lfs', 'enabled'),
485 ('vcs_git_lfs', 'enabled'),
486 )
486 )
487 GLOBAL_HG_SETTINGS = (
487 GLOBAL_HG_SETTINGS = (
488 ('extensions', 'largefiles'),
488 ('extensions', 'largefiles'),
489 ('phases', 'publish'),
489 ('phases', 'publish'),
490 ('extensions', 'evolve'),
490 ('extensions', 'evolve'),
491 ('extensions', 'topic'),
491 ('extensions', 'topic'),
492 ('experimental', 'evolution'),
492 ('experimental', 'evolution'),
493 ('experimental', 'evolution.exchange'),
493 ('experimental', 'evolution.exchange'),
494 )
494 )
495
495
496 GLOBAL_GIT_SETTINGS = (
496 GLOBAL_GIT_SETTINGS = (
497 ('vcs_git_lfs', 'enabled'),
497 ('vcs_git_lfs', 'enabled'),
498 )
498 )
499
499
500 SVN_BRANCH_SECTION = 'vcs_svn_branch'
500 SVN_BRANCH_SECTION = 'vcs_svn_branch'
501 SVN_TAG_SECTION = 'vcs_svn_tag'
501 SVN_TAG_SECTION = 'vcs_svn_tag'
502 SSL_SETTING = ('web', 'push_ssl')
503 PATH_SETTING = ('paths', '/')
502 PATH_SETTING = ('paths', '/')
504
503
505 def __init__(self, sa=None, repo=None):
504 def __init__(self, sa=None, repo=None):
506 self.global_settings = SettingsModel(sa=sa)
505 self.global_settings = SettingsModel(sa=sa)
507 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
506 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
508 self._ui_settings = (
507 self._ui_settings = (
509 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
508 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
510 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
509 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
511
510
512 @property
511 @property
513 @assert_repo_settings
512 @assert_repo_settings
514 def inherit_global_settings(self):
513 def inherit_global_settings(self):
515 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
514 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
516 return setting.app_settings_value if setting else True
515 return setting.app_settings_value if setting else True
517
516
518 @inherit_global_settings.setter
517 @inherit_global_settings.setter
519 @assert_repo_settings
518 @assert_repo_settings
520 def inherit_global_settings(self, value):
519 def inherit_global_settings(self, value):
521 self.repo_settings.create_or_update_setting(
520 self.repo_settings.create_or_update_setting(
522 self.INHERIT_SETTINGS, value, type_='bool')
521 self.INHERIT_SETTINGS, value, type_='bool')
523
522
524 def get_keyname(self, key_name, prefix='rhodecode_'):
523 def get_keyname(self, key_name, prefix='rhodecode_'):
525 return f'{prefix}{key_name}'
524 return f'{prefix}{key_name}'
526
525
527 def get_global_svn_branch_patterns(self):
526 def get_global_svn_branch_patterns(self):
528 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
527 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
529
528
530 @assert_repo_settings
529 @assert_repo_settings
531 def get_repo_svn_branch_patterns(self):
530 def get_repo_svn_branch_patterns(self):
532 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
531 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
533
532
534 def get_global_svn_tag_patterns(self):
533 def get_global_svn_tag_patterns(self):
535 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
534 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
536
535
537 @assert_repo_settings
536 @assert_repo_settings
538 def get_repo_svn_tag_patterns(self):
537 def get_repo_svn_tag_patterns(self):
539 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
538 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
540
539
541 def get_global_settings(self):
540 def get_global_settings(self):
542 return self._collect_all_settings(global_=True)
541 return self._collect_all_settings(global_=True)
543
542
544 @assert_repo_settings
543 @assert_repo_settings
545 def get_repo_settings(self):
544 def get_repo_settings(self):
546 return self._collect_all_settings(global_=False)
545 return self._collect_all_settings(global_=False)
547
546
548 @assert_repo_settings
547 @assert_repo_settings
549 def get_repo_settings_inherited(self):
548 def get_repo_settings_inherited(self):
550 global_settings = self.get_global_settings()
549 global_settings = self.get_global_settings()
551 global_settings.update(self.get_repo_settings())
550 global_settings.update(self.get_repo_settings())
552 return global_settings
551 return global_settings
553
552
554 @assert_repo_settings
553 @assert_repo_settings
555 def create_or_update_repo_settings(
554 def create_or_update_repo_settings(
556 self, data, inherit_global_settings=False):
555 self, data, inherit_global_settings=False):
557 from rhodecode.model.scm import ScmModel
556 from rhodecode.model.scm import ScmModel
558
557
559 self.inherit_global_settings = inherit_global_settings
558 self.inherit_global_settings = inherit_global_settings
560
559
561 repo = self.repo_settings.get_repo()
560 repo = self.repo_settings.get_repo()
562 if not inherit_global_settings:
561 if not inherit_global_settings:
563 if repo.repo_type == 'svn':
562 if repo.repo_type == 'svn':
564 self.create_repo_svn_settings(data)
563 self.create_repo_svn_settings(data)
565 else:
564 else:
566 self.create_or_update_repo_hook_settings(data)
565 self.create_or_update_repo_hook_settings(data)
567 self.create_or_update_repo_pr_settings(data)
566 self.create_or_update_repo_pr_settings(data)
568
567
569 if repo.repo_type == 'hg':
568 if repo.repo_type == 'hg':
570 self.create_or_update_repo_hg_settings(data)
569 self.create_or_update_repo_hg_settings(data)
571
570
572 if repo.repo_type == 'git':
571 if repo.repo_type == 'git':
573 self.create_or_update_repo_git_settings(data)
572 self.create_or_update_repo_git_settings(data)
574
573
575 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
574 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
576
575
577 @assert_repo_settings
576 @assert_repo_settings
578 def create_or_update_repo_hook_settings(self, data):
577 def create_or_update_repo_hook_settings(self, data):
579 for section, key in self.HOOKS_SETTINGS:
578 for section, key in self.HOOKS_SETTINGS:
580 data_key = self._get_form_ui_key(section, key)
579 data_key = self._get_form_ui_key(section, key)
581 if data_key not in data:
580 if data_key not in data:
582 raise ValueError(
581 raise ValueError(
583 f'The given data does not contain {data_key} key')
582 f'The given data does not contain {data_key} key')
584
583
585 active = data.get(data_key)
584 active = data.get(data_key)
586 repo_setting = self.repo_settings.get_ui_by_section_and_key(
585 repo_setting = self.repo_settings.get_ui_by_section_and_key(
587 section, key)
586 section, key)
588 if not repo_setting:
587 if not repo_setting:
589 global_setting = self.global_settings.\
588 global_setting = self.global_settings.\
590 get_ui_by_section_and_key(section, key)
589 get_ui_by_section_and_key(section, key)
591 self.repo_settings.create_ui_section_value(
590 self.repo_settings.create_ui_section_value(
592 section, global_setting.ui_value, key=key, active=active)
591 section, global_setting.ui_value, key=key, active=active)
593 else:
592 else:
594 repo_setting.ui_active = active
593 repo_setting.ui_active = active
595 Session().add(repo_setting)
594 Session().add(repo_setting)
596
595
597 def update_global_hook_settings(self, data):
596 def update_global_hook_settings(self, data):
598 for section, key in self.HOOKS_SETTINGS:
597 for section, key in self.HOOKS_SETTINGS:
599 data_key = self._get_form_ui_key(section, key)
598 data_key = self._get_form_ui_key(section, key)
600 if data_key not in data:
599 if data_key not in data:
601 raise ValueError(
600 raise ValueError(
602 f'The given data does not contain {data_key} key')
601 f'The given data does not contain {data_key} key')
603 active = data.get(data_key)
602 active = data.get(data_key)
604 repo_setting = self.global_settings.get_ui_by_section_and_key(
603 repo_setting = self.global_settings.get_ui_by_section_and_key(
605 section, key)
604 section, key)
606 repo_setting.ui_active = active
605 repo_setting.ui_active = active
607 Session().add(repo_setting)
606 Session().add(repo_setting)
608
607
609 @assert_repo_settings
608 @assert_repo_settings
610 def create_or_update_repo_pr_settings(self, data):
609 def create_or_update_repo_pr_settings(self, data):
611 return self._create_or_update_general_settings(
610 return self._create_or_update_general_settings(
612 self.repo_settings, data)
611 self.repo_settings, data)
613
612
614 def create_or_update_global_pr_settings(self, data):
613 def create_or_update_global_pr_settings(self, data):
615 return self._create_or_update_general_settings(
614 return self._create_or_update_general_settings(
616 self.global_settings, data)
615 self.global_settings, data)
617
616
618 @assert_repo_settings
617 @assert_repo_settings
619 def create_repo_svn_settings(self, data):
618 def create_repo_svn_settings(self, data):
620 return self._create_svn_settings(self.repo_settings, data)
619 return self._create_svn_settings(self.repo_settings, data)
621
620
622 def _set_evolution(self, settings, is_enabled):
621 def _set_evolution(self, settings, is_enabled):
623 if is_enabled:
622 if is_enabled:
624 # if evolve is active set evolution=all
623 # if evolve is active set evolution=all
625
624
626 self._create_or_update_ui(
625 self._create_or_update_ui(
627 settings, *('experimental', 'evolution'), value='all',
626 settings, *('experimental', 'evolution'), value='all',
628 active=True)
627 active=True)
629 self._create_or_update_ui(
628 self._create_or_update_ui(
630 settings, *('experimental', 'evolution.exchange'), value='yes',
629 settings, *('experimental', 'evolution.exchange'), value='yes',
631 active=True)
630 active=True)
632 # if evolve is active set topics server support
631 # if evolve is active set topics server support
633 self._create_or_update_ui(
632 self._create_or_update_ui(
634 settings, *('extensions', 'topic'), value='',
633 settings, *('extensions', 'topic'), value='',
635 active=True)
634 active=True)
636
635
637 else:
636 else:
638 self._create_or_update_ui(
637 self._create_or_update_ui(
639 settings, *('experimental', 'evolution'), value='',
638 settings, *('experimental', 'evolution'), value='',
640 active=False)
639 active=False)
641 self._create_or_update_ui(
640 self._create_or_update_ui(
642 settings, *('experimental', 'evolution.exchange'), value='no',
641 settings, *('experimental', 'evolution.exchange'), value='no',
643 active=False)
642 active=False)
644 self._create_or_update_ui(
643 self._create_or_update_ui(
645 settings, *('extensions', 'topic'), value='',
644 settings, *('extensions', 'topic'), value='',
646 active=False)
645 active=False)
647
646
648 @assert_repo_settings
647 @assert_repo_settings
649 def create_or_update_repo_hg_settings(self, data):
648 def create_or_update_repo_hg_settings(self, data):
650 largefiles, phases, evolve = \
649 largefiles, phases, evolve = \
651 self.HG_SETTINGS[:3]
650 self.HG_SETTINGS[:3]
652 largefiles_key, phases_key, evolve_key = \
651 largefiles_key, phases_key, evolve_key = \
653 self._get_settings_keys(self.HG_SETTINGS[:3], data)
652 self._get_settings_keys(self.HG_SETTINGS[:3], data)
654
653
655 self._create_or_update_ui(
654 self._create_or_update_ui(
656 self.repo_settings, *largefiles, value='',
655 self.repo_settings, *largefiles, value='',
657 active=data[largefiles_key])
656 active=data[largefiles_key])
658 self._create_or_update_ui(
657 self._create_or_update_ui(
659 self.repo_settings, *evolve, value='',
658 self.repo_settings, *evolve, value='',
660 active=data[evolve_key])
659 active=data[evolve_key])
661 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
660 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
662
661
663 self._create_or_update_ui(
662 self._create_or_update_ui(
664 self.repo_settings, *phases, value=safe_str(data[phases_key]))
663 self.repo_settings, *phases, value=safe_str(data[phases_key]))
665
664
666 def create_or_update_global_hg_settings(self, data):
665 def create_or_update_global_hg_settings(self, data):
667 opts_len = 3
666 opts_len = 3
668 largefiles, phases, evolve \
667 largefiles, phases, evolve \
669 = self.GLOBAL_HG_SETTINGS[:opts_len]
668 = self.GLOBAL_HG_SETTINGS[:opts_len]
670 largefiles_key, phases_key, evolve_key \
669 largefiles_key, phases_key, evolve_key \
671 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:opts_len], data)
670 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:opts_len], data)
672
671
673 self._create_or_update_ui(
672 self._create_or_update_ui(
674 self.global_settings, *largefiles, value='',
673 self.global_settings, *largefiles, value='',
675 active=data[largefiles_key])
674 active=data[largefiles_key])
676 self._create_or_update_ui(
675 self._create_or_update_ui(
677 self.global_settings, *phases, value=safe_str(data[phases_key]))
676 self.global_settings, *phases, value=safe_str(data[phases_key]))
678 self._create_or_update_ui(
677 self._create_or_update_ui(
679 self.global_settings, *evolve, value='',
678 self.global_settings, *evolve, value='',
680 active=data[evolve_key])
679 active=data[evolve_key])
681 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
680 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
682
681
683 def create_or_update_repo_git_settings(self, data):
682 def create_or_update_repo_git_settings(self, data):
684 # NOTE(marcink): # comma makes unpack work properly
683 # NOTE(marcink): # comma makes unpack work properly
685 lfs_enabled, \
684 lfs_enabled, \
686 = self.GIT_SETTINGS
685 = self.GIT_SETTINGS
687
686
688 lfs_enabled_key, \
687 lfs_enabled_key, \
689 = self._get_settings_keys(self.GIT_SETTINGS, data)
688 = self._get_settings_keys(self.GIT_SETTINGS, data)
690
689
691 self._create_or_update_ui(
690 self._create_or_update_ui(
692 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
691 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
693 active=data[lfs_enabled_key])
692 active=data[lfs_enabled_key])
694
693
695 def create_or_update_global_git_settings(self, data):
694 def create_or_update_global_git_settings(self, data):
696 lfs_enabled = self.GLOBAL_GIT_SETTINGS[0]
695 lfs_enabled = self.GLOBAL_GIT_SETTINGS[0]
697 lfs_enabled_key = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)[0]
696 lfs_enabled_key = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)[0]
698
697
699 self._create_or_update_ui(
698 self._create_or_update_ui(
700 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
699 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
701 active=data[lfs_enabled_key])
700 active=data[lfs_enabled_key])
702
701
703 def create_or_update_global_svn_settings(self, data):
702 def create_or_update_global_svn_settings(self, data):
704 # branch/tags patterns
703 # branch/tags patterns
705 self._create_svn_settings(self.global_settings, data)
704 self._create_svn_settings(self.global_settings, data)
706
705
707 def update_global_ssl_setting(self, value):
708 self._create_or_update_ui(
709 self.global_settings, *self.SSL_SETTING, value=value)
710
711 @assert_repo_settings
706 @assert_repo_settings
712 def delete_repo_svn_pattern(self, id_):
707 def delete_repo_svn_pattern(self, id_):
713 ui = self.repo_settings.UiDbModel.get(id_)
708 ui = self.repo_settings.UiDbModel.get(id_)
714 if ui and ui.repository.repo_name == self.repo_settings.repo:
709 if ui and ui.repository.repo_name == self.repo_settings.repo:
715 # only delete if it's the same repo as initialized settings
710 # only delete if it's the same repo as initialized settings
716 self.repo_settings.delete_ui(id_)
711 self.repo_settings.delete_ui(id_)
717 else:
712 else:
718 # raise error as if we wouldn't find this option
713 # raise error as if we wouldn't find this option
719 self.repo_settings.delete_ui(-1)
714 self.repo_settings.delete_ui(-1)
720
715
721 def delete_global_svn_pattern(self, id_):
716 def delete_global_svn_pattern(self, id_):
722 self.global_settings.delete_ui(id_)
717 self.global_settings.delete_ui(id_)
723
718
724 @assert_repo_settings
719 @assert_repo_settings
725 def get_repo_ui_settings(self, section=None, key=None):
720 def get_repo_ui_settings(self, section=None, key=None):
726 global_uis = self.global_settings.get_ui(section, key)
721 global_uis = self.global_settings.get_ui(section, key)
727 repo_uis = self.repo_settings.get_ui(section, key)
722 repo_uis = self.repo_settings.get_ui(section, key)
728
723
729 filtered_repo_uis = self._filter_ui_settings(repo_uis)
724 filtered_repo_uis = self._filter_ui_settings(repo_uis)
730 filtered_repo_uis_keys = [
725 filtered_repo_uis_keys = [
731 (s.section, s.key) for s in filtered_repo_uis]
726 (s.section, s.key) for s in filtered_repo_uis]
732
727
733 def _is_global_ui_filtered(ui):
728 def _is_global_ui_filtered(ui):
734 return (
729 return (
735 (ui.section, ui.key) in filtered_repo_uis_keys
730 (ui.section, ui.key) in filtered_repo_uis_keys
736 or ui.section in self._svn_sections)
731 or ui.section in self._svn_sections)
737
732
738 filtered_global_uis = [
733 filtered_global_uis = [
739 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
734 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
740
735
741 return filtered_global_uis + filtered_repo_uis
736 return filtered_global_uis + filtered_repo_uis
742
737
743 def get_global_ui_settings(self, section=None, key=None):
738 def get_global_ui_settings(self, section=None, key=None):
744 return self.global_settings.get_ui(section, key)
739 return self.global_settings.get_ui(section, key)
745
740
746 def get_ui_settings_as_config_obj(self, section=None, key=None):
741 def get_ui_settings_as_config_obj(self, section=None, key=None):
747 config = base.Config()
742 config = base.Config()
748
743
749 ui_settings = self.get_ui_settings(section=section, key=key)
744 ui_settings = self.get_ui_settings(section=section, key=key)
750
745
751 for entry in ui_settings:
746 for entry in ui_settings:
752 config.set(entry.section, entry.key, entry.value)
747 config.set(entry.section, entry.key, entry.value)
753
748
754 return config
749 return config
755
750
756 def get_ui_settings(self, section=None, key=None):
751 def get_ui_settings(self, section=None, key=None):
757 if not self.repo_settings or self.inherit_global_settings:
752 if not self.repo_settings or self.inherit_global_settings:
758 return self.get_global_ui_settings(section, key)
753 return self.get_global_ui_settings(section, key)
759 else:
754 else:
760 return self.get_repo_ui_settings(section, key)
755 return self.get_repo_ui_settings(section, key)
761
756
762 def get_svn_patterns(self, section=None):
757 def get_svn_patterns(self, section=None):
763 if not self.repo_settings:
758 if not self.repo_settings:
764 return self.get_global_ui_settings(section)
759 return self.get_global_ui_settings(section)
765 else:
760 else:
766 return self.get_repo_ui_settings(section)
761 return self.get_repo_ui_settings(section)
767
762
768 @assert_repo_settings
763 @assert_repo_settings
769 def get_repo_general_settings(self):
764 def get_repo_general_settings(self):
770 global_settings = self.global_settings.get_all_settings()
765 global_settings = self.global_settings.get_all_settings()
771 repo_settings = self.repo_settings.get_all_settings()
766 repo_settings = self.repo_settings.get_all_settings()
772 filtered_repo_settings = self._filter_general_settings(repo_settings)
767 filtered_repo_settings = self._filter_general_settings(repo_settings)
773 global_settings.update(filtered_repo_settings)
768 global_settings.update(filtered_repo_settings)
774 return global_settings
769 return global_settings
775
770
776 def get_global_general_settings(self):
771 def get_global_general_settings(self):
777 return self.global_settings.get_all_settings()
772 return self.global_settings.get_all_settings()
778
773
779 def get_general_settings(self):
774 def get_general_settings(self):
780 if not self.repo_settings or self.inherit_global_settings:
775 if not self.repo_settings or self.inherit_global_settings:
781 return self.get_global_general_settings()
776 return self.get_global_general_settings()
782 else:
777 else:
783 return self.get_repo_general_settings()
778 return self.get_repo_general_settings()
784
779
785 def _filter_ui_settings(self, settings):
780 def _filter_ui_settings(self, settings):
786 filtered_settings = [
781 filtered_settings = [
787 s for s in settings if self._should_keep_setting(s)]
782 s for s in settings if self._should_keep_setting(s)]
788 return filtered_settings
783 return filtered_settings
789
784
790 def _should_keep_setting(self, setting):
785 def _should_keep_setting(self, setting):
791 keep = (
786 keep = (
792 (setting.section, setting.key) in self._ui_settings or
787 (setting.section, setting.key) in self._ui_settings or
793 setting.section in self._svn_sections)
788 setting.section in self._svn_sections)
794 return keep
789 return keep
795
790
796 def _filter_general_settings(self, settings):
791 def _filter_general_settings(self, settings):
797 keys = [self.get_keyname(key) for key in self.GENERAL_SETTINGS]
792 keys = [self.get_keyname(key) for key in self.GENERAL_SETTINGS]
798 return {
793 return {
799 k: settings[k]
794 k: settings[k]
800 for k in settings if k in keys}
795 for k in settings if k in keys}
801
796
802 def _collect_all_settings(self, global_=False):
797 def _collect_all_settings(self, global_=False):
803 settings = self.global_settings if global_ else self.repo_settings
798 settings = self.global_settings if global_ else self.repo_settings
804 result = {}
799 result = {}
805
800
806 for section, key in self._ui_settings:
801 for section, key in self._ui_settings:
807 ui = settings.get_ui_by_section_and_key(section, key)
802 ui = settings.get_ui_by_section_and_key(section, key)
808 result_key = self._get_form_ui_key(section, key)
803 result_key = self._get_form_ui_key(section, key)
809
804
810 if ui:
805 if ui:
811 if section in ('hooks', 'extensions'):
806 if section in ('hooks', 'extensions'):
812 result[result_key] = ui.ui_active
807 result[result_key] = ui.ui_active
813 elif result_key in ['vcs_git_lfs_enabled']:
808 elif result_key in ['vcs_git_lfs_enabled']:
814 result[result_key] = ui.ui_active
809 result[result_key] = ui.ui_active
815 else:
810 else:
816 result[result_key] = ui.ui_value
811 result[result_key] = ui.ui_value
817
812
818 for name in self.GENERAL_SETTINGS:
813 for name in self.GENERAL_SETTINGS:
819 setting = settings.get_setting_by_name(name)
814 setting = settings.get_setting_by_name(name)
820 if setting:
815 if setting:
821 result_key = self.get_keyname(name)
816 result_key = self.get_keyname(name)
822 result[result_key] = setting.app_settings_value
817 result[result_key] = setting.app_settings_value
823
818
824 return result
819 return result
825
820
826 def _get_form_ui_key(self, section, key):
821 def _get_form_ui_key(self, section, key):
827 return '{section}_{key}'.format(
822 return '{section}_{key}'.format(
828 section=section, key=key.replace('.', '_'))
823 section=section, key=key.replace('.', '_'))
829
824
830 def _create_or_update_ui(
825 def _create_or_update_ui(
831 self, settings, section, key, value=None, active=None):
826 self, settings, section, key, value=None, active=None):
832 ui = settings.get_ui_by_section_and_key(section, key)
827 ui = settings.get_ui_by_section_and_key(section, key)
833 if not ui:
828 if not ui:
834 active = True if active is None else active
829 active = True if active is None else active
835 settings.create_ui_section_value(
830 settings.create_ui_section_value(
836 section, value, key=key, active=active)
831 section, value, key=key, active=active)
837 else:
832 else:
838 if active is not None:
833 if active is not None:
839 ui.ui_active = active
834 ui.ui_active = active
840 if value is not None:
835 if value is not None:
841 ui.ui_value = value
836 ui.ui_value = value
842 Session().add(ui)
837 Session().add(ui)
843
838
844 def _create_svn_settings(self, settings, data):
839 def _create_svn_settings(self, settings, data):
845 svn_settings = {
840 svn_settings = {
846 'new_svn_branch': self.SVN_BRANCH_SECTION,
841 'new_svn_branch': self.SVN_BRANCH_SECTION,
847 'new_svn_tag': self.SVN_TAG_SECTION
842 'new_svn_tag': self.SVN_TAG_SECTION
848 }
843 }
849 for key in svn_settings:
844 for key in svn_settings:
850 if data.get(key):
845 if data.get(key):
851 settings.create_ui_section_value(svn_settings[key], data[key])
846 settings.create_ui_section_value(svn_settings[key], data[key])
852
847
853 def _create_or_update_general_settings(self, settings, data):
848 def _create_or_update_general_settings(self, settings, data):
854 for name in self.GENERAL_SETTINGS:
849 for name in self.GENERAL_SETTINGS:
855 data_key = self.get_keyname(name)
850 data_key = self.get_keyname(name)
856 if data_key not in data:
851 if data_key not in data:
857 raise ValueError(
852 raise ValueError(
858 f'The given data does not contain {data_key} key')
853 f'The given data does not contain {data_key} key')
859 setting = settings.create_or_update_setting(
854 setting = settings.create_or_update_setting(
860 name, data[data_key], 'bool')
855 name, data[data_key], 'bool')
861 Session().add(setting)
856 Session().add(setting)
862
857
863 def _get_settings_keys(self, settings, data):
858 def _get_settings_keys(self, settings, data):
864 data_keys = [self._get_form_ui_key(*s) for s in settings]
859 data_keys = [self._get_form_ui_key(*s) for s in settings]
865 for data_key in data_keys:
860 for data_key in data_keys:
866 if data_key not in data:
861 if data_key not in data:
867 raise ValueError(
862 raise ValueError(
868 f'The given data does not contain {data_key} key')
863 f'The given data does not contain {data_key} key')
869 return data_keys
864 return data_keys
870
865
871 def create_largeobjects_dirs_if_needed(self, repo_store_path):
866 def create_largeobjects_dirs_if_needed(self, repo_store_path):
872 """
867 """
873 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
868 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
874 does a repository scan if enabled in the settings.
869 does a repository scan if enabled in the settings.
875 """
870 """
876
871
877 from rhodecode.lib.vcs.backends.hg import largefiles_store
872 from rhodecode.lib.vcs.backends.hg import largefiles_store
878 from rhodecode.lib.vcs.backends.git import lfs_store
873 from rhodecode.lib.vcs.backends.git import lfs_store
879
874
880 paths = [
875 paths = [
881 largefiles_store(repo_store_path),
876 largefiles_store(repo_store_path),
882 lfs_store(repo_store_path)]
877 lfs_store(repo_store_path)]
883
878
884 for path in paths:
879 for path in paths:
885 if os.path.isdir(path):
880 if os.path.isdir(path):
886 continue
881 continue
887 if os.path.isfile(path):
882 if os.path.isfile(path):
888 continue
883 continue
889 # not a file nor dir, we try to create it
884 # not a file nor dir, we try to create it
890 try:
885 try:
891 os.makedirs(path)
886 os.makedirs(path)
892 except Exception:
887 except Exception:
893 log.warning('Failed to create largefiles dir:%s', path)
888 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,399 +1,414 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 import io
18 import io
19 import shlex
19 import shlex
20
20
21 import math
21 import math
22 import re
22 import re
23 import os
23 import os
24 import datetime
24 import datetime
25 import logging
25 import logging
26 import queue
26 import queue
27 import subprocess
27 import subprocess
28
28
29
29
30 from dateutil.parser import parse
30 from dateutil.parser import parse
31 from pyramid.interfaces import IRoutesMapper
31 from pyramid.interfaces import IRoutesMapper
32 from pyramid.settings import asbool
32 from pyramid.settings import asbool
33 from pyramid.path import AssetResolver
33 from pyramid.path import AssetResolver
34 from threading import Thread
34 from threading import Thread
35
35
36 from rhodecode.config.jsroutes import generate_jsroutes_content
36 from rhodecode.config.jsroutes import generate_jsroutes_content
37 from rhodecode.lib.base import get_auth_user
37 from rhodecode.lib.base import get_auth_user
38 from rhodecode.lib.celerylib.loader import set_celery_conf
38 from rhodecode.lib.celerylib.loader import set_celery_conf
39
39
40 import rhodecode
40 import rhodecode
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 def add_renderer_globals(event):
46 def add_renderer_globals(event):
47 from rhodecode.lib import helpers
47 from rhodecode.lib import helpers
48
48
49 # TODO: When executed in pyramid view context the request is not available
49 # TODO: When executed in pyramid view context the request is not available
50 # in the event. Find a better solution to get the request.
50 # in the event. Find a better solution to get the request.
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52 request = event['request'] or get_current_request()
52 request = event['request'] or get_current_request()
53
53
54 # Add Pyramid translation as '_' to context
54 # Add Pyramid translation as '_' to context
55 event['_'] = request.translate
55 event['_'] = request.translate
56 event['_ungettext'] = request.plularize
56 event['_ungettext'] = request.plularize
57 event['h'] = helpers
57 event['h'] = helpers
58
58
59
59
60 def set_user_lang(event):
60 def set_user_lang(event):
61 request = event.request
61 request = event.request
62 cur_user = getattr(request, 'user', None)
62 cur_user = getattr(request, 'user', None)
63
63
64 if cur_user:
64 if cur_user:
65 user_lang = cur_user.get_instance().user_data.get('language')
65 user_lang = cur_user.get_instance().user_data.get('language')
66 if user_lang:
66 if user_lang:
67 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
67 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
68 event.request._LOCALE_ = user_lang
68 event.request._LOCALE_ = user_lang
69
69
70
70
71 def update_celery_conf(event):
71 def update_celery_conf(event):
72 log.debug('Setting celery config from new request')
72 log.debug('Setting celery config from new request')
73 set_celery_conf(request=event.request, registry=event.request.registry)
73 set_celery_conf(request=event.request, registry=event.request.registry)
74
74
75
75
76 def add_request_user_context(event):
76 def add_request_user_context(event):
77 """
77 """
78 Adds auth user into request context
78 Adds auth user into request context
79 """
79 """
80
80
81 request = event.request
81 request = event.request
82 # access req_id as soon as possible
82 # access req_id as soon as possible
83 req_id = request.req_id
83 req_id = request.req_id
84
84
85 if hasattr(request, 'vcs_call'):
85 if hasattr(request, 'vcs_call'):
86 # skip vcs calls
86 # skip vcs calls
87 return
87 return
88
88
89 if hasattr(request, 'rpc_method'):
89 if hasattr(request, 'rpc_method'):
90 # skip api calls
90 # skip api calls
91 return
91 return
92
92
93 auth_user, auth_token = get_auth_user(request)
93 auth_user, auth_token = get_auth_user(request)
94 request.user = auth_user
94 request.user = auth_user
95 request.user_auth_token = auth_token
95 request.user_auth_token = auth_token
96 request.environ['rc_auth_user'] = auth_user
96 request.environ['rc_auth_user'] = auth_user
97 request.environ['rc_auth_user_id'] = str(auth_user.user_id)
97 request.environ['rc_auth_user_id'] = str(auth_user.user_id)
98 request.environ['rc_req_id'] = req_id
98 request.environ['rc_req_id'] = req_id
99
99
100
100
101 def reset_log_bucket(event):
101 def reset_log_bucket(event):
102 """
102 """
103 reset the log bucket on new request
103 reset the log bucket on new request
104 """
104 """
105 request = event.request
105 request = event.request
106 request.req_id_records_init()
106 request.req_id_records_init()
107
107
108
108
109 def scan_repositories_if_enabled(event):
109 def scan_repositories_if_enabled(event):
110 """
110 """
111 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
111 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 does a repository scan if enabled in the settings.
112 does a repository scan if enabled in the settings.
113 """
113 """
114 settings = event.app.registry.settings
114 settings = event.app.registry.settings
115 vcs_server_enabled = settings['vcs.server.enable']
115 vcs_server_enabled = settings['vcs.server.enable']
116 import_on_startup = settings['startup.import_repos']
116 import_on_startup = settings['startup.import_repos']
117 if vcs_server_enabled and import_on_startup:
117 if vcs_server_enabled and import_on_startup:
118 from rhodecode.model.scm import ScmModel
118 from rhodecode.model.scm import ScmModel
119 from rhodecode.lib.utils import repo2db_mapper
119 from rhodecode.lib.utils import repo2db_mapper
120 scm = ScmModel()
120 scm = ScmModel()
121 repositories = scm.repo_scan(scm.repos_path)
121 repositories = scm.repo_scan(scm.repos_path)
122 repo2db_mapper(repositories, remove_obsolete=False)
122 repo2db_mapper(repositories, remove_obsolete=False)
123
123
124
124
125 def write_metadata_if_needed(event):
125 def write_metadata_if_needed(event):
126 """
126 """
127 Writes upgrade metadata
127 Writes upgrade metadata
128 """
128 """
129 import rhodecode
129 import rhodecode
130 from rhodecode.lib import system_info
130 from rhodecode.lib import system_info
131 from rhodecode.lib import ext_json
131 from rhodecode.lib import ext_json
132
132
133 fname = '.rcmetadata.json'
133 fname = '.rcmetadata.json'
134 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
134 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
135 metadata_destination = os.path.join(ini_loc, fname)
135 metadata_destination = os.path.join(ini_loc, fname)
136
136
137 def get_update_age():
137 def get_update_age():
138 now = datetime.datetime.utcnow()
138 now = datetime.datetime.utcnow()
139
139
140 with open(metadata_destination, 'rb') as f:
140 with open(metadata_destination, 'rb') as f:
141 data = ext_json.json.loads(f.read())
141 data = ext_json.json.loads(f.read())
142 if 'created_on' in data:
142 if 'created_on' in data:
143 update_date = parse(data['created_on'])
143 update_date = parse(data['created_on'])
144 diff = now - update_date
144 diff = now - update_date
145 return diff.total_seconds() / 60.0
145 return diff.total_seconds() / 60.0
146
146
147 return 0
147 return 0
148
148
149 def write():
149 def write():
150 configuration = system_info.SysInfo(
150 configuration = system_info.SysInfo(
151 system_info.rhodecode_config)()['value']
151 system_info.rhodecode_config)()['value']
152 license_token = configuration['config']['license_token']
152 license_token = configuration['config']['license_token']
153
153
154 setup = dict(
154 setup = dict(
155 workers=configuration['config']['server:main'].get(
155 workers=configuration['config']['server:main'].get(
156 'workers', '?'),
156 'workers', '?'),
157 worker_type=configuration['config']['server:main'].get(
157 worker_type=configuration['config']['server:main'].get(
158 'worker_class', 'sync'),
158 'worker_class', 'sync'),
159 )
159 )
160 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
161 del dbinfo['url']
161 del dbinfo['url']
162
162
163 metadata = dict(
163 metadata = dict(
164 desc='upgrade metadata info',
164 desc='upgrade metadata info',
165 license_token=license_token,
165 license_token=license_token,
166 created_on=datetime.datetime.utcnow().isoformat(),
166 created_on=datetime.datetime.utcnow().isoformat(),
167 usage=system_info.SysInfo(system_info.usage_info)()['value'],
167 usage=system_info.SysInfo(system_info.usage_info)()['value'],
168 platform=system_info.SysInfo(system_info.platform_type)()['value'],
168 platform=system_info.SysInfo(system_info.platform_type)()['value'],
169 database=dbinfo,
169 database=dbinfo,
170 cpu=system_info.SysInfo(system_info.cpu)()['value'],
170 cpu=system_info.SysInfo(system_info.cpu)()['value'],
171 memory=system_info.SysInfo(system_info.memory)()['value'],
171 memory=system_info.SysInfo(system_info.memory)()['value'],
172 setup=setup
172 setup=setup
173 )
173 )
174
174
175 with open(metadata_destination, 'wb') as f:
175 with open(metadata_destination, 'wb') as f:
176 f.write(ext_json.json.dumps(metadata))
176 f.write(ext_json.json.dumps(metadata))
177
177
178 settings = event.app.registry.settings
178 settings = event.app.registry.settings
179 if settings.get('metadata.skip'):
179 if settings.get('metadata.skip'):
180 return
180 return
181
181
182 # only write this every 24h, workers restart caused unwanted delays
182 # only write this every 24h, workers restart caused unwanted delays
183 try:
183 try:
184 age_in_min = get_update_age()
184 age_in_min = get_update_age()
185 except Exception:
185 except Exception:
186 age_in_min = 0
186 age_in_min = 0
187
187
188 if age_in_min > 60 * 60 * 24:
188 if age_in_min > 60 * 60 * 24:
189 return
189 return
190
190
191 try:
191 try:
192 write()
192 write()
193 except Exception:
193 except Exception:
194 pass
194 pass
195
195
196
196
197 def write_usage_data(event):
197 def write_usage_data(event):
198 import rhodecode
198 import rhodecode
199 from rhodecode.lib import system_info
199 from rhodecode.lib import system_info
200 from rhodecode.lib import ext_json
200 from rhodecode.lib import ext_json
201
201
202 settings = event.app.registry.settings
202 settings = event.app.registry.settings
203 instance_tag = settings.get('metadata.write_usage_tag')
203 instance_tag = settings.get('metadata.write_usage_tag')
204 if not settings.get('metadata.write_usage'):
204 if not settings.get('metadata.write_usage'):
205 return
205 return
206
206
207 def get_update_age(dest_file):
207 def get_update_age(dest_file):
208 now = datetime.datetime.utcnow()
208 now = datetime.datetime.now(datetime.UTC)
209
209
210 with open(dest_file, 'rb') as f:
210 with open(dest_file, 'rb') as f:
211 data = ext_json.json.loads(f.read())
211 data = ext_json.json.loads(f.read())
212 if 'created_on' in data:
212 if 'created_on' in data:
213 update_date = parse(data['created_on'])
213 update_date = parse(data['created_on'])
214 diff = now - update_date
214 diff = now - update_date
215 return math.ceil(diff.total_seconds() / 60.0)
215 return math.ceil(diff.total_seconds() / 60.0)
216
216
217 return 0
217 return 0
218
218
219 utc_date = datetime.datetime.utcnow()
219 utc_date = datetime.datetime.now(datetime.UTC)
220 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
220 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
221 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
221 fname = f'.rc_usage_{utc_date.year}{utc_date.month:02d}{utc_date.day:02d}_{hour_quarter}.json'
222 date=utc_date, hour=hour_quarter)
223 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
222 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
224
223
225 usage_dir = os.path.join(ini_loc, '.rcusage')
224 usage_dir = os.path.join(ini_loc, '.rcusage')
226 if not os.path.isdir(usage_dir):
225 if not os.path.isdir(usage_dir):
227 os.makedirs(usage_dir)
226 os.makedirs(usage_dir)
228 usage_metadata_destination = os.path.join(usage_dir, fname)
227 usage_metadata_destination = os.path.join(usage_dir, fname)
229
228
230 try:
229 try:
231 age_in_min = get_update_age(usage_metadata_destination)
230 age_in_min = get_update_age(usage_metadata_destination)
232 except Exception:
231 except Exception:
233 age_in_min = 0
232 age_in_min = 0
234
233
235 # write every 6th hour
234 # write every 6th hour
236 if age_in_min and age_in_min < 60 * 6:
235 if age_in_min and age_in_min < 60 * 6:
237 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
236 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
238 age_in_min, 60 * 6)
237 age_in_min, 60 * 6)
239 return
238 return
240
239
241 def write(dest_file):
240 def write(dest_file):
242 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
241 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
243 license_token = configuration['config']['license_token']
242 license_token = configuration['config']['license_token']
244
243
245 metadata = dict(
244 metadata = dict(
246 desc='Usage data',
245 desc='Usage data',
247 instance_tag=instance_tag,
246 instance_tag=instance_tag,
248 license_token=license_token,
247 license_token=license_token,
249 created_on=datetime.datetime.utcnow().isoformat(),
248 created_on=datetime.datetime.utcnow().isoformat(),
250 usage=system_info.SysInfo(system_info.usage_info)()['value'],
249 usage=system_info.SysInfo(system_info.usage_info)()['value'],
251 )
250 )
252
251
253 with open(dest_file, 'wb') as f:
252 with open(dest_file, 'wb') as f:
254 f.write(ext_json.formatted_json(metadata))
253 f.write(ext_json.formatted_json(metadata))
255
254
256 try:
255 try:
257 log.debug('Writing usage file at: %s', usage_metadata_destination)
256 log.debug('Writing usage file at: %s', usage_metadata_destination)
258 write(usage_metadata_destination)
257 write(usage_metadata_destination)
259 except Exception:
258 except Exception:
260 pass
259 pass
261
260
262
261
263 def write_js_routes_if_enabled(event):
262 def write_js_routes_if_enabled(event):
264 registry = event.app.registry
263 registry = event.app.registry
265
264
266 mapper = registry.queryUtility(IRoutesMapper)
265 mapper = registry.queryUtility(IRoutesMapper)
267 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
266 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
268
267
269 def _extract_route_information(route):
268 def _extract_route_information(route):
270 """
269 """
271 Convert a route into tuple(name, path, args), eg:
270 Convert a route into tuple(name, path, args), eg:
272 ('show_user', '/profile/%(username)s', ['username'])
271 ('show_user', '/profile/%(username)s', ['username'])
273 """
272 """
274
273
275 route_path = route.pattern
274 route_path = route.pattern
276 pattern = route.pattern
275 pattern = route.pattern
277
276
278 def replace(matchobj):
277 def replace(matchobj):
279 if matchobj.group(1):
278 if matchobj.group(1):
280 return "%%(%s)s" % matchobj.group(1).split(':')[0]
279 return "%%(%s)s" % matchobj.group(1).split(':')[0]
281 else:
280 else:
282 return "%%(%s)s" % matchobj.group(2)
281 return "%%(%s)s" % matchobj.group(2)
283
282
284 route_path = _argument_prog.sub(replace, route_path)
283 route_path = _argument_prog.sub(replace, route_path)
285
284
286 if not route_path.startswith('/'):
285 if not route_path.startswith('/'):
287 route_path = f'/{route_path}'
286 route_path = f'/{route_path}'
288
287
289 return (
288 return (
290 route.name,
289 route.name,
291 route_path,
290 route_path,
292 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
291 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
293 for arg in _argument_prog.findall(pattern)]
292 for arg in _argument_prog.findall(pattern)]
294 )
293 )
295
294
296 def get_routes():
295 def get_routes():
297 # pyramid routes
296 # pyramid routes
298 for route in mapper.get_routes():
297 for route in mapper.get_routes():
299 if not route.name.startswith('__'):
298 if not route.name.startswith('__'):
300 yield _extract_route_information(route)
299 yield _extract_route_information(route)
301
300
302 if asbool(registry.settings.get('generate_js_files', 'false')):
301 if asbool(registry.settings.get('generate_js_files', 'false')):
303 static_path = AssetResolver().resolve('rhodecode:public').abspath()
302 static_path = AssetResolver().resolve('rhodecode:public').abspath()
304 jsroutes = get_routes()
303 jsroutes = get_routes()
305 jsroutes_file_content = generate_jsroutes_content(jsroutes)
304 jsroutes_file_content = generate_jsroutes_content(jsroutes)
306 jsroutes_file_path = os.path.join(
305 jsroutes_file_path = os.path.join(
307 static_path, 'js', 'rhodecode', 'routes.js')
306 static_path, 'js', 'rhodecode', 'routes.js')
308
307
309 try:
308 try:
310 with open(jsroutes_file_path, 'w', encoding='utf-8') as f:
309 with open(jsroutes_file_path, 'w', encoding='utf-8') as f:
311 f.write(jsroutes_file_content)
310 f.write(jsroutes_file_content)
312 log.debug('generated JS files in %s', jsroutes_file_path)
311 log.debug('generated JS files in %s', jsroutes_file_path)
313 except Exception:
312 except Exception:
314 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
313 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
315
314
316
315
316 def import_license_if_present(event):
317 """
318 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
319 does a import license key based on a presence of the file.
320 """
321 settings = event.app.registry.settings
322
323 license_file_path = settings.get('license.import_path')
324 force = settings.get('license.import_path_mode') == 'force'
325 if license_file_path:
326 from rhodecode.model.meta import Session
327 from rhodecode.model.license import apply_license_from_file
328 apply_license_from_file(license_file_path, force=force)
329 Session().commit()
330
331
317 class Subscriber(object):
332 class Subscriber(object):
318 """
333 """
319 Base class for subscribers to the pyramid event system.
334 Base class for subscribers to the pyramid event system.
320 """
335 """
321 def __call__(self, event):
336 def __call__(self, event):
322 self.run(event)
337 self.run(event)
323
338
324 def run(self, event):
339 def run(self, event):
325 raise NotImplementedError('Subclass has to implement this.')
340 raise NotImplementedError('Subclass has to implement this.')
326
341
327
342
328 class AsyncSubscriber(Subscriber):
343 class AsyncSubscriber(Subscriber):
329 """
344 """
330 Subscriber that handles the execution of events in a separate task to not
345 Subscriber that handles the execution of events in a separate task to not
331 block the execution of the code which triggers the event. It puts the
346 block the execution of the code which triggers the event. It puts the
332 received events into a queue from which the worker process takes them in
347 received events into a queue from which the worker process takes them in
333 order.
348 order.
334 """
349 """
335 def __init__(self):
350 def __init__(self):
336 self._stop = False
351 self._stop = False
337 self._eventq = queue.Queue()
352 self._eventq = queue.Queue()
338 self._worker = self.create_worker()
353 self._worker = self.create_worker()
339 self._worker.start()
354 self._worker.start()
340
355
341 def __call__(self, event):
356 def __call__(self, event):
342 self._eventq.put(event)
357 self._eventq.put(event)
343
358
344 def create_worker(self):
359 def create_worker(self):
345 worker = Thread(target=self.do_work)
360 worker = Thread(target=self.do_work)
346 worker.daemon = True
361 worker.daemon = True
347 return worker
362 return worker
348
363
349 def stop_worker(self):
364 def stop_worker(self):
350 self._stop = False
365 self._stop = False
351 self._eventq.put(None)
366 self._eventq.put(None)
352 self._worker.join()
367 self._worker.join()
353
368
354 def do_work(self):
369 def do_work(self):
355 while not self._stop:
370 while not self._stop:
356 event = self._eventq.get()
371 event = self._eventq.get()
357 if event is not None:
372 if event is not None:
358 self.run(event)
373 self.run(event)
359
374
360
375
361 class AsyncSubprocessSubscriber(AsyncSubscriber):
376 class AsyncSubprocessSubscriber(AsyncSubscriber):
362 """
377 """
363 Subscriber that uses the subprocess module to execute a command if an
378 Subscriber that uses the subprocess module to execute a command if an
364 event is received. Events are handled asynchronously::
379 event is received. Events are handled asynchronously::
365
380
366 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
381 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
367 subscriber(dummyEvent) # running __call__(event)
382 subscriber(dummyEvent) # running __call__(event)
368
383
369 """
384 """
370
385
371 def __init__(self, cmd, timeout=None):
386 def __init__(self, cmd, timeout=None):
372 if not isinstance(cmd, (list, tuple)):
387 if not isinstance(cmd, (list, tuple)):
373 cmd = shlex.split(cmd)
388 cmd = shlex.split(cmd)
374 super().__init__()
389 super().__init__()
375 self._cmd = cmd
390 self._cmd = cmd
376 self._timeout = timeout
391 self._timeout = timeout
377
392
378 def run(self, event):
393 def run(self, event):
379 cmd = self._cmd
394 cmd = self._cmd
380 timeout = self._timeout
395 timeout = self._timeout
381 log.debug('Executing command %s.', cmd)
396 log.debug('Executing command %s.', cmd)
382
397
383 try:
398 try:
384 output = subprocess.check_output(
399 output = subprocess.check_output(
385 cmd, timeout=timeout, stderr=subprocess.STDOUT)
400 cmd, timeout=timeout, stderr=subprocess.STDOUT)
386 log.debug('Command finished %s', cmd)
401 log.debug('Command finished %s', cmd)
387 if output:
402 if output:
388 log.debug('Command output: %s', output)
403 log.debug('Command output: %s', output)
389 except subprocess.TimeoutExpired as e:
404 except subprocess.TimeoutExpired as e:
390 log.exception('Timeout while executing command.')
405 log.exception('Timeout while executing command.')
391 if e.output:
406 if e.output:
392 log.error('Command output: %s', e.output)
407 log.error('Command output: %s', e.output)
393 except subprocess.CalledProcessError as e:
408 except subprocess.CalledProcessError as e:
394 log.exception('Error while executing command.')
409 log.exception('Error while executing command.')
395 if e.output:
410 if e.output:
396 log.error('Command output: %s', e.output)
411 log.error('Command output: %s', e.output)
397 except Exception:
412 except Exception:
398 log.exception(
413 log.exception(
399 'Exception while executing command %s.', cmd)
414 'Exception while executing command %s.', cmd)
@@ -1,79 +1,50 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Security Admin')}
4 ${_('Security Admin')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='security')}
17 ${self.admin_menu(active='security')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="panel panel-default">
24 <div class="panel panel-default">
25 <div class="panel-heading">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Security Audit')}</h3>
26 <h3 class="panel-title">${_('Security Audit')}</h3>
27 </div>
27 </div>
28 <div class="panel-body">
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30 <p>
30 <p>
31 ${_('You can scan your repositories for exposed secrets, passwords, etc')}
31 ${_('You can scan your repositories for exposed secrets, passwords, etc')}
32 </p>
32 </p>
33 </div>
33 </div>
34 </div>
34 </div>
35
35
36 <div class="panel panel-default">
36 <div class="panel panel-default">
37 <div class="panel-heading">
37 <div class="panel-heading">
38 <h3 class="panel-title">${_('Allowed client versions')}</h3>
38 <h3 class="panel-title">${_('Allowed client versions')}</h3>
39 </div>
39 </div>
40 <div class="panel-body">
40 <div class="panel-body">
41 %if c.rhodecode_edition_id != 'EE':
42 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
41 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
43 <p>
42 <p>
44 ${_('Some outdated client versions may have security vulnerabilities. This section have rules for whitelisting versions of clients for Git, Mercurial and SVN.')}
43 ${_('Some outdated client versions may have security vulnerabilities. This section have rules for whitelisting versions of clients for Git, Mercurial and SVN.')}
45 </p>
44 </p>
46 %else:
47 <div class="inner form" id="container">
48 </div>
49 %endif
50 </div>
45 </div>
51
46
47
52 </div>
48 </div>
53
49
54 <script>
55 $(document).ready(function() {
56 $.ajax({
57 url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'),
58 type: 'GET',
59 success: function(response) {
60 $('#container').html(response);
61 },
62 });
63 $(document).on('submit', '#allowed_clients_form', function(event) {
64 event.preventDefault();
65 var formData = $(this).serialize();
66
67 $.ajax({
68 url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'),
69 type: 'POST',
70 data: formData,
71 success: function(response) {
72 $('#container').html(response);
73 },
74 });
75 });
76 });
77 </script>
78
79 </%def>
50 </%def>
@@ -1,323 +1,308 b''
1 ## snippet for displaying vcs settings
1 ## snippet for displaying vcs settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 ## ${vcss.vcs_settings_fields()}
4 ## ${vcss.vcs_settings_fields()}
5
5
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, **kwargs)">
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, **kwargs)">
7 % if display_globals:
7 % if display_globals:
8 <div class="panel panel-default">
8
9 <div class="panel-heading" id="general">
10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"></a></h3>
11 </div>
12 <div class="panel-body">
13 <div class="field">
14 <div class="checkbox">
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 </div>
18 <div class="label">
19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 </div>
21 </div>
22 </div>
23 </div>
24 % endif
9 % endif
25
10
26 % if display_globals or repo_type in ['git', 'hg']:
11 % if display_globals or repo_type in ['git', 'hg']:
27 <div class="panel panel-default">
12 <div class="panel panel-default">
28 <div class="panel-heading" id="vcs-hooks-options">
13 <div class="panel-heading" id="vcs-hooks-options">
29 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"></a></h3>
14 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"></a></h3>
30 </div>
15 </div>
31 <div class="panel-body">
16 <div class="panel-body">
32 <div class="field">
17 <div class="field">
33 <div class="checkbox">
18 <div class="checkbox">
34 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
19 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
35 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
20 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
36 </div>
21 </div>
37
22
38 <div class="label">
23 <div class="label">
39 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
24 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
40 </div>
25 </div>
41 <div class="checkbox">
26 <div class="checkbox">
42 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
27 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
43 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
28 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
44 </div>
29 </div>
45 <div class="label">
30 <div class="label">
46 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
31 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
47 </div>
32 </div>
48 <div class="checkbox">
33 <div class="checkbox">
49 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
34 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
50 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
35 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
51 </div>
36 </div>
52 <div class="label">
37 <div class="label">
53 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
38 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
54 </div>
39 </div>
55 </div>
40 </div>
56 </div>
41 </div>
57 </div>
42 </div>
58 % endif
43 % endif
59
44
60 % if display_globals or repo_type in ['hg']:
45 % if display_globals or repo_type in ['hg']:
61 <div class="panel panel-default">
46 <div class="panel panel-default">
62 <div class="panel-heading" id="vcs-hg-options">
47 <div class="panel-heading" id="vcs-hg-options">
63 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"></a></h3>
48 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"></a></h3>
64 </div>
49 </div>
65 <div class="panel-body">
50 <div class="panel-body">
66 <div class="checkbox">
51 <div class="checkbox">
67 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
52 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
68 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
53 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
69 </div>
54 </div>
70 <div class="label">
55 <div class="label">
71 % if display_globals:
56 % if display_globals:
72 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
57 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
73 % else:
58 % else:
74 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
59 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
75 % endif
60 % endif
76 </div>
61 </div>
77
62
78 <div class="checkbox">
63 <div class="checkbox">
79 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
64 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
80 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
65 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
81 </div>
66 </div>
82 <div class="label">
67 <div class="label">
83 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
68 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
84 </div>
69 </div>
85
70
86 <div class="checkbox">
71 <div class="checkbox">
87 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
72 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
88 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
73 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
89 </div>
74 </div>
90 <div class="label">
75 <div class="label">
91 % if display_globals:
76 % if display_globals:
92 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
77 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
93 % else:
78 % else:
94 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
79 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
95 % endif
80 % endif
96 </div>
81 </div>
97
82
98 </div>
83 </div>
99 </div>
84 </div>
100 % endif
85 % endif
101
86
102 % if display_globals or repo_type in ['git']:
87 % if display_globals or repo_type in ['git']:
103 <div class="panel panel-default">
88 <div class="panel panel-default">
104 <div class="panel-heading" id="vcs-git-options">
89 <div class="panel-heading" id="vcs-git-options">
105 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"></a></h3>
90 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"></a></h3>
106 </div>
91 </div>
107 <div class="panel-body">
92 <div class="panel-body">
108 <div class="checkbox">
93 <div class="checkbox">
109 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
94 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
110 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
95 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
111 </div>
96 </div>
112 <div class="label">
97 <div class="label">
113 % if display_globals:
98 % if display_globals:
114 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
99 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
115 % else:
100 % else:
116 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
101 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
117 % endif
102 % endif
118 </div>
103 </div>
119 </div>
104 </div>
120 </div>
105 </div>
121 % endif
106 % endif
122
107
123 % if display_globals or repo_type in ['svn']:
108 % if display_globals or repo_type in ['svn']:
124 <div class="panel panel-default">
109 <div class="panel panel-default">
125 <div class="panel-heading" id="vcs-svn-options">
110 <div class="panel-heading" id="vcs-svn-options">
126 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"></a></h3>
111 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"></a></h3>
127 </div>
112 </div>
128 <div class="panel-body">
113 <div class="panel-body">
129 % if display_globals:
114 % if display_globals:
130 <div class="field">
115 <div class="field">
131 <div class="content" >
116 <div class="content" >
132 <label>${_('mod_dav config')}</label><br/>
117 <label>${_('mod_dav config')}</label><br/>
133 <code>path: ${c.svn_config_path}</code>
118 <code>path: ${c.svn_config_path}</code>
134 </div>
119 </div>
135 <br/>
120 <br/>
136
121
137 <div>
122 <div>
138
123
139 % if c.svn_generate_config:
124 % if c.svn_generate_config:
140 <span class="buttons">
125 <span class="buttons">
141 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Re-generate Apache Config')}</button>
126 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Re-generate Apache Config')}</button>
142 </span>
127 </span>
143 % endif
128 % endif
144 </div>
129 </div>
145 </div>
130 </div>
146 % endif
131 % endif
147
132
148 <div class="field">
133 <div class="field">
149 <div class="content" >
134 <div class="content" >
150 <label>${_('Repository patterns')}</label><br/>
135 <label>${_('Repository patterns')}</label><br/>
151 </div>
136 </div>
152 </div>
137 </div>
153 <div class="label">
138 <div class="label">
154 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
139 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
155 </div>
140 </div>
156
141
157 <div class="field branch_patterns">
142 <div class="field branch_patterns">
158 <div class="input" >
143 <div class="input" >
159 <label>${_('Branches')}:</label><br/>
144 <label>${_('Branches')}:</label><br/>
160 </div>
145 </div>
161 % if svn_branch_patterns:
146 % if svn_branch_patterns:
162 % for branch in svn_branch_patterns:
147 % for branch in svn_branch_patterns:
163 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
148 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
164 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
149 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
165 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
150 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
166 % if kwargs.get('disabled') != 'disabled':
151 % if kwargs.get('disabled') != 'disabled':
167 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
152 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
168 ${_('Delete')}
153 ${_('Delete')}
169 </span>
154 </span>
170 % endif
155 % endif
171 </div>
156 </div>
172 % endfor
157 % endfor
173 %endif
158 %endif
174 </div>
159 </div>
175 % if kwargs.get('disabled') != 'disabled':
160 % if kwargs.get('disabled') != 'disabled':
176 <div class="field branch_patterns">
161 <div class="field branch_patterns">
177 <div class="input" >
162 <div class="input" >
178 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
163 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
179 </div>
164 </div>
180 </div>
165 </div>
181 % endif
166 % endif
182 <div class="field tag_patterns">
167 <div class="field tag_patterns">
183 <div class="input" >
168 <div class="input" >
184 <label>${_('Tags')}:</label><br/>
169 <label>${_('Tags')}:</label><br/>
185 </div>
170 </div>
186 % if svn_tag_patterns:
171 % if svn_tag_patterns:
187 % for tag in svn_tag_patterns:
172 % for tag in svn_tag_patterns:
188 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
173 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
189 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
174 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
190 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
175 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
191 % if kwargs.get('disabled') != 'disabled':
176 % if kwargs.get('disabled') != 'disabled':
192 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
177 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
193 ${_('Delete')}
178 ${_('Delete')}
194 </span>
179 </span>
195 %endif
180 %endif
196 </div>
181 </div>
197 % endfor
182 % endfor
198 % endif
183 % endif
199 </div>
184 </div>
200 % if kwargs.get('disabled') != 'disabled':
185 % if kwargs.get('disabled') != 'disabled':
201 <div class="field tag_patterns">
186 <div class="field tag_patterns">
202 <div class="input" >
187 <div class="input" >
203 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
188 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
204 </div>
189 </div>
205 </div>
190 </div>
206 %endif
191 %endif
207 </div>
192 </div>
208 </div>
193 </div>
209 % else:
194 % else:
210 ${h.hidden('new_svn_branch' + suffix, '')}
195 ${h.hidden('new_svn_branch' + suffix, '')}
211 ${h.hidden('new_svn_tag' + suffix, '')}
196 ${h.hidden('new_svn_tag' + suffix, '')}
212 % endif
197 % endif
213
198
214
199
215 % if display_globals or repo_type in ['hg', 'git']:
200 % if display_globals or repo_type in ['hg', 'git']:
216 <div class="panel panel-default">
201 <div class="panel panel-default">
217 <div class="panel-heading" id="vcs-pull-requests-options">
202 <div class="panel-heading" id="vcs-pull-requests-options">
218 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
203 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
219 </div>
204 </div>
220 <div class="panel-body">
205 <div class="panel-body">
221 <div class="checkbox">
206 <div class="checkbox">
222 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
207 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
223 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
208 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
224 </div>
209 </div>
225 <div class="label">
210 <div class="label">
226 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
211 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
227 </div>
212 </div>
228 <div class="checkbox">
213 <div class="checkbox">
229 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
214 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
230 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
215 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
231 </div>
216 </div>
232 <div class="label">
217 <div class="label">
233 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
218 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
234 </div>
219 </div>
235 </div>
220 </div>
236 </div>
221 </div>
237 % endif
222 % endif
238
223
239 % if display_globals or repo_type in ['hg', 'git', 'svn']:
224 % if display_globals or repo_type in ['hg', 'git', 'svn']:
240 <div class="panel panel-default">
225 <div class="panel panel-default">
241 <div class="panel-heading" id="vcs-pull-requests-options">
226 <div class="panel-heading" id="vcs-pull-requests-options">
242 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
227 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
243 </div>
228 </div>
244 <div class="panel-body">
229 <div class="panel-body">
245 <div class="checkbox">
230 <div class="checkbox">
246 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
231 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
247 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
232 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
248 </div>
233 </div>
249 </div>
234 </div>
250 </div>
235 </div>
251 % endif
236 % endif
252
237
253 % if display_globals or repo_type in ['hg',]:
238 % if display_globals or repo_type in ['hg',]:
254 <div class="panel panel-default">
239 <div class="panel panel-default">
255 <div class="panel-heading" id="vcs-pull-requests-options">
240 <div class="panel-heading" id="vcs-pull-requests-options">
256 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"></a></h3>
241 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"></a></h3>
257 </div>
242 </div>
258 <div class="panel-body">
243 <div class="panel-body">
259 ## Specific HG settings
244 ## Specific HG settings
260 <div class="checkbox">
245 <div class="checkbox">
261 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
246 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
262 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
247 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
263 </div>
248 </div>
264 <div class="label">
249 <div class="label">
265 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
250 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
266 </div>
251 </div>
267
252
268 <div class="checkbox">
253 <div class="checkbox">
269 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
254 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
270 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
255 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
271 </div>
256 </div>
272 <div class="label">
257 <div class="label">
273 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
258 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
274 </div>
259 </div>
275
260
276
261
277 </div>
262 </div>
278 </div>
263 </div>
279 % endif
264 % endif
280
265
281 % if display_globals or repo_type in ['git']:
266 % if display_globals or repo_type in ['git']:
282 <div class="panel panel-default">
267 <div class="panel panel-default">
283 <div class="panel-heading" id="vcs-pull-requests-options">
268 <div class="panel-heading" id="vcs-pull-requests-options">
284 <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"></a></h3>
269 <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"></a></h3>
285 </div>
270 </div>
286 <div class="panel-body">
271 <div class="panel-body">
287 ## <div class="checkbox">
272 ## <div class="checkbox">
288 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
273 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
289 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
274 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
290 ## </div>
275 ## </div>
291 ## <div class="label">
276 ## <div class="label">
292 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
277 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
293 ## </div>
278 ## </div>
294
279
295 <div class="checkbox">
280 <div class="checkbox">
296 ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
281 ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
297 <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
282 <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
298 </div>
283 </div>
299 <div class="label">
284 <div class="label">
300 <span class="help-block">${_('Delete branch after merging it into destination branch.')}</span>
285 <span class="help-block">${_('Delete branch after merging it into destination branch.')}</span>
301 </div>
286 </div>
302 </div>
287 </div>
303 </div>
288 </div>
304 % endif
289 % endif
305
290
306 <script type="text/javascript">
291 <script type="text/javascript">
307
292
308 $(document).ready(function() {
293 $(document).ready(function() {
309 /* On click handler for the `Generate Apache Config` button. It sends a
294 /* On click handler for the `Generate Apache Config` button. It sends a
310 POST request to trigger the (re)generation of the mod_dav_svn config. */
295 POST request to trigger the (re)generation of the mod_dav_svn config. */
311 $('#vcs_svn_generate_cfg').on('click', function(event) {
296 $('#vcs_svn_generate_cfg').on('click', function(event) {
312 event.preventDefault();
297 event.preventDefault();
313 var url = "${h.route_path('admin_settings_vcs_svn_generate_cfg')}";
298 var url = "${h.route_path('admin_settings_vcs_svn_generate_cfg')}";
314 var jqxhr = $.post(url, {'csrf_token': CSRF_TOKEN});
299 var jqxhr = $.post(url, {'csrf_token': CSRF_TOKEN});
315 jqxhr.done(function(data) {
300 jqxhr.done(function(data) {
316 $.Topic('/notifications').publish(data);
301 $.Topic('/notifications').publish(data);
317 });
302 });
318 });
303 });
319 });
304 });
320
305
321 </script>
306 </script>
322 </%def>
307 </%def>
323
308
@@ -1,155 +1,154 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 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 urllib.parse
20 import urllib.parse
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24 import simplejson as json
24 import simplejson as json
25
25
26 from rhodecode.lib.vcs.backends.base import Config
26 from rhodecode.lib.vcs.backends.base import Config
27 from rhodecode.tests.lib.middleware import mock_scm_app
27 from rhodecode.tests.lib.middleware import mock_scm_app
28 import rhodecode.lib.middleware.simplehg as simplehg
28 import rhodecode.lib.middleware.simplehg as simplehg
29
29
30
30
31 def get_environ(url):
31 def get_environ(url):
32 """Construct a minimum WSGI environ based on the URL."""
32 """Construct a minimum WSGI environ based on the URL."""
33 parsed_url = urllib.parse.urlparse(url)
33 parsed_url = urllib.parse.urlparse(url)
34 environ = {
34 environ = {
35 'PATH_INFO': parsed_url.path,
35 'PATH_INFO': parsed_url.path,
36 'QUERY_STRING': parsed_url.query,
36 'QUERY_STRING': parsed_url.query,
37 }
37 }
38
38
39 return environ
39 return environ
40
40
41
41
42 @pytest.mark.parametrize(
42 @pytest.mark.parametrize(
43 'url, expected_action',
43 'url, expected_action',
44 [
44 [
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
50 ('/foo/bar?cmd=batch', 'push'),
50 ('/foo/bar?cmd=batch', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
52 # Edge case: unknown argument: assume push
52 # Edge case: unknown argument: assume push
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
55 # Edge case: not cmd argument
55 # Edge case: not cmd argument
56 ('/foo/bar?key=tip', 'push'),
56 ('/foo/bar?key=tip', 'push'),
57 ])
57 ])
58 def test_get_action(url, expected_action, request_stub):
58 def test_get_action(url, expected_action, request_stub):
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
60 registry=request_stub.registry)
60 registry=request_stub.registry)
61 assert expected_action == app._get_action(get_environ(url))
61 assert expected_action == app._get_action(get_environ(url))
62
62
63
63
64 @pytest.mark.parametrize(
64 @pytest.mark.parametrize(
65 'environ, expected_xargs, expected_batch',
65 'environ, expected_xargs, expected_batch',
66 [
66 [
67 ({},
67 ({},
68 [''], ['push']),
68 [''], ['push']),
69
69
70 ({'HTTP_X_HGARG_1': ''},
70 ({'HTTP_X_HGARG_1': ''},
71 [''], ['push']),
71 [''], ['push']),
72
72
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
74 ['listkeys namespace=phases'], ['pull']),
74 ['listkeys namespace=phases'], ['pull']),
75
75
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
78
78
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
80 ['namespace=phases'], ['push']),
80 ['namespace=phases'], ['push']),
81
81
82 ])
82 ])
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
84 app = simplehg.SimpleHg
84 app = simplehg.SimpleHg
85
85
86 result = app._get_xarg_headers(environ)
86 result = app._get_xarg_headers(environ)
87 result_batch = app._get_batch_cmd(environ)
87 result_batch = app._get_batch_cmd(environ)
88 assert expected_xargs == result
88 assert expected_xargs == result
89 assert expected_batch == result_batch
89 assert expected_batch == result_batch
90
90
91
91
92 @pytest.mark.parametrize(
92 @pytest.mark.parametrize(
93 'url, expected_repo_name',
93 'url, expected_repo_name',
94 [
94 [
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
98 # Repos with trailing slashes.
98 # Repos with trailing slashes.
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
102 ])
102 ])
103 def test_get_repository_name(url, expected_repo_name, request_stub):
103 def test_get_repository_name(url, expected_repo_name, request_stub):
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
105 registry=request_stub.registry)
105 registry=request_stub.registry)
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
107
107
108
108
109 def test_get_config(user_util, baseapp, request_stub):
109 def test_get_config(user_util, baseapp, request_stub):
110 repo = user_util.create_repo(repo_type='git')
110 repo = user_util.create_repo(repo_type='git')
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
112 registry=request_stub.registry)
112 registry=request_stub.registry)
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
114
114
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
116
116
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
119 hg_config_org = config
119 hg_config_org = config
120
120
121 expected_config = [
121 expected_config = [
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
123 ('web', 'push_ssl', 'False'),
124 ('web', 'allow_push', '*'),
123 ('web', 'allow_push', '*'),
125 ('web', 'allow_archive', 'gz zip bz2'),
124 ('web', 'allow_archive', 'gz zip bz2'),
126 ('web', 'baseurl', '/'),
125 ('web', 'baseurl', '/'),
127 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
126 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
128 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
127 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
129 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
128 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
130 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
129 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
131 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
130 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
132 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
131 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
133 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
132 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
134 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
133 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
135 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
134 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
136 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
135 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
137 ('phases', 'publish', 'True'),
136 ('phases', 'publish', 'True'),
138 ('extensions', 'largefiles', ''),
137 ('extensions', 'largefiles', ''),
139 ('paths', '/', hg_config_org.get('paths', '/')),
138 ('paths', '/', hg_config_org.get('paths', '/')),
140 ('rhodecode', 'RC_SCM_DATA', '[["foo","FOO","bar","BAR"]]')
139 ('rhodecode', 'RC_SCM_DATA', '[["foo","FOO","bar","BAR"]]')
141 ]
140 ]
142 for entry in expected_config:
141 for entry in expected_config:
143 assert entry in hg_config
142 assert entry in hg_config
144
143
145
144
146 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
145 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
147 config = {
146 config = {
148 'auth_ret_code': '',
147 'auth_ret_code': '',
149 'base_path': '',
148 'base_path': '',
150 'vcs.scm_app_implementation':
149 'vcs.scm_app_implementation':
151 'rhodecode.tests.lib.middleware.mock_scm_app',
150 'rhodecode.tests.lib.middleware.mock_scm_app',
152 }
151 }
153 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
152 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
154 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
153 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
155 assert wsgi_app is mock_scm_app.mock_hg_wsgi
154 assert wsgi_app is mock_scm_app.mock_hg_wsgi
@@ -1,451 +1,448 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 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 mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.str_utils import base64_to_str
23 from rhodecode.lib.str_utils import base64_to_str
24 from rhodecode.lib.utils2 import AttributeDict
24 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.tests.utils import CustomTestApp
25 from rhodecode.tests.utils import CustomTestApp
26
26
27 from rhodecode.lib.caching_query import FromCache
27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.middleware import simplevcs
28 from rhodecode.lib.middleware import simplevcs
29 from rhodecode.lib.middleware.https_fixup import HttpsFixup
29 from rhodecode.lib.middleware.https_fixup import HttpsFixup
30 from rhodecode.lib.middleware.utils import scm_app_http
30 from rhodecode.lib.middleware.utils import scm_app_http
31 from rhodecode.model.db import User, _hash_key
31 from rhodecode.model.db import User, _hash_key
32 from rhodecode.model.meta import Session, cache as db_cache
32 from rhodecode.model.meta import Session, cache as db_cache
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
34 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
35 from rhodecode.tests.lib.middleware import mock_scm_app
35 from rhodecode.tests.lib.middleware import mock_scm_app
36
36
37
37
38 class StubVCSController(simplevcs.SimpleVCS):
38 class StubVCSController(simplevcs.SimpleVCS):
39
39
40 SCM = 'hg'
40 SCM = 'hg'
41 stub_response_body = tuple()
41 stub_response_body = tuple()
42
42
43 def __init__(self, *args, **kwargs):
43 def __init__(self, *args, **kwargs):
44 super(StubVCSController, self).__init__(*args, **kwargs)
44 super(StubVCSController, self).__init__(*args, **kwargs)
45 self._action = 'pull'
45 self._action = 'pull'
46 self._is_shadow_repo_dir = True
46 self._is_shadow_repo_dir = True
47 self._name = HG_REPO
47 self._name = HG_REPO
48 self.set_repo_names(None)
48 self.set_repo_names(None)
49
49
50 @property
50 @property
51 def is_shadow_repo_dir(self):
51 def is_shadow_repo_dir(self):
52 return self._is_shadow_repo_dir
52 return self._is_shadow_repo_dir
53
53
54 def _get_repository_name(self, environ):
54 def _get_repository_name(self, environ):
55 return self._name
55 return self._name
56
56
57 def _get_action(self, environ):
57 def _get_action(self, environ):
58 return self._action
58 return self._action
59
59
60 def _create_wsgi_app(self, repo_path, repo_name, config):
60 def _create_wsgi_app(self, repo_path, repo_name, config):
61 def fake_app(environ, start_response):
61 def fake_app(environ, start_response):
62 headers = [
62 headers = [
63 ('Http-Accept', 'application/mercurial')
63 ('Http-Accept', 'application/mercurial')
64 ]
64 ]
65 start_response('200 OK', headers)
65 start_response('200 OK', headers)
66 return self.stub_response_body
66 return self.stub_response_body
67 return fake_app
67 return fake_app
68
68
69 def _create_config(self, extras, repo_name, scheme='http'):
69 def _create_config(self, extras, repo_name, scheme='http'):
70 return None
70 return None
71
71
72
72
73 @pytest.fixture()
73 @pytest.fixture()
74 def vcscontroller(baseapp, config_stub, request_stub):
74 def vcscontroller(baseapp, config_stub, request_stub):
75 from rhodecode.config.middleware import ce_auth_resources
75 from rhodecode.config.middleware import ce_auth_resources
76
76
77 config_stub.testing_securitypolicy()
77 config_stub.testing_securitypolicy()
78 config_stub.include('rhodecode.authentication')
78 config_stub.include('rhodecode.authentication')
79
79
80 for resource in ce_auth_resources:
80 for resource in ce_auth_resources:
81 config_stub.include(resource)
81 config_stub.include(resource)
82
82
83 controller = StubVCSController(
83 controller = StubVCSController(
84 baseapp.config.get_settings(), request_stub.registry)
84 baseapp.config.get_settings(), request_stub.registry)
85 app = HttpsFixup(controller, baseapp.config.get_settings())
85 app = HttpsFixup(controller, baseapp.config.get_settings())
86 app = CustomTestApp(app)
86 app = CustomTestApp(app)
87
87
88 _remove_default_user_from_query_cache()
88 _remove_default_user_from_query_cache()
89
89
90 # Sanity checks that things are set up correctly
90 # Sanity checks that things are set up correctly
91 app.get('/' + HG_REPO, status=200)
91 app.get('/' + HG_REPO, status=200)
92
92
93 app.controller = controller
93 app.controller = controller
94 return app
94 return app
95
95
96
96
97 def _remove_default_user_from_query_cache():
97 def _remove_default_user_from_query_cache():
98 user = User.get_default_user(cache=True)
98 user = User.get_default_user(cache=True)
99 query = Session().query(User).filter(User.username == user.username)
99 query = Session().query(User).filter(User.username == user.username)
100 query = query.options(
100 query = query.options(
101 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
101 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
102
102
103 db_cache.invalidate(
103 db_cache.invalidate(
104 query, {},
104 query, {},
105 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
105 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
106
106
107 Session().expire(user)
107 Session().expire(user)
108
108
109
109
110 def test_handles_exceptions_during_permissions_checks(
110 def test_handles_exceptions_during_permissions_checks(
111 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
111 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
112
112
113 test_password = 'qweqwe'
113 test_password = 'qweqwe'
114 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
114 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
115 test_username = test_user.username
115 test_username = test_user.username
116
116
117 enable_auth_plugins.enable([
117 enable_auth_plugins.enable([
118 'egg:rhodecode-enterprise-ce#headers',
118 'egg:rhodecode-enterprise-ce#headers',
119 'egg:rhodecode-enterprise-ce#token',
119 'egg:rhodecode-enterprise-ce#token',
120 'egg:rhodecode-enterprise-ce#rhodecode'],
120 'egg:rhodecode-enterprise-ce#rhodecode'],
121 override={
121 override={
122 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
122 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
123 })
123 })
124
124
125 user_and_pass = f'{test_username}:{test_password}'
125 user_and_pass = f'{test_username}:{test_password}'
126 auth_password = base64_to_str(user_and_pass)
126 auth_password = base64_to_str(user_and_pass)
127
127
128 extra_environ = {
128 extra_environ = {
129 'AUTH_TYPE': 'Basic',
129 'AUTH_TYPE': 'Basic',
130 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
130 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
131 'REMOTE_USER': test_username,
131 'REMOTE_USER': test_username,
132 }
132 }
133
133
134 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
134 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
135 vcscontroller.get('/', status=200, extra_environ=extra_environ)
135 vcscontroller.get('/', status=200, extra_environ=extra_environ)
136
136
137 # Simulate trouble during permission checks
137 # Simulate trouble during permission checks
138 with mock.patch('rhodecode.model.db.User.get_by_username',
138 with mock.patch('rhodecode.model.db.User.get_by_username',
139 side_effect=Exception('permission_error_test')) as get_user:
139 side_effect=Exception('permission_error_test')) as get_user:
140 # Verify that a correct 500 is returned and check that the expected
140 # Verify that a correct 500 is returned and check that the expected
141 # code path was hit.
141 # code path was hit.
142 vcscontroller.get('/', status=500, extra_environ=extra_environ)
142 vcscontroller.get('/', status=500, extra_environ=extra_environ)
143 assert get_user.called
143 assert get_user.called
144
144
145
145
146 class StubFailVCSController(simplevcs.SimpleVCS):
146 class StubFailVCSController(simplevcs.SimpleVCS):
147 def _handle_request(self, environ, start_response):
147 def _handle_request(self, environ, start_response):
148 raise Exception("BOOM")
148 raise Exception("BOOM")
149
149
150
150
151 @pytest.fixture(scope='module')
151 @pytest.fixture(scope='module')
152 def fail_controller(baseapp):
152 def fail_controller(baseapp):
153 controller = StubFailVCSController(
153 controller = StubFailVCSController(
154 baseapp.config.get_settings(), baseapp.config)
154 baseapp.config.get_settings(), baseapp.config)
155 controller = HttpsFixup(controller, baseapp.config.get_settings())
155 controller = HttpsFixup(controller, baseapp.config.get_settings())
156 controller = CustomTestApp(controller)
156 controller = CustomTestApp(controller)
157 return controller
157 return controller
158
158
159
159
160 def test_handles_exceptions_as_internal_server_error(fail_controller):
160 def test_handles_exceptions_as_internal_server_error(fail_controller):
161 fail_controller.get('/', status=500)
161 fail_controller.get('/', status=500)
162
162
163
163
164 def test_provides_traceback_for_appenlight(fail_controller):
164 def test_provides_traceback_for_appenlight(fail_controller):
165 response = fail_controller.get(
165 response = fail_controller.get(
166 '/', status=500, extra_environ={'appenlight.client': 'fake'})
166 '/', status=500, extra_environ={'appenlight.client': 'fake'})
167 assert 'appenlight.__traceback' in response.request.environ
167 assert 'appenlight.__traceback' in response.request.environ
168
168
169
169
170 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
170 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
171 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
171 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
172 assert controller.scm_app is scm_app_http
172 assert controller.scm_app is scm_app_http
173
173
174
174
175 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
175 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
176 config = baseapp.config.get_settings().copy()
176 config = baseapp.config.get_settings().copy()
177 config['vcs.scm_app_implementation'] = (
177 config['vcs.scm_app_implementation'] = (
178 'rhodecode.tests.lib.middleware.mock_scm_app')
178 'rhodecode.tests.lib.middleware.mock_scm_app')
179 controller = StubVCSController(config, request_stub.registry)
179 controller = StubVCSController(config, request_stub.registry)
180 assert controller.scm_app is mock_scm_app
180 assert controller.scm_app is mock_scm_app
181
181
182
182
183 @pytest.mark.parametrize('query_string, expected', [
183 @pytest.mark.parametrize('query_string, expected', [
184 ('cmd=stub_command', True),
184 ('cmd=stub_command', True),
185 ('cmd=listkeys', False),
185 ('cmd=listkeys', False),
186 ])
186 ])
187 def test_should_check_locking(query_string, expected):
187 def test_should_check_locking(query_string, expected):
188 result = simplevcs._should_check_locking(query_string)
188 result = simplevcs._should_check_locking(query_string)
189 assert result == expected
189 assert result == expected
190
190
191
191
192 class TestShadowRepoRegularExpression(object):
192 class TestShadowRepoRegularExpression(object):
193 pr_segment = 'pull-request'
193 pr_segment = 'pull-request'
194 shadow_segment = 'repository'
194 shadow_segment = 'repository'
195
195
196 @pytest.mark.parametrize('url, expected', [
196 @pytest.mark.parametrize('url, expected', [
197 # repo with/without groups
197 # repo with/without groups
198 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
198 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
199 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
199 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
200 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
200 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
201 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
201 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
202
202
203 # pull request ID
203 # pull request ID
204 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
204 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
205 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
205 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
206 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
206 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
207 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
207 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
208
208
209 # unicode
209 # unicode
210 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
210 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
211 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
211 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
212
212
213 # trailing/leading slash
213 # trailing/leading slash
214 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
214 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
215 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
215 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
216 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
216 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
217
217
218 # misc
218 # misc
219 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
219 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
220 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
220 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
221 ])
221 ])
222 def test_shadow_repo_regular_expression(self, url, expected):
222 def test_shadow_repo_regular_expression(self, url, expected):
223 from rhodecode.lib.middleware.simplevcs import SimpleVCS
223 from rhodecode.lib.middleware.simplevcs import SimpleVCS
224 url = url.format(
224 url = url.format(
225 pr_segment=self.pr_segment,
225 pr_segment=self.pr_segment,
226 shadow_segment=self.shadow_segment)
226 shadow_segment=self.shadow_segment)
227 match_obj = SimpleVCS.shadow_repo_re.match(url)
227 match_obj = SimpleVCS.shadow_repo_re.match(url)
228 assert (match_obj is not None) == expected
228 assert (match_obj is not None) == expected
229
229
230
230
231 @pytest.mark.backends('git', 'hg')
231 @pytest.mark.backends('git', 'hg')
232 class TestShadowRepoExposure(object):
232 class TestShadowRepoExposure(object):
233
233
234 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
234 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
235 self, baseapp, request_stub):
235 self, baseapp, request_stub):
236 """
236 """
237 Check that a pull action to a shadow repo is propagated to the
237 Check that a pull action to a shadow repo is propagated to the
238 underlying wsgi app.
238 underlying wsgi app.
239 """
239 """
240 controller = StubVCSController(
240 controller = StubVCSController(
241 baseapp.config.get_settings(), request_stub.registry)
241 baseapp.config.get_settings(), request_stub.registry)
242 controller._check_ssl = mock.Mock()
243 controller.is_shadow_repo = True
242 controller.is_shadow_repo = True
244 controller._action = 'pull'
243 controller._action = 'pull'
245 controller._is_shadow_repo_dir = True
244 controller._is_shadow_repo_dir = True
246 controller.stub_response_body = (b'dummy body value',)
245 controller.stub_response_body = (b'dummy body value',)
247 controller._get_default_cache_ttl = mock.Mock(
246 controller._get_default_cache_ttl = mock.Mock(
248 return_value=(False, 0))
247 return_value=(False, 0))
249
248
250 environ_stub = {
249 environ_stub = {
251 'HTTP_HOST': 'test.example.com',
250 'HTTP_HOST': 'test.example.com',
252 'HTTP_ACCEPT': 'application/mercurial',
251 'HTTP_ACCEPT': 'application/mercurial',
253 'REQUEST_METHOD': 'GET',
252 'REQUEST_METHOD': 'GET',
254 'wsgi.url_scheme': 'http',
253 'wsgi.url_scheme': 'http',
255 }
254 }
256
255
257 response = controller(environ_stub, mock.Mock())
256 response = controller(environ_stub, mock.Mock())
258 response_body = b''.join(response)
257 response_body = b''.join(response)
259
258
260 # Assert that we got the response from the wsgi app.
259 # Assert that we got the response from the wsgi app.
261 assert response_body == b''.join(controller.stub_response_body)
260 assert response_body == b''.join(controller.stub_response_body)
262
261
263 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
262 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
264 """
263 """
265 Check that a pull action to a shadow repo is propagated to the
264 Check that a pull action to a shadow repo is propagated to the
266 underlying wsgi app.
265 underlying wsgi app.
267 """
266 """
268 controller = StubVCSController(
267 controller = StubVCSController(
269 baseapp.config.get_settings(), request_stub.registry)
268 baseapp.config.get_settings(), request_stub.registry)
270 controller._check_ssl = mock.Mock()
271 controller.is_shadow_repo = True
269 controller.is_shadow_repo = True
272 controller._action = 'pull'
270 controller._action = 'pull'
273 controller._is_shadow_repo_dir = False
271 controller._is_shadow_repo_dir = False
274 controller.stub_response_body = (b'dummy body value',)
272 controller.stub_response_body = (b'dummy body value',)
275 environ_stub = {
273 environ_stub = {
276 'HTTP_HOST': 'test.example.com',
274 'HTTP_HOST': 'test.example.com',
277 'HTTP_ACCEPT': 'application/mercurial',
275 'HTTP_ACCEPT': 'application/mercurial',
278 'REQUEST_METHOD': 'GET',
276 'REQUEST_METHOD': 'GET',
279 'wsgi.url_scheme': 'http',
277 'wsgi.url_scheme': 'http',
280 }
278 }
281
279
282 response = controller(environ_stub, mock.Mock())
280 response = controller(environ_stub, mock.Mock())
283 response_body = b''.join(response)
281 response_body = b''.join(response)
284
282
285 # Assert that we got the response from the wsgi app.
283 # Assert that we got the response from the wsgi app.
286 assert b'404 Not Found' in response_body
284 assert b'404 Not Found' in response_body
287
285
288 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
286 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
289 """
287 """
290 Check that a push action to a shadow repo is aborted.
288 Check that a push action to a shadow repo is aborted.
291 """
289 """
292 controller = StubVCSController(
290 controller = StubVCSController(
293 baseapp.config.get_settings(), request_stub.registry)
291 baseapp.config.get_settings(), request_stub.registry)
294 controller._check_ssl = mock.Mock()
295 controller.is_shadow_repo = True
292 controller.is_shadow_repo = True
296 controller._action = 'push'
293 controller._action = 'push'
297 controller.stub_response_body = (b'dummy body value',)
294 controller.stub_response_body = (b'dummy body value',)
298 environ_stub = {
295 environ_stub = {
299 'HTTP_HOST': 'test.example.com',
296 'HTTP_HOST': 'test.example.com',
300 'HTTP_ACCEPT': 'application/mercurial',
297 'HTTP_ACCEPT': 'application/mercurial',
301 'REQUEST_METHOD': 'GET',
298 'REQUEST_METHOD': 'GET',
302 'wsgi.url_scheme': 'http',
299 'wsgi.url_scheme': 'http',
303 }
300 }
304
301
305 response = controller(environ_stub, mock.Mock())
302 response = controller(environ_stub, mock.Mock())
306 response_body = b''.join(response)
303 response_body = b''.join(response)
307
304
308 assert response_body != controller.stub_response_body
305 assert response_body != controller.stub_response_body
309 # Assert that a 406 error is returned.
306 # Assert that a 406 error is returned.
310 assert b'406 Not Acceptable' in response_body
307 assert b'406 Not Acceptable' in response_body
311
308
312 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
309 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
313 """
310 """
314 Check that the set_repo_names method sets all names to the one returned
311 Check that the set_repo_names method sets all names to the one returned
315 by the _get_repository_name method on a request to a non shadow repo.
312 by the _get_repository_name method on a request to a non shadow repo.
316 """
313 """
317 environ_stub = {}
314 environ_stub = {}
318 controller = StubVCSController(
315 controller = StubVCSController(
319 baseapp.config.get_settings(), request_stub.registry)
316 baseapp.config.get_settings(), request_stub.registry)
320 controller._name = 'RepoGroup/MyRepo'
317 controller._name = 'RepoGroup/MyRepo'
321 controller.set_repo_names(environ_stub)
318 controller.set_repo_names(environ_stub)
322 assert not controller.is_shadow_repo
319 assert not controller.is_shadow_repo
323 assert (controller.url_repo_name ==
320 assert (controller.url_repo_name ==
324 controller.acl_repo_name ==
321 controller.acl_repo_name ==
325 controller.vcs_repo_name ==
322 controller.vcs_repo_name ==
326 controller._get_repository_name(environ_stub))
323 controller._get_repository_name(environ_stub))
327
324
328 def test_set_repo_names_with_shadow(
325 def test_set_repo_names_with_shadow(
329 self, baseapp, pr_util, config_stub, request_stub):
326 self, baseapp, pr_util, config_stub, request_stub):
330 """
327 """
331 Check that the set_repo_names method sets correct names on a request
328 Check that the set_repo_names method sets correct names on a request
332 to a shadow repo.
329 to a shadow repo.
333 """
330 """
334 from rhodecode.model.pull_request import PullRequestModel
331 from rhodecode.model.pull_request import PullRequestModel
335
332
336 pull_request = pr_util.create_pull_request()
333 pull_request = pr_util.create_pull_request()
337 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
334 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
338 target=pull_request.target_repo.repo_name,
335 target=pull_request.target_repo.repo_name,
339 pr_id=pull_request.pull_request_id,
336 pr_id=pull_request.pull_request_id,
340 pr_segment=TestShadowRepoRegularExpression.pr_segment,
337 pr_segment=TestShadowRepoRegularExpression.pr_segment,
341 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
338 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
342 controller = StubVCSController(
339 controller = StubVCSController(
343 baseapp.config.get_settings(), request_stub.registry)
340 baseapp.config.get_settings(), request_stub.registry)
344 controller._name = shadow_url
341 controller._name = shadow_url
345 controller.set_repo_names({})
342 controller.set_repo_names({})
346
343
347 # Get file system path to shadow repo for assertions.
344 # Get file system path to shadow repo for assertions.
348 workspace_id = PullRequestModel()._workspace_id(pull_request)
345 workspace_id = PullRequestModel()._workspace_id(pull_request)
349 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
346 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
350
347
351 assert controller.vcs_repo_name == vcs_repo_name
348 assert controller.vcs_repo_name == vcs_repo_name
352 assert controller.url_repo_name == shadow_url
349 assert controller.url_repo_name == shadow_url
353 assert controller.acl_repo_name == pull_request.target_repo.repo_name
350 assert controller.acl_repo_name == pull_request.target_repo.repo_name
354 assert controller.is_shadow_repo
351 assert controller.is_shadow_repo
355
352
356 def test_set_repo_names_with_shadow_but_missing_pr(
353 def test_set_repo_names_with_shadow_but_missing_pr(
357 self, baseapp, pr_util, config_stub, request_stub):
354 self, baseapp, pr_util, config_stub, request_stub):
358 """
355 """
359 Checks that the set_repo_names method enforces matching target repos
356 Checks that the set_repo_names method enforces matching target repos
360 and pull request IDs.
357 and pull request IDs.
361 """
358 """
362 pull_request = pr_util.create_pull_request()
359 pull_request = pr_util.create_pull_request()
363 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
360 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
364 target=pull_request.target_repo.repo_name,
361 target=pull_request.target_repo.repo_name,
365 pr_id=999999999,
362 pr_id=999999999,
366 pr_segment=TestShadowRepoRegularExpression.pr_segment,
363 pr_segment=TestShadowRepoRegularExpression.pr_segment,
367 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
364 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
368 controller = StubVCSController(
365 controller = StubVCSController(
369 baseapp.config.get_settings(), request_stub.registry)
366 baseapp.config.get_settings(), request_stub.registry)
370 controller._name = shadow_url
367 controller._name = shadow_url
371 controller.set_repo_names({})
368 controller.set_repo_names({})
372
369
373 assert not controller.is_shadow_repo
370 assert not controller.is_shadow_repo
374 assert (controller.url_repo_name ==
371 assert (controller.url_repo_name ==
375 controller.acl_repo_name ==
372 controller.acl_repo_name ==
376 controller.vcs_repo_name)
373 controller.vcs_repo_name)
377
374
378
375
379 @pytest.mark.usefixtures('baseapp')
376 @pytest.mark.usefixtures('baseapp')
380 class TestGenerateVcsResponse(object):
377 class TestGenerateVcsResponse(object):
381
378
382 def test_ensures_that_start_response_is_called_early_enough(self):
379 def test_ensures_that_start_response_is_called_early_enough(self):
383 self.call_controller_with_response_body(iter(['a', 'b']))
380 self.call_controller_with_response_body(iter(['a', 'b']))
384 assert self.start_response.called
381 assert self.start_response.called
385
382
386 def test_invalidates_cache_after_body_is_consumed(self):
383 def test_invalidates_cache_after_body_is_consumed(self):
387 result = self.call_controller_with_response_body(iter(['a', 'b']))
384 result = self.call_controller_with_response_body(iter(['a', 'b']))
388 assert not self.was_cache_invalidated()
385 assert not self.was_cache_invalidated()
389 # Consume the result
386 # Consume the result
390 list(result)
387 list(result)
391 assert self.was_cache_invalidated()
388 assert self.was_cache_invalidated()
392
389
393 def test_raises_unknown_exceptions(self):
390 def test_raises_unknown_exceptions(self):
394 result = self.call_controller_with_response_body(
391 result = self.call_controller_with_response_body(
395 self.raise_result_iter(vcs_kind='unknown'))
392 self.raise_result_iter(vcs_kind='unknown'))
396 with pytest.raises(Exception):
393 with pytest.raises(Exception):
397 list(result)
394 list(result)
398
395
399 def call_controller_with_response_body(self, response_body):
396 def call_controller_with_response_body(self, response_body):
400 settings = {
397 settings = {
401 'base_path': 'fake_base_path',
398 'base_path': 'fake_base_path',
402 'vcs.hooks.protocol.v2': 'celery',
399 'vcs.hooks.protocol.v2': 'celery',
403 'vcs.hooks.direct_calls': False,
400 'vcs.hooks.direct_calls': False,
404 }
401 }
405 registry = AttributeDict()
402 registry = AttributeDict()
406 controller = StubVCSController(settings, registry)
403 controller = StubVCSController(settings, registry)
407 controller._invalidate_cache = mock.Mock()
404 controller._invalidate_cache = mock.Mock()
408 controller.stub_response_body = response_body
405 controller.stub_response_body = response_body
409 self.start_response = mock.Mock()
406 self.start_response = mock.Mock()
410 result = controller._generate_vcs_response(
407 result = controller._generate_vcs_response(
411 environ={}, start_response=self.start_response,
408 environ={}, start_response=self.start_response,
412 repo_path='fake_repo_path',
409 repo_path='fake_repo_path',
413 extras={}, action='push')
410 extras={}, action='push')
414 self.controller = controller
411 self.controller = controller
415 return result
412 return result
416
413
417 def raise_result_iter(self, vcs_kind='repo_locked'):
414 def raise_result_iter(self, vcs_kind='repo_locked'):
418 """
415 """
419 Simulates an exception due to a vcs raised exception if kind vcs_kind
416 Simulates an exception due to a vcs raised exception if kind vcs_kind
420 """
417 """
421 raise self.vcs_exception(vcs_kind=vcs_kind)
418 raise self.vcs_exception(vcs_kind=vcs_kind)
422 yield "never_reached"
419 yield "never_reached"
423
420
424 def vcs_exception(self, vcs_kind='repo_locked'):
421 def vcs_exception(self, vcs_kind='repo_locked'):
425 locked_exception = Exception('TEST_MESSAGE')
422 locked_exception = Exception('TEST_MESSAGE')
426 locked_exception._vcs_kind = vcs_kind
423 locked_exception._vcs_kind = vcs_kind
427 return locked_exception
424 return locked_exception
428
425
429 def was_cache_invalidated(self):
426 def was_cache_invalidated(self):
430 return self.controller._invalidate_cache.called
427 return self.controller._invalidate_cache.called
431
428
432
429
433 class TestInitializeGenerator(object):
430 class TestInitializeGenerator(object):
434
431
435 def test_drains_first_element(self):
432 def test_drains_first_element(self):
436 gen = self.factory(['__init__', 1, 2])
433 gen = self.factory(['__init__', 1, 2])
437 result = list(gen)
434 result = list(gen)
438 assert result == [1, 2]
435 assert result == [1, 2]
439
436
440 @pytest.mark.parametrize('values', [
437 @pytest.mark.parametrize('values', [
441 [],
438 [],
442 [1, 2],
439 [1, 2],
443 ])
440 ])
444 def test_raises_value_error(self, values):
441 def test_raises_value_error(self, values):
445 with pytest.raises(ValueError):
442 with pytest.raises(ValueError):
446 self.factory(values)
443 self.factory(values)
447
444
448 @simplevcs.initialize_generator
445 @simplevcs.initialize_generator
449 def factory(self, iterable):
446 def factory(self, iterable):
450 for elem in iterable:
447 for elem in iterable:
451 yield elem
448 yield elem
@@ -1,1108 +1,1097 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 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 mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import str2bool
23 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.settings import VcsSettingsModel, UiSetting
25 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26
26
27
27
28 HOOKS_FORM_DATA = {
28 HOOKS_FORM_DATA = {
29 'hooks_changegroup_repo_size': True,
29 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_push_logger': True,
30 'hooks_changegroup_push_logger': True,
31 'hooks_outgoing_pull_logger': True
31 'hooks_outgoing_pull_logger': True
32 }
32 }
33
33
34 SVN_FORM_DATA = {
34 SVN_FORM_DATA = {
35 'new_svn_branch': 'test-branch',
35 'new_svn_branch': 'test-branch',
36 'new_svn_tag': 'test-tag'
36 'new_svn_tag': 'test-tag'
37 }
37 }
38
38
39 GENERAL_FORM_DATA = {
39 GENERAL_FORM_DATA = {
40 'rhodecode_pr_merge_enabled': True,
40 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_use_outdated_comments': True,
41 'rhodecode_use_outdated_comments': True,
42 'rhodecode_hg_use_rebase_for_merging': True,
42 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_close_branch_before_merging': True,
43 'rhodecode_hg_close_branch_before_merging': True,
44 'rhodecode_git_use_rebase_for_merging': True,
44 'rhodecode_git_use_rebase_for_merging': True,
45 'rhodecode_git_close_branch_before_merging': True,
45 'rhodecode_git_close_branch_before_merging': True,
46 'rhodecode_diff_cache': True,
46 'rhodecode_diff_cache': True,
47 }
47 }
48
48
49
49
50 class TestInheritGlobalSettingsProperty(object):
50 class TestInheritGlobalSettingsProperty(object):
51 def test_get_raises_exception_when_repository_not_specified(self):
51 def test_get_raises_exception_when_repository_not_specified(self):
52 model = VcsSettingsModel()
52 model = VcsSettingsModel()
53 with pytest.raises(Exception) as exc_info:
53 with pytest.raises(Exception) as exc_info:
54 model.inherit_global_settings
54 model.inherit_global_settings
55 assert str(exc_info.value) == 'Repository is not specified'
55 assert str(exc_info.value) == 'Repository is not specified'
56
56
57 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
57 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
58 model = VcsSettingsModel(repo=repo_stub.repo_name)
58 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 assert model.inherit_global_settings is True
59 assert model.inherit_global_settings is True
60
60
61 def test_value_is_returned(self, repo_stub, settings_util):
61 def test_value_is_returned(self, repo_stub, settings_util):
62 model = VcsSettingsModel(repo=repo_stub.repo_name)
62 model = VcsSettingsModel(repo=repo_stub.repo_name)
63 settings_util.create_repo_rhodecode_setting(
63 settings_util.create_repo_rhodecode_setting(
64 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
64 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
65 assert model.inherit_global_settings is False
65 assert model.inherit_global_settings is False
66
66
67 def test_value_is_set(self, repo_stub):
67 def test_value_is_set(self, repo_stub):
68 model = VcsSettingsModel(repo=repo_stub.repo_name)
68 model = VcsSettingsModel(repo=repo_stub.repo_name)
69 model.inherit_global_settings = False
69 model.inherit_global_settings = False
70 setting = model.repo_settings.get_setting_by_name(
70 setting = model.repo_settings.get_setting_by_name(
71 VcsSettingsModel.INHERIT_SETTINGS)
71 VcsSettingsModel.INHERIT_SETTINGS)
72 try:
72 try:
73 assert setting.app_settings_type == 'bool'
73 assert setting.app_settings_type == 'bool'
74 assert setting.app_settings_value is False
74 assert setting.app_settings_value is False
75 finally:
75 finally:
76 Session().delete(setting)
76 Session().delete(setting)
77 Session().commit()
77 Session().commit()
78
78
79 def test_set_raises_exception_when_repository_not_specified(self):
79 def test_set_raises_exception_when_repository_not_specified(self):
80 model = VcsSettingsModel()
80 model = VcsSettingsModel()
81 with pytest.raises(Exception) as exc_info:
81 with pytest.raises(Exception) as exc_info:
82 model.inherit_global_settings = False
82 model.inherit_global_settings = False
83 assert str(exc_info.value) == 'Repository is not specified'
83 assert str(exc_info.value) == 'Repository is not specified'
84
84
85
85
86 class TestVcsSettingsModel(object):
86 class TestVcsSettingsModel(object):
87 def test_global_svn_branch_patterns(self):
87 def test_global_svn_branch_patterns(self):
88 model = VcsSettingsModel()
88 model = VcsSettingsModel()
89 expected_result = {'test': 'test'}
89 expected_result = {'test': 'test'}
90 with mock.patch.object(model, 'global_settings') as settings_mock:
90 with mock.patch.object(model, 'global_settings') as settings_mock:
91 get_settings = settings_mock.get_ui_by_section
91 get_settings = settings_mock.get_ui_by_section
92 get_settings.return_value = expected_result
92 get_settings.return_value = expected_result
93 settings_mock.return_value = expected_result
93 settings_mock.return_value = expected_result
94 result = model.get_global_svn_branch_patterns()
94 result = model.get_global_svn_branch_patterns()
95
95
96 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
96 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
97 assert expected_result == result
97 assert expected_result == result
98
98
99 def test_repo_svn_branch_patterns(self):
99 def test_repo_svn_branch_patterns(self):
100 model = VcsSettingsModel()
100 model = VcsSettingsModel()
101 expected_result = {'test': 'test'}
101 expected_result = {'test': 'test'}
102 with mock.patch.object(model, 'repo_settings') as settings_mock:
102 with mock.patch.object(model, 'repo_settings') as settings_mock:
103 get_settings = settings_mock.get_ui_by_section
103 get_settings = settings_mock.get_ui_by_section
104 get_settings.return_value = expected_result
104 get_settings.return_value = expected_result
105 settings_mock.return_value = expected_result
105 settings_mock.return_value = expected_result
106 result = model.get_repo_svn_branch_patterns()
106 result = model.get_repo_svn_branch_patterns()
107
107
108 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
108 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
109 assert expected_result == result
109 assert expected_result == result
110
110
111 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
111 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
112 self):
112 self):
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 with pytest.raises(Exception) as exc_info:
114 with pytest.raises(Exception) as exc_info:
115 model.get_repo_svn_branch_patterns()
115 model.get_repo_svn_branch_patterns()
116 assert str(exc_info.value) == 'Repository is not specified'
116 assert str(exc_info.value) == 'Repository is not specified'
117
117
118 def test_global_svn_tag_patterns(self):
118 def test_global_svn_tag_patterns(self):
119 model = VcsSettingsModel()
119 model = VcsSettingsModel()
120 expected_result = {'test': 'test'}
120 expected_result = {'test': 'test'}
121 with mock.patch.object(model, 'global_settings') as settings_mock:
121 with mock.patch.object(model, 'global_settings') as settings_mock:
122 get_settings = settings_mock.get_ui_by_section
122 get_settings = settings_mock.get_ui_by_section
123 get_settings.return_value = expected_result
123 get_settings.return_value = expected_result
124 settings_mock.return_value = expected_result
124 settings_mock.return_value = expected_result
125 result = model.get_global_svn_tag_patterns()
125 result = model.get_global_svn_tag_patterns()
126
126
127 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
127 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
128 assert expected_result == result
128 assert expected_result == result
129
129
130 def test_repo_svn_tag_patterns(self):
130 def test_repo_svn_tag_patterns(self):
131 model = VcsSettingsModel()
131 model = VcsSettingsModel()
132 expected_result = {'test': 'test'}
132 expected_result = {'test': 'test'}
133 with mock.patch.object(model, 'repo_settings') as settings_mock:
133 with mock.patch.object(model, 'repo_settings') as settings_mock:
134 get_settings = settings_mock.get_ui_by_section
134 get_settings = settings_mock.get_ui_by_section
135 get_settings.return_value = expected_result
135 get_settings.return_value = expected_result
136 settings_mock.return_value = expected_result
136 settings_mock.return_value = expected_result
137 result = model.get_repo_svn_tag_patterns()
137 result = model.get_repo_svn_tag_patterns()
138
138
139 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
139 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
140 assert expected_result == result
140 assert expected_result == result
141
141
142 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
142 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
143 model = VcsSettingsModel()
143 model = VcsSettingsModel()
144 with pytest.raises(Exception) as exc_info:
144 with pytest.raises(Exception) as exc_info:
145 model.get_repo_svn_tag_patterns()
145 model.get_repo_svn_tag_patterns()
146 assert str(exc_info.value) == 'Repository is not specified'
146 assert str(exc_info.value) == 'Repository is not specified'
147
147
148 def test_get_global_settings(self):
148 def test_get_global_settings(self):
149 expected_result = {'test': 'test'}
149 expected_result = {'test': 'test'}
150 model = VcsSettingsModel()
150 model = VcsSettingsModel()
151 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
151 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
152 collect_mock.return_value = expected_result
152 collect_mock.return_value = expected_result
153 result = model.get_global_settings()
153 result = model.get_global_settings()
154
154
155 collect_mock.assert_called_once_with(global_=True)
155 collect_mock.assert_called_once_with(global_=True)
156 assert result == expected_result
156 assert result == expected_result
157
157
158 def test_get_repo_settings(self, repo_stub):
158 def test_get_repo_settings(self, repo_stub):
159 model = VcsSettingsModel(repo=repo_stub.repo_name)
159 model = VcsSettingsModel(repo=repo_stub.repo_name)
160 expected_result = {'test': 'test'}
160 expected_result = {'test': 'test'}
161 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
161 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
162 collect_mock.return_value = expected_result
162 collect_mock.return_value = expected_result
163 result = model.get_repo_settings()
163 result = model.get_repo_settings()
164
164
165 collect_mock.assert_called_once_with(global_=False)
165 collect_mock.assert_called_once_with(global_=False)
166 assert result == expected_result
166 assert result == expected_result
167
167
168 @pytest.mark.parametrize('settings, global_', [
168 @pytest.mark.parametrize('settings, global_', [
169 ('global_settings', True),
169 ('global_settings', True),
170 ('repo_settings', False)
170 ('repo_settings', False)
171 ])
171 ])
172 def test_collect_all_settings(self, settings, global_):
172 def test_collect_all_settings(self, settings, global_):
173 model = VcsSettingsModel()
173 model = VcsSettingsModel()
174 result_mock = self._mock_result()
174 result_mock = self._mock_result()
175
175
176 settings_patch = mock.patch.object(model, settings)
176 settings_patch = mock.patch.object(model, settings)
177 with settings_patch as settings_mock:
177 with settings_patch as settings_mock:
178 settings_mock.get_ui_by_section_and_key.return_value = result_mock
178 settings_mock.get_ui_by_section_and_key.return_value = result_mock
179 settings_mock.get_setting_by_name.return_value = result_mock
179 settings_mock.get_setting_by_name.return_value = result_mock
180 result = model._collect_all_settings(global_=global_)
180 result = model._collect_all_settings(global_=global_)
181
181
182 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
182 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
183 self._assert_get_settings_calls(
183 self._assert_get_settings_calls(
184 settings_mock, ui_settings, model.GENERAL_SETTINGS)
184 settings_mock, ui_settings, model.GENERAL_SETTINGS)
185 self._assert_collect_all_settings_result(
185 self._assert_collect_all_settings_result(
186 ui_settings, model.GENERAL_SETTINGS, result)
186 ui_settings, model.GENERAL_SETTINGS, result)
187
187
188 @pytest.mark.parametrize('settings, global_', [
188 @pytest.mark.parametrize('settings, global_', [
189 ('global_settings', True),
189 ('global_settings', True),
190 ('repo_settings', False)
190 ('repo_settings', False)
191 ])
191 ])
192 def test_collect_all_settings_without_empty_value(self, settings, global_):
192 def test_collect_all_settings_without_empty_value(self, settings, global_):
193 model = VcsSettingsModel()
193 model = VcsSettingsModel()
194
194
195 settings_patch = mock.patch.object(model, settings)
195 settings_patch = mock.patch.object(model, settings)
196 with settings_patch as settings_mock:
196 with settings_patch as settings_mock:
197 settings_mock.get_ui_by_section_and_key.return_value = None
197 settings_mock.get_ui_by_section_and_key.return_value = None
198 settings_mock.get_setting_by_name.return_value = None
198 settings_mock.get_setting_by_name.return_value = None
199 result = model._collect_all_settings(global_=global_)
199 result = model._collect_all_settings(global_=global_)
200
200
201 assert result == {}
201 assert result == {}
202
202
203 def _mock_result(self):
203 def _mock_result(self):
204 result_mock = mock.Mock()
204 result_mock = mock.Mock()
205 result_mock.ui_value = 'ui_value'
205 result_mock.ui_value = 'ui_value'
206 result_mock.ui_active = True
206 result_mock.ui_active = True
207 result_mock.app_settings_value = 'setting_value'
207 result_mock.app_settings_value = 'setting_value'
208 return result_mock
208 return result_mock
209
209
210 def _assert_get_settings_calls(
210 def _assert_get_settings_calls(
211 self, settings_mock, ui_settings, general_settings):
211 self, settings_mock, ui_settings, general_settings):
212 assert (
212 assert (
213 settings_mock.get_ui_by_section_and_key.call_count ==
213 settings_mock.get_ui_by_section_and_key.call_count ==
214 len(ui_settings))
214 len(ui_settings))
215 assert (
215 assert (
216 settings_mock.get_setting_by_name.call_count ==
216 settings_mock.get_setting_by_name.call_count ==
217 len(general_settings))
217 len(general_settings))
218
218
219 for section, key in ui_settings:
219 for section, key in ui_settings:
220 expected_call = mock.call(section, key)
220 expected_call = mock.call(section, key)
221 assert (
221 assert (
222 expected_call in
222 expected_call in
223 settings_mock.get_ui_by_section_and_key.call_args_list)
223 settings_mock.get_ui_by_section_and_key.call_args_list)
224
224
225 for name in general_settings:
225 for name in general_settings:
226 expected_call = mock.call(name)
226 expected_call = mock.call(name)
227 assert (
227 assert (
228 expected_call in
228 expected_call in
229 settings_mock.get_setting_by_name.call_args_list)
229 settings_mock.get_setting_by_name.call_args_list)
230
230
231 def _assert_collect_all_settings_result(
231 def _assert_collect_all_settings_result(
232 self, ui_settings, general_settings, result):
232 self, ui_settings, general_settings, result):
233 expected_result = {}
233 expected_result = {}
234 for section, key in ui_settings:
234 for section, key in ui_settings:
235 key = '{}_{}'.format(section, key.replace('.', '_'))
235 key = '{}_{}'.format(section, key.replace('.', '_'))
236
236
237 if section in ('extensions', 'hooks'):
237 if section in ('extensions', 'hooks'):
238 value = True
238 value = True
239 elif key in ['vcs_git_lfs_enabled']:
239 elif key in ['vcs_git_lfs_enabled']:
240 value = True
240 value = True
241 else:
241 else:
242 value = 'ui_value'
242 value = 'ui_value'
243 expected_result[key] = value
243 expected_result[key] = value
244
244
245 for name in general_settings:
245 for name in general_settings:
246 key = 'rhodecode_' + name
246 key = 'rhodecode_' + name
247 expected_result[key] = 'setting_value'
247 expected_result[key] = 'setting_value'
248
248
249 assert expected_result == result
249 assert expected_result == result
250
250
251
251
252 class TestCreateOrUpdateRepoHookSettings(object):
252 class TestCreateOrUpdateRepoHookSettings(object):
253 def test_create_when_no_repo_object_found(self, repo_stub):
253 def test_create_when_no_repo_object_found(self, repo_stub):
254 model = VcsSettingsModel(repo=repo_stub.repo_name)
254 model = VcsSettingsModel(repo=repo_stub.repo_name)
255
255
256 self._create_settings(model, HOOKS_FORM_DATA)
256 self._create_settings(model, HOOKS_FORM_DATA)
257
257
258 cleanup = []
258 cleanup = []
259 try:
259 try:
260 for section, key in model.HOOKS_SETTINGS:
260 for section, key in model.HOOKS_SETTINGS:
261 ui = model.repo_settings.get_ui_by_section_and_key(
261 ui = model.repo_settings.get_ui_by_section_and_key(
262 section, key)
262 section, key)
263 assert ui.ui_active is True
263 assert ui.ui_active is True
264 cleanup.append(ui)
264 cleanup.append(ui)
265 finally:
265 finally:
266 for ui in cleanup:
266 for ui in cleanup:
267 Session().delete(ui)
267 Session().delete(ui)
268 Session().commit()
268 Session().commit()
269
269
270 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
270 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
271 model = VcsSettingsModel(repo=repo_stub.repo_name)
271 model = VcsSettingsModel(repo=repo_stub.repo_name)
272
272
273 deleted_key = 'hooks_changegroup_repo_size'
273 deleted_key = 'hooks_changegroup_repo_size'
274 data = HOOKS_FORM_DATA.copy()
274 data = HOOKS_FORM_DATA.copy()
275 data.pop(deleted_key)
275 data.pop(deleted_key)
276
276
277 with pytest.raises(ValueError) as exc_info:
277 with pytest.raises(ValueError) as exc_info:
278 model.create_or_update_repo_hook_settings(data)
278 model.create_or_update_repo_hook_settings(data)
279 Session().commit()
279 Session().commit()
280
280
281 msg = 'The given data does not contain {} key'.format(deleted_key)
281 msg = 'The given data does not contain {} key'.format(deleted_key)
282 assert str(exc_info.value) == msg
282 assert str(exc_info.value) == msg
283
283
284 def test_update_when_repo_object_found(self, repo_stub, settings_util):
284 def test_update_when_repo_object_found(self, repo_stub, settings_util):
285 model = VcsSettingsModel(repo=repo_stub.repo_name)
285 model = VcsSettingsModel(repo=repo_stub.repo_name)
286 for section, key in model.HOOKS_SETTINGS:
286 for section, key in model.HOOKS_SETTINGS:
287 settings_util.create_repo_rhodecode_ui(
287 settings_util.create_repo_rhodecode_ui(
288 repo_stub, section, None, key=key, active=False)
288 repo_stub, section, None, key=key, active=False)
289 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
289 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
290 Session().commit()
290 Session().commit()
291
291
292 for section, key in model.HOOKS_SETTINGS:
292 for section, key in model.HOOKS_SETTINGS:
293 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
293 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
294 assert ui.ui_active is True
294 assert ui.ui_active is True
295
295
296 def _create_settings(self, model, data):
296 def _create_settings(self, model, data):
297 global_patch = mock.patch.object(model, 'global_settings')
297 global_patch = mock.patch.object(model, 'global_settings')
298 global_setting = mock.Mock()
298 global_setting = mock.Mock()
299 global_setting.ui_value = 'Test value'
299 global_setting.ui_value = 'Test value'
300 with global_patch as global_mock:
300 with global_patch as global_mock:
301 global_mock.get_ui_by_section_and_key.return_value = global_setting
301 global_mock.get_ui_by_section_and_key.return_value = global_setting
302 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
302 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
303 Session().commit()
303 Session().commit()
304
304
305
305
306 class TestUpdateGlobalHookSettings(object):
306 class TestUpdateGlobalHookSettings(object):
307 def test_update_raises_exception_when_data_incomplete(self):
307 def test_update_raises_exception_when_data_incomplete(self):
308 model = VcsSettingsModel()
308 model = VcsSettingsModel()
309
309
310 deleted_key = 'hooks_changegroup_repo_size'
310 deleted_key = 'hooks_changegroup_repo_size'
311 data = HOOKS_FORM_DATA.copy()
311 data = HOOKS_FORM_DATA.copy()
312 data.pop(deleted_key)
312 data.pop(deleted_key)
313
313
314 with pytest.raises(ValueError) as exc_info:
314 with pytest.raises(ValueError) as exc_info:
315 model.update_global_hook_settings(data)
315 model.update_global_hook_settings(data)
316 Session().commit()
316 Session().commit()
317
317
318 msg = 'The given data does not contain {} key'.format(deleted_key)
318 msg = 'The given data does not contain {} key'.format(deleted_key)
319 assert str(exc_info.value) == msg
319 assert str(exc_info.value) == msg
320
320
321 def test_update_global_hook_settings(self, settings_util):
321 def test_update_global_hook_settings(self, settings_util):
322 model = VcsSettingsModel()
322 model = VcsSettingsModel()
323 setting_mock = mock.MagicMock()
323 setting_mock = mock.MagicMock()
324 setting_mock.ui_active = False
324 setting_mock.ui_active = False
325 get_settings_patcher = mock.patch.object(
325 get_settings_patcher = mock.patch.object(
326 model.global_settings, 'get_ui_by_section_and_key',
326 model.global_settings, 'get_ui_by_section_and_key',
327 return_value=setting_mock)
327 return_value=setting_mock)
328 session_patcher = mock.patch('rhodecode.model.settings.Session')
328 session_patcher = mock.patch('rhodecode.model.settings.Session')
329 with get_settings_patcher as get_settings_mock, session_patcher:
329 with get_settings_patcher as get_settings_mock, session_patcher:
330 model.update_global_hook_settings(HOOKS_FORM_DATA)
330 model.update_global_hook_settings(HOOKS_FORM_DATA)
331 Session().commit()
331 Session().commit()
332
332
333 assert setting_mock.ui_active is True
333 assert setting_mock.ui_active is True
334 assert get_settings_mock.call_count == 3
334 assert get_settings_mock.call_count == 3
335
335
336
336
337 class TestCreateOrUpdateRepoGeneralSettings(object):
337 class TestCreateOrUpdateRepoGeneralSettings(object):
338 def test_calls_create_or_update_general_settings(self, repo_stub):
338 def test_calls_create_or_update_general_settings(self, repo_stub):
339 model = VcsSettingsModel(repo=repo_stub.repo_name)
339 model = VcsSettingsModel(repo=repo_stub.repo_name)
340 create_patch = mock.patch.object(
340 create_patch = mock.patch.object(
341 model, '_create_or_update_general_settings')
341 model, '_create_or_update_general_settings')
342 with create_patch as create_mock:
342 with create_patch as create_mock:
343 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
343 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
344 Session().commit()
344 Session().commit()
345
345
346 create_mock.assert_called_once_with(
346 create_mock.assert_called_once_with(
347 model.repo_settings, GENERAL_FORM_DATA)
347 model.repo_settings, GENERAL_FORM_DATA)
348
348
349 def test_raises_exception_when_repository_is_not_specified(self):
349 def test_raises_exception_when_repository_is_not_specified(self):
350 model = VcsSettingsModel()
350 model = VcsSettingsModel()
351 with pytest.raises(Exception) as exc_info:
351 with pytest.raises(Exception) as exc_info:
352 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
352 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
353 assert str(exc_info.value) == 'Repository is not specified'
353 assert str(exc_info.value) == 'Repository is not specified'
354
354
355
355
356 class TestCreateOrUpdatGlobalGeneralSettings(object):
356 class TestCreateOrUpdatGlobalGeneralSettings(object):
357 def test_calls_create_or_update_general_settings(self):
357 def test_calls_create_or_update_general_settings(self):
358 model = VcsSettingsModel()
358 model = VcsSettingsModel()
359 create_patch = mock.patch.object(
359 create_patch = mock.patch.object(
360 model, '_create_or_update_general_settings')
360 model, '_create_or_update_general_settings')
361 with create_patch as create_mock:
361 with create_patch as create_mock:
362 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
362 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
363 create_mock.assert_called_once_with(
363 create_mock.assert_called_once_with(
364 model.global_settings, GENERAL_FORM_DATA)
364 model.global_settings, GENERAL_FORM_DATA)
365
365
366
366
367 class TestCreateOrUpdateGeneralSettings(object):
367 class TestCreateOrUpdateGeneralSettings(object):
368 def test_create_when_no_repo_settings_found(self, repo_stub):
368 def test_create_when_no_repo_settings_found(self, repo_stub):
369 model = VcsSettingsModel(repo=repo_stub.repo_name)
369 model = VcsSettingsModel(repo=repo_stub.repo_name)
370 model._create_or_update_general_settings(
370 model._create_or_update_general_settings(
371 model.repo_settings, GENERAL_FORM_DATA)
371 model.repo_settings, GENERAL_FORM_DATA)
372
372
373 cleanup = []
373 cleanup = []
374 try:
374 try:
375 for name in model.GENERAL_SETTINGS:
375 for name in model.GENERAL_SETTINGS:
376 setting = model.repo_settings.get_setting_by_name(name)
376 setting = model.repo_settings.get_setting_by_name(name)
377 assert setting.app_settings_value is True
377 assert setting.app_settings_value is True
378 cleanup.append(setting)
378 cleanup.append(setting)
379 finally:
379 finally:
380 for setting in cleanup:
380 for setting in cleanup:
381 Session().delete(setting)
381 Session().delete(setting)
382 Session().commit()
382 Session().commit()
383
383
384 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
384 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
385 model = VcsSettingsModel(repo=repo_stub.repo_name)
385 model = VcsSettingsModel(repo=repo_stub.repo_name)
386
386
387 deleted_key = 'rhodecode_pr_merge_enabled'
387 deleted_key = 'rhodecode_pr_merge_enabled'
388 data = GENERAL_FORM_DATA.copy()
388 data = GENERAL_FORM_DATA.copy()
389 data.pop(deleted_key)
389 data.pop(deleted_key)
390
390
391 with pytest.raises(ValueError) as exc_info:
391 with pytest.raises(ValueError) as exc_info:
392 model._create_or_update_general_settings(model.repo_settings, data)
392 model._create_or_update_general_settings(model.repo_settings, data)
393 Session().commit()
393 Session().commit()
394
394
395 msg = 'The given data does not contain {} key'.format(deleted_key)
395 msg = 'The given data does not contain {} key'.format(deleted_key)
396 assert str(exc_info.value) == msg
396 assert str(exc_info.value) == msg
397
397
398 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
398 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
399 model = VcsSettingsModel(repo=repo_stub.repo_name)
399 model = VcsSettingsModel(repo=repo_stub.repo_name)
400 for name in model.GENERAL_SETTINGS:
400 for name in model.GENERAL_SETTINGS:
401 settings_util.create_repo_rhodecode_setting(
401 settings_util.create_repo_rhodecode_setting(
402 repo_stub, name, False, 'bool')
402 repo_stub, name, False, 'bool')
403
403
404 model._create_or_update_general_settings(
404 model._create_or_update_general_settings(
405 model.repo_settings, GENERAL_FORM_DATA)
405 model.repo_settings, GENERAL_FORM_DATA)
406 Session().commit()
406 Session().commit()
407
407
408 for name in model.GENERAL_SETTINGS:
408 for name in model.GENERAL_SETTINGS:
409 setting = model.repo_settings.get_setting_by_name(name)
409 setting = model.repo_settings.get_setting_by_name(name)
410 assert setting.app_settings_value is True
410 assert setting.app_settings_value is True
411
411
412
412
413 class TestCreateRepoSvnSettings(object):
413 class TestCreateRepoSvnSettings(object):
414 def test_calls_create_svn_settings(self, repo_stub):
414 def test_calls_create_svn_settings(self, repo_stub):
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 with mock.patch.object(model, '_create_svn_settings') as create_mock:
416 with mock.patch.object(model, '_create_svn_settings') as create_mock:
417 model.create_repo_svn_settings(SVN_FORM_DATA)
417 model.create_repo_svn_settings(SVN_FORM_DATA)
418 Session().commit()
418 Session().commit()
419
419
420 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
420 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
421
421
422 def test_raises_exception_when_repository_is_not_specified(self):
422 def test_raises_exception_when_repository_is_not_specified(self):
423 model = VcsSettingsModel()
423 model = VcsSettingsModel()
424 with pytest.raises(Exception) as exc_info:
424 with pytest.raises(Exception) as exc_info:
425 model.create_repo_svn_settings(SVN_FORM_DATA)
425 model.create_repo_svn_settings(SVN_FORM_DATA)
426 Session().commit()
426 Session().commit()
427
427
428 assert str(exc_info.value) == 'Repository is not specified'
428 assert str(exc_info.value) == 'Repository is not specified'
429
429
430
430
431 class TestCreateSvnSettings(object):
431 class TestCreateSvnSettings(object):
432 def test_create(self, repo_stub):
432 def test_create(self, repo_stub):
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
434 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
435 Session().commit()
435 Session().commit()
436
436
437 branch_ui = model.repo_settings.get_ui_by_section(
437 branch_ui = model.repo_settings.get_ui_by_section(
438 model.SVN_BRANCH_SECTION)
438 model.SVN_BRANCH_SECTION)
439 tag_ui = model.repo_settings.get_ui_by_section(
439 tag_ui = model.repo_settings.get_ui_by_section(
440 model.SVN_TAG_SECTION)
440 model.SVN_TAG_SECTION)
441
441
442 try:
442 try:
443 assert len(branch_ui) == 1
443 assert len(branch_ui) == 1
444 assert len(tag_ui) == 1
444 assert len(tag_ui) == 1
445 finally:
445 finally:
446 Session().delete(branch_ui[0])
446 Session().delete(branch_ui[0])
447 Session().delete(tag_ui[0])
447 Session().delete(tag_ui[0])
448 Session().commit()
448 Session().commit()
449
449
450 def test_create_tag(self, repo_stub):
450 def test_create_tag(self, repo_stub):
451 model = VcsSettingsModel(repo=repo_stub.repo_name)
451 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 data = SVN_FORM_DATA.copy()
452 data = SVN_FORM_DATA.copy()
453 data.pop('new_svn_branch')
453 data.pop('new_svn_branch')
454 model._create_svn_settings(model.repo_settings, data)
454 model._create_svn_settings(model.repo_settings, data)
455 Session().commit()
455 Session().commit()
456
456
457 branch_ui = model.repo_settings.get_ui_by_section(
457 branch_ui = model.repo_settings.get_ui_by_section(
458 model.SVN_BRANCH_SECTION)
458 model.SVN_BRANCH_SECTION)
459 tag_ui = model.repo_settings.get_ui_by_section(
459 tag_ui = model.repo_settings.get_ui_by_section(
460 model.SVN_TAG_SECTION)
460 model.SVN_TAG_SECTION)
461
461
462 try:
462 try:
463 assert len(branch_ui) == 0
463 assert len(branch_ui) == 0
464 assert len(tag_ui) == 1
464 assert len(tag_ui) == 1
465 finally:
465 finally:
466 Session().delete(tag_ui[0])
466 Session().delete(tag_ui[0])
467 Session().commit()
467 Session().commit()
468
468
469 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
469 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
470 model = VcsSettingsModel(repo=repo_stub.repo_name)
470 model = VcsSettingsModel(repo=repo_stub.repo_name)
471 model._create_svn_settings(model.repo_settings, {})
471 model._create_svn_settings(model.repo_settings, {})
472 Session().commit()
472 Session().commit()
473
473
474 branch_ui = model.repo_settings.get_ui_by_section(
474 branch_ui = model.repo_settings.get_ui_by_section(
475 model.SVN_BRANCH_SECTION)
475 model.SVN_BRANCH_SECTION)
476 tag_ui = model.repo_settings.get_ui_by_section(
476 tag_ui = model.repo_settings.get_ui_by_section(
477 model.SVN_TAG_SECTION)
477 model.SVN_TAG_SECTION)
478
478
479 assert len(branch_ui) == 0
479 assert len(branch_ui) == 0
480 assert len(tag_ui) == 0
480 assert len(tag_ui) == 0
481
481
482 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
482 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
483 model = VcsSettingsModel(repo=repo_stub.repo_name)
483 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 data = {
484 data = {
485 'new_svn_branch': '',
485 'new_svn_branch': '',
486 'new_svn_tag': ''
486 'new_svn_tag': ''
487 }
487 }
488 model._create_svn_settings(model.repo_settings, data)
488 model._create_svn_settings(model.repo_settings, data)
489 Session().commit()
489 Session().commit()
490
490
491 branch_ui = model.repo_settings.get_ui_by_section(
491 branch_ui = model.repo_settings.get_ui_by_section(
492 model.SVN_BRANCH_SECTION)
492 model.SVN_BRANCH_SECTION)
493 tag_ui = model.repo_settings.get_ui_by_section(
493 tag_ui = model.repo_settings.get_ui_by_section(
494 model.SVN_TAG_SECTION)
494 model.SVN_TAG_SECTION)
495
495
496 assert len(branch_ui) == 0
496 assert len(branch_ui) == 0
497 assert len(tag_ui) == 0
497 assert len(tag_ui) == 0
498
498
499
499
500 class TestCreateOrUpdateUi(object):
500 class TestCreateOrUpdateUi(object):
501 def test_create(self, repo_stub):
501 def test_create(self, repo_stub):
502 model = VcsSettingsModel(repo=repo_stub.repo_name)
502 model = VcsSettingsModel(repo=repo_stub.repo_name)
503 model._create_or_update_ui(
503 model._create_or_update_ui(
504 model.repo_settings, 'test-section', 'test-key', active=False,
504 model.repo_settings, 'test-section', 'test-key', active=False,
505 value='False')
505 value='False')
506 Session().commit()
506 Session().commit()
507
507
508 created_ui = model.repo_settings.get_ui_by_section_and_key(
508 created_ui = model.repo_settings.get_ui_by_section_and_key(
509 'test-section', 'test-key')
509 'test-section', 'test-key')
510
510
511 try:
511 try:
512 assert created_ui.ui_active is False
512 assert created_ui.ui_active is False
513 assert str2bool(created_ui.ui_value) is False
513 assert str2bool(created_ui.ui_value) is False
514 finally:
514 finally:
515 Session().delete(created_ui)
515 Session().delete(created_ui)
516 Session().commit()
516 Session().commit()
517
517
518 def test_update(self, repo_stub, settings_util):
518 def test_update(self, repo_stub, settings_util):
519 model = VcsSettingsModel(repo=repo_stub.repo_name)
519 model = VcsSettingsModel(repo=repo_stub.repo_name)
520 # care about only 3 first settings
520 # care about only 3 first settings
521 largefiles, phases, evolve = model.HG_SETTINGS[:3]
521 largefiles, phases, evolve = model.HG_SETTINGS[:3]
522
522
523 section = 'test-section'
523 section = 'test-section'
524 key = 'test-key'
524 key = 'test-key'
525 settings_util.create_repo_rhodecode_ui(
525 settings_util.create_repo_rhodecode_ui(
526 repo_stub, section, 'True', key=key, active=True)
526 repo_stub, section, 'True', key=key, active=True)
527
527
528 model._create_or_update_ui(
528 model._create_or_update_ui(
529 model.repo_settings, section, key, active=False, value='False')
529 model.repo_settings, section, key, active=False, value='False')
530 Session().commit()
530 Session().commit()
531
531
532 created_ui = model.repo_settings.get_ui_by_section_and_key(
532 created_ui = model.repo_settings.get_ui_by_section_and_key(
533 section, key)
533 section, key)
534 assert created_ui.ui_active is False
534 assert created_ui.ui_active is False
535 assert str2bool(created_ui.ui_value) is False
535 assert str2bool(created_ui.ui_value) is False
536
536
537
537
538 class TestCreateOrUpdateRepoHgSettings(object):
538 class TestCreateOrUpdateRepoHgSettings(object):
539 FORM_DATA = {
539 FORM_DATA = {
540 'extensions_largefiles': False,
540 'extensions_largefiles': False,
541 'extensions_evolve': False,
541 'extensions_evolve': False,
542 'phases_publish': False
542 'phases_publish': False
543 }
543 }
544
544
545 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
545 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
546 model = VcsSettingsModel(repo=repo_stub.repo_name)
546 model = VcsSettingsModel(repo=repo_stub.repo_name)
547 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
547 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
548 model.create_or_update_repo_hg_settings(self.FORM_DATA)
548 model.create_or_update_repo_hg_settings(self.FORM_DATA)
549 expected_calls = [
549 expected_calls = [
550 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
550 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
551 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
551 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
552 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
552 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
553 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
553 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
554 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
554 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
555 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
555 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
556 ]
556 ]
557 assert expected_calls == create_mock.call_args_list
557 assert expected_calls == create_mock.call_args_list
558
558
559 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
559 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
560 def test_key_is_not_found(self, repo_stub, field_to_remove):
560 def test_key_is_not_found(self, repo_stub, field_to_remove):
561 model = VcsSettingsModel(repo=repo_stub.repo_name)
561 model = VcsSettingsModel(repo=repo_stub.repo_name)
562 data = self.FORM_DATA.copy()
562 data = self.FORM_DATA.copy()
563 data.pop(field_to_remove)
563 data.pop(field_to_remove)
564 with pytest.raises(ValueError) as exc_info:
564 with pytest.raises(ValueError) as exc_info:
565 model.create_or_update_repo_hg_settings(data)
565 model.create_or_update_repo_hg_settings(data)
566 Session().commit()
566 Session().commit()
567
567
568 expected_message = 'The given data does not contain {} key'.format(
568 expected_message = 'The given data does not contain {} key'.format(
569 field_to_remove)
569 field_to_remove)
570 assert str(exc_info.value) == expected_message
570 assert str(exc_info.value) == expected_message
571
571
572 def test_create_raises_exception_when_repository_not_specified(self):
572 def test_create_raises_exception_when_repository_not_specified(self):
573 model = VcsSettingsModel()
573 model = VcsSettingsModel()
574 with pytest.raises(Exception) as exc_info:
574 with pytest.raises(Exception) as exc_info:
575 model.create_or_update_repo_hg_settings(self.FORM_DATA)
575 model.create_or_update_repo_hg_settings(self.FORM_DATA)
576 Session().commit()
576 Session().commit()
577
577
578 assert str(exc_info.value) == 'Repository is not specified'
578 assert str(exc_info.value) == 'Repository is not specified'
579
579
580
580
581 class TestUpdateGlobalSslSetting(object):
582 def test_updates_global_hg_settings(self):
583 model = VcsSettingsModel()
584 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
585 model.update_global_ssl_setting('False')
586 Session().commit()
587
588 create_mock.assert_called_once_with(
589 model.global_settings, 'web', 'push_ssl', value='False')
590
591
592 class TestCreateOrUpdateGlobalHgSettings(object):
581 class TestCreateOrUpdateGlobalHgSettings(object):
593 FORM_DATA = {
582 FORM_DATA = {
594 'extensions_largefiles': False,
583 'extensions_largefiles': False,
595 'phases_publish': False,
584 'phases_publish': False,
596 'extensions_evolve': False
585 'extensions_evolve': False
597 }
586 }
598
587
599 def test_creates_repo_hg_settings_when_data_is_correct(self):
588 def test_creates_repo_hg_settings_when_data_is_correct(self):
600 model = VcsSettingsModel()
589 model = VcsSettingsModel()
601 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
590 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
602 model.create_or_update_global_hg_settings(self.FORM_DATA)
591 model.create_or_update_global_hg_settings(self.FORM_DATA)
603 Session().commit()
592 Session().commit()
604
593
605 expected_calls = [
594 expected_calls = [
606 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
595 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
607 mock.call(model.global_settings, 'phases', 'publish', value='False'),
596 mock.call(model.global_settings, 'phases', 'publish', value='False'),
608 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
597 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
609 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
598 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
610 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
599 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
611 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
600 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
612 ]
601 ]
613
602
614 assert expected_calls == create_mock.call_args_list
603 assert expected_calls == create_mock.call_args_list
615
604
616 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
605 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
617 def test_key_is_not_found(self, repo_stub, field_to_remove):
606 def test_key_is_not_found(self, repo_stub, field_to_remove):
618 model = VcsSettingsModel(repo=repo_stub.repo_name)
607 model = VcsSettingsModel(repo=repo_stub.repo_name)
619 data = self.FORM_DATA.copy()
608 data = self.FORM_DATA.copy()
620 data.pop(field_to_remove)
609 data.pop(field_to_remove)
621 with pytest.raises(Exception) as exc_info:
610 with pytest.raises(Exception) as exc_info:
622 model.create_or_update_global_hg_settings(data)
611 model.create_or_update_global_hg_settings(data)
623 Session().commit()
612 Session().commit()
624
613
625 expected_message = 'The given data does not contain {} key'.format(
614 expected_message = 'The given data does not contain {} key'.format(
626 field_to_remove)
615 field_to_remove)
627 assert str(exc_info.value) == expected_message
616 assert str(exc_info.value) == expected_message
628
617
629
618
630 class TestCreateOrUpdateGlobalGitSettings(object):
619 class TestCreateOrUpdateGlobalGitSettings(object):
631 FORM_DATA = {
620 FORM_DATA = {
632 'vcs_git_lfs_enabled': False,
621 'vcs_git_lfs_enabled': False,
633 }
622 }
634
623
635 def test_creates_repo_hg_settings_when_data_is_correct(self):
624 def test_creates_repo_hg_settings_when_data_is_correct(self):
636 model = VcsSettingsModel()
625 model = VcsSettingsModel()
637 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
626 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
638 model.create_or_update_global_git_settings(self.FORM_DATA)
627 model.create_or_update_global_git_settings(self.FORM_DATA)
639 Session().commit()
628 Session().commit()
640
629
641 expected_calls = [
630 expected_calls = [
642 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
631 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
643 ]
632 ]
644 assert expected_calls == create_mock.call_args_list
633 assert expected_calls == create_mock.call_args_list
645
634
646
635
647 class TestDeleteRepoSvnPattern(object):
636 class TestDeleteRepoSvnPattern(object):
648 def test_success_when_repo_is_set(self, backend_svn, settings_util):
637 def test_success_when_repo_is_set(self, backend_svn, settings_util):
649 repo = backend_svn.create_repo()
638 repo = backend_svn.create_repo()
650 repo_name = repo.repo_name
639 repo_name = repo.repo_name
651
640
652 model = VcsSettingsModel(repo=repo_name)
641 model = VcsSettingsModel(repo=repo_name)
653 entry = settings_util.create_repo_rhodecode_ui(
642 entry = settings_util.create_repo_rhodecode_ui(
654 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
643 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
655 Session().commit()
644 Session().commit()
656
645
657 model.delete_repo_svn_pattern(entry.ui_id)
646 model.delete_repo_svn_pattern(entry.ui_id)
658
647
659 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
648 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
660 repo_name = backend_svn.repo_name
649 repo_name = backend_svn.repo_name
661 model = VcsSettingsModel(repo=repo_name)
650 model = VcsSettingsModel(repo=repo_name)
662 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
651 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
663 with delete_ui_patch as delete_ui_mock:
652 with delete_ui_patch as delete_ui_mock:
664 model.delete_repo_svn_pattern(123)
653 model.delete_repo_svn_pattern(123)
665 Session().commit()
654 Session().commit()
666
655
667 delete_ui_mock.assert_called_once_with(-1)
656 delete_ui_mock.assert_called_once_with(-1)
668
657
669 def test_raises_exception_when_repository_is_not_specified(self):
658 def test_raises_exception_when_repository_is_not_specified(self):
670 model = VcsSettingsModel()
659 model = VcsSettingsModel()
671 with pytest.raises(Exception) as exc_info:
660 with pytest.raises(Exception) as exc_info:
672 model.delete_repo_svn_pattern(123)
661 model.delete_repo_svn_pattern(123)
673 assert str(exc_info.value) == 'Repository is not specified'
662 assert str(exc_info.value) == 'Repository is not specified'
674
663
675
664
676 class TestDeleteGlobalSvnPattern(object):
665 class TestDeleteGlobalSvnPattern(object):
677 def test_delete_global_svn_pattern_calls_delete_ui(self):
666 def test_delete_global_svn_pattern_calls_delete_ui(self):
678 model = VcsSettingsModel()
667 model = VcsSettingsModel()
679 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
668 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
680 with delete_ui_patch as delete_ui_mock:
669 with delete_ui_patch as delete_ui_mock:
681 model.delete_global_svn_pattern(123)
670 model.delete_global_svn_pattern(123)
682 delete_ui_mock.assert_called_once_with(123)
671 delete_ui_mock.assert_called_once_with(123)
683
672
684
673
685 class TestFilterUiSettings(object):
674 class TestFilterUiSettings(object):
686 def test_settings_are_filtered(self):
675 def test_settings_are_filtered(self):
687 model = VcsSettingsModel()
676 model = VcsSettingsModel()
688 repo_settings = [
677 repo_settings = [
689 UiSetting('extensions', 'largefiles', '', True),
678 UiSetting('extensions', 'largefiles', '', True),
690 UiSetting('phases', 'publish', 'True', True),
679 UiSetting('phases', 'publish', 'True', True),
691 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
680 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
692 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
681 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
693 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
682 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
694 UiSetting(
683 UiSetting(
695 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
684 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
696 'test_branch', True),
685 'test_branch', True),
697 UiSetting(
686 UiSetting(
698 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
687 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
699 'test_tag', True),
688 'test_tag', True),
700 ]
689 ]
701 non_repo_settings = [
690 non_repo_settings = [
702 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
691 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
703 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
692 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
704 UiSetting('hooks', 'test2', 'hook', True),
693 UiSetting('hooks', 'test2', 'hook', True),
705 UiSetting(
694 UiSetting(
706 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
695 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
707 'test_tag', True),
696 'test_tag', True),
708 ]
697 ]
709 settings = repo_settings + non_repo_settings
698 settings = repo_settings + non_repo_settings
710 filtered_settings = model._filter_ui_settings(settings)
699 filtered_settings = model._filter_ui_settings(settings)
711 assert sorted(filtered_settings) == sorted(repo_settings)
700 assert sorted(filtered_settings) == sorted(repo_settings)
712
701
713
702
714 class TestFilterGeneralSettings(object):
703 class TestFilterGeneralSettings(object):
715 def test_settings_are_filtered(self):
704 def test_settings_are_filtered(self):
716 model = VcsSettingsModel()
705 model = VcsSettingsModel()
717 settings = {
706 settings = {
718 'rhodecode_abcde': 'value1',
707 'rhodecode_abcde': 'value1',
719 'rhodecode_vwxyz': 'value2',
708 'rhodecode_vwxyz': 'value2',
720 }
709 }
721 general_settings = {
710 general_settings = {
722 'rhodecode_{}'.format(key): 'value'
711 'rhodecode_{}'.format(key): 'value'
723 for key in VcsSettingsModel.GENERAL_SETTINGS
712 for key in VcsSettingsModel.GENERAL_SETTINGS
724 }
713 }
725 settings.update(general_settings)
714 settings.update(general_settings)
726
715
727 filtered_settings = model._filter_general_settings(general_settings)
716 filtered_settings = model._filter_general_settings(general_settings)
728 assert sorted(filtered_settings) == sorted(general_settings)
717 assert sorted(filtered_settings) == sorted(general_settings)
729
718
730
719
731 class TestGetRepoUiSettings(object):
720 class TestGetRepoUiSettings(object):
732 def test_global_uis_are_returned_when_no_repo_uis_found(
721 def test_global_uis_are_returned_when_no_repo_uis_found(
733 self, repo_stub):
722 self, repo_stub):
734 model = VcsSettingsModel(repo=repo_stub.repo_name)
723 model = VcsSettingsModel(repo=repo_stub.repo_name)
735 result = model.get_repo_ui_settings()
724 result = model.get_repo_ui_settings()
736 svn_sections = (
725 svn_sections = (
737 VcsSettingsModel.SVN_TAG_SECTION,
726 VcsSettingsModel.SVN_TAG_SECTION,
738 VcsSettingsModel.SVN_BRANCH_SECTION)
727 VcsSettingsModel.SVN_BRANCH_SECTION)
739 expected_result = [
728 expected_result = [
740 s for s in model.global_settings.get_ui()
729 s for s in model.global_settings.get_ui()
741 if s.section not in svn_sections]
730 if s.section not in svn_sections]
742 assert sorted(result) == sorted(expected_result)
731 assert sorted(result) == sorted(expected_result)
743
732
744 def test_repo_uis_are_overriding_global_uis(
733 def test_repo_uis_are_overriding_global_uis(
745 self, repo_stub, settings_util):
734 self, repo_stub, settings_util):
746 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
735 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
747 settings_util.create_repo_rhodecode_ui(
736 settings_util.create_repo_rhodecode_ui(
748 repo_stub, section, 'repo', key=key, active=False)
737 repo_stub, section, 'repo', key=key, active=False)
749 model = VcsSettingsModel(repo=repo_stub.repo_name)
738 model = VcsSettingsModel(repo=repo_stub.repo_name)
750 result = model.get_repo_ui_settings()
739 result = model.get_repo_ui_settings()
751 for setting in result:
740 for setting in result:
752 locator = (setting.section, setting.key)
741 locator = (setting.section, setting.key)
753 if locator in VcsSettingsModel.HOOKS_SETTINGS:
742 if locator in VcsSettingsModel.HOOKS_SETTINGS:
754 assert setting.value == 'repo'
743 assert setting.value == 'repo'
755
744
756 assert setting.active is False
745 assert setting.active is False
757
746
758 def test_global_svn_patterns_are_not_in_list(
747 def test_global_svn_patterns_are_not_in_list(
759 self, repo_stub, settings_util):
748 self, repo_stub, settings_util):
760 svn_sections = (
749 svn_sections = (
761 VcsSettingsModel.SVN_TAG_SECTION,
750 VcsSettingsModel.SVN_TAG_SECTION,
762 VcsSettingsModel.SVN_BRANCH_SECTION)
751 VcsSettingsModel.SVN_BRANCH_SECTION)
763 for section in svn_sections:
752 for section in svn_sections:
764 settings_util.create_rhodecode_ui(
753 settings_util.create_rhodecode_ui(
765 section, 'repo', key='deadbeef' + section, active=False)
754 section, 'repo', key='deadbeef' + section, active=False)
766 Session().commit()
755 Session().commit()
767
756
768 model = VcsSettingsModel(repo=repo_stub.repo_name)
757 model = VcsSettingsModel(repo=repo_stub.repo_name)
769 result = model.get_repo_ui_settings()
758 result = model.get_repo_ui_settings()
770 for setting in result:
759 for setting in result:
771 assert setting.section not in svn_sections
760 assert setting.section not in svn_sections
772
761
773 def test_repo_uis_filtered_by_section_are_returned(
762 def test_repo_uis_filtered_by_section_are_returned(
774 self, repo_stub, settings_util):
763 self, repo_stub, settings_util):
775 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
764 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
776 settings_util.create_repo_rhodecode_ui(
765 settings_util.create_repo_rhodecode_ui(
777 repo_stub, section, 'repo', key=key, active=False)
766 repo_stub, section, 'repo', key=key, active=False)
778 model = VcsSettingsModel(repo=repo_stub.repo_name)
767 model = VcsSettingsModel(repo=repo_stub.repo_name)
779 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
768 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
780 result = model.get_repo_ui_settings(section=section)
769 result = model.get_repo_ui_settings(section=section)
781 for setting in result:
770 for setting in result:
782 assert setting.section == section
771 assert setting.section == section
783
772
784 def test_repo_uis_filtered_by_key_are_returned(
773 def test_repo_uis_filtered_by_key_are_returned(
785 self, repo_stub, settings_util):
774 self, repo_stub, settings_util):
786 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
775 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
787 settings_util.create_repo_rhodecode_ui(
776 settings_util.create_repo_rhodecode_ui(
788 repo_stub, section, 'repo', key=key, active=False)
777 repo_stub, section, 'repo', key=key, active=False)
789 model = VcsSettingsModel(repo=repo_stub.repo_name)
778 model = VcsSettingsModel(repo=repo_stub.repo_name)
790 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
779 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
791 result = model.get_repo_ui_settings(key=key)
780 result = model.get_repo_ui_settings(key=key)
792 for setting in result:
781 for setting in result:
793 assert setting.key == key
782 assert setting.key == key
794
783
795 def test_raises_exception_when_repository_is_not_specified(self):
784 def test_raises_exception_when_repository_is_not_specified(self):
796 model = VcsSettingsModel()
785 model = VcsSettingsModel()
797 with pytest.raises(Exception) as exc_info:
786 with pytest.raises(Exception) as exc_info:
798 model.get_repo_ui_settings()
787 model.get_repo_ui_settings()
799 assert str(exc_info.value) == 'Repository is not specified'
788 assert str(exc_info.value) == 'Repository is not specified'
800
789
801
790
802 class TestGetRepoGeneralSettings(object):
791 class TestGetRepoGeneralSettings(object):
803 def test_global_settings_are_returned_when_no_repo_settings_found(
792 def test_global_settings_are_returned_when_no_repo_settings_found(
804 self, repo_stub):
793 self, repo_stub):
805 model = VcsSettingsModel(repo=repo_stub.repo_name)
794 model = VcsSettingsModel(repo=repo_stub.repo_name)
806 result = model.get_repo_general_settings()
795 result = model.get_repo_general_settings()
807 expected_result = model.global_settings.get_all_settings()
796 expected_result = model.global_settings.get_all_settings()
808 assert sorted(result) == sorted(expected_result)
797 assert sorted(result) == sorted(expected_result)
809
798
810 def test_repo_uis_are_overriding_global_uis(
799 def test_repo_uis_are_overriding_global_uis(
811 self, repo_stub, settings_util):
800 self, repo_stub, settings_util):
812 for key in VcsSettingsModel.GENERAL_SETTINGS:
801 for key in VcsSettingsModel.GENERAL_SETTINGS:
813 settings_util.create_repo_rhodecode_setting(
802 settings_util.create_repo_rhodecode_setting(
814 repo_stub, key, 'abcde', type_='unicode')
803 repo_stub, key, 'abcde', type_='unicode')
815 Session().commit()
804 Session().commit()
816
805
817 model = VcsSettingsModel(repo=repo_stub.repo_name)
806 model = VcsSettingsModel(repo=repo_stub.repo_name)
818 result = model.get_repo_ui_settings()
807 result = model.get_repo_ui_settings()
819 for key in result:
808 for key in result:
820 if key in VcsSettingsModel.GENERAL_SETTINGS:
809 if key in VcsSettingsModel.GENERAL_SETTINGS:
821 assert result[key] == 'abcde'
810 assert result[key] == 'abcde'
822
811
823 def test_raises_exception_when_repository_is_not_specified(self):
812 def test_raises_exception_when_repository_is_not_specified(self):
824 model = VcsSettingsModel()
813 model = VcsSettingsModel()
825 with pytest.raises(Exception) as exc_info:
814 with pytest.raises(Exception) as exc_info:
826 model.get_repo_general_settings()
815 model.get_repo_general_settings()
827 assert str(exc_info.value) == 'Repository is not specified'
816 assert str(exc_info.value) == 'Repository is not specified'
828
817
829
818
830 class TestGetGlobalGeneralSettings(object):
819 class TestGetGlobalGeneralSettings(object):
831 def test_global_settings_are_returned(self, repo_stub):
820 def test_global_settings_are_returned(self, repo_stub):
832 model = VcsSettingsModel()
821 model = VcsSettingsModel()
833 result = model.get_global_general_settings()
822 result = model.get_global_general_settings()
834 expected_result = model.global_settings.get_all_settings()
823 expected_result = model.global_settings.get_all_settings()
835 assert sorted(result) == sorted(expected_result)
824 assert sorted(result) == sorted(expected_result)
836
825
837 def test_repo_uis_are_not_overriding_global_uis(
826 def test_repo_uis_are_not_overriding_global_uis(
838 self, repo_stub, settings_util):
827 self, repo_stub, settings_util):
839 for key in VcsSettingsModel.GENERAL_SETTINGS:
828 for key in VcsSettingsModel.GENERAL_SETTINGS:
840 settings_util.create_repo_rhodecode_setting(
829 settings_util.create_repo_rhodecode_setting(
841 repo_stub, key, 'abcde', type_='unicode')
830 repo_stub, key, 'abcde', type_='unicode')
842 Session().commit()
831 Session().commit()
843
832
844 model = VcsSettingsModel(repo=repo_stub.repo_name)
833 model = VcsSettingsModel(repo=repo_stub.repo_name)
845 result = model.get_global_general_settings()
834 result = model.get_global_general_settings()
846 expected_result = model.global_settings.get_all_settings()
835 expected_result = model.global_settings.get_all_settings()
847 assert sorted(result) == sorted(expected_result)
836 assert sorted(result) == sorted(expected_result)
848
837
849
838
850 class TestGetGlobalUiSettings(object):
839 class TestGetGlobalUiSettings(object):
851 def test_global_uis_are_returned(self, repo_stub):
840 def test_global_uis_are_returned(self, repo_stub):
852 model = VcsSettingsModel()
841 model = VcsSettingsModel()
853 result = model.get_global_ui_settings()
842 result = model.get_global_ui_settings()
854 expected_result = model.global_settings.get_ui()
843 expected_result = model.global_settings.get_ui()
855 assert sorted(result) == sorted(expected_result)
844 assert sorted(result) == sorted(expected_result)
856
845
857 def test_repo_uis_are_not_overriding_global_uis(
846 def test_repo_uis_are_not_overriding_global_uis(
858 self, repo_stub, settings_util):
847 self, repo_stub, settings_util):
859 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
848 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
860 settings_util.create_repo_rhodecode_ui(
849 settings_util.create_repo_rhodecode_ui(
861 repo_stub, section, 'repo', key=key, active=False)
850 repo_stub, section, 'repo', key=key, active=False)
862 Session().commit()
851 Session().commit()
863
852
864 model = VcsSettingsModel(repo=repo_stub.repo_name)
853 model = VcsSettingsModel(repo=repo_stub.repo_name)
865 result = model.get_global_ui_settings()
854 result = model.get_global_ui_settings()
866 expected_result = model.global_settings.get_ui()
855 expected_result = model.global_settings.get_ui()
867 assert sorted(result) == sorted(expected_result)
856 assert sorted(result) == sorted(expected_result)
868
857
869 def test_ui_settings_filtered_by_section(
858 def test_ui_settings_filtered_by_section(
870 self, repo_stub, settings_util):
859 self, repo_stub, settings_util):
871 model = VcsSettingsModel(repo=repo_stub.repo_name)
860 model = VcsSettingsModel(repo=repo_stub.repo_name)
872 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
861 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
873 result = model.get_global_ui_settings(section=section)
862 result = model.get_global_ui_settings(section=section)
874 expected_result = model.global_settings.get_ui(section=section)
863 expected_result = model.global_settings.get_ui(section=section)
875 assert sorted(result) == sorted(expected_result)
864 assert sorted(result) == sorted(expected_result)
876
865
877 def test_ui_settings_filtered_by_key(
866 def test_ui_settings_filtered_by_key(
878 self, repo_stub, settings_util):
867 self, repo_stub, settings_util):
879 model = VcsSettingsModel(repo=repo_stub.repo_name)
868 model = VcsSettingsModel(repo=repo_stub.repo_name)
880 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
869 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
881 result = model.get_global_ui_settings(key=key)
870 result = model.get_global_ui_settings(key=key)
882 expected_result = model.global_settings.get_ui(key=key)
871 expected_result = model.global_settings.get_ui(key=key)
883 assert sorted(result) == sorted(expected_result)
872 assert sorted(result) == sorted(expected_result)
884
873
885
874
886 class TestGetGeneralSettings(object):
875 class TestGetGeneralSettings(object):
887 def test_global_settings_are_returned_when_inherited_is_true(
876 def test_global_settings_are_returned_when_inherited_is_true(
888 self, repo_stub, settings_util):
877 self, repo_stub, settings_util):
889 model = VcsSettingsModel(repo=repo_stub.repo_name)
878 model = VcsSettingsModel(repo=repo_stub.repo_name)
890 model.inherit_global_settings = True
879 model.inherit_global_settings = True
891 for key in VcsSettingsModel.GENERAL_SETTINGS:
880 for key in VcsSettingsModel.GENERAL_SETTINGS:
892 settings_util.create_repo_rhodecode_setting(
881 settings_util.create_repo_rhodecode_setting(
893 repo_stub, key, 'abcde', type_='unicode')
882 repo_stub, key, 'abcde', type_='unicode')
894 Session().commit()
883 Session().commit()
895
884
896 result = model.get_general_settings()
885 result = model.get_general_settings()
897 expected_result = model.get_global_general_settings()
886 expected_result = model.get_global_general_settings()
898 assert sorted(result) == sorted(expected_result)
887 assert sorted(result) == sorted(expected_result)
899
888
900 def test_repo_settings_are_returned_when_inherited_is_false(
889 def test_repo_settings_are_returned_when_inherited_is_false(
901 self, repo_stub, settings_util):
890 self, repo_stub, settings_util):
902 model = VcsSettingsModel(repo=repo_stub.repo_name)
891 model = VcsSettingsModel(repo=repo_stub.repo_name)
903 model.inherit_global_settings = False
892 model.inherit_global_settings = False
904 for key in VcsSettingsModel.GENERAL_SETTINGS:
893 for key in VcsSettingsModel.GENERAL_SETTINGS:
905 settings_util.create_repo_rhodecode_setting(
894 settings_util.create_repo_rhodecode_setting(
906 repo_stub, key, 'abcde', type_='unicode')
895 repo_stub, key, 'abcde', type_='unicode')
907 Session().commit()
896 Session().commit()
908
897
909 result = model.get_general_settings()
898 result = model.get_general_settings()
910 expected_result = model.get_repo_general_settings()
899 expected_result = model.get_repo_general_settings()
911 assert sorted(result) == sorted(expected_result)
900 assert sorted(result) == sorted(expected_result)
912
901
913 def test_global_settings_are_returned_when_no_repository_specified(self):
902 def test_global_settings_are_returned_when_no_repository_specified(self):
914 model = VcsSettingsModel()
903 model = VcsSettingsModel()
915 result = model.get_general_settings()
904 result = model.get_general_settings()
916 expected_result = model.get_global_general_settings()
905 expected_result = model.get_global_general_settings()
917 assert sorted(result) == sorted(expected_result)
906 assert sorted(result) == sorted(expected_result)
918
907
919
908
920 class TestGetUiSettings(object):
909 class TestGetUiSettings(object):
921 def test_global_settings_are_returned_when_inherited_is_true(
910 def test_global_settings_are_returned_when_inherited_is_true(
922 self, repo_stub, settings_util):
911 self, repo_stub, settings_util):
923 model = VcsSettingsModel(repo=repo_stub.repo_name)
912 model = VcsSettingsModel(repo=repo_stub.repo_name)
924 model.inherit_global_settings = True
913 model.inherit_global_settings = True
925 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
914 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
926 settings_util.create_repo_rhodecode_ui(
915 settings_util.create_repo_rhodecode_ui(
927 repo_stub, section, 'repo', key=key, active=True)
916 repo_stub, section, 'repo', key=key, active=True)
928 Session().commit()
917 Session().commit()
929
918
930 result = model.get_ui_settings()
919 result = model.get_ui_settings()
931 expected_result = model.get_global_ui_settings()
920 expected_result = model.get_global_ui_settings()
932 assert sorted(result) == sorted(expected_result)
921 assert sorted(result) == sorted(expected_result)
933
922
934 def test_repo_settings_are_returned_when_inherited_is_false(
923 def test_repo_settings_are_returned_when_inherited_is_false(
935 self, repo_stub, settings_util):
924 self, repo_stub, settings_util):
936 model = VcsSettingsModel(repo=repo_stub.repo_name)
925 model = VcsSettingsModel(repo=repo_stub.repo_name)
937 model.inherit_global_settings = False
926 model.inherit_global_settings = False
938 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
927 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
939 settings_util.create_repo_rhodecode_ui(
928 settings_util.create_repo_rhodecode_ui(
940 repo_stub, section, 'repo', key=key, active=True)
929 repo_stub, section, 'repo', key=key, active=True)
941 Session().commit()
930 Session().commit()
942
931
943 result = model.get_ui_settings()
932 result = model.get_ui_settings()
944 expected_result = model.get_repo_ui_settings()
933 expected_result = model.get_repo_ui_settings()
945 assert sorted(result) == sorted(expected_result)
934 assert sorted(result) == sorted(expected_result)
946
935
947 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
936 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
948 model = VcsSettingsModel(repo=repo_stub.repo_name)
937 model = VcsSettingsModel(repo=repo_stub.repo_name)
949 model.inherit_global_settings = False
938 model.inherit_global_settings = False
950
939
951 args = ('section', 'key')
940 args = ('section', 'key')
952 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
941 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
953 model.get_ui_settings(*args)
942 model.get_ui_settings(*args)
954 Session().commit()
943 Session().commit()
955
944
956 settings_mock.assert_called_once_with(*args)
945 settings_mock.assert_called_once_with(*args)
957
946
958 def test_global_settings_filtered_by_section_and_key(self):
947 def test_global_settings_filtered_by_section_and_key(self):
959 model = VcsSettingsModel()
948 model = VcsSettingsModel()
960 args = ('section', 'key')
949 args = ('section', 'key')
961 with mock.patch.object(model, 'get_global_ui_settings') as (
950 with mock.patch.object(model, 'get_global_ui_settings') as (
962 settings_mock):
951 settings_mock):
963 model.get_ui_settings(*args)
952 model.get_ui_settings(*args)
964 settings_mock.assert_called_once_with(*args)
953 settings_mock.assert_called_once_with(*args)
965
954
966 def test_global_settings_are_returned_when_no_repository_specified(self):
955 def test_global_settings_are_returned_when_no_repository_specified(self):
967 model = VcsSettingsModel()
956 model = VcsSettingsModel()
968 result = model.get_ui_settings()
957 result = model.get_ui_settings()
969 expected_result = model.get_global_ui_settings()
958 expected_result = model.get_global_ui_settings()
970 assert sorted(result) == sorted(expected_result)
959 assert sorted(result) == sorted(expected_result)
971
960
972
961
973 class TestGetSvnPatterns(object):
962 class TestGetSvnPatterns(object):
974 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
963 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
975 model = VcsSettingsModel(repo=repo_stub.repo_name)
964 model = VcsSettingsModel(repo=repo_stub.repo_name)
976 args = ('section', )
965 args = ('section', )
977 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
966 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
978 model.get_svn_patterns(*args)
967 model.get_svn_patterns(*args)
979
968
980 Session().commit()
969 Session().commit()
981 settings_mock.assert_called_once_with(*args)
970 settings_mock.assert_called_once_with(*args)
982
971
983 def test_global_settings_filtered_by_section_and_key(self):
972 def test_global_settings_filtered_by_section_and_key(self):
984 model = VcsSettingsModel()
973 model = VcsSettingsModel()
985 args = ('section', )
974 args = ('section', )
986 with mock.patch.object(model, 'get_global_ui_settings') as (
975 with mock.patch.object(model, 'get_global_ui_settings') as (
987 settings_mock):
976 settings_mock):
988 model.get_svn_patterns(*args)
977 model.get_svn_patterns(*args)
989 settings_mock.assert_called_once_with(*args)
978 settings_mock.assert_called_once_with(*args)
990
979
991
980
992 class TestCreateOrUpdateRepoSettings(object):
981 class TestCreateOrUpdateRepoSettings(object):
993 FORM_DATA = {
982 FORM_DATA = {
994 'inherit_global_settings': False,
983 'inherit_global_settings': False,
995 'hooks_changegroup_repo_size': False,
984 'hooks_changegroup_repo_size': False,
996 'hooks_changegroup_push_logger': False,
985 'hooks_changegroup_push_logger': False,
997 'hooks_outgoing_pull_logger': False,
986 'hooks_outgoing_pull_logger': False,
998 'extensions_largefiles': False,
987 'extensions_largefiles': False,
999 'extensions_evolve': False,
988 'extensions_evolve': False,
1000 'vcs_git_lfs_enabled': False,
989 'vcs_git_lfs_enabled': False,
1001 'phases_publish': 'False',
990 'phases_publish': 'False',
1002 'rhodecode_pr_merge_enabled': False,
991 'rhodecode_pr_merge_enabled': False,
1003 'rhodecode_use_outdated_comments': False,
992 'rhodecode_use_outdated_comments': False,
1004 'new_svn_branch': '',
993 'new_svn_branch': '',
1005 'new_svn_tag': ''
994 'new_svn_tag': ''
1006 }
995 }
1007
996
1008 def test_get_raises_exception_when_repository_not_specified(self):
997 def test_get_raises_exception_when_repository_not_specified(self):
1009 model = VcsSettingsModel()
998 model = VcsSettingsModel()
1010 with pytest.raises(Exception) as exc_info:
999 with pytest.raises(Exception) as exc_info:
1011 model.create_or_update_repo_settings(data=self.FORM_DATA)
1000 model.create_or_update_repo_settings(data=self.FORM_DATA)
1012 Session().commit()
1001 Session().commit()
1013
1002
1014 assert str(exc_info.value) == 'Repository is not specified'
1003 assert str(exc_info.value) == 'Repository is not specified'
1015
1004
1016 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1005 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1017 repo = backend_svn.create_repo()
1006 repo = backend_svn.create_repo()
1018 model = VcsSettingsModel(repo=repo)
1007 model = VcsSettingsModel(repo=repo)
1019 with self._patch_model(model) as mocks:
1008 with self._patch_model(model) as mocks:
1020 model.create_or_update_repo_settings(
1009 model.create_or_update_repo_settings(
1021 data=self.FORM_DATA, inherit_global_settings=False)
1010 data=self.FORM_DATA, inherit_global_settings=False)
1022 Session().commit()
1011 Session().commit()
1023
1012
1024 mocks['create_repo_svn_settings'].assert_called_once_with(
1013 mocks['create_repo_svn_settings'].assert_called_once_with(
1025 self.FORM_DATA)
1014 self.FORM_DATA)
1026 non_called_methods = (
1015 non_called_methods = (
1027 'create_or_update_repo_hook_settings',
1016 'create_or_update_repo_hook_settings',
1028 'create_or_update_repo_pr_settings',
1017 'create_or_update_repo_pr_settings',
1029 'create_or_update_repo_hg_settings')
1018 'create_or_update_repo_hg_settings')
1030 for method in non_called_methods:
1019 for method in non_called_methods:
1031 assert mocks[method].call_count == 0
1020 assert mocks[method].call_count == 0
1032
1021
1033 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1022 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1034 repo = backend_hg.create_repo()
1023 repo = backend_hg.create_repo()
1035 model = VcsSettingsModel(repo=repo)
1024 model = VcsSettingsModel(repo=repo)
1036 with self._patch_model(model) as mocks:
1025 with self._patch_model(model) as mocks:
1037 model.create_or_update_repo_settings(
1026 model.create_or_update_repo_settings(
1038 data=self.FORM_DATA, inherit_global_settings=False)
1027 data=self.FORM_DATA, inherit_global_settings=False)
1039 Session().commit()
1028 Session().commit()
1040
1029
1041 assert mocks['create_repo_svn_settings'].call_count == 0
1030 assert mocks['create_repo_svn_settings'].call_count == 0
1042 called_methods = (
1031 called_methods = (
1043 'create_or_update_repo_hook_settings',
1032 'create_or_update_repo_hook_settings',
1044 'create_or_update_repo_pr_settings',
1033 'create_or_update_repo_pr_settings',
1045 'create_or_update_repo_hg_settings')
1034 'create_or_update_repo_hg_settings')
1046 for method in called_methods:
1035 for method in called_methods:
1047 mocks[method].assert_called_once_with(self.FORM_DATA)
1036 mocks[method].assert_called_once_with(self.FORM_DATA)
1048
1037
1049 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1038 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1050 self, backend_git):
1039 self, backend_git):
1051 repo = backend_git.create_repo()
1040 repo = backend_git.create_repo()
1052 model = VcsSettingsModel(repo=repo)
1041 model = VcsSettingsModel(repo=repo)
1053 with self._patch_model(model) as mocks:
1042 with self._patch_model(model) as mocks:
1054 model.create_or_update_repo_settings(
1043 model.create_or_update_repo_settings(
1055 data=self.FORM_DATA, inherit_global_settings=False)
1044 data=self.FORM_DATA, inherit_global_settings=False)
1056
1045
1057 assert mocks['create_repo_svn_settings'].call_count == 0
1046 assert mocks['create_repo_svn_settings'].call_count == 0
1058 called_methods = (
1047 called_methods = (
1059 'create_or_update_repo_hook_settings',
1048 'create_or_update_repo_hook_settings',
1060 'create_or_update_repo_pr_settings')
1049 'create_or_update_repo_pr_settings')
1061 non_called_methods = (
1050 non_called_methods = (
1062 'create_repo_svn_settings',
1051 'create_repo_svn_settings',
1063 'create_or_update_repo_hg_settings'
1052 'create_or_update_repo_hg_settings'
1064 )
1053 )
1065 for method in called_methods:
1054 for method in called_methods:
1066 mocks[method].assert_called_once_with(self.FORM_DATA)
1055 mocks[method].assert_called_once_with(self.FORM_DATA)
1067 for method in non_called_methods:
1056 for method in non_called_methods:
1068 assert mocks[method].call_count == 0
1057 assert mocks[method].call_count == 0
1069
1058
1070 def test_no_methods_are_called_when_settings_are_inherited(
1059 def test_no_methods_are_called_when_settings_are_inherited(
1071 self, backend):
1060 self, backend):
1072 repo = backend.create_repo()
1061 repo = backend.create_repo()
1073 model = VcsSettingsModel(repo=repo)
1062 model = VcsSettingsModel(repo=repo)
1074 with self._patch_model(model) as mocks:
1063 with self._patch_model(model) as mocks:
1075 model.create_or_update_repo_settings(
1064 model.create_or_update_repo_settings(
1076 data=self.FORM_DATA, inherit_global_settings=True)
1065 data=self.FORM_DATA, inherit_global_settings=True)
1077 for method_name in mocks:
1066 for method_name in mocks:
1078 assert mocks[method_name].call_count == 0
1067 assert mocks[method_name].call_count == 0
1079
1068
1080 def test_cache_is_marked_for_invalidation(self, repo_stub):
1069 def test_cache_is_marked_for_invalidation(self, repo_stub):
1081 model = VcsSettingsModel(repo=repo_stub)
1070 model = VcsSettingsModel(repo=repo_stub)
1082 invalidation_patcher = mock.patch(
1071 invalidation_patcher = mock.patch(
1083 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1072 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1084 with invalidation_patcher as invalidation_mock:
1073 with invalidation_patcher as invalidation_mock:
1085 model.create_or_update_repo_settings(
1074 model.create_or_update_repo_settings(
1086 data=self.FORM_DATA, inherit_global_settings=True)
1075 data=self.FORM_DATA, inherit_global_settings=True)
1087 Session().commit()
1076 Session().commit()
1088
1077
1089 invalidation_mock.assert_called_once_with(
1078 invalidation_mock.assert_called_once_with(
1090 repo_stub.repo_name, delete=True)
1079 repo_stub.repo_name, delete=True)
1091
1080
1092 def test_inherit_flag_is_saved(self, repo_stub):
1081 def test_inherit_flag_is_saved(self, repo_stub):
1093 model = VcsSettingsModel(repo=repo_stub)
1082 model = VcsSettingsModel(repo=repo_stub)
1094 model.inherit_global_settings = True
1083 model.inherit_global_settings = True
1095 with self._patch_model(model):
1084 with self._patch_model(model):
1096 model.create_or_update_repo_settings(
1085 model.create_or_update_repo_settings(
1097 data=self.FORM_DATA, inherit_global_settings=False)
1086 data=self.FORM_DATA, inherit_global_settings=False)
1098 Session().commit()
1087 Session().commit()
1099
1088
1100 assert model.inherit_global_settings is False
1089 assert model.inherit_global_settings is False
1101
1090
1102 def _patch_model(self, model):
1091 def _patch_model(self, model):
1103 return mock.patch.multiple(
1092 return mock.patch.multiple(
1104 model,
1093 model,
1105 create_repo_svn_settings=mock.DEFAULT,
1094 create_repo_svn_settings=mock.DEFAULT,
1106 create_or_update_repo_hook_settings=mock.DEFAULT,
1095 create_or_update_repo_hook_settings=mock.DEFAULT,
1107 create_or_update_repo_pr_settings=mock.DEFAULT,
1096 create_or_update_repo_pr_settings=mock.DEFAULT,
1108 create_or_update_repo_hg_settings=mock.DEFAULT)
1097 create_or_update_repo_hg_settings=mock.DEFAULT)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now