##// END OF EJS Templates
fix default permissions migrations that can cause troubles for some old users
marcink -
r2795:456a5190 beta
parent child Browse files
Show More
@@ -1,617 +1,622 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.lib.utils import ask_ok
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 40 UserRepoGroupToPerm
41 41
42 42 from sqlalchemy.engine import create_engine
43 43 from rhodecode.model.repos_group import ReposGroupModel
44 44 #from rhodecode.model import meta
45 45 from rhodecode.model.meta import Session, Base
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def notify(msg):
52 52 """
53 53 Notification for migrations messages
54 54 """
55 55 ml = len(msg) + (4 * 2)
56 56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57 57
58 58
59 59 class DbManage(object):
60 60 def __init__(self, log_sql, dbconf, root, tests=False):
61 61 self.dbname = dbconf.split('/')[-1]
62 62 self.tests = tests
63 63 self.root = root
64 64 self.dburi = dbconf
65 65 self.log_sql = log_sql
66 66 self.db_exists = False
67 67 self.init_db()
68 68
69 69 def init_db(self):
70 70 engine = create_engine(self.dburi, echo=self.log_sql)
71 71 init_model(engine)
72 72 self.sa = Session()
73 73
74 74 def create_tables(self, override=False, defaults={}):
75 75 """
76 76 Create a auth database
77 77 """
78 78 quiet = defaults.get('quiet')
79 79 log.info("Any existing database is going to be destroyed")
80 80 if self.tests or quiet:
81 81 destroy = True
82 82 else:
83 83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 84 if not destroy:
85 85 sys.exit()
86 86 if destroy:
87 87 Base.metadata.drop_all()
88 88
89 89 checkfirst = not override
90 90 Base.metadata.create_all(checkfirst=checkfirst)
91 91 log.info('Created tables for %s' % self.dbname)
92 92
93 93 def set_db_version(self):
94 94 ver = DbMigrateVersion()
95 95 ver.version = __dbversion__
96 96 ver.repository_id = 'rhodecode_db_migrations'
97 97 ver.repository_path = 'versions'
98 98 self.sa.add(ver)
99 99 log.info('db version set to: %s' % __dbversion__)
100 100
101 101 def upgrade(self):
102 102 """
103 103 Upgrades given database schema to given revision following
104 104 all needed steps, to perform the upgrade
105 105
106 106 """
107 107
108 108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 110 DatabaseNotControlledError
111 111
112 112 if 'sqlite' in self.dburi:
113 113 print (
114 114 '********************** WARNING **********************\n'
115 115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 116 'Earlier versions are known to fail on some migrations\n'
117 117 '*****************************************************\n'
118 118 )
119 119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 120 'sure You backed up your database before. '
121 121 'Continue ? [y/n]')
122 122 if not upgrade:
123 123 sys.exit('Nothing done')
124 124
125 125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 126 'rhodecode/lib/dbmigrate')
127 127 db_uri = self.dburi
128 128
129 129 try:
130 130 curr_version = api.db_version(db_uri, repository_path)
131 131 msg = ('Found current database under version'
132 132 ' control with version %s' % curr_version)
133 133
134 134 except (RuntimeError, DatabaseNotControlledError):
135 135 curr_version = 1
136 136 msg = ('Current database is not under version control. Setting'
137 137 ' as version %s' % curr_version)
138 138 api.version_control(db_uri, repository_path, curr_version)
139 139
140 140 notify(msg)
141 141
142 142 if curr_version == __dbversion__:
143 143 sys.exit('This database is already at the newest version')
144 144
145 145 #======================================================================
146 146 # UPGRADE STEPS
147 147 #======================================================================
148 148
149 149 class UpgradeSteps(object):
150 150 """
151 151 Those steps follow schema versions so for example schema
152 152 for example schema with seq 002 == step_2 and so on.
153 153 """
154 154
155 155 def __init__(self, klass):
156 156 self.klass = klass
157 157
158 158 def step_0(self):
159 159 # step 0 is the schema upgrade, and than follow proper upgrades
160 160 notify('attempting to do database upgrade to version %s' \
161 161 % __dbversion__)
162 162 api.upgrade(db_uri, repository_path, __dbversion__)
163 163 notify('Schema upgrade completed')
164 164
165 165 def step_1(self):
166 166 pass
167 167
168 168 def step_2(self):
169 169 notify('Patching repo paths for newer version of RhodeCode')
170 170 self.klass.fix_repo_paths()
171 171
172 172 notify('Patching default user of RhodeCode')
173 173 self.klass.fix_default_user()
174 174
175 175 log.info('Changing ui settings')
176 176 self.klass.create_ui_settings()
177 177
178 178 def step_3(self):
179 179 notify('Adding additional settings into RhodeCode db')
180 180 self.klass.fix_settings()
181 181 notify('Adding ldap defaults')
182 182 self.klass.create_ldap_options(skip_existing=True)
183 183
184 184 def step_4(self):
185 185 notify('create permissions and fix groups')
186 186 self.klass.create_permissions()
187 187 self.klass.fixup_groups()
188 188
189 189 def step_5(self):
190 190 pass
191 191
192 192 def step_6(self):
193 193
194 194 notify('re-checking permissions')
195 195 self.klass.create_permissions()
196 196
197 197 notify('installing new UI options')
198 198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 199 Session().add(sett4)
200 200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 201 Session().add(sett5)
202 202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 203 Session().add(sett6)
204 204
205 205 notify('fixing old PULL hook')
206 206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 207 if _pull:
208 208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 209 Session().add(_pull)
210 210
211 211 notify('fixing old PUSH hook')
212 212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 213 if _push:
214 214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 215 Session().add(_push)
216 216
217 217 notify('installing new pre-push hook')
218 218 hooks4 = RhodeCodeUi()
219 219 hooks4.ui_section = 'hooks'
220 220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 222 Session().add(hooks4)
223 223
224 224 notify('installing new pre-pull hook')
225 225 hooks6 = RhodeCodeUi()
226 226 hooks6.ui_section = 'hooks'
227 227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 229 Session().add(hooks6)
230 230
231 231 notify('installing hgsubversion option')
232 232 # enable hgsubversion disabled by default
233 233 hgsubversion = RhodeCodeUi()
234 234 hgsubversion.ui_section = 'extensions'
235 235 hgsubversion.ui_key = 'hgsubversion'
236 236 hgsubversion.ui_value = ''
237 237 hgsubversion.ui_active = False
238 238 Session().add(hgsubversion)
239 239
240 240 notify('installing hg git option')
241 241 # enable hggit disabled by default
242 242 hggit = RhodeCodeUi()
243 243 hggit.ui_section = 'extensions'
244 244 hggit.ui_key = 'hggit'
245 245 hggit.ui_value = ''
246 246 hggit.ui_active = False
247 247 Session().add(hggit)
248 248
249 249 notify('re-check default permissions')
250 self.klass.populate_default_permissions()
250 default_user = User.get_by_username(User.DEFAULT_USER)
251 perm = Permission.get_by_key('hg.fork.repository')
252 reg_perm = UserToPerm()
253 reg_perm.user = default_user
254 reg_perm.permission = perm
255 Session().add(reg_perm)
251 256
252 257 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
253 258
254 259 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
255 260 _step = None
256 261 for step in upgrade_steps:
257 262 notify('performing upgrade step %s' % step)
258 263 getattr(UpgradeSteps(self), 'step_%s' % step)()
259 264 self.sa.commit()
260 265 _step = step
261 266
262 267 notify('upgrade to version %s successful' % _step)
263 268
264 269 def fix_repo_paths(self):
265 270 """
266 271 Fixes a old rhodecode version path into new one without a '*'
267 272 """
268 273
269 274 paths = self.sa.query(RhodeCodeUi)\
270 275 .filter(RhodeCodeUi.ui_key == '/')\
271 276 .scalar()
272 277
273 278 paths.ui_value = paths.ui_value.replace('*', '')
274 279
275 280 try:
276 281 self.sa.add(paths)
277 282 self.sa.commit()
278 283 except:
279 284 self.sa.rollback()
280 285 raise
281 286
282 287 def fix_default_user(self):
283 288 """
284 289 Fixes a old default user with some 'nicer' default values,
285 290 used mostly for anonymous access
286 291 """
287 292 def_user = self.sa.query(User)\
288 293 .filter(User.username == 'default')\
289 294 .one()
290 295
291 296 def_user.name = 'Anonymous'
292 297 def_user.lastname = 'User'
293 298 def_user.email = 'anonymous@rhodecode.org'
294 299
295 300 try:
296 301 self.sa.add(def_user)
297 302 self.sa.commit()
298 303 except:
299 304 self.sa.rollback()
300 305 raise
301 306
302 307 def fix_settings(self):
303 308 """
304 309 Fixes rhodecode settings adds ga_code key for google analytics
305 310 """
306 311
307 312 hgsettings3 = RhodeCodeSetting('ga_code', '')
308 313
309 314 try:
310 315 self.sa.add(hgsettings3)
311 316 self.sa.commit()
312 317 except:
313 318 self.sa.rollback()
314 319 raise
315 320
316 321 def admin_prompt(self, second=False, defaults={}):
317 322 if not self.tests:
318 323 import getpass
319 324
320 325 # defaults
321 326 username = defaults.get('username')
322 327 password = defaults.get('password')
323 328 email = defaults.get('email')
324 329
325 330 def get_password():
326 331 password = getpass.getpass('Specify admin password '
327 332 '(min 6 chars):')
328 333 confirm = getpass.getpass('Confirm password:')
329 334
330 335 if password != confirm:
331 336 log.error('passwords mismatch')
332 337 return False
333 338 if len(password) < 6:
334 339 log.error('password is to short use at least 6 characters')
335 340 return False
336 341
337 342 return password
338 343 if username is None:
339 344 username = raw_input('Specify admin username:')
340 345 if password is None:
341 346 password = get_password()
342 347 if not password:
343 348 #second try
344 349 password = get_password()
345 350 if not password:
346 351 sys.exit()
347 352 if email is None:
348 353 email = raw_input('Specify admin email:')
349 354 self.create_user(username, password, email, True)
350 355 else:
351 356 log.info('creating admin and regular test users')
352 357 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
353 358 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
354 359 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
355 360 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
356 361 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
357 362
358 363 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
359 364 TEST_USER_ADMIN_EMAIL, True)
360 365
361 366 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
362 367 TEST_USER_REGULAR_EMAIL, False)
363 368
364 369 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
365 370 TEST_USER_REGULAR2_EMAIL, False)
366 371
367 372 def create_ui_settings(self):
368 373 """
369 374 Creates ui settings, fills out hooks
370 375 and disables dotencode
371 376 """
372 377
373 378 #HOOKS
374 379 hooks1_key = RhodeCodeUi.HOOK_UPDATE
375 380 hooks1_ = self.sa.query(RhodeCodeUi)\
376 381 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
377 382
378 383 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
379 384 hooks1.ui_section = 'hooks'
380 385 hooks1.ui_key = hooks1_key
381 386 hooks1.ui_value = 'hg update >&2'
382 387 hooks1.ui_active = False
383 388 self.sa.add(hooks1)
384 389
385 390 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
386 391 hooks2_ = self.sa.query(RhodeCodeUi)\
387 392 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
388 393 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
389 394 hooks2.ui_section = 'hooks'
390 395 hooks2.ui_key = hooks2_key
391 396 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
392 397 self.sa.add(hooks2)
393 398
394 399 hooks3 = RhodeCodeUi()
395 400 hooks3.ui_section = 'hooks'
396 401 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
397 402 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
398 403 self.sa.add(hooks3)
399 404
400 405 hooks4 = RhodeCodeUi()
401 406 hooks4.ui_section = 'hooks'
402 407 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
403 408 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
404 409 self.sa.add(hooks4)
405 410
406 411 hooks5 = RhodeCodeUi()
407 412 hooks5.ui_section = 'hooks'
408 413 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
409 414 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
410 415 self.sa.add(hooks5)
411 416
412 417 hooks6 = RhodeCodeUi()
413 418 hooks6.ui_section = 'hooks'
414 419 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
415 420 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
416 421 self.sa.add(hooks6)
417 422
418 423 # enable largefiles
419 424 largefiles = RhodeCodeUi()
420 425 largefiles.ui_section = 'extensions'
421 426 largefiles.ui_key = 'largefiles'
422 427 largefiles.ui_value = ''
423 428 self.sa.add(largefiles)
424 429
425 430 # enable hgsubversion disabled by default
426 431 hgsubversion = RhodeCodeUi()
427 432 hgsubversion.ui_section = 'extensions'
428 433 hgsubversion.ui_key = 'hgsubversion'
429 434 hgsubversion.ui_value = ''
430 435 hgsubversion.ui_active = False
431 436 self.sa.add(hgsubversion)
432 437
433 438 # enable hggit disabled by default
434 439 hggit = RhodeCodeUi()
435 440 hggit.ui_section = 'extensions'
436 441 hggit.ui_key = 'hggit'
437 442 hggit.ui_value = ''
438 443 hggit.ui_active = False
439 444 self.sa.add(hggit)
440 445
441 446 def create_ldap_options(self, skip_existing=False):
442 447 """Creates ldap settings"""
443 448
444 449 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
445 450 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
446 451 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
447 452 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
448 453 ('ldap_filter', ''), ('ldap_search_scope', ''),
449 454 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
450 455 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
451 456
452 457 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
453 458 log.debug('Skipping option %s' % k)
454 459 continue
455 460 setting = RhodeCodeSetting(k, v)
456 461 self.sa.add(setting)
457 462
458 463 def fixup_groups(self):
459 464 def_usr = User.get_by_username('default')
460 465 for g in RepoGroup.query().all():
461 466 g.group_name = g.get_new_name(g.name)
462 467 self.sa.add(g)
463 468 # get default perm
464 469 default = UserRepoGroupToPerm.query()\
465 470 .filter(UserRepoGroupToPerm.group == g)\
466 471 .filter(UserRepoGroupToPerm.user == def_usr)\
467 472 .scalar()
468 473
469 474 if default is None:
470 475 log.debug('missing default permission for group %s adding' % g)
471 476 ReposGroupModel()._create_default_perms(g)
472 477
473 478 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
474 479 _path = defaults.get('repos_location')
475 480 if retries == 3:
476 481 log.info('Setting up repositories config')
477 482
478 483 if _path is not None:
479 484 path = _path
480 485 elif not self.tests and not test_repo_path:
481 486 path = raw_input(
482 487 'Enter a valid absolute path to store repositories. '
483 488 'All repositories in that path will be added automatically:'
484 489 )
485 490 else:
486 491 path = test_repo_path
487 492 path_ok = True
488 493
489 494 # check proper dir
490 495 if not os.path.isdir(path):
491 496 path_ok = False
492 497 log.error('Given path %s is not a valid directory' % path)
493 498
494 499 elif not os.path.isabs(path):
495 500 path_ok = False
496 501 log.error('Given path %s is not an absolute path' % path)
497 502
498 503 # check write access
499 504 elif not os.access(path, os.W_OK) and path_ok:
500 505 path_ok = False
501 506 log.error('No write permission to given path %s' % path)
502 507
503 508 if retries == 0:
504 509 sys.exit('max retries reached')
505 510 if path_ok is False:
506 511 retries -= 1
507 512 return self.config_prompt(test_repo_path, retries)
508 513
509 514 return path
510 515
511 516 def create_settings(self, path):
512 517
513 518 self.create_ui_settings()
514 519
515 520 #HG UI OPTIONS
516 521 web1 = RhodeCodeUi()
517 522 web1.ui_section = 'web'
518 523 web1.ui_key = 'push_ssl'
519 524 web1.ui_value = 'false'
520 525
521 526 web2 = RhodeCodeUi()
522 527 web2.ui_section = 'web'
523 528 web2.ui_key = 'allow_archive'
524 529 web2.ui_value = 'gz zip bz2'
525 530
526 531 web3 = RhodeCodeUi()
527 532 web3.ui_section = 'web'
528 533 web3.ui_key = 'allow_push'
529 534 web3.ui_value = '*'
530 535
531 536 web4 = RhodeCodeUi()
532 537 web4.ui_section = 'web'
533 538 web4.ui_key = 'baseurl'
534 539 web4.ui_value = '/'
535 540
536 541 paths = RhodeCodeUi()
537 542 paths.ui_section = 'paths'
538 543 paths.ui_key = '/'
539 544 paths.ui_value = path
540 545
541 546 phases = RhodeCodeUi()
542 547 phases.ui_section = 'phases'
543 548 phases.ui_key = 'publish'
544 549 phases.ui_value = False
545 550
546 551 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
547 552 sett2 = RhodeCodeSetting('title', 'RhodeCode')
548 553 sett3 = RhodeCodeSetting('ga_code', '')
549 554
550 555 sett4 = RhodeCodeSetting('show_public_icon', True)
551 556 sett5 = RhodeCodeSetting('show_private_icon', True)
552 557 sett6 = RhodeCodeSetting('stylify_metatags', False)
553 558
554 559 self.sa.add(web1)
555 560 self.sa.add(web2)
556 561 self.sa.add(web3)
557 562 self.sa.add(web4)
558 563 self.sa.add(paths)
559 564 self.sa.add(sett1)
560 565 self.sa.add(sett2)
561 566 self.sa.add(sett3)
562 567 self.sa.add(sett4)
563 568 self.sa.add(sett5)
564 569 self.sa.add(sett6)
565 570
566 571 self.create_ldap_options()
567 572
568 573 log.info('created ui config')
569 574
570 575 def create_user(self, username, password, email='', admin=False):
571 576 log.info('creating user %s' % username)
572 577 UserModel().create_or_update(username, password, email,
573 578 firstname='RhodeCode', lastname='Admin',
574 579 active=True, admin=admin)
575 580
576 581 def create_default_user(self):
577 582 log.info('creating default user')
578 583 # create default user for handling default permissions.
579 584 UserModel().create_or_update(username='default',
580 585 password=str(uuid.uuid1())[:8],
581 586 email='anonymous@rhodecode.org',
582 587 firstname='Anonymous', lastname='User')
583 588
584 589 def create_permissions(self):
585 590 # module.(access|create|change|delete)_[name]
586 591 # module.(none|read|write|admin)
587 592
588 593 for p in Permission.PERMS:
589 594 if not Permission.get_by_key(p[0]):
590 595 new_perm = Permission()
591 596 new_perm.permission_name = p[0]
592 597 new_perm.permission_longname = p[0]
593 598 self.sa.add(new_perm)
594 599
595 600 def populate_default_permissions(self):
596 601 log.info('creating default user permissions')
597 602
598 603 default_user = User.get_by_username('default')
599 604
600 605 for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
601 606 'hg.fork.repository', 'repository.read']:
602 607
603 608 perm = self.sa.query(Permission)\
604 609 .filter(Permission.permission_name == def_perm)\
605 610 .scalar()
606 611 if not perm:
607 612 raise Exception(
608 613 'CRITICAL: permission %s not found inside database !!'
609 614 % def_perm
610 615 )
611 616 if not UserToPerm.query()\
612 617 .filter(UserToPerm.permission == perm)\
613 618 .filter(UserToPerm.user == default_user).scalar():
614 619 reg_perm = UserToPerm()
615 620 reg_perm.user = default_user
616 621 reg_perm.permission = perm
617 622 self.sa.add(reg_perm)
General Comments 0
You need to be logged in to leave comments. Login now