##// END OF EJS Templates
core: avoid using rhodecode.test packages inside main packages as tests are removed during build which can cause some problems in some edge case calls
super-admin -
r5618:bdbdb63f default
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 # Copyright (C) 2010-2024 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 # bootstrap data available for tests and setup clean install
20
21 TEST_USER_ADMIN_LOGIN = 'test_admin'
22 TEST_USER_ADMIN_PASS = 'test12'
23 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
24
25 TEST_USER_REGULAR_LOGIN = 'test_regular'
26 TEST_USER_REGULAR_PASS = 'test12'
27 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
28
29 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
30 TEST_USER_REGULAR2_PASS = 'test12'
31 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
32
33 HG_REPO = 'vcs_test_hg'
34 GIT_REPO = 'vcs_test_git'
35 SVN_REPO = 'vcs_test_svn'
36
37 NEW_HG_REPO = 'vcs_test_hg_new'
38 NEW_GIT_REPO = 'vcs_test_git_new'
39
40 HG_FORK = 'vcs_test_hg_fork'
41 GIT_FORK = 'vcs_test_git_fork'
@@ -1,50 +1,50 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 pytest
19 import pytest
20
20
21 from rhodecode.model.meta import Session
21 from rhodecode.model.meta import Session
22 from rhodecode.model.user import UserModel
22 from rhodecode.model.user import UserModel
23 from rhodecode.model.auth_token import AuthTokenModel
23 from rhodecode.model.auth_token import AuthTokenModel
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25
24
26
25
27 @pytest.fixture(scope="class")
26 @pytest.fixture(scope="class")
28 def testuser_api(request, baseapp):
27 def testuser_api(request, baseapp):
29 cls = request.cls
28 cls = request.cls
29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30
30
31 # ADMIN USER
31 # ADMIN USER
32 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
32 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
33 cls.apikey = cls.usr.api_key
33 cls.apikey = cls.usr.api_key
34
34
35 # REGULAR USER
35 # REGULAR USER
36 cls.test_user = UserModel().create_or_update(
36 cls.test_user = UserModel().create_or_update(
37 username='test-api',
37 username='test-api',
38 password='test',
38 password='test',
39 email='test@api.rhodecode.org',
39 email='test@api.rhodecode.org',
40 firstname='first',
40 firstname='first',
41 lastname='last'
41 lastname='last'
42 )
42 )
43 # create TOKEN for user, if he doesn't have one
43 # create TOKEN for user, if he doesn't have one
44 if not cls.test_user.api_key:
44 if not cls.test_user.api_key:
45 AuthTokenModel().create(
45 AuthTokenModel().create(
46 user=cls.test_user, description='TEST_USER_TOKEN')
46 user=cls.test_user, description='TEST_USER_TOKEN')
47
47
48 Session().commit()
48 Session().commit()
49 cls.TEST_USER_LOGIN = cls.test_user.username
49 cls.TEST_USER_LOGIN = cls.test_user.username
50 cls.apikey_regular = cls.test_user.api_key
50 cls.apikey_regular = cls.test_user.api_key
@@ -1,423 +1,479 b''
1 # Copyright (C) 2011-2024 RhodeCode GmbH
1 # Copyright (C) 2011-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 itertools
20 import itertools
21 import base64
21 import base64
22
22
23 from rhodecode.api import (
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
25
25
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
28 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
28 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
29 from rhodecode.lib import system_info
29 from rhodecode.lib import system_info
30 from rhodecode.lib import user_sessions
30 from rhodecode.lib import user_sessions
31 from rhodecode.lib import exc_tracking
31 from rhodecode.lib import exc_tracking
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.model.db import UserIpMap
34 from rhodecode.model.db import UserIpMap
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36 from rhodecode.apps.file_store import utils as store_utils
36 from rhodecode.apps.file_store import utils as store_utils
37 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
37 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
38 FileOverSizeException
38 FileOverSizeException
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 @jsonrpc_method()
43 @jsonrpc_method()
44 def get_server_info(request, apiuser):
44 def get_server_info(request, apiuser):
45 """
45 """
46 Returns the |RCE| server information.
46 Returns the |RCE| server information.
47
47
48 This includes the running version of |RCE| and all installed
48 This includes the running version of |RCE| and all installed
49 packages. This command takes the following options:
49 packages. This command takes the following options:
50
50
51 :param apiuser: This is filled automatically from the |authtoken|.
51 :param apiuser: This is filled automatically from the |authtoken|.
52 :type apiuser: AuthUser
52 :type apiuser: AuthUser
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 id : <id_given_in_input>
58 id : <id_given_in_input>
59 result : {
59 result : {
60 'modules': [<module name>,...]
60 'modules': [<module name>,...]
61 'py_version': <python version>,
61 'py_version': <python version>,
62 'platform': <platform type>,
62 'platform': <platform type>,
63 'rhodecode_version': <rhodecode version>
63 'rhodecode_version': <rhodecode version>
64 }
64 }
65 error : null
65 error : null
66 """
66 """
67
67
68 if not has_superadmin_permission(apiuser):
68 if not has_superadmin_permission(apiuser):
69 raise JSONRPCForbidden()
69 raise JSONRPCForbidden()
70
70
71 server_info = ScmModel().get_server_info(request.environ)
71 server_info = ScmModel().get_server_info(request.environ)
72 # rhodecode-index requires those
72 # rhodecode-index requires those
73
73
74 server_info['index_storage'] = server_info['search']['value']['location']
74 server_info['index_storage'] = server_info['search']['value']['location']
75 server_info['storage'] = server_info['storage']['value']['path']
75 server_info['storage'] = server_info['storage']['value']['path']
76
76
77 return server_info
77 return server_info
78
78
79
79
80 @jsonrpc_method()
80 @jsonrpc_method()
81 def get_repo_store(request, apiuser):
81 def get_repo_store(request, apiuser):
82 """
82 """
83 Returns the |RCE| repository storage information.
83 Returns the |RCE| repository storage information.
84
84
85 :param apiuser: This is filled automatically from the |authtoken|.
85 :param apiuser: This is filled automatically from the |authtoken|.
86 :type apiuser: AuthUser
86 :type apiuser: AuthUser
87
87
88 Example output:
88 Example output:
89
89
90 .. code-block:: bash
90 .. code-block:: bash
91
91
92 id : <id_given_in_input>
92 id : <id_given_in_input>
93 result : {
93 result : {
94 'modules': [<module name>,...]
94 'modules': [<module name>,...]
95 'py_version': <python version>,
95 'py_version': <python version>,
96 'platform': <platform type>,
96 'platform': <platform type>,
97 'rhodecode_version': <rhodecode version>
97 'rhodecode_version': <rhodecode version>
98 }
98 }
99 error : null
99 error : null
100 """
100 """
101
101
102 if not has_superadmin_permission(apiuser):
102 if not has_superadmin_permission(apiuser):
103 raise JSONRPCForbidden()
103 raise JSONRPCForbidden()
104
104
105 path = get_rhodecode_repo_store_path()
105 path = get_rhodecode_repo_store_path()
106 return {"path": path}
106 return {"path": path}
107
107
108
108
109 @jsonrpc_method()
109 @jsonrpc_method()
110 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
110 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
111 """
111 """
112 Displays the IP Address as seen from the |RCE| server.
112 Displays the IP Address as seen from the |RCE| server.
113
113
114 * This command displays the IP Address, as well as all the defined IP
114 * This command displays the IP Address, as well as all the defined IP
115 addresses for the specified user. If the ``userid`` is not set, the
115 addresses for the specified user. If the ``userid`` is not set, the
116 data returned is for the user calling the method.
116 data returned is for the user calling the method.
117
117
118 This command can only be run using an |authtoken| with admin rights to
118 This command can only be run using an |authtoken| with admin rights to
119 the specified repository.
119 the specified repository.
120
120
121 This command takes the following options:
121 This command takes the following options:
122
122
123 :param apiuser: This is filled automatically from |authtoken|.
123 :param apiuser: This is filled automatically from |authtoken|.
124 :type apiuser: AuthUser
124 :type apiuser: AuthUser
125 :param userid: Sets the userid for which associated IP Address data
125 :param userid: Sets the userid for which associated IP Address data
126 is returned.
126 is returned.
127 :type userid: Optional(str or int)
127 :type userid: Optional(str or int)
128
128
129 Example output:
129 Example output:
130
130
131 .. code-block:: bash
131 .. code-block:: bash
132
132
133 id : <id_given_in_input>
133 id : <id_given_in_input>
134 result : {
134 result : {
135 "server_ip_addr": "<ip_from_clien>",
135 "server_ip_addr": "<ip_from_clien>",
136 "user_ips": [
136 "user_ips": [
137 {
137 {
138 "ip_addr": "<ip_with_mask>",
138 "ip_addr": "<ip_with_mask>",
139 "ip_range": ["<start_ip>", "<end_ip>"],
139 "ip_range": ["<start_ip>", "<end_ip>"],
140 },
140 },
141 ...
141 ...
142 ]
142 ]
143 }
143 }
144
144
145 """
145 """
146 if not has_superadmin_permission(apiuser):
146 if not has_superadmin_permission(apiuser):
147 raise JSONRPCForbidden()
147 raise JSONRPCForbidden()
148
148
149 userid = Optional.extract(userid, evaluate_locals=locals())
149 userid = Optional.extract(userid, evaluate_locals=locals())
150 userid = getattr(userid, 'user_id', userid)
150 userid = getattr(userid, 'user_id', userid)
151
151
152 user = get_user_or_error(userid)
152 user = get_user_or_error(userid)
153 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
153 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
154 return {
154 return {
155 'server_ip_addr': request.rpc_ip_addr,
155 'server_ip_addr': request.rpc_ip_addr,
156 'user_ips': ips
156 'user_ips': ips
157 }
157 }
158
158
159
159
160 @jsonrpc_method()
160 @jsonrpc_method()
161 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
161 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
162 """
162 """
163 Triggers a rescan of the specified repositories.
163 Triggers a rescan of the specified repositories.
164
164
165 * If the ``remove_obsolete`` option is set, it also deletes repositories
165 * If the ``remove_obsolete`` option is set, it also deletes repositories
166 that are found in the database but not on the file system, so called
166 that are found in the database but not on the file system, so called
167 "clean zombies".
167 "clean zombies".
168
168
169 This command can only be run using an |authtoken| with admin rights to
169 This command can only be run using an |authtoken| with admin rights to
170 the specified repository.
170 the specified repository.
171
171
172 This command takes the following options:
172 This command takes the following options:
173
173
174 :param apiuser: This is filled automatically from the |authtoken|.
174 :param apiuser: This is filled automatically from the |authtoken|.
175 :type apiuser: AuthUser
175 :type apiuser: AuthUser
176 :param remove_obsolete: Deletes repositories from the database that
176 :param remove_obsolete: Deletes repositories from the database that
177 are not found on the filesystem.
177 are not found on the filesystem.
178 :type remove_obsolete: Optional(``True`` | ``False``)
178 :type remove_obsolete: Optional(``True`` | ``False``)
179
179
180 Example output:
180 Example output:
181
181
182 .. code-block:: bash
182 .. code-block:: bash
183
183
184 id : <id_given_in_input>
184 id : <id_given_in_input>
185 result : {
185 result : {
186 'added': [<added repository name>,...]
186 'added': [<added repository name>,...]
187 'removed': [<removed repository name>,...]
187 'removed': [<removed repository name>,...]
188 }
188 }
189 error : null
189 error : null
190
190
191 Example error output:
191 Example error output:
192
192
193 .. code-block:: bash
193 .. code-block:: bash
194
194
195 id : <id_given_in_input>
195 id : <id_given_in_input>
196 result : null
196 result : null
197 error : {
197 error : {
198 'Error occurred during rescan repositories action'
198 'Error occurred during rescan repositories action'
199 }
199 }
200
200
201 """
201 """
202 if not has_superadmin_permission(apiuser):
202 if not has_superadmin_permission(apiuser):
203 raise JSONRPCForbidden()
203 raise JSONRPCForbidden()
204
204
205 try:
205 try:
206 rm_obsolete = Optional.extract(remove_obsolete)
206 rm_obsolete = Optional.extract(remove_obsolete)
207 added, removed = repo2db_mapper(ScmModel().repo_scan(),
207 added, removed = repo2db_mapper(ScmModel().repo_scan(),
208 remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
208 remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
209 return {'added': added, 'removed': removed}
209 return {'added': added, 'removed': removed}
210 except Exception:
210 except Exception:
211 log.exception('Failed to run repo rescann')
211 log.exception('Failed to run repo rescann')
212 raise JSONRPCError(
212 raise JSONRPCError(
213 'Error occurred during rescan repositories action'
213 'Error occurred during rescan repositories action'
214 )
214 )
215
215
216 @jsonrpc_method()
217 def cleanup_repos(request, apiuser, remove_obsolete=Optional(False)):
218 """
219 Triggers a rescan of the specified repositories.
220
221 * If the ``remove_obsolete`` option is set, it also deletes repositories
222 that are found in the database but not on the file system, so called
223 "clean zombies".
224
225 This command can only be run using an |authtoken| with admin rights to
226 the specified repository.
227
228 This command takes the following options:
229
230 :param apiuser: This is filled automatically from the |authtoken|.
231 :type apiuser: AuthUser
232 :param remove_obsolete: Deletes repositories from the database that
233 are not found on the filesystem.
234 :type remove_obsolete: Optional(``True`` | ``False``)
235
236 Example output:
237
238 .. code-block:: bash
239
240 id : <id_given_in_input>
241 result : {
242 'added': [<added repository name>,...]
243 'removed': [<removed repository name>,...]
244 }
245 error : null
246
247 Example error output:
248
249 .. code-block:: bash
250
251 id : <id_given_in_input>
252 result : null
253 error : {
254 'Error occurred during rescan repositories action'
255 }
256
257 """
258 if not has_superadmin_permission(apiuser):
259 raise JSONRPCForbidden()
260
261 try:
262 rm_obsolete = Optional.extract(remove_obsolete)
263 added, removed = repo2db_mapper(ScmModel().repo_scan(),
264 remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
265 return {'added': added, 'removed': removed}
266 except Exception:
267 log.exception('Failed to run repo rescann')
268 raise JSONRPCError(
269 'Error occurred during rescan repositories action'
270 )
271
216
272
217 @jsonrpc_method()
273 @jsonrpc_method()
218 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
274 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
219 """
275 """
220 Triggers a session cleanup action.
276 Triggers a session cleanup action.
221
277
222 If the ``older_then`` option is set, only sessions that hasn't been
278 If the ``older_then`` option is set, only sessions that hasn't been
223 accessed in the given number of days will be removed.
279 accessed in the given number of days will be removed.
224
280
225 This command can only be run using an |authtoken| with admin rights to
281 This command can only be run using an |authtoken| with admin rights to
226 the specified repository.
282 the specified repository.
227
283
228 This command takes the following options:
284 This command takes the following options:
229
285
230 :param apiuser: This is filled automatically from the |authtoken|.
286 :param apiuser: This is filled automatically from the |authtoken|.
231 :type apiuser: AuthUser
287 :type apiuser: AuthUser
232 :param older_then: Deletes session that hasn't been accessed
288 :param older_then: Deletes session that hasn't been accessed
233 in given number of days.
289 in given number of days.
234 :type older_then: Optional(int)
290 :type older_then: Optional(int)
235
291
236 Example output:
292 Example output:
237
293
238 .. code-block:: bash
294 .. code-block:: bash
239
295
240 id : <id_given_in_input>
296 id : <id_given_in_input>
241 result: {
297 result: {
242 "backend": "<type of backend>",
298 "backend": "<type of backend>",
243 "sessions_removed": <number_of_removed_sessions>
299 "sessions_removed": <number_of_removed_sessions>
244 }
300 }
245 error : null
301 error : null
246
302
247 Example error output:
303 Example error output:
248
304
249 .. code-block:: bash
305 .. code-block:: bash
250
306
251 id : <id_given_in_input>
307 id : <id_given_in_input>
252 result : null
308 result : null
253 error : {
309 error : {
254 'Error occurred during session cleanup'
310 'Error occurred during session cleanup'
255 }
311 }
256
312
257 """
313 """
258 if not has_superadmin_permission(apiuser):
314 if not has_superadmin_permission(apiuser):
259 raise JSONRPCForbidden()
315 raise JSONRPCForbidden()
260
316
261 older_then = safe_int(Optional.extract(older_then)) or 60
317 older_then = safe_int(Optional.extract(older_then)) or 60
262 older_than_seconds = 60 * 60 * 24 * older_then
318 older_than_seconds = 60 * 60 * 24 * older_then
263
319
264 config = system_info.rhodecode_config().get_value()['value']['config']
320 config = system_info.rhodecode_config().get_value()['value']['config']
265 session_model = user_sessions.get_session_handler(
321 session_model = user_sessions.get_session_handler(
266 config.get('beaker.session.type', 'memory'))(config)
322 config.get('beaker.session.type', 'memory'))(config)
267
323
268 backend = session_model.SESSION_TYPE
324 backend = session_model.SESSION_TYPE
269 try:
325 try:
270 cleaned = session_model.clean_sessions(
326 cleaned = session_model.clean_sessions(
271 older_than_seconds=older_than_seconds)
327 older_than_seconds=older_than_seconds)
272 return {'sessions_removed': cleaned, 'backend': backend}
328 return {'sessions_removed': cleaned, 'backend': backend}
273 except user_sessions.CleanupCommand as msg:
329 except user_sessions.CleanupCommand as msg:
274 return {'cleanup_command': str(msg), 'backend': backend}
330 return {'cleanup_command': str(msg), 'backend': backend}
275 except Exception as e:
331 except Exception as e:
276 log.exception('Failed session cleanup')
332 log.exception('Failed session cleanup')
277 raise JSONRPCError(
333 raise JSONRPCError(
278 'Error occurred during session cleanup'
334 'Error occurred during session cleanup'
279 )
335 )
280
336
281
337
282 @jsonrpc_method()
338 @jsonrpc_method()
283 def get_method(request, apiuser, pattern=Optional('*')):
339 def get_method(request, apiuser, pattern=Optional('*')):
284 """
340 """
285 Returns list of all available API methods. By default match pattern
341 Returns list of all available API methods. By default match pattern
286 os "*" but any other pattern can be specified. eg *comment* will return
342 os "*" but any other pattern can be specified. eg *comment* will return
287 all methods with comment inside them. If just single method is matched
343 all methods with comment inside them. If just single method is matched
288 returned data will also include method specification
344 returned data will also include method specification
289
345
290 This command can only be run using an |authtoken| with admin rights to
346 This command can only be run using an |authtoken| with admin rights to
291 the specified repository.
347 the specified repository.
292
348
293 This command takes the following options:
349 This command takes the following options:
294
350
295 :param apiuser: This is filled automatically from the |authtoken|.
351 :param apiuser: This is filled automatically from the |authtoken|.
296 :type apiuser: AuthUser
352 :type apiuser: AuthUser
297 :param pattern: pattern to match method names against
353 :param pattern: pattern to match method names against
298 :type pattern: Optional("*")
354 :type pattern: Optional("*")
299
355
300 Example output:
356 Example output:
301
357
302 .. code-block:: bash
358 .. code-block:: bash
303
359
304 id : <id_given_in_input>
360 id : <id_given_in_input>
305 "result": [
361 "result": [
306 "changeset_comment",
362 "changeset_comment",
307 "comment_pull_request",
363 "comment_pull_request",
308 "comment_commit"
364 "comment_commit"
309 ]
365 ]
310 error : null
366 error : null
311
367
312 .. code-block:: bash
368 .. code-block:: bash
313
369
314 id : <id_given_in_input>
370 id : <id_given_in_input>
315 "result": [
371 "result": [
316 "comment_commit",
372 "comment_commit",
317 {
373 {
318 "apiuser": "<RequiredType>",
374 "apiuser": "<RequiredType>",
319 "comment_type": "<Optional:u'note'>",
375 "comment_type": "<Optional:u'note'>",
320 "commit_id": "<RequiredType>",
376 "commit_id": "<RequiredType>",
321 "message": "<RequiredType>",
377 "message": "<RequiredType>",
322 "repoid": "<RequiredType>",
378 "repoid": "<RequiredType>",
323 "request": "<RequiredType>",
379 "request": "<RequiredType>",
324 "resolves_comment_id": "<Optional:None>",
380 "resolves_comment_id": "<Optional:None>",
325 "status": "<Optional:None>",
381 "status": "<Optional:None>",
326 "userid": "<Optional:<OptionalAttr:apiuser>>"
382 "userid": "<Optional:<OptionalAttr:apiuser>>"
327 }
383 }
328 ]
384 ]
329 error : null
385 error : null
330 """
386 """
331 from rhodecode.config import patches
387 from rhodecode.config import patches
332 inspect = patches.inspect_getargspec()
388 inspect = patches.inspect_getargspec()
333
389
334 if not has_superadmin_permission(apiuser):
390 if not has_superadmin_permission(apiuser):
335 raise JSONRPCForbidden()
391 raise JSONRPCForbidden()
336
392
337 pattern = Optional.extract(pattern)
393 pattern = Optional.extract(pattern)
338
394
339 matches = find_methods(request.registry.jsonrpc_methods, pattern)
395 matches = find_methods(request.registry.jsonrpc_methods, pattern)
340
396
341 args_desc = []
397 args_desc = []
342 matches_keys = list(matches.keys())
398 matches_keys = list(matches.keys())
343 if len(matches_keys) == 1:
399 if len(matches_keys) == 1:
344 func = matches[matches_keys[0]]
400 func = matches[matches_keys[0]]
345
401
346 argspec = inspect.getargspec(func)
402 argspec = inspect.getargspec(func)
347 arglist = argspec[0]
403 arglist = argspec[0]
348 defaults = list(map(repr, argspec[3] or []))
404 defaults = list(map(repr, argspec[3] or []))
349
405
350 default_empty = '<RequiredType>'
406 default_empty = '<RequiredType>'
351
407
352 # kw arguments required by this method
408 # kw arguments required by this method
353 func_kwargs = dict(itertools.zip_longest(
409 func_kwargs = dict(itertools.zip_longest(
354 reversed(arglist), reversed(defaults), fillvalue=default_empty))
410 reversed(arglist), reversed(defaults), fillvalue=default_empty))
355 args_desc.append(func_kwargs)
411 args_desc.append(func_kwargs)
356
412
357 return matches_keys + args_desc
413 return matches_keys + args_desc
358
414
359
415
360 @jsonrpc_method()
416 @jsonrpc_method()
361 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
417 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
362 """
418 """
363 Stores sent exception inside the built-in exception tracker in |RCE| server.
419 Stores sent exception inside the built-in exception tracker in |RCE| server.
364
420
365 This command can only be run using an |authtoken| with admin rights to
421 This command can only be run using an |authtoken| with admin rights to
366 the specified repository.
422 the specified repository.
367
423
368 This command takes the following options:
424 This command takes the following options:
369
425
370 :param apiuser: This is filled automatically from the |authtoken|.
426 :param apiuser: This is filled automatically from the |authtoken|.
371 :type apiuser: AuthUser
427 :type apiuser: AuthUser
372
428
373 :param exc_data_json: JSON data with exception e.g
429 :param exc_data_json: JSON data with exception e.g
374 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
430 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
375 :type exc_data_json: JSON data
431 :type exc_data_json: JSON data
376
432
377 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
433 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
378 :type prefix: Optional("rhodecode")
434 :type prefix: Optional("rhodecode")
379
435
380 Example output:
436 Example output:
381
437
382 .. code-block:: bash
438 .. code-block:: bash
383
439
384 id : <id_given_in_input>
440 id : <id_given_in_input>
385 "result": {
441 "result": {
386 "exc_id": 139718459226384,
442 "exc_id": 139718459226384,
387 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
443 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
388 }
444 }
389 error : null
445 error : null
390 """
446 """
391 if not has_superadmin_permission(apiuser):
447 if not has_superadmin_permission(apiuser):
392 raise JSONRPCForbidden()
448 raise JSONRPCForbidden()
393
449
394 prefix = Optional.extract(prefix)
450 prefix = Optional.extract(prefix)
395 exc_id = exc_tracking.generate_id()
451 exc_id = exc_tracking.generate_id()
396
452
397 try:
453 try:
398 exc_data = json.loads(exc_data_json)
454 exc_data = json.loads(exc_data_json)
399 except Exception:
455 except Exception:
400 log.error('Failed to parse JSON: %r', exc_data_json)
456 log.error('Failed to parse JSON: %r', exc_data_json)
401 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
457 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
402 'Please make sure it contains a valid JSON.')
458 'Please make sure it contains a valid JSON.')
403
459
404 try:
460 try:
405 exc_traceback = exc_data['exc_traceback']
461 exc_traceback = exc_data['exc_traceback']
406 exc_type_name = exc_data['exc_type_name']
462 exc_type_name = exc_data['exc_type_name']
407 exc_value = ''
463 exc_value = ''
408 except KeyError as err:
464 except KeyError as err:
409 raise JSONRPCError(
465 raise JSONRPCError(
410 f'Missing exc_traceback, or exc_type_name '
466 f'Missing exc_traceback, or exc_type_name '
411 f'in exc_data_json field. Missing: {err}')
467 f'in exc_data_json field. Missing: {err}')
412
468
413 class ExcType:
469 class ExcType:
414 __name__ = exc_type_name
470 __name__ = exc_type_name
415
471
416 exc_info = (ExcType(), exc_value, exc_traceback)
472 exc_info = (ExcType(), exc_value, exc_traceback)
417
473
418 exc_tracking._store_exception(
474 exc_tracking._store_exception(
419 exc_id=exc_id, exc_info=exc_info, prefix=prefix)
475 exc_id=exc_id, exc_info=exc_info, prefix=prefix)
420
476
421 exc_url = request.route_url(
477 exc_url = request.route_url(
422 'admin_settings_exception_tracker_show', exception_id=exc_id)
478 'admin_settings_exception_tracker_show', exception_id=exc_id)
423 return {'exc_id': exc_id, 'exc_url': exc_url}
479 return {'exc_id': exc_id, 'exc_url': exc_url}
@@ -1,658 +1,658 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 """
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.bootstrap 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
331
332 # Build HOOKS
332 # Build HOOKS
333 hooks = [
333 hooks = [
334 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
334 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
335
335
336 # HG
336 # HG
337 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
337 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
338 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
338 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
339 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
339 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
340 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
340 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
341 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
341 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
342 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
342 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
343
343
344 ]
344 ]
345
345
346 for key, value in hooks:
346 for key, value in hooks:
347 hook_obj = settings_model.get_ui_by_key(key)
347 hook_obj = settings_model.get_ui_by_key(key)
348 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
348 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
349 hooks2.ui_section = 'hooks'
349 hooks2.ui_section = 'hooks'
350 hooks2.ui_key = key
350 hooks2.ui_key = key
351 hooks2.ui_value = value
351 hooks2.ui_value = value
352 self.sa.add(hooks2)
352 self.sa.add(hooks2)
353
353
354 # enable largefiles
354 # enable largefiles
355 largefiles = RhodeCodeUi()
355 largefiles = RhodeCodeUi()
356 largefiles.ui_section = 'extensions'
356 largefiles.ui_section = 'extensions'
357 largefiles.ui_key = 'largefiles'
357 largefiles.ui_key = 'largefiles'
358 largefiles.ui_value = ''
358 largefiles.ui_value = ''
359 self.sa.add(largefiles)
359 self.sa.add(largefiles)
360
360
361 # enable hgevolve disabled by default
361 # enable hgevolve disabled by default
362 hgevolve = RhodeCodeUi()
362 hgevolve = RhodeCodeUi()
363 hgevolve.ui_section = 'extensions'
363 hgevolve.ui_section = 'extensions'
364 hgevolve.ui_key = 'evolve'
364 hgevolve.ui_key = 'evolve'
365 hgevolve.ui_value = ''
365 hgevolve.ui_value = ''
366 hgevolve.ui_active = False
366 hgevolve.ui_active = False
367 self.sa.add(hgevolve)
367 self.sa.add(hgevolve)
368
368
369 hgevolve = RhodeCodeUi()
369 hgevolve = RhodeCodeUi()
370 hgevolve.ui_section = 'experimental'
370 hgevolve.ui_section = 'experimental'
371 hgevolve.ui_key = 'evolution'
371 hgevolve.ui_key = 'evolution'
372 hgevolve.ui_value = ''
372 hgevolve.ui_value = ''
373 hgevolve.ui_active = False
373 hgevolve.ui_active = False
374 self.sa.add(hgevolve)
374 self.sa.add(hgevolve)
375
375
376 hgevolve = RhodeCodeUi()
376 hgevolve = RhodeCodeUi()
377 hgevolve.ui_section = 'experimental'
377 hgevolve.ui_section = 'experimental'
378 hgevolve.ui_key = 'evolution.exchange'
378 hgevolve.ui_key = 'evolution.exchange'
379 hgevolve.ui_value = ''
379 hgevolve.ui_value = ''
380 hgevolve.ui_active = False
380 hgevolve.ui_active = False
381 self.sa.add(hgevolve)
381 self.sa.add(hgevolve)
382
382
383 hgevolve = RhodeCodeUi()
383 hgevolve = RhodeCodeUi()
384 hgevolve.ui_section = 'extensions'
384 hgevolve.ui_section = 'extensions'
385 hgevolve.ui_key = 'topic'
385 hgevolve.ui_key = 'topic'
386 hgevolve.ui_value = ''
386 hgevolve.ui_value = ''
387 hgevolve.ui_active = False
387 hgevolve.ui_active = False
388 self.sa.add(hgevolve)
388 self.sa.add(hgevolve)
389
389
390 # enable hggit disabled by default
390 # enable hggit disabled by default
391 hggit = RhodeCodeUi()
391 hggit = RhodeCodeUi()
392 hggit.ui_section = 'extensions'
392 hggit.ui_section = 'extensions'
393 hggit.ui_key = 'hggit'
393 hggit.ui_key = 'hggit'
394 hggit.ui_value = ''
394 hggit.ui_value = ''
395 hggit.ui_active = False
395 hggit.ui_active = False
396 self.sa.add(hggit)
396 self.sa.add(hggit)
397
397
398 # set svn branch defaults
398 # set svn branch defaults
399 branches = ["/branches/*", "/trunk"]
399 branches = ["/branches/*", "/trunk"]
400 tags = ["/tags/*"]
400 tags = ["/tags/*"]
401
401
402 for branch in branches:
402 for branch in branches:
403 settings_model.create_ui_section_value(
403 settings_model.create_ui_section_value(
404 RhodeCodeUi.SVN_BRANCH_ID, branch)
404 RhodeCodeUi.SVN_BRANCH_ID, branch)
405
405
406 for tag in tags:
406 for tag in tags:
407 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
407 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
408
408
409 def create_auth_plugin_options(self, skip_existing=False):
409 def create_auth_plugin_options(self, skip_existing=False):
410 """
410 """
411 Create default auth plugin settings, and make it active
411 Create default auth plugin settings, and make it active
412
412
413 :param skip_existing:
413 :param skip_existing:
414 """
414 """
415 defaults = [
415 defaults = [
416 ('auth_plugins',
416 ('auth_plugins',
417 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
417 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
418 'list'),
418 'list'),
419
419
420 ('auth_authtoken_enabled',
420 ('auth_authtoken_enabled',
421 'True',
421 'True',
422 'bool'),
422 'bool'),
423
423
424 ('auth_rhodecode_enabled',
424 ('auth_rhodecode_enabled',
425 'True',
425 'True',
426 'bool'),
426 'bool'),
427 ]
427 ]
428 for k, v, t in defaults:
428 for k, v, t in defaults:
429 if (skip_existing and
429 if (skip_existing and
430 SettingsModel().get_setting_by_name(k) is not None):
430 SettingsModel().get_setting_by_name(k) is not None):
431 log.debug('Skipping option %s', k)
431 log.debug('Skipping option %s', k)
432 continue
432 continue
433 setting = RhodeCodeSetting(k, v, t)
433 setting = RhodeCodeSetting(k, v, t)
434 self.sa.add(setting)
434 self.sa.add(setting)
435
435
436 def create_default_options(self, skip_existing=False):
436 def create_default_options(self, skip_existing=False):
437 """Creates default settings"""
437 """Creates default settings"""
438
438
439 for k, v, t in [
439 for k, v, t in [
440 ('default_repo_enable_locking', False, 'bool'),
440 ('default_repo_enable_locking', False, 'bool'),
441 ('default_repo_enable_downloads', False, 'bool'),
441 ('default_repo_enable_downloads', False, 'bool'),
442 ('default_repo_enable_statistics', False, 'bool'),
442 ('default_repo_enable_statistics', False, 'bool'),
443 ('default_repo_private', False, 'bool'),
443 ('default_repo_private', False, 'bool'),
444 ('default_repo_type', 'hg', 'unicode')]:
444 ('default_repo_type', 'hg', 'unicode')]:
445
445
446 if (skip_existing and
446 if (skip_existing and
447 SettingsModel().get_setting_by_name(k) is not None):
447 SettingsModel().get_setting_by_name(k) is not None):
448 log.debug('Skipping option %s', k)
448 log.debug('Skipping option %s', k)
449 continue
449 continue
450 setting = RhodeCodeSetting(k, v, t)
450 setting = RhodeCodeSetting(k, v, t)
451 self.sa.add(setting)
451 self.sa.add(setting)
452
452
453 def fixup_groups(self):
453 def fixup_groups(self):
454 def_usr = User.get_default_user()
454 def_usr = User.get_default_user()
455 for g in RepoGroup.query().all():
455 for g in RepoGroup.query().all():
456 g.group_name = g.get_new_name(g.name)
456 g.group_name = g.get_new_name(g.name)
457 self.sa.add(g)
457 self.sa.add(g)
458 # get default perm
458 # get default perm
459 default = UserRepoGroupToPerm.query()\
459 default = UserRepoGroupToPerm.query()\
460 .filter(UserRepoGroupToPerm.group == g)\
460 .filter(UserRepoGroupToPerm.group == g)\
461 .filter(UserRepoGroupToPerm.user == def_usr)\
461 .filter(UserRepoGroupToPerm.user == def_usr)\
462 .scalar()
462 .scalar()
463
463
464 if default is None:
464 if default is None:
465 log.debug('missing default permission for group %s adding', g)
465 log.debug('missing default permission for group %s adding', g)
466 perm_obj = RepoGroupModel()._create_default_perms(g)
466 perm_obj = RepoGroupModel()._create_default_perms(g)
467 self.sa.add(perm_obj)
467 self.sa.add(perm_obj)
468
468
469 def reset_permissions(self, username):
469 def reset_permissions(self, username):
470 """
470 """
471 Resets permissions to default state, useful when old systems had
471 Resets permissions to default state, useful when old systems had
472 bad permissions, we must clean them up
472 bad permissions, we must clean them up
473
473
474 :param username:
474 :param username:
475 """
475 """
476 default_user = User.get_by_username(username)
476 default_user = User.get_by_username(username)
477 if not default_user:
477 if not default_user:
478 return
478 return
479
479
480 u2p = UserToPerm.query()\
480 u2p = UserToPerm.query()\
481 .filter(UserToPerm.user == default_user).all()
481 .filter(UserToPerm.user == default_user).all()
482 fixed = False
482 fixed = False
483 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
483 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
484 for p in u2p:
484 for p in u2p:
485 Session().delete(p)
485 Session().delete(p)
486 fixed = True
486 fixed = True
487 self.populate_default_permissions()
487 self.populate_default_permissions()
488 return fixed
488 return fixed
489
489
490 def config_prompt(self, test_repo_path='', retries=3):
490 def config_prompt(self, test_repo_path='', retries=3):
491 defaults = self.cli_args
491 defaults = self.cli_args
492 _path = defaults.get('repos_location')
492 _path = defaults.get('repos_location')
493 if retries == 3:
493 if retries == 3:
494 log.info('Setting up repositories config')
494 log.info('Setting up repositories config')
495
495
496 if _path is not None:
496 if _path is not None:
497 path = _path
497 path = _path
498 elif not self.tests and not test_repo_path:
498 elif not self.tests and not test_repo_path:
499 path = input(
499 path = input(
500 'Enter a valid absolute path to store repositories. '
500 'Enter a valid absolute path to store repositories. '
501 'All repositories in that path will be added automatically:'
501 'All repositories in that path will be added automatically:'
502 )
502 )
503 else:
503 else:
504 path = test_repo_path
504 path = test_repo_path
505 path_ok = True
505 path_ok = True
506
506
507 # check proper dir
507 # check proper dir
508 if not os.path.isdir(path):
508 if not os.path.isdir(path):
509 path_ok = False
509 path_ok = False
510 log.error('Given path %s is not a valid directory', path)
510 log.error('Given path %s is not a valid directory', path)
511
511
512 elif not os.path.isabs(path):
512 elif not os.path.isabs(path):
513 path_ok = False
513 path_ok = False
514 log.error('Given path %s is not an absolute path', path)
514 log.error('Given path %s is not an absolute path', path)
515
515
516 # check if path is at least readable.
516 # check if path is at least readable.
517 if not os.access(path, os.R_OK):
517 if not os.access(path, os.R_OK):
518 path_ok = False
518 path_ok = False
519 log.error('Given path %s is not readable', path)
519 log.error('Given path %s is not readable', path)
520
520
521 # check write access, warn user about non writeable paths
521 # check write access, warn user about non writeable paths
522 elif not os.access(path, os.W_OK) and path_ok:
522 elif not os.access(path, os.W_OK) and path_ok:
523 log.warning('No write permission to given path %s', path)
523 log.warning('No write permission to given path %s', path)
524
524
525 q = (f'Given path {path} is not writeable, do you want to '
525 q = (f'Given path {path} is not writeable, do you want to '
526 f'continue with read only mode ? [y/n]')
526 f'continue with read only mode ? [y/n]')
527 if not self.ask_ok(q):
527 if not self.ask_ok(q):
528 log.error('Canceled by user')
528 log.error('Canceled by user')
529 sys.exit(-1)
529 sys.exit(-1)
530
530
531 if retries == 0:
531 if retries == 0:
532 sys.exit('max retries reached')
532 sys.exit('max retries reached')
533 if not path_ok:
533 if not path_ok:
534 retries -= 1
534 retries -= 1
535 return self.config_prompt(test_repo_path, retries)
535 return self.config_prompt(test_repo_path, retries)
536
536
537 real_path = os.path.normpath(os.path.realpath(path))
537 real_path = os.path.normpath(os.path.realpath(path))
538
538
539 if real_path != os.path.normpath(path):
539 if real_path != os.path.normpath(path):
540 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
540 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
541 f'given path as {real_path} ? [y/n]')
541 f'given path as {real_path} ? [y/n]')
542 if not self.ask_ok(q):
542 if not self.ask_ok(q):
543 log.error('Canceled by user')
543 log.error('Canceled by user')
544 sys.exit(-1)
544 sys.exit(-1)
545
545
546 return real_path
546 return real_path
547
547
548 def create_settings(self, path):
548 def create_settings(self, path):
549
549
550 self.create_ui_settings(path)
550 self.create_ui_settings(path)
551
551
552 ui_config = [
552 ui_config = [
553 ('web', 'allow_archive', 'gz zip bz2'),
553 ('web', 'allow_archive', 'gz zip bz2'),
554 ('web', 'allow_push', '*'),
554 ('web', 'allow_push', '*'),
555 ('web', 'baseurl', '/'),
555 ('web', 'baseurl', '/'),
556 ('paths', '/', path),
556 ('paths', '/', path),
557 ('phases', 'publish', 'True')
557 ('phases', 'publish', 'True')
558 ]
558 ]
559 for section, key, value in ui_config:
559 for section, key, value in ui_config:
560 ui_conf = RhodeCodeUi()
560 ui_conf = RhodeCodeUi()
561 setattr(ui_conf, 'ui_section', section)
561 setattr(ui_conf, 'ui_section', section)
562 setattr(ui_conf, 'ui_key', key)
562 setattr(ui_conf, 'ui_key', key)
563 setattr(ui_conf, 'ui_value', value)
563 setattr(ui_conf, 'ui_value', value)
564 self.sa.add(ui_conf)
564 self.sa.add(ui_conf)
565
565
566 # rhodecode app settings
566 # rhodecode app settings
567 settings = [
567 settings = [
568 ('realm', 'RhodeCode', 'unicode'),
568 ('realm', 'RhodeCode', 'unicode'),
569 ('title', '', 'unicode'),
569 ('title', '', 'unicode'),
570 ('pre_code', '', 'unicode'),
570 ('pre_code', '', 'unicode'),
571 ('post_code', '', 'unicode'),
571 ('post_code', '', 'unicode'),
572
572
573 # Visual
573 # Visual
574 ('show_public_icon', True, 'bool'),
574 ('show_public_icon', True, 'bool'),
575 ('show_private_icon', True, 'bool'),
575 ('show_private_icon', True, 'bool'),
576 ('stylify_metatags', True, 'bool'),
576 ('stylify_metatags', True, 'bool'),
577 ('dashboard_items', 100, 'int'),
577 ('dashboard_items', 100, 'int'),
578 ('admin_grid_items', 25, 'int'),
578 ('admin_grid_items', 25, 'int'),
579
579
580 ('markup_renderer', 'markdown', 'unicode'),
580 ('markup_renderer', 'markdown', 'unicode'),
581
581
582 ('repository_fields', True, 'bool'),
582 ('repository_fields', True, 'bool'),
583 ('show_version', True, 'bool'),
583 ('show_version', True, 'bool'),
584 ('show_revision_number', True, 'bool'),
584 ('show_revision_number', True, 'bool'),
585 ('show_sha_length', 12, 'int'),
585 ('show_sha_length', 12, 'int'),
586
586
587 ('use_gravatar', False, 'bool'),
587 ('use_gravatar', False, 'bool'),
588 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
588 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
589
589
590 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
590 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
591 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
591 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
592 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
592 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
593 ('support_url', '', 'unicode'),
593 ('support_url', '', 'unicode'),
594 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
594 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
595
595
596 # VCS Settings
596 # VCS Settings
597 ('pr_merge_enabled', True, 'bool'),
597 ('pr_merge_enabled', True, 'bool'),
598 ('use_outdated_comments', True, 'bool'),
598 ('use_outdated_comments', True, 'bool'),
599 ('diff_cache', True, 'bool'),
599 ('diff_cache', True, 'bool'),
600 ]
600 ]
601
601
602 for key, val, type_ in settings:
602 for key, val, type_ in settings:
603 sett = RhodeCodeSetting(key, val, type_)
603 sett = RhodeCodeSetting(key, val, type_)
604 self.sa.add(sett)
604 self.sa.add(sett)
605
605
606 self.create_auth_plugin_options()
606 self.create_auth_plugin_options()
607 self.create_default_options()
607 self.create_default_options()
608
608
609 log.info('created ui config')
609 log.info('created ui config')
610
610
611 def create_user(self, username, password, email='', admin=False,
611 def create_user(self, username, password, email='', admin=False,
612 strict_creation_check=True, api_key=None):
612 strict_creation_check=True, api_key=None):
613 log.info('creating user `%s`', username)
613 log.info('creating user `%s`', username)
614 user = UserModel().create_or_update(
614 user = UserModel().create_or_update(
615 username, password, email, firstname='RhodeCode', lastname='Admin',
615 username, password, email, firstname='RhodeCode', lastname='Admin',
616 active=True, admin=admin, extern_type="rhodecode",
616 active=True, admin=admin, extern_type="rhodecode",
617 strict_creation_check=strict_creation_check)
617 strict_creation_check=strict_creation_check)
618
618
619 if api_key:
619 if api_key:
620 log.info('setting a new default auth token for user `%s`', username)
620 log.info('setting a new default auth token for user `%s`', username)
621 UserModel().add_auth_token(
621 UserModel().add_auth_token(
622 user=user, lifetime_minutes=-1,
622 user=user, lifetime_minutes=-1,
623 role=UserModel.auth_token_role.ROLE_ALL,
623 role=UserModel.auth_token_role.ROLE_ALL,
624 description='BUILTIN TOKEN')
624 description='BUILTIN TOKEN')
625
625
626 def create_default_user(self):
626 def create_default_user(self):
627 log.info('creating default user')
627 log.info('creating default user')
628 # create default user for handling default permissions.
628 # create default user for handling default permissions.
629 user = UserModel().create_or_update(username=User.DEFAULT_USER,
629 user = UserModel().create_or_update(username=User.DEFAULT_USER,
630 password=str(uuid.uuid1())[:20],
630 password=str(uuid.uuid1())[:20],
631 email=User.DEFAULT_USER_EMAIL,
631 email=User.DEFAULT_USER_EMAIL,
632 firstname='Anonymous',
632 firstname='Anonymous',
633 lastname='User',
633 lastname='User',
634 strict_creation_check=False)
634 strict_creation_check=False)
635 # based on configuration options activate/de-activate this user which
635 # based on configuration options activate/de-activate this user which
636 # controls anonymous access
636 # controls anonymous access
637 if self.cli_args.get('public_access') is False:
637 if self.cli_args.get('public_access') is False:
638 log.info('Public access disabled')
638 log.info('Public access disabled')
639 user.active = False
639 user.active = False
640 Session().add(user)
640 Session().add(user)
641 Session().commit()
641 Session().commit()
642
642
643 def create_permissions(self):
643 def create_permissions(self):
644 """
644 """
645 Creates all permissions defined in the system
645 Creates all permissions defined in the system
646 """
646 """
647 # module.(access|create|change|delete)_[name]
647 # module.(access|create|change|delete)_[name]
648 # module.(none|read|write|admin)
648 # module.(none|read|write|admin)
649 log.info('creating permissions')
649 log.info('creating permissions')
650 PermissionModel(self.sa).create_permissions()
650 PermissionModel(self.sa).create_permissions()
651
651
652 def populate_default_permissions(self):
652 def populate_default_permissions(self):
653 """
653 """
654 Populate default permissions. It will create only the default
654 Populate default permissions. It will create only the default
655 permissions that are missing, and not alter already defined ones
655 permissions that are missing, and not alter already defined ones
656 """
656 """
657 log.info('creating default user permissions')
657 log.info('creating default user permissions')
658 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
658 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,253 +1,224 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 os
19 import os
20 import time
20 import time
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import tempfile
23 import tempfile
24 from os.path import join as jn
24 from os.path import join as jn
25 import urllib.parse
25 import urllib.parse
26
26
27 import pytest
27 import pytest
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode.model.db import User
30 from rhodecode.model.db import User
31 from rhodecode.lib import auth
31 from rhodecode.lib import auth
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.helpers import flash
33 from rhodecode.lib.helpers import flash
34 from rhodecode.lib.str_utils import safe_str
34 from rhodecode.lib.str_utils import safe_str
35 from rhodecode.lib.hash_utils import sha1_safe
35 from rhodecode.lib.hash_utils import sha1_safe
36 from rhodecode.bootstrap import \
37 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, TEST_USER_REGULAR_EMAIL, \
39 TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL,\
40 HG_REPO, GIT_REPO, SVN_REPO,\
41 NEW_HG_REPO, NEW_GIT_REPO,\
42 HG_FORK, GIT_FORK
36
43
37 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
38
45
39 __all__ = [
40 'get_new_dir', 'TestController', 'console_printer',
41 'clear_cache_regions',
42 'assert_session_flash', 'login_user', 'no_newline_id_generator',
43 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
44 'NEW_HG_REPO', 'NEW_GIT_REPO',
45 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
46 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
47 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
48 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
49 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
50 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
51 ]
52
53
46
54 # SOME GLOBALS FOR TESTS
47 # SOME GLOBALS FOR TESTS
55 TEST_DIR = tempfile.gettempdir()
48 TEST_DIR = tempfile.gettempdir()
56
49
57 TEST_USER_ADMIN_LOGIN = 'test_admin'
58 TEST_USER_ADMIN_PASS = 'test12'
59 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
60
61 TEST_USER_REGULAR_LOGIN = 'test_regular'
62 TEST_USER_REGULAR_PASS = 'test12'
63 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
64
65 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
66 TEST_USER_REGULAR2_PASS = 'test12'
67 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
68
69 HG_REPO = 'vcs_test_hg'
70 GIT_REPO = 'vcs_test_git'
71 SVN_REPO = 'vcs_test_svn'
72
73 NEW_HG_REPO = 'vcs_test_hg_new'
74 NEW_GIT_REPO = 'vcs_test_git_new'
75
76 HG_FORK = 'vcs_test_hg_fork'
77 GIT_FORK = 'vcs_test_git_fork'
78
79 ## VCS
50 ## VCS
80 SCM_TESTS = ['hg', 'git']
51 SCM_TESTS = ['hg', 'git']
81 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
52 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
82
53
83 TESTS_TMP_PATH = tempfile.mkdtemp(prefix='rc_test_', dir=TEST_DIR)
54 TESTS_TMP_PATH = tempfile.mkdtemp(prefix='rc_test_', dir=TEST_DIR)
84
55
85 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
56 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
86 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, f'vcsgitclone{uniq_suffix}')
57 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, f'vcsgitclone{uniq_suffix}')
87 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, f'vcsgitpull{uniq_suffix}')
58 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, f'vcsgitpull{uniq_suffix}')
88
59
89 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
60 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
90 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, f'vcshgclone{uniq_suffix}')
61 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, f'vcshgclone{uniq_suffix}')
91 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, f'vcshgpull{uniq_suffix}')
62 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, f'vcshgpull{uniq_suffix}')
92
63
93 TEST_REPO_PREFIX = 'vcs-test'
64 TEST_REPO_PREFIX = 'vcs-test'
94
65
95
66
96 def clear_cache_regions(regions=None):
67 def clear_cache_regions(regions=None):
97 # dogpile
68 # dogpile
98 from rhodecode.lib.rc_cache import region_meta
69 from rhodecode.lib.rc_cache import region_meta
99 for region_name, region in region_meta.dogpile_cache_regions.items():
70 for region_name, region in region_meta.dogpile_cache_regions.items():
100 if not regions or region_name in regions:
71 if not regions or region_name in regions:
101 region.invalidate()
72 region.invalidate()
102
73
103
74
104 def get_new_dir(title):
75 def get_new_dir(title):
105 """
76 """
106 Returns always new directory path.
77 Returns always new directory path.
107 """
78 """
108 from rhodecode.tests.vcs.utils import get_normalized_path
79 from rhodecode.tests.vcs.utils import get_normalized_path
109 name_parts = [TEST_REPO_PREFIX]
80 name_parts = [TEST_REPO_PREFIX]
110 if title:
81 if title:
111 name_parts.append(title)
82 name_parts.append(title)
112 hex_str = sha1_safe(f'{os.getpid()} {time.time()}')
83 hex_str = sha1_safe(f'{os.getpid()} {time.time()}')
113 name_parts.append(hex_str)
84 name_parts.append(hex_str)
114 name = '-'.join(name_parts)
85 name = '-'.join(name_parts)
115 path = jn(TEST_DIR, name)
86 path = jn(TEST_DIR, name)
116 return get_normalized_path(path)
87 return get_normalized_path(path)
117
88
118
89
119 def repo_id_generator(name):
90 def repo_id_generator(name):
120 numeric_hash = 0
91 numeric_hash = 0
121 for char in name:
92 for char in name:
122 numeric_hash += (ord(char))
93 numeric_hash += (ord(char))
123 return numeric_hash
94 return numeric_hash
124
95
125
96
126 @pytest.mark.usefixtures('app', 'index_location')
97 @pytest.mark.usefixtures('app', 'index_location')
127 class TestController(object):
98 class TestController(object):
128
99
129 maxDiff = None
100 maxDiff = None
130
101
131 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
102 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
132 password=TEST_USER_ADMIN_PASS):
103 password=TEST_USER_ADMIN_PASS):
133 self._logged_username = username
104 self._logged_username = username
134 self._session = login_user_session(self.app, username, password)
105 self._session = login_user_session(self.app, username, password)
135 self.csrf_token = auth.get_csrf_token(self._session)
106 self.csrf_token = auth.get_csrf_token(self._session)
136
107
137 return self._session['rhodecode_user']
108 return self._session['rhodecode_user']
138
109
139 def logout_user(self):
110 def logout_user(self):
140 logout_user_session(self.app, auth.get_csrf_token(self._session))
111 logout_user_session(self.app, auth.get_csrf_token(self._session))
141 self.csrf_token = None
112 self.csrf_token = None
142 self._logged_username = None
113 self._logged_username = None
143 self._session = None
114 self._session = None
144
115
145 def _get_logged_user(self):
116 def _get_logged_user(self):
146 return User.get_by_username(self._logged_username)
117 return User.get_by_username(self._logged_username)
147
118
148
119
149 def login_user_session(
120 def login_user_session(
150 app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
121 app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
151
122
152 response = app.post(
123 response = app.post(
153 h.route_path('login'),
124 h.route_path('login'),
154 {'username': username, 'password': password})
125 {'username': username, 'password': password})
155 if 'invalid user name' in response.text:
126 if 'invalid user name' in response.text:
156 pytest.fail(f'could not login using {username} {password}')
127 pytest.fail(f'could not login using {username} {password}')
157
128
158 assert response.status == '302 Found'
129 assert response.status == '302 Found'
159 response = response.follow()
130 response = response.follow()
160 assert response.status == '200 OK'
131 assert response.status == '200 OK'
161
132
162 session = response.get_session_from_response()
133 session = response.get_session_from_response()
163 assert 'rhodecode_user' in session
134 assert 'rhodecode_user' in session
164 rc_user = session['rhodecode_user']
135 rc_user = session['rhodecode_user']
165 assert rc_user.get('username') == username
136 assert rc_user.get('username') == username
166 assert rc_user.get('is_authenticated')
137 assert rc_user.get('is_authenticated')
167
138
168 return session
139 return session
169
140
170
141
171 def logout_user_session(app, csrf_token):
142 def logout_user_session(app, csrf_token):
172 app.post(h.route_path('logout'), {'csrf_token': csrf_token}, status=302)
143 app.post(h.route_path('logout'), {'csrf_token': csrf_token}, status=302)
173
144
174
145
175 def login_user(app, username=TEST_USER_ADMIN_LOGIN,
146 def login_user(app, username=TEST_USER_ADMIN_LOGIN,
176 password=TEST_USER_ADMIN_PASS):
147 password=TEST_USER_ADMIN_PASS):
177 return login_user_session(app, username, password)['rhodecode_user']
148 return login_user_session(app, username, password)['rhodecode_user']
178
149
179
150
180 def assert_session_flash(response, msg=None, category=None, no_=None):
151 def assert_session_flash(response, msg=None, category=None, no_=None):
181 """
152 """
182 Assert on a flash message in the current session.
153 Assert on a flash message in the current session.
183
154
184 :param response: Response from give calll, it will contain flash
155 :param response: Response from give calll, it will contain flash
185 messages or bound session with them.
156 messages or bound session with them.
186 :param msg: The expected message. Will be evaluated if a
157 :param msg: The expected message. Will be evaluated if a
187 :class:`LazyString` is passed in.
158 :class:`LazyString` is passed in.
188 :param category: Optional. If passed, the message category will be
159 :param category: Optional. If passed, the message category will be
189 checked as well.
160 checked as well.
190 :param no_: Optional. If passed, the message will be checked to NOT
161 :param no_: Optional. If passed, the message will be checked to NOT
191 be in the flash session
162 be in the flash session
192 """
163 """
193 if msg is None and no_ is None:
164 if msg is None and no_ is None:
194 raise ValueError("Parameter msg or no_ is required.")
165 raise ValueError("Parameter msg or no_ is required.")
195
166
196 if msg and no_:
167 if msg and no_:
197 raise ValueError("Please specify either msg or no_, but not both")
168 raise ValueError("Please specify either msg or no_, but not both")
198
169
199 session = response.get_session_from_response()
170 session = response.get_session_from_response()
200 messages = flash.pop_messages(session=session)
171 messages = flash.pop_messages(session=session)
201 msg = _eval_if_lazy(msg)
172 msg = _eval_if_lazy(msg)
202
173
203 if no_:
174 if no_:
204 error_msg = f'unable to detect no_ message `{no_}` in empty flash list'
175 error_msg = f'unable to detect no_ message `{no_}` in empty flash list'
205 else:
176 else:
206 error_msg = f'unable to find message `{msg}` in empty flash list'
177 error_msg = f'unable to find message `{msg}` in empty flash list'
207 assert messages, error_msg
178 assert messages, error_msg
208 message = messages[0]
179 message = messages[0]
209
180
210 message_text = _eval_if_lazy(message.message) or ''
181 message_text = _eval_if_lazy(message.message) or ''
211
182
212 if no_:
183 if no_:
213 if no_ in message_text:
184 if no_ in message_text:
214 msg = f'msg `{no_}` found in session flash.'
185 msg = f'msg `{no_}` found in session flash.'
215 pytest.fail(safe_str(msg))
186 pytest.fail(safe_str(msg))
216 else:
187 else:
217
188
218 if msg not in message_text:
189 if msg not in message_text:
219 fail_msg = f'msg `{msg}` not found in ' \
190 fail_msg = f'msg `{msg}` not found in ' \
220 f'session flash: got `{message_text}` (type:{type(message_text)}) instead'
191 f'session flash: got `{message_text}` (type:{type(message_text)}) instead'
221
192
222 pytest.fail(safe_str(fail_msg))
193 pytest.fail(safe_str(fail_msg))
223
194
224 if category:
195 if category:
225 assert category == message.category
196 assert category == message.category
226
197
227
198
228 def _eval_if_lazy(value):
199 def _eval_if_lazy(value):
229 return value.eval() if hasattr(value, 'eval') else value
200 return value.eval() if hasattr(value, 'eval') else value
230
201
231
202
232 def no_newline_id_generator(test_name):
203 def no_newline_id_generator(test_name):
233 """
204 """
234 Generates a test name without spaces or newlines characters. Used for
205 Generates a test name without spaces or newlines characters. Used for
235 nicer output of progress of test
206 nicer output of progress of test
236 """
207 """
237
208
238 test_name = safe_str(test_name)\
209 test_name = safe_str(test_name)\
239 .replace('\n', '_N') \
210 .replace('\n', '_N') \
240 .replace('\r', '_N') \
211 .replace('\r', '_N') \
241 .replace('\t', '_T') \
212 .replace('\t', '_T') \
242 .replace(' ', '_S')
213 .replace(' ', '_S')
243
214
244 return test_name or 'test-with-empty-name'
215 return test_name or 'test-with-empty-name'
245
216
246 def console_printer(*msg):
217 def console_printer(*msg):
247 print_func = print
218 print_func = print
248 try:
219 try:
249 from rich import print as print_func
220 from rich import print as print_func
250 except ImportError:
221 except ImportError:
251 pass
222 pass
252
223
253 print_func(*msg)
224 print_func(*msg)
General Comments 0
You need to be logged in to leave comments. Login now