##// END OF EJS Templates
db: use unicode for some of the defaults to reduce sqlalchemy warnings.
marcink -
r1967:df3f2899 default
parent child Browse files
Show More
@@ -1,52 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.auth_token import AuthTokenModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27
28 28
29 29 @pytest.fixture(scope="class")
30 30 def testuser_api(request, pylonsapp):
31 31 cls = request.cls
32 32
33 33 # ADMIN USER
34 34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
35 35 cls.apikey = cls.usr.api_key
36 36
37 37 # REGULAR USER
38 38 cls.test_user = UserModel().create_or_update(
39 39 username='test-api',
40 40 password='test',
41 41 email='test@api.rhodecode.org',
42 42 firstname='first',
43 43 lastname='last'
44 44 )
45 45 # create TOKEN for user, if he doesn't have one
46 46 if not cls.test_user.api_key:
47 47 AuthTokenModel().create(
48 user=cls.test_user, description='TEST_USER_TOKEN')
48 user=cls.test_user, description=u'TEST_USER_TOKEN')
49 49
50 50 Session().commit()
51 51 cls.TEST_USER_LOGIN = cls.test_user.username
52 52 cls.apikey_regular = cls.test_user.api_key
@@ -1,620 +1,620 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args={}):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database under version '
162 162 'control with version %s' % curr_version)
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 310 from rhodecode.lib.vcs.backends.git import lfs_store
311 311
312 312 # Build HOOKS
313 313 hooks = [
314 314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315 315
316 316 # HG
317 317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
323 323
324 324 ]
325 325
326 326 for key, value in hooks:
327 327 hook_obj = settings_model.get_ui_by_key(key)
328 328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
329 329 hooks2.ui_section = 'hooks'
330 330 hooks2.ui_key = key
331 331 hooks2.ui_value = value
332 332 self.sa.add(hooks2)
333 333
334 334 # enable largefiles
335 335 largefiles = RhodeCodeUi()
336 336 largefiles.ui_section = 'extensions'
337 337 largefiles.ui_key = 'largefiles'
338 338 largefiles.ui_value = ''
339 339 self.sa.add(largefiles)
340 340
341 341 # set default largefiles cache dir, defaults to
342 342 # /repo_store_location/.cache/largefiles
343 343 largefiles = RhodeCodeUi()
344 344 largefiles.ui_section = 'largefiles'
345 345 largefiles.ui_key = 'usercache'
346 346 largefiles.ui_value = largefiles_store(repo_store_path)
347 347
348 348 self.sa.add(largefiles)
349 349
350 350 # set default lfs cache dir, defaults to
351 351 # /repo_store_location/.cache/lfs_store
352 352 lfsstore = RhodeCodeUi()
353 353 lfsstore.ui_section = 'vcs_git_lfs'
354 354 lfsstore.ui_key = 'store_location'
355 355 lfsstore.ui_value = lfs_store(repo_store_path)
356 356
357 357 self.sa.add(lfsstore)
358 358
359 359 # enable hgsubversion disabled by default
360 360 hgsubversion = RhodeCodeUi()
361 361 hgsubversion.ui_section = 'extensions'
362 362 hgsubversion.ui_key = 'hgsubversion'
363 363 hgsubversion.ui_value = ''
364 364 hgsubversion.ui_active = False
365 365 self.sa.add(hgsubversion)
366 366
367 367 # enable hgevolve disabled by default
368 368 hgevolve = RhodeCodeUi()
369 369 hgevolve.ui_section = 'extensions'
370 370 hgevolve.ui_key = 'evolve'
371 371 hgevolve.ui_value = ''
372 372 hgevolve.ui_active = False
373 373 self.sa.add(hgevolve)
374 374
375 375 # enable hggit disabled by default
376 376 hggit = RhodeCodeUi()
377 377 hggit.ui_section = 'extensions'
378 378 hggit.ui_key = 'hggit'
379 379 hggit.ui_value = ''
380 380 hggit.ui_active = False
381 381 self.sa.add(hggit)
382 382
383 383 # set svn branch defaults
384 384 branches = ["/branches/*", "/trunk"]
385 385 tags = ["/tags/*"]
386 386
387 387 for branch in branches:
388 388 settings_model.create_ui_section_value(
389 389 RhodeCodeUi.SVN_BRANCH_ID, branch)
390 390
391 391 for tag in tags:
392 392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
393 393
394 394 def create_auth_plugin_options(self, skip_existing=False):
395 395 """
396 396 Create default auth plugin settings, and make it active
397 397
398 398 :param skip_existing:
399 399 """
400 400
401 401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
402 402 ('auth_rhodecode_enabled', 'True', 'bool')]:
403 403 if (skip_existing and
404 404 SettingsModel().get_setting_by_name(k) is not None):
405 405 log.debug('Skipping option %s' % k)
406 406 continue
407 407 setting = RhodeCodeSetting(k, v, t)
408 408 self.sa.add(setting)
409 409
410 410 def create_default_options(self, skip_existing=False):
411 411 """Creates default settings"""
412 412
413 413 for k, v, t in [
414 414 ('default_repo_enable_locking', False, 'bool'),
415 415 ('default_repo_enable_downloads', False, 'bool'),
416 416 ('default_repo_enable_statistics', False, 'bool'),
417 417 ('default_repo_private', False, 'bool'),
418 418 ('default_repo_type', 'hg', 'unicode')]:
419 419
420 420 if (skip_existing and
421 421 SettingsModel().get_setting_by_name(k) is not None):
422 422 log.debug('Skipping option %s' % k)
423 423 continue
424 424 setting = RhodeCodeSetting(k, v, t)
425 425 self.sa.add(setting)
426 426
427 427 def fixup_groups(self):
428 428 def_usr = User.get_default_user()
429 429 for g in RepoGroup.query().all():
430 430 g.group_name = g.get_new_name(g.name)
431 431 self.sa.add(g)
432 432 # get default perm
433 433 default = UserRepoGroupToPerm.query()\
434 434 .filter(UserRepoGroupToPerm.group == g)\
435 435 .filter(UserRepoGroupToPerm.user == def_usr)\
436 436 .scalar()
437 437
438 438 if default is None:
439 439 log.debug('missing default permission for group %s adding' % g)
440 440 perm_obj = RepoGroupModel()._create_default_perms(g)
441 441 self.sa.add(perm_obj)
442 442
443 443 def reset_permissions(self, username):
444 444 """
445 445 Resets permissions to default state, useful when old systems had
446 446 bad permissions, we must clean them up
447 447
448 448 :param username:
449 449 """
450 450 default_user = User.get_by_username(username)
451 451 if not default_user:
452 452 return
453 453
454 454 u2p = UserToPerm.query()\
455 455 .filter(UserToPerm.user == default_user).all()
456 456 fixed = False
457 457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
458 458 for p in u2p:
459 459 Session().delete(p)
460 460 fixed = True
461 461 self.populate_default_permissions()
462 462 return fixed
463 463
464 464 def update_repo_info(self):
465 465 RepoModel.update_repoinfo()
466 466
467 467 def config_prompt(self, test_repo_path='', retries=3):
468 468 defaults = self.cli_args
469 469 _path = defaults.get('repos_location')
470 470 if retries == 3:
471 471 log.info('Setting up repositories config')
472 472
473 473 if _path is not None:
474 474 path = _path
475 475 elif not self.tests and not test_repo_path:
476 476 path = raw_input(
477 477 'Enter a valid absolute path to store repositories. '
478 478 'All repositories in that path will be added automatically:'
479 479 )
480 480 else:
481 481 path = test_repo_path
482 482 path_ok = True
483 483
484 484 # check proper dir
485 485 if not os.path.isdir(path):
486 486 path_ok = False
487 487 log.error('Given path %s is not a valid directory' % (path,))
488 488
489 489 elif not os.path.isabs(path):
490 490 path_ok = False
491 491 log.error('Given path %s is not an absolute path' % (path,))
492 492
493 493 # check if path is at least readable.
494 494 if not os.access(path, os.R_OK):
495 495 path_ok = False
496 496 log.error('Given path %s is not readable' % (path,))
497 497
498 498 # check write access, warn user about non writeable paths
499 499 elif not os.access(path, os.W_OK) and path_ok:
500 500 log.warning('No write permission to given path %s' % (path,))
501 501
502 502 q = ('Given path %s is not writeable, do you want to '
503 503 'continue with read only mode ? [y/n]' % (path,))
504 504 if not self.ask_ok(q):
505 505 log.error('Canceled by user')
506 506 sys.exit(-1)
507 507
508 508 if retries == 0:
509 509 sys.exit('max retries reached')
510 510 if not path_ok:
511 511 retries -= 1
512 512 return self.config_prompt(test_repo_path, retries)
513 513
514 514 real_path = os.path.normpath(os.path.realpath(path))
515 515
516 516 if real_path != os.path.normpath(path):
517 517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
518 518 'given path as %s ? [y/n]') % (real_path,)
519 519 if not self.ask_ok(q):
520 520 log.error('Canceled by user')
521 521 sys.exit(-1)
522 522
523 523 return real_path
524 524
525 525 def create_settings(self, path):
526 526
527 527 self.create_ui_settings(path)
528 528
529 529 ui_config = [
530 530 ('web', 'push_ssl', 'False'),
531 531 ('web', 'allow_archive', 'gz zip bz2'),
532 532 ('web', 'allow_push', '*'),
533 533 ('web', 'baseurl', '/'),
534 534 ('paths', '/', path),
535 535 ('phases', 'publish', 'True')
536 536 ]
537 537 for section, key, value in ui_config:
538 538 ui_conf = RhodeCodeUi()
539 539 setattr(ui_conf, 'ui_section', section)
540 540 setattr(ui_conf, 'ui_key', key)
541 541 setattr(ui_conf, 'ui_value', value)
542 542 self.sa.add(ui_conf)
543 543
544 544 # rhodecode app settings
545 545 settings = [
546 546 ('realm', 'RhodeCode', 'unicode'),
547 547 ('title', '', 'unicode'),
548 548 ('pre_code', '', 'unicode'),
549 549 ('post_code', '', 'unicode'),
550 550 ('show_public_icon', True, 'bool'),
551 551 ('show_private_icon', True, 'bool'),
552 552 ('stylify_metatags', False, 'bool'),
553 553 ('dashboard_items', 100, 'int'),
554 554 ('admin_grid_items', 25, 'int'),
555 555 ('show_version', True, 'bool'),
556 556 ('use_gravatar', False, 'bool'),
557 557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
558 558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
559 559 ('support_url', '', 'unicode'),
560 560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
561 561 ('show_revision_number', True, 'bool'),
562 562 ('show_sha_length', 12, 'int'),
563 563 ]
564 564
565 565 for key, val, type_ in settings:
566 566 sett = RhodeCodeSetting(key, val, type_)
567 567 self.sa.add(sett)
568 568
569 569 self.create_auth_plugin_options()
570 570 self.create_default_options()
571 571
572 572 log.info('created ui config')
573 573
574 574 def create_user(self, username, password, email='', admin=False,
575 575 strict_creation_check=True, api_key=None):
576 576 log.info('creating user %s' % username)
577 577 user = UserModel().create_or_update(
578 username, password, email, firstname='RhodeCode', lastname='Admin',
578 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
579 579 active=True, admin=admin, extern_type="rhodecode",
580 580 strict_creation_check=strict_creation_check)
581 581
582 582 if api_key:
583 583 log.info('setting a provided api key for the user %s', username)
584 584 from rhodecode.model.auth_token import AuthTokenModel
585 585 AuthTokenModel().create(
586 user=user, description='BUILTIN TOKEN')
586 user=user, description=u'BUILTIN TOKEN')
587 587
588 588 def create_default_user(self):
589 589 log.info('creating default user')
590 590 # create default user for handling default permissions.
591 591 user = UserModel().create_or_update(username=User.DEFAULT_USER,
592 592 password=str(uuid.uuid1())[:20],
593 593 email=User.DEFAULT_USER_EMAIL,
594 firstname='Anonymous',
595 lastname='User',
594 firstname=u'Anonymous',
595 lastname=u'User',
596 596 strict_creation_check=False)
597 597 # based on configuration options activate/deactive this user which
598 598 # controlls anonymous access
599 599 if self.cli_args.get('public_access') is False:
600 600 log.info('Public access disabled')
601 601 user.active = False
602 602 Session().add(user)
603 603 Session().commit()
604 604
605 605 def create_permissions(self):
606 606 """
607 607 Creates all permissions defined in the system
608 608 """
609 609 # module.(access|create|change|delete)_[name]
610 610 # module.(none|read|write|admin)
611 611 log.info('creating permissions')
612 612 PermissionModel(self.sa).create_permissions()
613 613
614 614 def populate_default_permissions(self):
615 615 """
616 616 Populate default permissions. It will create only the default
617 617 permissions that are missing, and not alter already defined ones
618 618 """
619 619 log.info('creating default user permissions')
620 620 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,910 +1,910 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27
28 28 import datetime
29 29 from pylons.i18n.translation import _
30 30
31 31 import ipaddress
32 32 from sqlalchemy.exc import DatabaseError
33 33
34 34 from rhodecode import events
35 35 from rhodecode.lib.user_log_filter import user_log_filter
36 36 from rhodecode.lib.utils2 import (
37 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 38 AttributeDict, str2bool)
39 39 from rhodecode.lib.exceptions import (
40 40 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
41 41 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
42 42 from rhodecode.lib.caching_query import FromCache
43 43 from rhodecode.model import BaseModel
44 44 from rhodecode.model.auth_token import AuthTokenModel
45 45 from rhodecode.model.db import (
46 46 _hash_key, true, false, or_, joinedload, User, UserToPerm,
47 47 UserEmailMap, UserIpMap, UserLog)
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo_group import RepoGroupModel
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class UserModel(BaseModel):
56 56 cls = User
57 57
58 58 def get(self, user_id, cache=False):
59 59 user = self.sa.query(User)
60 60 if cache:
61 61 user = user.options(
62 62 FromCache("sql_cache_short", "get_user_%s" % user_id))
63 63 return user.get(user_id)
64 64
65 65 def get_user(self, user):
66 66 return self._get_user(user)
67 67
68 68 def _serialize_user(self, user):
69 69 import rhodecode.lib.helpers as h
70 70
71 71 return {
72 72 'id': user.user_id,
73 73 'first_name': user.first_name,
74 74 'last_name': user.last_name,
75 75 'username': user.username,
76 76 'email': user.email,
77 77 'icon_link': h.gravatar_url(user.email, 30),
78 78 'value_display': h.escape(h.person(user)),
79 79 'value': user.username,
80 80 'value_type': 'user',
81 81 'active': user.active,
82 82 }
83 83
84 84 def get_users(self, name_contains=None, limit=20, only_active=True):
85 85
86 86 query = self.sa.query(User)
87 87 if only_active:
88 88 query = query.filter(User.active == true())
89 89
90 90 if name_contains:
91 91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 92 query = query.filter(
93 93 or_(
94 94 User.name.ilike(ilike_expression),
95 95 User.lastname.ilike(ilike_expression),
96 96 User.username.ilike(ilike_expression)
97 97 )
98 98 )
99 99 query = query.limit(limit)
100 100 users = query.all()
101 101
102 102 _users = [
103 103 self._serialize_user(user) for user in users
104 104 ]
105 105 return _users
106 106
107 107 def get_by_username(self, username, cache=False, case_insensitive=False):
108 108
109 109 if case_insensitive:
110 110 user = self.sa.query(User).filter(User.username.ilike(username))
111 111 else:
112 112 user = self.sa.query(User)\
113 113 .filter(User.username == username)
114 114 if cache:
115 115 name_key = _hash_key(username)
116 116 user = user.options(
117 117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 118 return user.scalar()
119 119
120 120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 121 return User.get_by_email(email, case_insensitive, cache)
122 122
123 123 def get_by_auth_token(self, auth_token, cache=False):
124 124 return User.get_by_auth_token(auth_token, cache)
125 125
126 126 def get_active_user_count(self, cache=False):
127 127 return User.query().filter(
128 128 User.active == True).filter(
129 129 User.username != User.DEFAULT_USER).count()
130 130
131 131 def create(self, form_data, cur_user=None):
132 132 if not cur_user:
133 133 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
134 134
135 135 user_data = {
136 136 'username': form_data['username'],
137 137 'password': form_data['password'],
138 138 'email': form_data['email'],
139 139 'firstname': form_data['firstname'],
140 140 'lastname': form_data['lastname'],
141 141 'active': form_data['active'],
142 142 'extern_type': form_data['extern_type'],
143 143 'extern_name': form_data['extern_name'],
144 144 'admin': False,
145 145 'cur_user': cur_user
146 146 }
147 147
148 148 if 'create_repo_group' in form_data:
149 149 user_data['create_repo_group'] = str2bool(
150 150 form_data.get('create_repo_group'))
151 151
152 152 try:
153 153 if form_data.get('password_change'):
154 154 user_data['force_password_change'] = True
155 155 return UserModel().create_or_update(**user_data)
156 156 except Exception:
157 157 log.error(traceback.format_exc())
158 158 raise
159 159
160 160 def update_user(self, user, skip_attrs=None, **kwargs):
161 161 from rhodecode.lib.auth import get_crypt_password
162 162
163 163 user = self._get_user(user)
164 164 if user.username == User.DEFAULT_USER:
165 165 raise DefaultUserException(
166 166 _("You can't Edit this user since it's"
167 167 " crucial for entire application"))
168 168
169 169 # first store only defaults
170 170 user_attrs = {
171 171 'updating_user_id': user.user_id,
172 172 'username': user.username,
173 173 'password': user.password,
174 174 'email': user.email,
175 175 'firstname': user.name,
176 176 'lastname': user.lastname,
177 177 'active': user.active,
178 178 'admin': user.admin,
179 179 'extern_name': user.extern_name,
180 180 'extern_type': user.extern_type,
181 181 'language': user.user_data.get('language')
182 182 }
183 183
184 184 # in case there's new_password, that comes from form, use it to
185 185 # store password
186 186 if kwargs.get('new_password'):
187 187 kwargs['password'] = kwargs['new_password']
188 188
189 189 # cleanups, my_account password change form
190 190 kwargs.pop('current_password', None)
191 191 kwargs.pop('new_password', None)
192 192
193 193 # cleanups, user edit password change form
194 194 kwargs.pop('password_confirmation', None)
195 195 kwargs.pop('password_change', None)
196 196
197 197 # create repo group on user creation
198 198 kwargs.pop('create_repo_group', None)
199 199
200 200 # legacy forms send name, which is the firstname
201 201 firstname = kwargs.pop('name', None)
202 202 if firstname:
203 203 kwargs['firstname'] = firstname
204 204
205 205 for k, v in kwargs.items():
206 206 # skip if we don't want to update this
207 207 if skip_attrs and k in skip_attrs:
208 208 continue
209 209
210 210 user_attrs[k] = v
211 211
212 212 try:
213 213 return self.create_or_update(**user_attrs)
214 214 except Exception:
215 215 log.error(traceback.format_exc())
216 216 raise
217 217
218 218 def create_or_update(
219 219 self, username, password, email, firstname='', lastname='',
220 220 active=True, admin=False, extern_type=None, extern_name=None,
221 221 cur_user=None, plugin=None, force_password_change=False,
222 222 allow_to_create_user=True, create_repo_group=None,
223 223 updating_user_id=None, language=None, strict_creation_check=True):
224 224 """
225 225 Creates a new instance if not found, or updates current one
226 226
227 227 :param username:
228 228 :param password:
229 229 :param email:
230 230 :param firstname:
231 231 :param lastname:
232 232 :param active:
233 233 :param admin:
234 234 :param extern_type:
235 235 :param extern_name:
236 236 :param cur_user:
237 237 :param plugin: optional plugin this method was called from
238 238 :param force_password_change: toggles new or existing user flag
239 239 for password change
240 240 :param allow_to_create_user: Defines if the method can actually create
241 241 new users
242 242 :param create_repo_group: Defines if the method should also
243 243 create an repo group with user name, and owner
244 244 :param updating_user_id: if we set it up this is the user we want to
245 245 update this allows to editing username.
246 246 :param language: language of user from interface.
247 247
248 248 :returns: new User object with injected `is_new_user` attribute.
249 249 """
250 250 if not cur_user:
251 251 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
252 252
253 253 from rhodecode.lib.auth import (
254 254 get_crypt_password, check_password, generate_auth_token)
255 255 from rhodecode.lib.hooks_base import (
256 256 log_create_user, check_allowed_create_user)
257 257
258 258 def _password_change(new_user, password):
259 259 # empty password
260 260 if not new_user.password:
261 261 return False
262 262
263 263 # password check is only needed for RhodeCode internal auth calls
264 264 # in case it's a plugin we don't care
265 265 if not plugin:
266 266
267 267 # first check if we gave crypted password back, and if it
268 268 # matches it's not password change
269 269 if new_user.password == password:
270 270 return False
271 271
272 272 password_match = check_password(password, new_user.password)
273 273 if not password_match:
274 274 return True
275 275
276 276 return False
277 277
278 278 # read settings on default personal repo group creation
279 279 if create_repo_group is None:
280 280 default_create_repo_group = RepoGroupModel()\
281 281 .get_default_create_personal_repo_group()
282 282 create_repo_group = default_create_repo_group
283 283
284 284 user_data = {
285 285 'username': username,
286 286 'password': password,
287 287 'email': email,
288 288 'firstname': firstname,
289 289 'lastname': lastname,
290 290 'active': active,
291 291 'admin': admin
292 292 }
293 293
294 294 if updating_user_id:
295 295 log.debug('Checking for existing account in RhodeCode '
296 296 'database with user_id `%s` ' % (updating_user_id,))
297 297 user = User.get(updating_user_id)
298 298 else:
299 299 log.debug('Checking for existing account in RhodeCode '
300 300 'database with username `%s` ' % (username,))
301 301 user = User.get_by_username(username, case_insensitive=True)
302 302
303 303 if user is None:
304 304 # we check internal flag if this method is actually allowed to
305 305 # create new user
306 306 if not allow_to_create_user:
307 307 msg = ('Method wants to create new user, but it is not '
308 308 'allowed to do so')
309 309 log.warning(msg)
310 310 raise NotAllowedToCreateUserError(msg)
311 311
312 312 log.debug('Creating new user %s', username)
313 313
314 314 # only if we create user that is active
315 315 new_active_user = active
316 316 if new_active_user and strict_creation_check:
317 317 # raises UserCreationError if it's not allowed for any reason to
318 318 # create new active user, this also executes pre-create hooks
319 319 check_allowed_create_user(user_data, cur_user, strict_check=True)
320 320 events.trigger(events.UserPreCreate(user_data))
321 321 new_user = User()
322 322 edit = False
323 323 else:
324 324 log.debug('updating user %s', username)
325 325 events.trigger(events.UserPreUpdate(user, user_data))
326 326 new_user = user
327 327 edit = True
328 328
329 329 # we're not allowed to edit default user
330 330 if user.username == User.DEFAULT_USER:
331 331 raise DefaultUserException(
332 332 _("You can't edit this user (`%(username)s`) since it's "
333 333 "crucial for entire application") % {'username': user.username})
334 334
335 335 # inject special attribute that will tell us if User is new or old
336 336 new_user.is_new_user = not edit
337 337 # for users that didn's specify auth type, we use RhodeCode built in
338 338 from rhodecode.authentication.plugins import auth_rhodecode
339 339 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
340 340 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
341 341
342 342 try:
343 343 new_user.username = username
344 344 new_user.admin = admin
345 345 new_user.email = email
346 346 new_user.active = active
347 347 new_user.extern_name = safe_unicode(extern_name)
348 348 new_user.extern_type = safe_unicode(extern_type)
349 349 new_user.name = firstname
350 350 new_user.lastname = lastname
351 351
352 352 # set password only if creating an user or password is changed
353 353 if not edit or _password_change(new_user, password):
354 354 reason = 'new password' if edit else 'new user'
355 355 log.debug('Updating password reason=>%s', reason)
356 356 new_user.password = get_crypt_password(password) if password else None
357 357
358 358 if force_password_change:
359 359 new_user.update_userdata(force_password_change=True)
360 360 if language:
361 361 new_user.update_userdata(language=language)
362 362 new_user.update_userdata(notification_status=True)
363 363
364 364 self.sa.add(new_user)
365 365
366 366 if not edit and create_repo_group:
367 367 RepoGroupModel().create_personal_repo_group(
368 368 new_user, commit_early=False)
369 369
370 370 if not edit:
371 371 # add the RSS token
372 372 AuthTokenModel().create(username,
373 description='Generated feed token',
373 description=u'Generated feed token',
374 374 role=AuthTokenModel.cls.ROLE_FEED)
375 375 kwargs = new_user.get_dict()
376 376 # backward compat, require api_keys present
377 377 kwargs['api_keys'] = kwargs['auth_tokens']
378 378 log_create_user(created_by=cur_user, **kwargs)
379 379 events.trigger(events.UserPostCreate(user_data))
380 380 return new_user
381 381 except (DatabaseError,):
382 382 log.error(traceback.format_exc())
383 383 raise
384 384
385 385 def create_registration(self, form_data):
386 386 from rhodecode.model.notification import NotificationModel
387 387 from rhodecode.model.notification import EmailNotificationModel
388 388
389 389 try:
390 390 form_data['admin'] = False
391 391 form_data['extern_name'] = 'rhodecode'
392 392 form_data['extern_type'] = 'rhodecode'
393 393 new_user = self.create(form_data)
394 394
395 395 self.sa.add(new_user)
396 396 self.sa.flush()
397 397
398 398 user_data = new_user.get_dict()
399 399 kwargs = {
400 400 # use SQLALCHEMY safe dump of user data
401 401 'user': AttributeDict(user_data),
402 402 'date': datetime.datetime.now()
403 403 }
404 404 notification_type = EmailNotificationModel.TYPE_REGISTRATION
405 405 # pre-generate the subject for notification itself
406 406 (subject,
407 407 _h, _e, # we don't care about those
408 408 body_plaintext) = EmailNotificationModel().render_email(
409 409 notification_type, **kwargs)
410 410
411 411 # create notification objects, and emails
412 412 NotificationModel().create(
413 413 created_by=new_user,
414 414 notification_subject=subject,
415 415 notification_body=body_plaintext,
416 416 notification_type=notification_type,
417 417 recipients=None, # all admins
418 418 email_kwargs=kwargs,
419 419 )
420 420
421 421 return new_user
422 422 except Exception:
423 423 log.error(traceback.format_exc())
424 424 raise
425 425
426 426 def _handle_user_repos(self, username, repositories, handle_mode=None):
427 427 _superadmin = self.cls.get_first_super_admin()
428 428 left_overs = True
429 429
430 430 from rhodecode.model.repo import RepoModel
431 431
432 432 if handle_mode == 'detach':
433 433 for obj in repositories:
434 434 obj.user = _superadmin
435 435 # set description we know why we super admin now owns
436 436 # additional repositories that were orphaned !
437 437 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
438 438 self.sa.add(obj)
439 439 left_overs = False
440 440 elif handle_mode == 'delete':
441 441 for obj in repositories:
442 442 RepoModel().delete(obj, forks='detach')
443 443 left_overs = False
444 444
445 445 # if nothing is done we have left overs left
446 446 return left_overs
447 447
448 448 def _handle_user_repo_groups(self, username, repository_groups,
449 449 handle_mode=None):
450 450 _superadmin = self.cls.get_first_super_admin()
451 451 left_overs = True
452 452
453 453 from rhodecode.model.repo_group import RepoGroupModel
454 454
455 455 if handle_mode == 'detach':
456 456 for r in repository_groups:
457 457 r.user = _superadmin
458 458 # set description we know why we super admin now owns
459 459 # additional repositories that were orphaned !
460 460 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
461 461 self.sa.add(r)
462 462 left_overs = False
463 463 elif handle_mode == 'delete':
464 464 for r in repository_groups:
465 465 RepoGroupModel().delete(r)
466 466 left_overs = False
467 467
468 468 # if nothing is done we have left overs left
469 469 return left_overs
470 470
471 471 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
472 472 _superadmin = self.cls.get_first_super_admin()
473 473 left_overs = True
474 474
475 475 from rhodecode.model.user_group import UserGroupModel
476 476
477 477 if handle_mode == 'detach':
478 478 for r in user_groups:
479 479 for user_user_group_to_perm in r.user_user_group_to_perm:
480 480 if user_user_group_to_perm.user.username == username:
481 481 user_user_group_to_perm.user = _superadmin
482 482 r.user = _superadmin
483 483 # set description we know why we super admin now owns
484 484 # additional repositories that were orphaned !
485 485 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
486 486 self.sa.add(r)
487 487 left_overs = False
488 488 elif handle_mode == 'delete':
489 489 for r in user_groups:
490 490 UserGroupModel().delete(r)
491 491 left_overs = False
492 492
493 493 # if nothing is done we have left overs left
494 494 return left_overs
495 495
496 496 def delete(self, user, cur_user=None, handle_repos=None,
497 497 handle_repo_groups=None, handle_user_groups=None):
498 498 if not cur_user:
499 499 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
500 500 user = self._get_user(user)
501 501
502 502 try:
503 503 if user.username == User.DEFAULT_USER:
504 504 raise DefaultUserException(
505 505 _(u"You can't remove this user since it's"
506 506 u" crucial for entire application"))
507 507
508 508 left_overs = self._handle_user_repos(
509 509 user.username, user.repositories, handle_repos)
510 510 if left_overs and user.repositories:
511 511 repos = [x.repo_name for x in user.repositories]
512 512 raise UserOwnsReposException(
513 513 _(u'user "%s" still owns %s repositories and cannot be '
514 514 u'removed. Switch owners or remove those repositories:%s')
515 515 % (user.username, len(repos), ', '.join(repos)))
516 516
517 517 left_overs = self._handle_user_repo_groups(
518 518 user.username, user.repository_groups, handle_repo_groups)
519 519 if left_overs and user.repository_groups:
520 520 repo_groups = [x.group_name for x in user.repository_groups]
521 521 raise UserOwnsRepoGroupsException(
522 522 _(u'user "%s" still owns %s repository groups and cannot be '
523 523 u'removed. Switch owners or remove those repository groups:%s')
524 524 % (user.username, len(repo_groups), ', '.join(repo_groups)))
525 525
526 526 left_overs = self._handle_user_user_groups(
527 527 user.username, user.user_groups, handle_user_groups)
528 528 if left_overs and user.user_groups:
529 529 user_groups = [x.users_group_name for x in user.user_groups]
530 530 raise UserOwnsUserGroupsException(
531 531 _(u'user "%s" still owns %s user groups and cannot be '
532 532 u'removed. Switch owners or remove those user groups:%s')
533 533 % (user.username, len(user_groups), ', '.join(user_groups)))
534 534
535 535 # we might change the user data with detach/delete, make sure
536 536 # the object is marked as expired before actually deleting !
537 537 self.sa.expire(user)
538 538 self.sa.delete(user)
539 539 from rhodecode.lib.hooks_base import log_delete_user
540 540 log_delete_user(deleted_by=cur_user, **user.get_dict())
541 541 except Exception:
542 542 log.error(traceback.format_exc())
543 543 raise
544 544
545 545 def reset_password_link(self, data, pwd_reset_url):
546 546 from rhodecode.lib.celerylib import tasks, run_task
547 547 from rhodecode.model.notification import EmailNotificationModel
548 548 user_email = data['email']
549 549 try:
550 550 user = User.get_by_email(user_email)
551 551 if user:
552 552 log.debug('password reset user found %s', user)
553 553
554 554 email_kwargs = {
555 555 'password_reset_url': pwd_reset_url,
556 556 'user': user,
557 557 'email': user_email,
558 558 'date': datetime.datetime.now()
559 559 }
560 560
561 561 (subject, headers, email_body,
562 562 email_body_plaintext) = EmailNotificationModel().render_email(
563 563 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
564 564
565 565 recipients = [user_email]
566 566
567 567 action_logger_generic(
568 568 'sending password reset email to user: {}'.format(
569 569 user), namespace='security.password_reset')
570 570
571 571 run_task(tasks.send_email, recipients, subject,
572 572 email_body_plaintext, email_body)
573 573
574 574 else:
575 575 log.debug("password reset email %s not found", user_email)
576 576 except Exception:
577 577 log.error(traceback.format_exc())
578 578 return False
579 579
580 580 return True
581 581
582 582 def reset_password(self, data):
583 583 from rhodecode.lib.celerylib import tasks, run_task
584 584 from rhodecode.model.notification import EmailNotificationModel
585 585 from rhodecode.lib import auth
586 586 user_email = data['email']
587 587 pre_db = True
588 588 try:
589 589 user = User.get_by_email(user_email)
590 590 new_passwd = auth.PasswordGenerator().gen_password(
591 591 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
592 592 if user:
593 593 user.password = auth.get_crypt_password(new_passwd)
594 594 # also force this user to reset his password !
595 595 user.update_userdata(force_password_change=True)
596 596
597 597 Session().add(user)
598 598
599 599 # now delete the token in question
600 600 UserApiKeys = AuthTokenModel.cls
601 601 UserApiKeys().query().filter(
602 602 UserApiKeys.api_key == data['token']).delete()
603 603
604 604 Session().commit()
605 605 log.info('successfully reset password for `%s`', user_email)
606 606
607 607 if new_passwd is None:
608 608 raise Exception('unable to generate new password')
609 609
610 610 pre_db = False
611 611
612 612 email_kwargs = {
613 613 'new_password': new_passwd,
614 614 'user': user,
615 615 'email': user_email,
616 616 'date': datetime.datetime.now()
617 617 }
618 618
619 619 (subject, headers, email_body,
620 620 email_body_plaintext) = EmailNotificationModel().render_email(
621 621 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
622 622 **email_kwargs)
623 623
624 624 recipients = [user_email]
625 625
626 626 action_logger_generic(
627 627 'sent new password to user: {} with email: {}'.format(
628 628 user, user_email), namespace='security.password_reset')
629 629
630 630 run_task(tasks.send_email, recipients, subject,
631 631 email_body_plaintext, email_body)
632 632
633 633 except Exception:
634 634 log.error('Failed to update user password')
635 635 log.error(traceback.format_exc())
636 636 if pre_db:
637 637 # we rollback only if local db stuff fails. If it goes into
638 638 # run_task, we're pass rollback state this wouldn't work then
639 639 Session().rollback()
640 640
641 641 return True
642 642
643 643 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
644 644 """
645 645 Fetches auth_user by user_id,or api_key if present.
646 646 Fills auth_user attributes with those taken from database.
647 647 Additionally set's is_authenitated if lookup fails
648 648 present in database
649 649
650 650 :param auth_user: instance of user to set attributes
651 651 :param user_id: user id to fetch by
652 652 :param api_key: api key to fetch by
653 653 :param username: username to fetch by
654 654 """
655 655 if user_id is None and api_key is None and username is None:
656 656 raise Exception('You need to pass user_id, api_key or username')
657 657
658 658 log.debug(
659 659 'AuthUser: fill data execution based on: '
660 660 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
661 661 try:
662 662 dbuser = None
663 663 if user_id:
664 664 dbuser = self.get(user_id)
665 665 elif api_key:
666 666 dbuser = self.get_by_auth_token(api_key)
667 667 elif username:
668 668 dbuser = self.get_by_username(username)
669 669
670 670 if not dbuser:
671 671 log.warning(
672 672 'Unable to lookup user by id:%s api_key:%s username:%s',
673 673 user_id, api_key, username)
674 674 return False
675 675 if not dbuser.active:
676 676 log.debug('User `%s:%s` is inactive, skipping fill data',
677 677 username, user_id)
678 678 return False
679 679
680 680 log.debug('AuthUser: filling found user:%s data', dbuser)
681 681 user_data = dbuser.get_dict()
682 682
683 683 user_data.update({
684 684 # set explicit the safe escaped values
685 685 'first_name': dbuser.first_name,
686 686 'last_name': dbuser.last_name,
687 687 })
688 688
689 689 for k, v in user_data.items():
690 690 # properties of auth user we dont update
691 691 if k not in ['auth_tokens', 'permissions']:
692 692 setattr(auth_user, k, v)
693 693
694 694 # few extras
695 695 setattr(auth_user, 'feed_token', dbuser.feed_token)
696 696 except Exception:
697 697 log.error(traceback.format_exc())
698 698 auth_user.is_authenticated = False
699 699 return False
700 700
701 701 return True
702 702
703 703 def has_perm(self, user, perm):
704 704 perm = self._get_perm(perm)
705 705 user = self._get_user(user)
706 706
707 707 return UserToPerm.query().filter(UserToPerm.user == user)\
708 708 .filter(UserToPerm.permission == perm).scalar() is not None
709 709
710 710 def grant_perm(self, user, perm):
711 711 """
712 712 Grant user global permissions
713 713
714 714 :param user:
715 715 :param perm:
716 716 """
717 717 user = self._get_user(user)
718 718 perm = self._get_perm(perm)
719 719 # if this permission is already granted skip it
720 720 _perm = UserToPerm.query()\
721 721 .filter(UserToPerm.user == user)\
722 722 .filter(UserToPerm.permission == perm)\
723 723 .scalar()
724 724 if _perm:
725 725 return
726 726 new = UserToPerm()
727 727 new.user = user
728 728 new.permission = perm
729 729 self.sa.add(new)
730 730 return new
731 731
732 732 def revoke_perm(self, user, perm):
733 733 """
734 734 Revoke users global permissions
735 735
736 736 :param user:
737 737 :param perm:
738 738 """
739 739 user = self._get_user(user)
740 740 perm = self._get_perm(perm)
741 741
742 742 obj = UserToPerm.query()\
743 743 .filter(UserToPerm.user == user)\
744 744 .filter(UserToPerm.permission == perm)\
745 745 .scalar()
746 746 if obj:
747 747 self.sa.delete(obj)
748 748
749 749 def add_extra_email(self, user, email):
750 750 """
751 751 Adds email address to UserEmailMap
752 752
753 753 :param user:
754 754 :param email:
755 755 """
756 756 from rhodecode.model import forms
757 757 form = forms.UserExtraEmailForm()()
758 758 data = form.to_python({'email': email})
759 759 user = self._get_user(user)
760 760
761 761 obj = UserEmailMap()
762 762 obj.user = user
763 763 obj.email = data['email']
764 764 self.sa.add(obj)
765 765 return obj
766 766
767 767 def delete_extra_email(self, user, email_id):
768 768 """
769 769 Removes email address from UserEmailMap
770 770
771 771 :param user:
772 772 :param email_id:
773 773 """
774 774 user = self._get_user(user)
775 775 obj = UserEmailMap.query().get(email_id)
776 776 if obj and obj.user_id == user.user_id:
777 777 self.sa.delete(obj)
778 778
779 779 def parse_ip_range(self, ip_range):
780 780 ip_list = []
781 781
782 782 def make_unique(value):
783 783 seen = []
784 784 return [c for c in value if not (c in seen or seen.append(c))]
785 785
786 786 # firsts split by commas
787 787 for ip_range in ip_range.split(','):
788 788 if not ip_range:
789 789 continue
790 790 ip_range = ip_range.strip()
791 791 if '-' in ip_range:
792 792 start_ip, end_ip = ip_range.split('-', 1)
793 793 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
794 794 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
795 795 parsed_ip_range = []
796 796
797 797 for index in xrange(int(start_ip), int(end_ip) + 1):
798 798 new_ip = ipaddress.ip_address(index)
799 799 parsed_ip_range.append(str(new_ip))
800 800 ip_list.extend(parsed_ip_range)
801 801 else:
802 802 ip_list.append(ip_range)
803 803
804 804 return make_unique(ip_list)
805 805
806 806 def add_extra_ip(self, user, ip, description=None):
807 807 """
808 808 Adds ip address to UserIpMap
809 809
810 810 :param user:
811 811 :param ip:
812 812 """
813 813 from rhodecode.model import forms
814 814 form = forms.UserExtraIpForm()()
815 815 data = form.to_python({'ip': ip})
816 816 user = self._get_user(user)
817 817
818 818 obj = UserIpMap()
819 819 obj.user = user
820 820 obj.ip_addr = data['ip']
821 821 obj.description = description
822 822 self.sa.add(obj)
823 823 return obj
824 824
825 825 def delete_extra_ip(self, user, ip_id):
826 826 """
827 827 Removes ip address from UserIpMap
828 828
829 829 :param user:
830 830 :param ip_id:
831 831 """
832 832 user = self._get_user(user)
833 833 obj = UserIpMap.query().get(ip_id)
834 834 if obj and obj.user_id == user.user_id:
835 835 self.sa.delete(obj)
836 836
837 837 def get_accounts_in_creation_order(self, current_user=None):
838 838 """
839 839 Get accounts in order of creation for deactivation for license limits
840 840
841 841 pick currently logged in user, and append to the list in position 0
842 842 pick all super-admins in order of creation date and add it to the list
843 843 pick all other accounts in order of creation and add it to the list.
844 844
845 845 Based on that list, the last accounts can be disabled as they are
846 846 created at the end and don't include any of the super admins as well
847 847 as the current user.
848 848
849 849 :param current_user: optionally current user running this operation
850 850 """
851 851
852 852 if not current_user:
853 853 current_user = get_current_rhodecode_user()
854 854 active_super_admins = [
855 855 x.user_id for x in User.query()
856 856 .filter(User.user_id != current_user.user_id)
857 857 .filter(User.active == true())
858 858 .filter(User.admin == true())
859 859 .order_by(User.created_on.asc())]
860 860
861 861 active_regular_users = [
862 862 x.user_id for x in User.query()
863 863 .filter(User.user_id != current_user.user_id)
864 864 .filter(User.active == true())
865 865 .filter(User.admin == false())
866 866 .order_by(User.created_on.asc())]
867 867
868 868 list_of_accounts = [current_user.user_id]
869 869 list_of_accounts += active_super_admins
870 870 list_of_accounts += active_regular_users
871 871
872 872 return list_of_accounts
873 873
874 874 def deactivate_last_users(self, expected_users, current_user=None):
875 875 """
876 876 Deactivate accounts that are over the license limits.
877 877 Algorithm of which accounts to disabled is based on the formula:
878 878
879 879 Get current user, then super admins in creation order, then regular
880 880 active users in creation order.
881 881
882 882 Using that list we mark all accounts from the end of it as inactive.
883 883 This way we block only latest created accounts.
884 884
885 885 :param expected_users: list of users in special order, we deactivate
886 886 the end N ammoun of users from that list
887 887 """
888 888
889 889 list_of_accounts = self.get_accounts_in_creation_order(
890 890 current_user=current_user)
891 891
892 892 for acc_id in list_of_accounts[expected_users + 1:]:
893 893 user = User.get(acc_id)
894 894 log.info('Deactivating account %s for license unlock', user)
895 895 user.active = False
896 896 Session().add(user)
897 897 Session().commit()
898 898
899 899 return
900 900
901 901 def get_user_log(self, user, filter_term):
902 902 user_log = UserLog.query()\
903 903 .filter(or_(UserLog.user_id == user.user_id,
904 904 UserLog.username == user.username))\
905 905 .options(joinedload(UserLog.user))\
906 906 .options(joinedload(UserLog.repository))\
907 907 .order_by(UserLog.action_date.desc())
908 908
909 909 user_log = user_log_filter(user_log, filter_term)
910 910 return user_log
@@ -1,341 +1,341 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helpers for fixture generation
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import tempfile
28 28 import shutil
29 29
30 30 import configobj
31 31
32 32 from rhodecode.tests import *
33 33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.model.repo_group import RepoGroupModel
38 38 from rhodecode.model.user_group import UserGroupModel
39 39 from rhodecode.model.gist import GistModel
40 40 from rhodecode.model.auth_token import AuthTokenModel
41 41
42 42 dn = os.path.dirname
43 43 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
44 44
45 45
46 46 def error_function(*args, **kwargs):
47 47 raise Exception('Total Crash !')
48 48
49 49
50 50 class TestINI(object):
51 51 """
52 52 Allows to create a new test.ini file as a copy of existing one with edited
53 53 data. Example usage::
54 54
55 55 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
56 56 print 'paster server %s' % new_test_ini
57 57 """
58 58
59 59 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
60 60 destroy=True, dir=None):
61 61 self.ini_file_path = ini_file_path
62 62 self.ini_params = ini_params
63 63 self.new_path = None
64 64 self.new_path_prefix = new_file_prefix
65 65 self._destroy = destroy
66 66 self._dir = dir
67 67
68 68 def __enter__(self):
69 69 return self.create()
70 70
71 71 def __exit__(self, exc_type, exc_val, exc_tb):
72 72 self.destroy()
73 73
74 74 def create(self):
75 75 config = configobj.ConfigObj(
76 76 self.ini_file_path, file_error=True, write_empty_values=True)
77 77
78 78 for data in self.ini_params:
79 79 section, ini_params = data.items()[0]
80 80 for key, val in ini_params.items():
81 81 config[section][key] = val
82 82 with tempfile.NamedTemporaryFile(
83 83 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
84 84 delete=False) as new_ini_file:
85 85 config.write(new_ini_file)
86 86 self.new_path = new_ini_file.name
87 87
88 88 return self.new_path
89 89
90 90 def destroy(self):
91 91 if self._destroy:
92 92 os.remove(self.new_path)
93 93
94 94
95 95 class Fixture(object):
96 96
97 97 def anon_access(self, status):
98 98 """
99 99 Context process for disabling anonymous access. use like:
100 100 fixture = Fixture()
101 101 with fixture.anon_access(False):
102 102 #tests
103 103
104 104 after this block anon access will be set to `not status`
105 105 """
106 106
107 107 class context(object):
108 108 def __enter__(self):
109 109 anon = User.get_default_user()
110 110 anon.active = status
111 111 Session().add(anon)
112 112 Session().commit()
113 113 time.sleep(1.5) # must sleep for cache (1s to expire)
114 114
115 115 def __exit__(self, exc_type, exc_val, exc_tb):
116 116 anon = User.get_default_user()
117 117 anon.active = not status
118 118 Session().add(anon)
119 119 Session().commit()
120 120
121 121 return context()
122 122
123 123 def _get_repo_create_params(self, **custom):
124 124 defs = {
125 125 'repo_name': None,
126 126 'repo_type': 'hg',
127 127 'clone_uri': '',
128 128 'repo_group': '-1',
129 129 'repo_description': 'DESC',
130 130 'repo_private': False,
131 131 'repo_landing_rev': 'rev:tip',
132 132 'repo_copy_permissions': False,
133 133 'repo_state': Repository.STATE_CREATED,
134 134 }
135 135 defs.update(custom)
136 136 if 'repo_name_full' not in custom:
137 137 defs.update({'repo_name_full': defs['repo_name']})
138 138
139 139 # fix the repo name if passed as repo_name_full
140 140 if defs['repo_name']:
141 141 defs['repo_name'] = defs['repo_name'].split('/')[-1]
142 142
143 143 return defs
144 144
145 145 def _get_group_create_params(self, **custom):
146 146 defs = {
147 147 'group_name': None,
148 148 'group_description': 'DESC',
149 149 'perm_updates': [],
150 150 'perm_additions': [],
151 151 'perm_deletions': [],
152 152 'group_parent_id': -1,
153 153 'enable_locking': False,
154 154 'recursive': False,
155 155 }
156 156 defs.update(custom)
157 157
158 158 return defs
159 159
160 160 def _get_user_create_params(self, name, **custom):
161 161 defs = {
162 162 'username': name,
163 163 'password': 'qweqwe',
164 164 'email': '%s+test@rhodecode.org' % name,
165 165 'firstname': 'TestUser',
166 166 'lastname': 'Test',
167 167 'active': True,
168 168 'admin': False,
169 169 'extern_type': 'rhodecode',
170 170 'extern_name': None,
171 171 }
172 172 defs.update(custom)
173 173
174 174 return defs
175 175
176 176 def _get_user_group_create_params(self, name, **custom):
177 177 defs = {
178 178 'users_group_name': name,
179 179 'user_group_description': 'DESC',
180 180 'users_group_active': True,
181 181 'user_group_data': {},
182 182 }
183 183 defs.update(custom)
184 184
185 185 return defs
186 186
187 187 def create_repo(self, name, **kwargs):
188 188 repo_group = kwargs.get('repo_group')
189 189 if isinstance(repo_group, RepoGroup):
190 190 kwargs['repo_group'] = repo_group.group_id
191 191 name = name.split(Repository.NAME_SEP)[-1]
192 192 name = Repository.NAME_SEP.join((repo_group.group_name, name))
193 193
194 194 if 'skip_if_exists' in kwargs:
195 195 del kwargs['skip_if_exists']
196 196 r = Repository.get_by_repo_name(name)
197 197 if r:
198 198 return r
199 199
200 200 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
201 201 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
202 202 RepoModel().create(form_data, cur_user)
203 203 Session().commit()
204 204 repo = Repository.get_by_repo_name(name)
205 205 assert repo
206 206 return repo
207 207
208 208 def create_fork(self, repo_to_fork, fork_name, **kwargs):
209 209 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
210 210
211 211 form_data = self._get_repo_create_params(repo_name=fork_name,
212 212 fork_parent_id=repo_to_fork.repo_id,
213 213 repo_type=repo_to_fork.repo_type,
214 214 **kwargs)
215 215 #TODO: fix it !!
216 216 form_data['description'] = form_data['repo_description']
217 217 form_data['private'] = form_data['repo_private']
218 218 form_data['landing_rev'] = form_data['repo_landing_rev']
219 219
220 220 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
221 221 RepoModel().create_fork(form_data, cur_user=owner)
222 222 Session().commit()
223 223 r = Repository.get_by_repo_name(fork_name)
224 224 assert r
225 225 return r
226 226
227 227 def destroy_repo(self, repo_name, **kwargs):
228 228 RepoModel().delete(repo_name, **kwargs)
229 229 Session().commit()
230 230
231 231 def destroy_repo_on_filesystem(self, repo_name):
232 232 rm_path = os.path.join(RepoModel().repos_path, repo_name)
233 233 if os.path.isdir(rm_path):
234 234 shutil.rmtree(rm_path)
235 235
236 236 def create_repo_group(self, name, **kwargs):
237 237 if 'skip_if_exists' in kwargs:
238 238 del kwargs['skip_if_exists']
239 239 gr = RepoGroup.get_by_group_name(group_name=name)
240 240 if gr:
241 241 return gr
242 242 form_data = self._get_group_create_params(group_name=name, **kwargs)
243 243 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
244 244 gr = RepoGroupModel().create(
245 245 group_name=form_data['group_name'],
246 246 group_description=form_data['group_name'],
247 247 owner=owner)
248 248 Session().commit()
249 249 gr = RepoGroup.get_by_group_name(gr.group_name)
250 250 return gr
251 251
252 252 def destroy_repo_group(self, repogroupid):
253 253 RepoGroupModel().delete(repogroupid)
254 254 Session().commit()
255 255
256 256 def create_user(self, name, **kwargs):
257 257 if 'skip_if_exists' in kwargs:
258 258 del kwargs['skip_if_exists']
259 259 user = User.get_by_username(name)
260 260 if user:
261 261 return user
262 262 form_data = self._get_user_create_params(name, **kwargs)
263 263 user = UserModel().create(form_data)
264 264
265 265 # create token for user
266 266 AuthTokenModel().create(
267 user=user, description='TEST_USER_TOKEN')
267 user=user, description=u'TEST_USER_TOKEN')
268 268
269 269 Session().commit()
270 270 user = User.get_by_username(user.username)
271 271 return user
272 272
273 273 def destroy_user(self, userid):
274 274 UserModel().delete(userid)
275 275 Session().commit()
276 276
277 277 def destroy_users(self, userid_iter):
278 278 for user_id in userid_iter:
279 279 if User.get_by_username(user_id):
280 280 UserModel().delete(user_id)
281 281 Session().commit()
282 282
283 283 def create_user_group(self, name, **kwargs):
284 284 if 'skip_if_exists' in kwargs:
285 285 del kwargs['skip_if_exists']
286 286 gr = UserGroup.get_by_group_name(group_name=name)
287 287 if gr:
288 288 return gr
289 289 # map active flag to the real attribute. For API consistency of fixtures
290 290 if 'active' in kwargs:
291 291 kwargs['users_group_active'] = kwargs['active']
292 292 del kwargs['active']
293 293 form_data = self._get_user_group_create_params(name, **kwargs)
294 294 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
295 295 user_group = UserGroupModel().create(
296 296 name=form_data['users_group_name'],
297 297 description=form_data['user_group_description'],
298 298 owner=owner, active=form_data['users_group_active'],
299 299 group_data=form_data['user_group_data'])
300 300 Session().commit()
301 301 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
302 302 return user_group
303 303
304 304 def destroy_user_group(self, usergroupid):
305 305 UserGroupModel().delete(user_group=usergroupid, force=True)
306 306 Session().commit()
307 307
308 308 def create_gist(self, **kwargs):
309 309 form_data = {
310 310 'description': 'new-gist',
311 311 'owner': TEST_USER_ADMIN_LOGIN,
312 312 'gist_type': GistModel.cls.GIST_PUBLIC,
313 313 'lifetime': -1,
314 314 'acl_level': Gist.ACL_LEVEL_PUBLIC,
315 315 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
316 316 }
317 317 form_data.update(kwargs)
318 318 gist = GistModel().create(
319 319 description=form_data['description'], owner=form_data['owner'],
320 320 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
321 321 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
322 322 )
323 323 Session().commit()
324 324 return gist
325 325
326 326 def destroy_gists(self, gistid=None):
327 327 for g in GistModel.cls.get_all():
328 328 if gistid:
329 329 if gistid == g.gist_access_id:
330 330 GistModel().delete(g)
331 331 else:
332 332 GistModel().delete(g)
333 333 Session().commit()
334 334
335 335 def load_resource(self, resource_name, strip=False):
336 336 with open(os.path.join(FIXTURES, resource_name)) as f:
337 337 source = f.read()
338 338 if strip:
339 339 source = source.strip()
340 340
341 341 return source
General Comments 0
You need to be logged in to leave comments. Login now