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