##// END OF EJS Templates
dbmigrate: fixed migration problems
super-admin -
r5166:f519a80b default
parent child Browse files
Show More
@@ -1,1591 +1,1600 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import sys
22 22 import time
23 23 import hashlib
24 24 import logging
25 25 import datetime
26 26 import warnings
27 27 import ipaddress
28 28 import functools
29 29 import traceback
30 30 import collections
31 31
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.exc import OperationalError
35 35 from sqlalchemy.ext.declarative import declared_attr
36 36 from sqlalchemy.ext.hybrid import hybrid_property
37 37 from sqlalchemy.orm import (
38 38 relationship, joinedload, class_mapper, validates, aliased)
39 39 from sqlalchemy.sql.expression import true
40 40 from beaker.cache import cache_region, region_invalidate
41 41 from webob.exc import HTTPNotFound
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 from rhodecode.lib import enc_utils
44 45 from rhodecode.translation import _
45 46
46 47 from rhodecode.lib.vcs import get_backend
47 48 from rhodecode.lib.vcs.utils.helpers import get_scm
48 49 from rhodecode.lib.vcs.exceptions import VCSError
49 50 from rhodecode.lib.vcs.backends.base import (
50 51 EmptyCommit, Reference, MergeFailureReason)
51 52 from rhodecode.lib.utils2 import (
52 53 str2bool, safe_str, get_commit_safe, remove_prefix, md5_safe,
53 54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
54 55 from rhodecode.lib.ext_json import json
55 56 from rhodecode.lib.caching_query import FromCache
56 57 from rhodecode.lib.encrypt import AESCipher
57 58
58 59 from rhodecode.model.meta import Base, Session
59 60
60 61 URL_SEP = '/'
61 62 log = logging.getLogger(__name__)
62 63
63 64 #==============================================================================
64 65 # BASE CLASSES
65 66 #==============================================================================
66 67
67 68 _hash_key = lambda k: md5_safe(k)
68 69
69 70
70 71 # this is propagated from .ini file beaker.session.secret
71 72 # and initialized at environment.py
72 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY: bytes = b''
73 74
74 75 # used to sort permissions by types, '#' used here is not allowed to be in
75 76 # usernames, and it's very early in sorted string.printable table.
76 77 PERMISSION_TYPE_SORT = {
77 78 'admin': '####',
78 79 'write': '###',
79 80 'read': '##',
80 81 'none': '#',
81 82 }
82 83
83 84
84 85 def display_sort(obj):
85 86 """
86 87 Sort function used to sort permissions in .permissions() function of
87 88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 89 of all other resources
89 90 """
90 91
91 92 if obj.username == User.DEFAULT_USER:
92 93 return '#####'
93 94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 95 return prefix + obj.username
95 96
96 97
97 98 class EncryptedValue(TypeDecorator):
98 99 """
99 100 Special column for encrypted data, use like::
100 101
101 102 value = Column("encrypted_value", EncryptedValue(40), nullable=False)
102 103
103 104 This column is intelligent so if value is in unencrypted form it return
104 105 unencrypted form, but on save it always encrypts
105 106 """
106 107 impl = String
107 108
108 109 def process_bind_param(self, value, dialect):
109 if not value:
110 return value
111 if value.startswith('enc$aes$'):
112 # protect against double encrypting if someone manually starts doing
113 raise ValueError('value needs to be in unencrypted format, ie. '
114 'not starting with enc$aes$')
115 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
116
117 def process_result_value(self, value, dialect):
110 """
111 Setter for storing value
112 """
113 import rhodecode
118 114 if not value:
119 115 return value
120 116
121 parts = value.split('$', 3)
122 if not len(parts) == 3:
123 # probably not encrypted values
124 return value
125 else:
126 if parts[0] != 'enc':
127 # parts ok but without our header ?
117 # protect against double encrypting if values is already encrypted
118 if value.startswith('enc$aes$') \
119 or value.startswith('enc$aes_hmac$') \
120 or value.startswith('enc2$'):
121 raise ValueError('value needs to be in unencrypted format, '
122 'ie. not starting with enc$ or enc2$')
123
124 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
125 bytes_val = enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
126 return safe_str(bytes_val)
127
128 def process_result_value(self, value, dialect):
129 """
130 Getter for retrieving value
131 """
132
133 import rhodecode
134 if not value:
128 135 return value
129 136
130 # at that stage we know it's our encryption
131 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
132 return decrypted_data
137 enc_strict_mode = rhodecode.ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
138
139 bytes_val = enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
140
141 return safe_str(bytes_val)
133 142
134 143
135 144 class BaseModel(object):
136 145 """
137 146 Base Model for all classes
138 147 """
139 148
140 149 @classmethod
141 150 def _get_keys(cls):
142 151 """return column names for this model """
143 152 return class_mapper(cls).c.keys()
144 153
145 154 def get_dict(self):
146 155 """
147 156 return dict with keys and values corresponding
148 157 to this model data """
149 158
150 159 d = {}
151 160 for k in self._get_keys():
152 161 d[k] = getattr(self, k)
153 162
154 163 # also use __json__() if present to get additional fields
155 164 _json_attr = getattr(self, '__json__', None)
156 165 if _json_attr:
157 166 # update with attributes from __json__
158 167 if callable(_json_attr):
159 168 _json_attr = _json_attr()
160 169 for k, val in _json_attr.items():
161 170 d[k] = val
162 171 return d
163 172
164 173 def get_appstruct(self):
165 174 """return list with keys and values tuples corresponding
166 175 to this model data """
167 176
168 177 l = []
169 178 for k in self._get_keys():
170 179 l.append((k, getattr(self, k),))
171 180 return l
172 181
173 182 def populate_obj(self, populate_dict):
174 183 """populate model with data from given populate_dict"""
175 184
176 185 for k in self._get_keys():
177 186 if k in populate_dict:
178 187 setattr(self, k, populate_dict[k])
179 188
180 189 @classmethod
181 190 def query(cls):
182 191 return Session().query(cls)
183 192
184 193 @classmethod
185 194 def get(cls, id_):
186 195 if id_:
187 196 return cls.query().get(id_)
188 197
189 198 @classmethod
190 199 def get_or_404(cls, id_):
191 200 try:
192 201 id_ = int(id_)
193 202 except (TypeError, ValueError):
194 203 raise HTTPNotFound
195 204
196 205 res = cls.query().get(id_)
197 206 if not res:
198 207 raise HTTPNotFound
199 208 return res
200 209
201 210 @classmethod
202 211 def getAll(cls):
203 212 # deprecated and left for backward compatibility
204 213 return cls.get_all()
205 214
206 215 @classmethod
207 216 def get_all(cls):
208 217 return cls.query().all()
209 218
210 219 @classmethod
211 220 def delete(cls, id_):
212 221 obj = cls.query().get(id_)
213 222 Session().delete(obj)
214 223
215 224 def __repr__(self):
216 225 if hasattr(self, '__unicode__'):
217 226 # python repr needs to return str
218 227 try:
219 228 return safe_str(self.__unicode__())
220 229 except UnicodeDecodeError:
221 230 pass
222 231 return '<DB:%s>' % (self.__class__.__name__)
223 232
224 233
225 234 class RhodeCodeSetting(Base, BaseModel):
226 235 __tablename__ = 'rhodecode_settings'
227 236 __table_args__ = (
228 237 UniqueConstraint('app_settings_name'),
229 238 {'extend_existing': True, 'mysql_engine': 'InnoDB',
230 239 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
231 240 )
232 241
233 242 SETTINGS_TYPES = {
234 243 'str': safe_str,
235 244 'int': safe_int,
236 245 'unicode': safe_str,
237 246 'bool': str2bool,
238 247 'list': functools.partial(aslist, sep=',')
239 248 }
240 249 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
241 250 GLOBAL_CONF_KEY = 'app_settings'
242 251
243 252 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 253 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
245 254 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
246 255 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
247 256
248 257 def __init__(self, key='', val='', type='unicode'):
249 258 self.app_settings_name = key
250 259 self.app_settings_value = val
251 260 self.app_settings_type = type
252 261
253 262 @validates('_app_settings_value')
254 263 def validate_settings_value(self, key, val):
255 264 assert type(val) == str
256 265 return val
257 266
258 267 @hybrid_property
259 268 def app_settings_value(self):
260 269 v = self._app_settings_value
261 270 _type = self.app_settings_type
262 271 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
263 272 return converter(v)
264 273
265 274 @app_settings_value.setter
266 275 def app_settings_value(self, val):
267 276 """
268 277 Setter that will always make sure we use unicode in app_settings_value
269 278
270 279 :param val:
271 280 """
272 281 self._app_settings_value = safe_str(val)
273 282
274 283 @hybrid_property
275 284 def app_settings_type(self):
276 285 return self._app_settings_type
277 286
278 287 @app_settings_type.setter
279 288 def app_settings_type(self, val):
280 289 if val not in self.SETTINGS_TYPES:
281 290 raise Exception('type must be one of %s got %s'
282 291 % (self.SETTINGS_TYPES.keys(), val))
283 292 self._app_settings_type = val
284 293
285 294 def __unicode__(self):
286 295 return u"<%s('%s:%s[%s]')>" % (
287 296 self.__class__.__name__,
288 297 self.app_settings_name, self.app_settings_value, self.app_settings_type
289 298 )
290 299
291 300
292 301 class RhodeCodeUi(Base, BaseModel):
293 302 __tablename__ = 'rhodecode_ui'
294 303 __table_args__ = (
295 304 UniqueConstraint('ui_key'),
296 305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
298 307 )
299 308
300 309 HOOK_REPO_SIZE = 'changegroup.repo_size'
301 310 # HG
302 311 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
303 312 HOOK_PULL = 'outgoing.pull_logger'
304 313 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
305 314 HOOK_PUSH = 'changegroup.push_logger'
306 315
307 316 # TODO: johbo: Unify way how hooks are configured for git and hg,
308 317 # git part is currently hardcoded.
309 318
310 319 # SVN PATTERNS
311 320 SVN_BRANCH_ID = 'vcs_svn_branch'
312 321 SVN_TAG_ID = 'vcs_svn_tag'
313 322
314 323 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
315 324 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
316 325 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
317 326 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
318 327 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
319 328
320 329 def __repr__(self):
321 330 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
322 331 self.ui_key, self.ui_value)
323 332
324 333
325 334 class RepoRhodeCodeSetting(Base, BaseModel):
326 335 __tablename__ = 'repo_rhodecode_settings'
327 336 __table_args__ = (
328 337 UniqueConstraint(
329 338 'app_settings_name', 'repository_id',
330 339 name='uq_repo_rhodecode_setting_name_repo_id'),
331 340 {'extend_existing': True, 'mysql_engine': 'InnoDB',
332 341 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
333 342 )
334 343
335 344 # TODO: Move it to some common place with RhodeCodeSetting
336 345 SETTINGS_TYPES = {
337 346 'str': safe_str,
338 347 'int': safe_int,
339 348 'unicode': safe_str,
340 349 'bool': str2bool,
341 350 'list': functools.partial(aslist, sep=',')
342 351 }
343 352
344 353 repository_id = Column(
345 354 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
346 355 nullable=False)
347 356 app_settings_id = Column(
348 357 "app_settings_id", Integer(), nullable=False, unique=True,
349 358 default=None, primary_key=True)
350 359 app_settings_name = Column(
351 360 "app_settings_name", String(255), nullable=True, unique=None,
352 361 default=None)
353 362 _app_settings_value = Column(
354 363 "app_settings_value", String(4096), nullable=True, unique=None,
355 364 default=None)
356 365 _app_settings_type = Column(
357 366 "app_settings_type", String(255), nullable=True, unique=None,
358 367 default=None)
359 368
360 369 repository = relationship('Repository')
361 370
362 371 def __init__(self, repository_id, key='', val='', type='unicode'):
363 372 self.repository_id = repository_id
364 373 self.app_settings_name = key
365 374 self.app_settings_value = val
366 375 self.app_settings_type = type
367 376
368 377 @validates('_app_settings_value')
369 378 def validate_settings_value(self, key, val):
370 379 assert type(val) == str
371 380 return val
372 381
373 382 @hybrid_property
374 383 def app_settings_value(self):
375 384 v = self._app_settings_value
376 385 type_ = self.app_settings_type
377 386 converter = (
378 387 self.SETTINGS_TYPES.get(type_) or self.SETTINGS_TYPES['unicode'])
379 388 return converter(v)
380 389
381 390 @app_settings_value.setter
382 391 def app_settings_value(self, val):
383 392 """
384 393 Setter that will always make sure we use unicode in app_settings_value
385 394
386 395 :param val:
387 396 """
388 397 self._app_settings_value = safe_str(val)
389 398
390 399 @hybrid_property
391 400 def app_settings_type(self):
392 401 return self._app_settings_type
393 402
394 403 @app_settings_type.setter
395 404 def app_settings_type(self, val):
396 405 if val not in self.SETTINGS_TYPES:
397 406 raise Exception('type must be one of %s got %s'
398 407 % (self.SETTINGS_TYPES.keys(), val))
399 408 self._app_settings_type = val
400 409
401 410 def __unicode__(self):
402 411 return u"<%s('%s:%s:%s[%s]')>" % (
403 412 self.__class__.__name__, self.repository.repo_name,
404 413 self.app_settings_name, self.app_settings_value,
405 414 self.app_settings_type
406 415 )
407 416
408 417
409 418 class RepoRhodeCodeUi(Base, BaseModel):
410 419 __tablename__ = 'repo_rhodecode_ui'
411 420 __table_args__ = (
412 421 UniqueConstraint(
413 422 'repository_id', 'ui_section', 'ui_key',
414 423 name='uq_repo_rhodecode_ui_repository_id_section_key'),
415 424 {'extend_existing': True, 'mysql_engine': 'InnoDB',
416 425 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
417 426 )
418 427
419 428 repository_id = Column(
420 429 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
421 430 nullable=False)
422 431 ui_id = Column(
423 432 "ui_id", Integer(), nullable=False, unique=True, default=None,
424 433 primary_key=True)
425 434 ui_section = Column(
426 435 "ui_section", String(255), nullable=True, unique=None, default=None)
427 436 ui_key = Column(
428 437 "ui_key", String(255), nullable=True, unique=None, default=None)
429 438 ui_value = Column(
430 439 "ui_value", String(255), nullable=True, unique=None, default=None)
431 440 ui_active = Column(
432 441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
433 442
434 443 repository = relationship('Repository')
435 444
436 445 def __repr__(self):
437 446 return '<%s[%s:%s]%s=>%s]>' % (
438 447 self.__class__.__name__, self.repository.repo_name,
439 448 self.ui_section, self.ui_key, self.ui_value)
440 449
441 450
442 451 class User(Base, BaseModel):
443 452 __tablename__ = 'users'
444 453 __table_args__ = (
445 454 UniqueConstraint('username'), UniqueConstraint('email'),
446 455 Index('u_username_idx', 'username'),
447 456 Index('u_email_idx', 'email'),
448 457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
449 458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
450 459 )
451 460 DEFAULT_USER = 'default'
452 461 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
453 462
454 463 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
455 464 username = Column("username", String(255), nullable=True, unique=None, default=None)
456 465 password = Column("password", String(255), nullable=True, unique=None, default=None)
457 466 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
458 467 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
459 468 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
460 469 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
461 470 _email = Column("email", String(255), nullable=True, unique=None, default=None)
462 471 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
463 472 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
464 473 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
465 474 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
466 475 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
467 476 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
468 477 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
469 478
470 479 user_log = relationship('UserLog')
471 480 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
472 481
473 482 repositories = relationship('Repository')
474 483 repository_groups = relationship('RepoGroup')
475 484 user_groups = relationship('UserGroup')
476 485
477 486 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
478 487 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
479 488
480 489 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
481 490 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
482 491
483 492 group_member = relationship('UserGroupMember', cascade='all')
484 493
485 494 notifications = relationship('UserNotification', cascade='all')
486 495 # notifications assigned to this user
487 496 user_created_notifications = relationship('Notification', cascade='all')
488 497 # comments created by this user
489 498 user_comments = relationship('ChangesetComment', cascade='all')
490 499 user_emails = relationship('UserEmailMap', cascade='all')
491 500 user_ip_map = relationship('UserIpMap', cascade='all')
492 501 user_auth_tokens = relationship('UserApiKeys', cascade='all')
493 502 # gists
494 503 user_gists = relationship('Gist', cascade='all')
495 504 # user pull requests
496 505 user_pull_requests = relationship('PullRequest', cascade='all')
497 506
498 507 def __unicode__(self):
499 508 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
500 509 self.user_id, self.username)
501 510
502 511 @hybrid_property
503 512 def email(self):
504 513 return self._email
505 514
506 515 @email.setter
507 516 def email(self, val):
508 517 self._email = val.lower() if val else None
509 518
510 519 @property
511 520 def firstname(self):
512 521 # alias for future
513 522 return self.name
514 523
515 524 @property
516 525 def username_and_name(self):
517 526 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
518 527
519 528 @property
520 529 def username_or_name_or_email(self):
521 530 full_name = self.full_name if self.full_name is not ' ' else None
522 531 return self.username or full_name or self.email
523 532
524 533 @property
525 534 def full_name(self):
526 535 return '%s %s' % (self.firstname, self.lastname)
527 536
528 537 @property
529 538 def full_contact(self):
530 539 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
531 540
532 541 @property
533 542 def short_contact(self):
534 543 return '%s %s' % (self.firstname, self.lastname)
535 544
536 545 @property
537 546 def is_admin(self):
538 547 return self.admin
539 548
540 549 @classmethod
541 550 def get_by_username(cls, username, case_insensitive=False, cache=False):
542 551 if case_insensitive:
543 552 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
544 553 else:
545 554 q = cls.query().filter(cls.username == username)
546 555
547 556 if cache:
548 557 q = q.options(FromCache(
549 558 "sql_cache_short",
550 559 "get_user_%s" % _hash_key(username)))
551 560 return q.scalar()
552 561
553 562 @classmethod
554 563 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
555 564 q = cls.query().filter(cls.api_key == auth_token)
556 565
557 566 if cache:
558 567 q = q.options(FromCache("sql_cache_short",
559 568 "get_auth_token_%s" % auth_token))
560 569 res = q.scalar()
561 570
562 571 if fallback and not res:
563 572 #fallback to additional keys
564 573 _res = UserApiKeys.query()\
565 574 .filter(UserApiKeys.api_key == auth_token)\
566 575 .filter(or_(UserApiKeys.expires == -1,
567 576 UserApiKeys.expires >= time.time()))\
568 577 .first()
569 578 if _res:
570 579 res = _res.user
571 580 return res
572 581
573 582 @classmethod
574 583 def get_by_email(cls, email, case_insensitive=False, cache=False):
575 584
576 585 if case_insensitive:
577 586 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
578 587
579 588 else:
580 589 q = cls.query().filter(cls.email == email)
581 590
582 591 if cache:
583 592 q = q.options(FromCache("sql_cache_short",
584 593 "get_email_key_%s" % email))
585 594
586 595 ret = q.scalar()
587 596 if ret is None:
588 597 q = UserEmailMap.query()
589 598 # try fetching in alternate email map
590 599 if case_insensitive:
591 600 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
592 601 else:
593 602 q = q.filter(UserEmailMap.email == email)
594 603 q = q.options(joinedload(UserEmailMap.user))
595 604 if cache:
596 605 q = q.options(FromCache("sql_cache_short",
597 606 "get_email_map_key_%s" % email))
598 607 ret = getattr(q.scalar(), 'user', None)
599 608
600 609 return ret
601 610
602 611 @classmethod
603 612 def get_first_admin(cls):
604 613 user = User.query().filter(User.admin == True).first()
605 614 if user is None:
606 615 raise Exception('Missing administrative account!')
607 616 return user
608 617
609 618 @classmethod
610 619 def get_default_user(cls, cache=False):
611 620 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
612 621 if user is None:
613 622 raise Exception('Missing default account!')
614 623 return user
615 624
616 625
617 626 class UserApiKeys(Base, BaseModel):
618 627 __tablename__ = 'user_api_keys'
619 628 __table_args__ = (
620 629 Index('uak_api_key_idx', 'api_key'),
621 630 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
622 631 UniqueConstraint('api_key'),
623 632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
624 633 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
625 634 )
626 635
627 636
628 637 # ApiKey role
629 638 ROLE_ALL = 'token_role_all'
630 639 ROLE_HTTP = 'token_role_http'
631 640 ROLE_VCS = 'token_role_vcs'
632 641 ROLE_API = 'token_role_api'
633 642 ROLE_FEED = 'token_role_feed'
634 643 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
635 644
636 645 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
637 646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
638 647 api_key = Column("api_key", String(255), nullable=False, unique=True)
639 648 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
640 649 expires = Column('expires', Float(53), nullable=False)
641 650 role = Column('role', String(255), nullable=True)
642 651 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
643 652
644 653 user = relationship('User', lazy='joined')
645 654
646 655
647 656 class UserEmailMap(Base, BaseModel):
648 657 __tablename__ = 'user_email_map'
649 658 __table_args__ = (
650 659 Index('uem_email_idx', 'email'),
651 660 UniqueConstraint('email'),
652 661 {'extend_existing': True, 'mysql_engine': 'InnoDB',
653 662 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
654 663 )
655 664
656 665
657 666 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
658 667 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
659 668 _email = Column("email", String(255), nullable=True, unique=False, default=None)
660 669 user = relationship('User', lazy='joined')
661 670
662 671 @validates('_email')
663 672 def validate_email(self, key, email):
664 673 # check if this email is not main one
665 674 main_email = Session().query(User).filter(User.email == email).scalar()
666 675 if main_email is not None:
667 676 raise AttributeError('email %s is present is user table' % email)
668 677 return email
669 678
670 679 @hybrid_property
671 680 def email(self):
672 681 return self._email
673 682
674 683 @email.setter
675 684 def email(self, val):
676 685 self._email = val.lower() if val else None
677 686
678 687
679 688 class UserIpMap(Base, BaseModel):
680 689 __tablename__ = 'user_ip_map'
681 690 __table_args__ = (
682 691 UniqueConstraint('user_id', 'ip_addr'),
683 692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
684 693 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
685 694 )
686 695
687 696
688 697 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
689 698 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
690 699 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
691 700 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
692 701 description = Column("description", String(10000), nullable=True, unique=None, default=None)
693 702 user = relationship('User', lazy='joined')
694 703
695 704 def __unicode__(self):
696 705 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
697 706 self.user_id, self.ip_addr)
698 707
699 708
700 709 class UserLog(Base, BaseModel):
701 710 __tablename__ = 'user_logs'
702 711 __table_args__ = (
703 712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
704 713 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
705 714 )
706 715 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
707 716 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
708 717 username = Column("username", String(255), nullable=True, unique=None, default=None)
709 718 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
710 719 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
711 720 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
712 721 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
713 722 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
714 723
715 724 def __unicode__(self):
716 725 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
717 726 self.repository_name,
718 727 self.action)
719 728
720 729 user = relationship('User')
721 730 repository = relationship('Repository', cascade='')
722 731
723 732
724 733 class UserGroup(Base, BaseModel):
725 734 __tablename__ = 'users_groups'
726 735 __table_args__ = (
727 736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
728 737 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
729 738 )
730 739
731 740 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
732 741 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
733 742 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
734 743 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
735 744 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
736 745 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
737 746 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
738 747 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
739 748
740 749 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
741 750 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
742 751 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
743 752 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
744 753 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
745 754 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
746 755
747 756 user = relationship('User')
748 757
749 758 def __unicode__(self):
750 759 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
751 760 self.users_group_id,
752 761 self.users_group_name)
753 762
754 763 @classmethod
755 764 def get_by_group_name(cls, group_name, cache=False,
756 765 case_insensitive=False):
757 766 if case_insensitive:
758 767 q = cls.query().filter(func.lower(cls.users_group_name) ==
759 768 func.lower(group_name))
760 769
761 770 else:
762 771 q = cls.query().filter(cls.users_group_name == group_name)
763 772 if cache:
764 773 q = q.options(FromCache(
765 774 "sql_cache_short",
766 775 "get_group_%s" % _hash_key(group_name)))
767 776 return q.scalar()
768 777
769 778 @classmethod
770 779 def get(cls, user_group_id, cache=False):
771 780 user_group = cls.query()
772 781 if cache:
773 782 user_group = user_group.options(FromCache("sql_cache_short",
774 783 "get_users_group_%s" % user_group_id))
775 784 return user_group.get(user_group_id)
776 785
777 786
778 787 class UserGroupMember(Base, BaseModel):
779 788 __tablename__ = 'users_groups_members'
780 789 __table_args__ = (
781 790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
782 791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
783 792 )
784 793
785 794 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
786 795 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
787 796 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
788 797
789 798 user = relationship('User', lazy='joined')
790 799 users_group = relationship('UserGroup')
791 800
792 801 def __init__(self, gr_id='', u_id=''):
793 802 self.users_group_id = gr_id
794 803 self.user_id = u_id
795 804
796 805
797 806 class RepositoryField(Base, BaseModel):
798 807 __tablename__ = 'repositories_fields'
799 808 __table_args__ = (
800 809 UniqueConstraint('repository_id', 'field_key'), # no-multi field
801 810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
802 811 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
803 812 )
804 813 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
805 814
806 815 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
807 816 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
808 817 field_key = Column("field_key", String(250))
809 818 field_label = Column("field_label", String(1024), nullable=False)
810 819 field_value = Column("field_value", String(10000), nullable=False)
811 820 field_desc = Column("field_desc", String(1024), nullable=False)
812 821 field_type = Column("field_type", String(255), nullable=False, unique=None)
813 822 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
814 823
815 824 repository = relationship('Repository')
816 825
817 826 @classmethod
818 827 def get_by_key_name(cls, key, repo):
819 828 row = cls.query()\
820 829 .filter(cls.repository == repo)\
821 830 .filter(cls.field_key == key).scalar()
822 831 return row
823 832
824 833
825 834 class Repository(Base, BaseModel):
826 835 __tablename__ = 'repositories'
827 836 __table_args__ = (
828 837 UniqueConstraint('repo_name'),
829 838 Index('r_repo_name_idx', 'repo_name'),
830 839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
831 840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
832 841 )
833 842 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
834 843 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
835 844
836 845 STATE_CREATED = 'repo_state_created'
837 846 STATE_PENDING = 'repo_state_pending'
838 847 STATE_ERROR = 'repo_state_error'
839 848
840 849 LOCK_AUTOMATIC = 'lock_auto'
841 850 LOCK_API = 'lock_api'
842 851 LOCK_WEB = 'lock_web'
843 852 LOCK_PULL = 'lock_pull'
844 853
845 854 NAME_SEP = URL_SEP
846 855
847 856 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
848 857 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
849 858 repo_state = Column("repo_state", String(255), nullable=True)
850 859
851 860 clone_uri = Column("clone_uri", EncryptedValue(255), nullable=True, unique=False, default=None)
852 861 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
853 862 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
854 863 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
855 864 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
856 865 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
857 866 description = Column("description", String(10000), nullable=True, unique=None, default=None)
858 867 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
859 868 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
860 869 _landing_revision = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
861 870 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
862 871 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
863 872 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
864 873
865 874 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
866 875 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
867 876
868 877 user = relationship('User')
869 878 fork = relationship('Repository', remote_side=repo_id)
870 879 group = relationship('RepoGroup')
871 880 repo_to_perm = relationship(
872 881 'UserRepoToPerm', cascade='all',
873 882 order_by='UserRepoToPerm.repo_to_perm_id')
874 883 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
875 884 stats = relationship('Statistics', cascade='all', uselist=False)
876 885
877 886 followers = relationship(
878 887 'UserFollowing',
879 888 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
880 889 cascade='all')
881 890 extra_fields = relationship(
882 891 'RepositoryField', cascade="all, delete, delete-orphan")
883 892 logs = relationship('UserLog')
884 893 comments = relationship(
885 894 'ChangesetComment', cascade="all, delete, delete-orphan")
886 895 pull_requests_org = relationship(
887 896 'PullRequest',
888 897 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
889 898 cascade="all, delete, delete-orphan")
890 899 pull_requests_other = relationship(
891 900 'PullRequest',
892 901 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
893 902 cascade="all, delete, delete-orphan")
894 903 ui = relationship('RepoRhodeCodeUi', cascade="all")
895 904 settings = relationship('RepoRhodeCodeSetting', cascade="all")
896 905
897 906 def __unicode__(self):
898 907 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
899 908 safe_str(self.repo_name))
900 909
901 910 @classmethod
902 911 def get_by_repo_name(cls, repo_name):
903 912 q = Session().query(cls).filter(cls.repo_name == repo_name)
904 913 q = q.options(joinedload(Repository.fork))\
905 914 .options(joinedload(Repository.user))\
906 915 .options(joinedload(Repository.group))
907 916 return q.scalar()
908 917
909 918
910 919 class RepoGroup(Base, BaseModel):
911 920 __tablename__ = 'groups'
912 921 __table_args__ = (
913 922 UniqueConstraint('group_name', 'group_parent_id'),
914 923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
915 924 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
916 925 )
917 926
918 927
919 928 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
920 929 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
921 930 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
922 931 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
923 932 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
924 933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
925 934 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
926 935
927 936 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
928 937 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
929 938 parent_group = relationship('RepoGroup', remote_side=group_id)
930 939 user = relationship('User')
931 940
932 941 def __init__(self, group_name='', parent_group=None):
933 942 self.group_name = group_name
934 943 self.parent_group = parent_group
935 944
936 945 def __unicode__(self):
937 946 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
938 947 self.group_name)
939 948
940 949 @classmethod
941 950 def url_sep(cls):
942 951 return URL_SEP
943 952
944 953 @classmethod
945 954 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
946 955 if case_insensitive:
947 956 gr = cls.query().filter(func.lower(cls.group_name)
948 957 == func.lower(group_name))
949 958 else:
950 959 gr = cls.query().filter(cls.group_name == group_name)
951 960 if cache:
952 961 gr = gr.options(FromCache(
953 962 "sql_cache_short",
954 963 "get_group_%s" % _hash_key(group_name)))
955 964 return gr.scalar()
956 965
957 966
958 967 class Permission(Base, BaseModel):
959 968 __tablename__ = 'permissions'
960 969 __table_args__ = (
961 970 Index('p_perm_name_idx', 'permission_name'),
962 971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
963 972 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
964 973 )
965 974 PERMS = [
966 975 ('hg.admin', _('RhodeCode Super Administrator')),
967 976
968 977 ('repository.none', _('Repository no access')),
969 978 ('repository.read', _('Repository read access')),
970 979 ('repository.write', _('Repository write access')),
971 980 ('repository.admin', _('Repository admin access')),
972 981
973 982 ('group.none', _('Repository group no access')),
974 983 ('group.read', _('Repository group read access')),
975 984 ('group.write', _('Repository group write access')),
976 985 ('group.admin', _('Repository group admin access')),
977 986
978 987 ('usergroup.none', _('User group no access')),
979 988 ('usergroup.read', _('User group read access')),
980 989 ('usergroup.write', _('User group write access')),
981 990 ('usergroup.admin', _('User group admin access')),
982 991
983 992 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
984 993 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
985 994
986 995 ('hg.usergroup.create.false', _('User Group creation disabled')),
987 996 ('hg.usergroup.create.true', _('User Group creation enabled')),
988 997
989 998 ('hg.create.none', _('Repository creation disabled')),
990 999 ('hg.create.repository', _('Repository creation enabled')),
991 1000 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
992 1001 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
993 1002
994 1003 ('hg.fork.none', _('Repository forking disabled')),
995 1004 ('hg.fork.repository', _('Repository forking enabled')),
996 1005
997 1006 ('hg.register.none', _('Registration disabled')),
998 1007 ('hg.register.manual_activate', _('User Registration with manual account activation')),
999 1008 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1000 1009
1001 1010 ('hg.extern_activate.manual', _('Manual activation of external account')),
1002 1011 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1003 1012
1004 1013 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
1005 1014 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
1006 1015 ]
1007 1016
1008 1017 # definition of system default permissions for DEFAULT user
1009 1018 DEFAULT_USER_PERMISSIONS = [
1010 1019 'repository.read',
1011 1020 'group.read',
1012 1021 'usergroup.read',
1013 1022 'hg.create.repository',
1014 1023 'hg.repogroup.create.false',
1015 1024 'hg.usergroup.create.false',
1016 1025 'hg.create.write_on_repogroup.true',
1017 1026 'hg.fork.repository',
1018 1027 'hg.register.manual_activate',
1019 1028 'hg.extern_activate.auto',
1020 1029 'hg.inherit_default_perms.true',
1021 1030 ]
1022 1031
1023 1032 # defines which permissions are more important higher the more important
1024 1033 # Weight defines which permissions are more important.
1025 1034 # The higher number the more important.
1026 1035 PERM_WEIGHTS = {
1027 1036 'repository.none': 0,
1028 1037 'repository.read': 1,
1029 1038 'repository.write': 3,
1030 1039 'repository.admin': 4,
1031 1040
1032 1041 'group.none': 0,
1033 1042 'group.read': 1,
1034 1043 'group.write': 3,
1035 1044 'group.admin': 4,
1036 1045
1037 1046 'usergroup.none': 0,
1038 1047 'usergroup.read': 1,
1039 1048 'usergroup.write': 3,
1040 1049 'usergroup.admin': 4,
1041 1050
1042 1051 'hg.repogroup.create.false': 0,
1043 1052 'hg.repogroup.create.true': 1,
1044 1053
1045 1054 'hg.usergroup.create.false': 0,
1046 1055 'hg.usergroup.create.true': 1,
1047 1056
1048 1057 'hg.fork.none': 0,
1049 1058 'hg.fork.repository': 1,
1050 1059 'hg.create.none': 0,
1051 1060 'hg.create.repository': 1
1052 1061 }
1053 1062
1054 1063 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1055 1064 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
1056 1065 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
1057 1066
1058 1067 def __unicode__(self):
1059 1068 return u"<%s('%s:%s')>" % (
1060 1069 self.__class__.__name__, self.permission_id, self.permission_name
1061 1070 )
1062 1071
1063 1072 @classmethod
1064 1073 def get_by_key(cls, key):
1065 1074 return cls.query().filter(cls.permission_name == key).scalar()
1066 1075
1067 1076
1068 1077 class UserRepoToPerm(Base, BaseModel):
1069 1078 __tablename__ = 'repo_to_perm'
1070 1079 __table_args__ = (
1071 1080 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1072 1081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1073 1082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1074 1083 )
1075 1084 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1076 1085 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1077 1086 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1078 1087 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1079 1088
1080 1089 user = relationship('User')
1081 1090 repository = relationship('Repository')
1082 1091 permission = relationship('Permission')
1083 1092
1084 1093 def __unicode__(self):
1085 1094 return u'<%s => %s >' % (self.user, self.repository)
1086 1095
1087 1096
1088 1097 class UserUserGroupToPerm(Base, BaseModel):
1089 1098 __tablename__ = 'user_user_group_to_perm'
1090 1099 __table_args__ = (
1091 1100 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1092 1101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1093 1102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1094 1103 )
1095 1104 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1096 1105 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1097 1106 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1098 1107 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1099 1108
1100 1109 user = relationship('User')
1101 1110 user_group = relationship('UserGroup')
1102 1111 permission = relationship('Permission')
1103 1112
1104 1113 def __unicode__(self):
1105 1114 return u'<%s => %s >' % (self.user, self.user_group)
1106 1115
1107 1116
1108 1117 class UserToPerm(Base, BaseModel):
1109 1118 __tablename__ = 'user_to_perm'
1110 1119 __table_args__ = (
1111 1120 UniqueConstraint('user_id', 'permission_id'),
1112 1121 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1113 1122 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1114 1123 )
1115 1124 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1116 1125 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1117 1126 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1118 1127
1119 1128 user = relationship('User')
1120 1129 permission = relationship('Permission', lazy='joined')
1121 1130
1122 1131 def __unicode__(self):
1123 1132 return u'<%s => %s >' % (self.user, self.permission)
1124 1133
1125 1134
1126 1135 class UserGroupRepoToPerm(Base, BaseModel):
1127 1136 __tablename__ = 'users_group_repo_to_perm'
1128 1137 __table_args__ = (
1129 1138 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1130 1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1131 1140 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1132 1141 )
1133 1142 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1134 1143 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1135 1144 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1136 1145 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1137 1146
1138 1147 users_group = relationship('UserGroup')
1139 1148 permission = relationship('Permission')
1140 1149 repository = relationship('Repository')
1141 1150
1142 1151 def __unicode__(self):
1143 1152 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1144 1153
1145 1154
1146 1155 class UserGroupUserGroupToPerm(Base, BaseModel):
1147 1156 __tablename__ = 'user_group_user_group_to_perm'
1148 1157 __table_args__ = (
1149 1158 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1150 1159 CheckConstraint('target_user_group_id != user_group_id'),
1151 1160 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1152 1161 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1153 1162 )
1154 1163 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1155 1164 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1156 1165 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1157 1166 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1158 1167
1159 1168 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1160 1169 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1161 1170 permission = relationship('Permission')
1162 1171
1163 1172 def __unicode__(self):
1164 1173 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1165 1174
1166 1175
1167 1176 class UserGroupToPerm(Base, BaseModel):
1168 1177 __tablename__ = 'users_group_to_perm'
1169 1178 __table_args__ = (
1170 1179 UniqueConstraint('users_group_id', 'permission_id',),
1171 1180 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 1181 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 1182 )
1174 1183 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1175 1184 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1176 1185 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1177 1186
1178 1187 users_group = relationship('UserGroup')
1179 1188 permission = relationship('Permission')
1180 1189
1181 1190
1182 1191 class UserRepoGroupToPerm(Base, BaseModel):
1183 1192 __tablename__ = 'user_repo_group_to_perm'
1184 1193 __table_args__ = (
1185 1194 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1186 1195 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1187 1196 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1188 1197 )
1189 1198
1190 1199 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1191 1200 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1192 1201 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1193 1202 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1194 1203
1195 1204 user = relationship('User')
1196 1205 group = relationship('RepoGroup')
1197 1206 permission = relationship('Permission')
1198 1207
1199 1208
1200 1209 class UserGroupRepoGroupToPerm(Base, BaseModel):
1201 1210 __tablename__ = 'users_group_repo_group_to_perm'
1202 1211 __table_args__ = (
1203 1212 UniqueConstraint('users_group_id', 'group_id'),
1204 1213 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1205 1214 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1206 1215 )
1207 1216
1208 1217 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1209 1218 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1210 1219 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1211 1220 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1212 1221
1213 1222 users_group = relationship('UserGroup')
1214 1223 permission = relationship('Permission')
1215 1224 group = relationship('RepoGroup')
1216 1225
1217 1226 @classmethod
1218 1227 def create(cls, user_group, repository_group, permission):
1219 1228 n = cls()
1220 1229 n.users_group = user_group
1221 1230 n.group = repository_group
1222 1231 n.permission = permission
1223 1232 Session().add(n)
1224 1233 return n
1225 1234
1226 1235 def __unicode__(self):
1227 1236 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
1228 1237
1229 1238
1230 1239 class Statistics(Base, BaseModel):
1231 1240 __tablename__ = 'statistics'
1232 1241 __table_args__ = (
1233 1242 UniqueConstraint('repository_id'),
1234 1243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1235 1244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1236 1245 )
1237 1246 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 1247 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1239 1248 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1240 1249 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1241 1250 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1242 1251 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1243 1252
1244 1253 repository = relationship('Repository', single_parent=True)
1245 1254
1246 1255
1247 1256 class UserFollowing(Base, BaseModel):
1248 1257 __tablename__ = 'user_followings'
1249 1258 __table_args__ = (
1250 1259 UniqueConstraint('user_id', 'follows_repository_id'),
1251 1260 UniqueConstraint('user_id', 'follows_user_id'),
1252 1261 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 1262 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1254 1263 )
1255 1264
1256 1265 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 1266 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1258 1267 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1259 1268 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1260 1269 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1261 1270
1262 1271 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1263 1272
1264 1273 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1265 1274 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1266 1275
1267 1276
1268 1277 class CacheKey(Base, BaseModel):
1269 1278 __tablename__ = 'cache_invalidation'
1270 1279 __table_args__ = (
1271 1280 UniqueConstraint('cache_key'),
1272 1281 Index('key_idx', 'cache_key'),
1273 1282 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1274 1283 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1275 1284 )
1276 1285 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1277 1286 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1278 1287 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1279 1288 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1280 1289
1281 1290 def __init__(self, cache_key, cache_args=''):
1282 1291 self.cache_key = cache_key
1283 1292 self.cache_args = cache_args
1284 1293 self.cache_active = False
1285 1294
1286 1295
1287 1296 class ChangesetComment(Base, BaseModel):
1288 1297 __tablename__ = 'changeset_comments'
1289 1298 __table_args__ = (
1290 1299 Index('cc_revision_idx', 'revision'),
1291 1300 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1292 1301 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1293 1302 )
1294 1303
1295 1304 COMMENT_OUTDATED = u'comment_outdated'
1296 1305
1297 1306 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1298 1307 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1299 1308 revision = Column('revision', String(40), nullable=True)
1300 1309 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1301 1310 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
1302 1311 line_no = Column('line_no', Unicode(10), nullable=True)
1303 1312 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1304 1313 f_path = Column('f_path', Unicode(1000), nullable=True)
1305 1314 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1306 1315 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1307 1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1308 1317 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1309 1318 renderer = Column('renderer', Unicode(64), nullable=True)
1310 1319 display_state = Column('display_state', Unicode(128), nullable=True)
1311 1320
1312 1321 author = relationship('User', lazy='joined')
1313 1322 repo = relationship('Repository')
1314 1323 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1315 1324 pull_request = relationship('PullRequest', lazy='joined')
1316 1325 pull_request_version = relationship('PullRequestVersion')
1317 1326
1318 1327 def __repr__(self):
1319 1328 if self.comment_id:
1320 1329 return '<DB:ChangesetComment #%s>' % self.comment_id
1321 1330 else:
1322 1331 return '<DB:ChangesetComment at %#x>' % id(self)
1323 1332
1324 1333
1325 1334 class ChangesetStatus(Base, BaseModel):
1326 1335 __tablename__ = 'changeset_statuses'
1327 1336 __table_args__ = (
1328 1337 Index('cs_revision_idx', 'revision'),
1329 1338 Index('cs_version_idx', 'version'),
1330 1339 UniqueConstraint('repo_id', 'revision', 'version'),
1331 1340 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1332 1341 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1333 1342 )
1334 1343 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1335 1344 STATUS_APPROVED = 'approved'
1336 1345 STATUS_REJECTED = 'rejected'
1337 1346 STATUS_UNDER_REVIEW = 'under_review'
1338 1347
1339 1348 STATUSES = [
1340 1349 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1341 1350 (STATUS_APPROVED, _("Approved")),
1342 1351 (STATUS_REJECTED, _("Rejected")),
1343 1352 (STATUS_UNDER_REVIEW, _("Under Review")),
1344 1353 ]
1345 1354
1346 1355 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1347 1356 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1348 1357 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1349 1358 revision = Column('revision', String(40), nullable=False)
1350 1359 status = Column('status', String(128), nullable=False, default=DEFAULT)
1351 1360 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1352 1361 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1353 1362 version = Column('version', Integer(), nullable=False, default=0)
1354 1363 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1355 1364
1356 1365 author = relationship('User', lazy='joined')
1357 1366 repo = relationship('Repository')
1358 1367 comment = relationship('ChangesetComment', lazy='joined')
1359 1368 pull_request = relationship('PullRequest', lazy='joined')
1360 1369
1361 1370 def __unicode__(self):
1362 1371 return u"<%s('%s[%s]:%s')>" % (
1363 1372 self.__class__.__name__,
1364 1373 self.status, self.version, self.author
1365 1374 )
1366 1375
1367 1376
1368 1377 class _PullRequestBase(BaseModel):
1369 1378 """
1370 1379 Common attributes of pull request and version entries.
1371 1380 """
1372 1381
1373 1382 STATUS_NEW = u'new'
1374 1383 STATUS_OPEN = u'open'
1375 1384 STATUS_CLOSED = u'closed'
1376 1385
1377 1386 title = Column('title', Unicode(255), nullable=True)
1378 1387 description = Column(
1379 1388 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
1380 1389 nullable=True)
1381 1390
1382 1391 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
1383 1392 created_on = Column(
1384 1393 'created_on', DateTime(timezone=False), nullable=False,
1385 1394 default=datetime.datetime.now)
1386 1395 updated_on = Column(
1387 1396 'updated_on', DateTime(timezone=False), nullable=False,
1388 1397 default=datetime.datetime.now)
1389 1398
1390 1399 @declared_attr
1391 1400 def user_id(cls):
1392 1401 return Column(
1393 1402 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1394 1403 unique=None)
1395 1404
1396 1405 _revisions = Column(
1397 1406 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1398 1407
1399 1408 @declared_attr
1400 1409 def org_repo_id(cls):
1401 1410 return Column(
1402 1411 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
1403 1412 nullable=False)
1404 1413
1405 1414 org_ref = Column('org_ref', Unicode(255), nullable=False)
1406 1415
1407 1416 @declared_attr
1408 1417 def other_repo_id(cls):
1409 1418 return Column(
1410 1419 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
1411 1420 nullable=False)
1412 1421
1413 1422 other_ref = Column('other_ref', Unicode(255), nullable=False)
1414 1423 _last_merge_org_rev = Column(
1415 1424 'last_merge_org_rev', String(40), nullable=True)
1416 1425 _last_merge_other_rev = Column(
1417 1426 'last_merge_other_rev', String(40), nullable=True)
1418 1427 _last_merge_status = Column('merge_status', Integer(), nullable=True)
1419 1428 merge_rev = Column('merge_rev', String(40), nullable=True)
1420 1429
1421 1430 @declared_attr
1422 1431 def author(cls):
1423 1432 return relationship('User', lazy='joined')
1424 1433
1425 1434 @declared_attr
1426 1435 def source_repo(cls):
1427 1436 return relationship(
1428 1437 'Repository',
1429 1438 primaryjoin='%s.org_repo_id==Repository.repo_id' % cls.__name__)
1430 1439
1431 1440 @declared_attr
1432 1441 def target_repo(cls):
1433 1442 return relationship(
1434 1443 'Repository',
1435 1444 primaryjoin='%s.other_repo_id==Repository.repo_id' % cls.__name__)
1436 1445
1437 1446
1438 1447 class PullRequest(Base, _PullRequestBase):
1439 1448 __tablename__ = 'pull_requests'
1440 1449 __table_args__ = (
1441 1450 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1442 1451 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1443 1452 )
1444 1453
1445 1454 pull_request_id = Column(
1446 1455 'pull_request_id', Integer(), nullable=False, primary_key=True)
1447 1456
1448 1457 def __repr__(self):
1449 1458 if self.pull_request_id:
1450 1459 return '<DB:PullRequest #%s>' % self.pull_request_id
1451 1460 else:
1452 1461 return '<DB:PullRequest at %#x>' % id(self)
1453 1462
1454 1463 reviewers = relationship('PullRequestReviewers',
1455 1464 cascade="all, delete, delete-orphan")
1456 1465 statuses = relationship('ChangesetStatus')
1457 1466 comments = relationship('ChangesetComment',
1458 1467 cascade="all, delete, delete-orphan")
1459 1468 versions = relationship('PullRequestVersion',
1460 1469 cascade="all, delete, delete-orphan")
1461 1470
1462 1471
1463 1472 class PullRequestVersion(Base, _PullRequestBase):
1464 1473 __tablename__ = 'pull_request_versions'
1465 1474 __table_args__ = (
1466 1475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1467 1476 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1468 1477 )
1469 1478
1470 1479 pull_request_version_id = Column(
1471 1480 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
1472 1481 pull_request_id = Column(
1473 1482 'pull_request_id', Integer(),
1474 1483 ForeignKey('pull_requests.pull_request_id'), nullable=False)
1475 1484 pull_request = relationship('PullRequest')
1476 1485
1477 1486 def __repr__(self):
1478 1487 if self.pull_request_version_id:
1479 1488 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
1480 1489 else:
1481 1490 return '<DB:PullRequestVersion at %#x>' % id(self)
1482 1491
1483 1492
1484 1493 class PullRequestReviewers(Base, BaseModel):
1485 1494 __tablename__ = 'pull_request_reviewers'
1486 1495 __table_args__ = (
1487 1496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1488 1497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1489 1498 )
1490 1499
1491 1500 def __init__(self, user=None, pull_request=None):
1492 1501 self.user = user
1493 1502 self.pull_request = pull_request
1494 1503
1495 1504 pull_requests_reviewers_id = Column(
1496 1505 'pull_requests_reviewers_id', Integer(), nullable=False,
1497 1506 primary_key=True)
1498 1507 pull_request_id = Column(
1499 1508 "pull_request_id", Integer(),
1500 1509 ForeignKey('pull_requests.pull_request_id'), nullable=False)
1501 1510 user_id = Column(
1502 1511 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1503 1512
1504 1513 user = relationship('User')
1505 1514 pull_request = relationship('PullRequest')
1506 1515
1507 1516
1508 1517 class Notification(Base, BaseModel):
1509 1518 __tablename__ = 'notifications'
1510 1519 __table_args__ = (
1511 1520 Index('notification_type_idx', 'type'),
1512 1521 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1513 1522 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1514 1523 )
1515 1524
1516 1525 TYPE_CHANGESET_COMMENT = u'cs_comment'
1517 1526 TYPE_MESSAGE = u'message'
1518 1527 TYPE_MENTION = u'mention'
1519 1528 TYPE_REGISTRATION = u'registration'
1520 1529 TYPE_PULL_REQUEST = u'pull_request'
1521 1530 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1522 1531
1523 1532 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1524 1533 subject = Column('subject', Unicode(512), nullable=True)
1525 1534 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1526 1535 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1527 1536 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1528 1537 type_ = Column('type', Unicode(255))
1529 1538
1530 1539 created_by_user = relationship('User')
1531 1540 notifications_to_users = relationship('UserNotification', lazy='joined',
1532 1541 cascade="all, delete, delete-orphan")
1533 1542
1534 1543
1535 1544 class UserNotification(Base, BaseModel):
1536 1545 __tablename__ = 'user_to_notification'
1537 1546 __table_args__ = (
1538 1547 UniqueConstraint('user_id', 'notification_id'),
1539 1548 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 1549 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1541 1550 )
1542 1551 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1543 1552 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1544 1553 read = Column('read', Boolean, default=False)
1545 1554 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1546 1555
1547 1556 user = relationship('User', lazy="joined")
1548 1557 notification = relationship('Notification', lazy="joined",
1549 1558 order_by=lambda: Notification.created_on.desc(),)
1550 1559
1551 1560
1552 1561 class Gist(Base, BaseModel):
1553 1562 __tablename__ = 'gists'
1554 1563 __table_args__ = (
1555 1564 Index('g_gist_access_id_idx', 'gist_access_id'),
1556 1565 Index('g_created_on_idx', 'created_on'),
1557 1566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1558 1567 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1559 1568 )
1560 1569 GIST_PUBLIC = u'public'
1561 1570 GIST_PRIVATE = u'private'
1562 1571 DEFAULT_FILENAME = u'gistfile1.txt'
1563 1572
1564 1573 ACL_LEVEL_PUBLIC = u'acl_public'
1565 1574 ACL_LEVEL_PRIVATE = u'acl_private'
1566 1575
1567 1576 gist_id = Column('gist_id', Integer(), primary_key=True)
1568 1577 gist_access_id = Column('gist_access_id', Unicode(250))
1569 1578 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1570 1579 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1571 1580 gist_expires = Column('gist_expires', Float(53), nullable=False)
1572 1581 gist_type = Column('gist_type', Unicode(128), nullable=False)
1573 1582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1574 1583 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1575 1584 acl_level = Column('acl_level', Unicode(128), nullable=True)
1576 1585
1577 1586 owner = relationship('User')
1578 1587
1579 1588 def __repr__(self):
1580 1589 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
1581 1590
1582 1591
1583 1592 class DbMigrateVersion(Base, BaseModel):
1584 1593 __tablename__ = 'db_migrate_version'
1585 1594 __table_args__ = (
1586 1595 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1587 1596 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1588 1597 )
1589 1598 repository_id = Column('repository_id', String(250), primary_key=True)
1590 1599 repository_path = Column('repository_path', Text)
1591 1600 version = Column('version', Integer)
@@ -1,134 +1,134 b''
1 1 import logging
2 2 import datetime
3 3
4 4 from sqlalchemy import *
5 5 from sqlalchemy.exc import DatabaseError
6 6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 7 from sqlalchemy.orm.session import Session
8 8 from sqlalchemy.ext.declarative import declarative_base
9 9
10 10 from rhodecode.lib.dbmigrate.migrate import *
11 11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 12
13 13 from rhodecode.model.meta import Base
14 14 from rhodecode.model import meta
15 15 from rhodecode.lib.dbmigrate.versions import _reset_base
16 16
17 17 log = logging.getLogger(__name__)
18 18
19 19
20 20 def upgrade(migrate_engine):
21 21 """
22 22 Upgrade operations go here.
23 23 Don't create your own engine; bind migrate_engine to your metadata
24 24 """
25 25 _reset_base(migrate_engine)
26 26 from rhodecode.lib.dbmigrate.schema import db_1_5_0
27 27 #==========================================================================
28 28 # USER LOGS
29 29 #==========================================================================
30 30
31 31 tbl = db_1_5_0.UserLog.__table__
32 32 username = Column("username", String(255, convert_unicode=False),
33 33 nullable=True, unique=None, default=None)
34 34 # create username column
35 35 username.create(table=tbl)
36 36
37 37 _Session = meta.Session()
38 ## after adding that column fix all usernames
38 # after adding that column fix all usernames
39 39 users_log = _Session.query(db_1_5_0.UserLog)\
40 40 .options(joinedload(db_1_5_0.UserLog.user))\
41 41 .options(joinedload(db_1_5_0.UserLog.repository)).all()
42 42
43 43 for entry in users_log:
44 44 entry.username = entry.user.username
45 45 _Session.add(entry)
46 46 _Session.commit()
47 47
48 48 #alter username to not null
49 49 tbl_name = db_1_5_0.UserLog.__tablename__
50 50 tbl = Table(tbl_name,
51 51 MetaData(bind=migrate_engine), autoload=True,
52 52 autoload_with=migrate_engine)
53 53 col = tbl.columns.username
54 54
55 55 # remove nullability from revision field
56 56 col.alter(nullable=False)
57 57
58 58 # issue fixups
59 59 fixups(db_1_5_0, meta.Session)
60 60
61 61
62 62 def downgrade(migrate_engine):
63 63 meta = MetaData()
64 64 meta.bind = migrate_engine
65 65
66 66
67 67 def get_by_key(cls, key):
68 68 return cls.query().filter(cls.permission_name == key).scalar()
69 69
70 70
71 71 def get_by_name(cls, key):
72 72 return cls.query().filter(cls.app_settings_name == key).scalar()
73 73
74 74
75 75 def fixups(models, _SESSION):
76 76 # ** create default permissions ** #
77 #=====================================
77
78 78 for p in models.Permission.PERMS:
79 79 if not get_by_key(models.Permission, p[0]):
80 80 new_perm = models.Permission()
81 81 new_perm.permission_name = p[0]
82 82 new_perm.permission_longname = p[0] #translation err with p[1]
83 83 print('Creating new permission %s' % p[0])
84 84 _SESSION().add(new_perm)
85 85
86 86 _SESSION().commit()
87 87
88 88 # ** populate default permissions ** #
89 #=====================================
90 89
91 90 user = models.User.query().filter(models.User.username == 'default').scalar()
92 91
93 92 def _make_perm(perm):
94 93 new_perm = models.UserToPerm()
95 94 new_perm.user = user
96 95 new_perm.permission = get_by_key(models.Permission, perm)
97 96 return new_perm
98 97
99 98 def _get_group(perm_name):
100 99 return '.'.join(perm_name.split('.')[:1])
101 100
102 101 perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
103 defined_perms_groups = map(
104 _get_group, (x.permission.permission_name for x in perms))
105 log.debug('GOT ALREADY DEFINED:%s', perms)
102 defined_perms_groups = list(map(
103 _get_group, (x.permission.permission_name for x in perms)))
104 perms_show = [p.__dict__ for p in perms]
105 log.debug('GOT ALREADY DEFINED:%s', perms_show)
106 106 DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
107 107
108 108 # for every default permission that needs to be created, we check if
109 109 # it's group is already defined, if it's not we create default perm
110 110 for perm_name in DEFAULT_PERMS:
111 111 gr = _get_group(perm_name)
112 112 if gr not in defined_perms_groups:
113 113 log.debug('GR:%s not found, creating permission %s', gr, perm_name)
114 114 new_perm = _make_perm(perm_name)
115 115 _SESSION().add(new_perm)
116 116 _SESSION().commit()
117 117
118 118 # ** create default options ** #
119 119 #===============================
120 120 skip_existing = True
121 121 for k, v in [
122 122 ('default_repo_enable_locking', False),
123 123 ('default_repo_enable_downloads', False),
124 124 ('default_repo_enable_statistics', False),
125 125 ('default_repo_private', False),
126 126 ('default_repo_type', 'hg')]:
127 127
128 128 if skip_existing and get_by_name(models.RhodeCodeSetting, k) is not None:
129 129 log.debug('Skipping option %s', k)
130 130 continue
131 131 setting = models.RhodeCodeSetting(k, v)
132 132 _SESSION().add(setting)
133 133
134 134 _SESSION().commit()
@@ -1,149 +1,152 b''
1 1 import logging
2 2 import datetime
3 3
4 4 from sqlalchemy import *
5 5 from sqlalchemy.exc import DatabaseError
6 6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 7 from sqlalchemy.orm.session import Session
8 8 from sqlalchemy.ext.declarative import declarative_base
9 9
10 10 from rhodecode.lib.dbmigrate.migrate import *
11 11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 12
13 13 from rhodecode.model.meta import Base
14 14 from rhodecode.model import meta
15 15 from rhodecode.lib.dbmigrate.versions import _reset_base
16 16
17 17 log = logging.getLogger(__name__)
18 18
19 19
20 20 def upgrade(migrate_engine):
21 21 """
22 22 Upgrade operations go here.
23 23 Don't create your own engine; bind migrate_engine to your metadata
24 24 """
25 25 _reset_base(migrate_engine)
26 26 from rhodecode.lib.dbmigrate.schema import db_1_7_0
27 27
28 28 #==========================================================================
29 29 # UserUserGroupToPerm
30 30 #==========================================================================
31 31 tbl = db_1_7_0.UserUserGroupToPerm.__table__
32 32 tbl.create()
33 33
34 34 #==========================================================================
35 35 # UserGroupUserGroupToPerm
36 36 #==========================================================================
37 37 tbl = db_1_7_0.UserGroupUserGroupToPerm.__table__
38 38 tbl.create()
39 39
40 40 #==========================================================================
41 41 # Gist
42 42 #==========================================================================
43 43 tbl = db_1_7_0.Gist.__table__
44 44 tbl.create()
45 45
46 46 #==========================================================================
47 47 # UserGroup
48 48 #==========================================================================
49 49 tbl = db_1_7_0.UserGroup.__table__
50 50 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
51 51 nullable=True, unique=False, default=None)
52 52 # create username column
53 53 user_id.create(table=tbl)
54 54
55 55 #==========================================================================
56 56 # RepoGroup
57 57 #==========================================================================
58 58 tbl = db_1_7_0.RepoGroup.__table__
59 59 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
60 60 nullable=True, unique=False, default=None)
61 61 # create username column
62 62 user_id.create(table=tbl)
63 63
64 64 # issue fixups
65 65 fixups(db_1_7_0, meta.Session)
66 66
67 67
68 68 def downgrade(migrate_engine):
69 69 meta = MetaData()
70 70 meta.bind = migrate_engine
71 71
72 72
73 def get_by_key(cls, key):
74 return cls.query().filter(cls.permission_name == key).scalar()
75
76
73 77 def fixups(models, _SESSION):
74 78 # ** create default permissions ** #
75 79 #=====================================
76 80 for p in models.Permission.PERMS:
77 if not models.Permission.get_by_key(p[0]):
81 if not get_by_key(models.Permission, p[0]):
78 82 new_perm = models.Permission()
79 83 new_perm.permission_name = p[0]
80 84 new_perm.permission_longname = p[0] #translation err with p[1]
81 85 _SESSION().add(new_perm)
82 86
83 87 _SESSION().commit()
84 88
85 89 # ** populate default permissions ** #
86 90 #=====================================
87 91
88 92 user = models.User.query().filter(models.User.username == 'default').scalar()
89 93
90 94 def _make_perm(perm):
91 95 new_perm = models.UserToPerm()
92 96 new_perm.user = user
93 97 new_perm.permission = models.Permission.get_by_key(perm)
94 98 return new_perm
95 99
96 100 def _get_group(perm_name):
97 101 return '.'.join(perm_name.split('.')[:1])
98 102
99 103 perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
100 defined_perms_groups = map(_get_group,
101 (x.permission.permission_name for x in perms))
104 defined_perms_groups = list(map(_get_group, (x.permission.permission_name for x in perms)))
102 105 log.debug('GOT ALREADY DEFINED:%s', perms)
103 106 DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
104 107
105 108 # for every default permission that needs to be created, we check if
106 109 # it's group is already defined, if it's not we create default perm
107 110 for perm_name in DEFAULT_PERMS:
108 111 gr = _get_group(perm_name)
109 112 if gr not in defined_perms_groups:
110 113 log.debug('GR:%s not found, creating permission %s', gr, perm_name)
111 114 new_perm = _make_perm(perm_name)
112 115 _SESSION().add(new_perm)
113 116 _SESSION().commit()
114 117
115 118 #fix all usergroups
116 119
117 120 def _create_default_perms(user_group):
118 121 # create default permission
119 122 default_perm = 'usergroup.read'
120 123 def_user = models.User.get_default_user()
121 124 for p in def_user.user_perms:
122 125 if p.permission.permission_name.startswith('usergroup.'):
123 126 default_perm = p.permission.permission_name
124 127 break
125 128
126 129 user_group_to_perm = models.UserUserGroupToPerm()
127 130 user_group_to_perm.permission = models.Permission.get_by_key(default_perm)
128 131
129 132 user_group_to_perm.user_group = user_group
130 133 user_group_to_perm.user_id = def_user.user_id
131 134 return user_group_to_perm
132 135
133 136 for ug in models.UserGroup.get_all():
134 137 perm_obj = _create_default_perms(ug)
135 138 _SESSION().add(perm_obj)
136 139 _SESSION().commit()
137 140
138 141 adm = models.User.get_first_admin()
139 142 # fix owners of UserGroup
140 143 for ug in _SESSION().query(models.UserGroup).all():
141 144 ug.user_id = adm.user_id
142 145 _SESSION().add(ug)
143 146 _SESSION().commit()
144 147
145 148 # fix owners of RepoGroup
146 149 for ug in _SESSION().query(models.RepoGroup).all():
147 150 ug.user_id = adm.user_id
148 151 _SESSION().add(ug)
149 152 _SESSION().commit()
@@ -1,88 +1,87 b''
1 1
2 2
3 3 import hashlib
4 4 import logging
5 5
6 6 from alembic.migration import MigrationContext
7 7 from alembic.operations import Operations
8 8 from sqlalchemy import Text, String, Column
9 9 from sqlalchemy.engine import reflection
10 10 from sqlalchemy.sql import text
11 11
12 12 from rhodecode.lib.dbmigrate.versions import _reset_base
13 from rhodecode.lib.hash_utils import sha1_safe
13 14 from rhodecode.lib.utils2 import safe_str
14 15 from rhodecode.model import meta, init_model_encryption
15 16
16 17
17 18 log = logging.getLogger(__name__)
18 19
19 20
20 21 def upgrade(migrate_engine):
21 22 """
22 23 Upgrade operations go here.
23 24 Don't create your own engine; bind migrate_engine to your metadata
24 25 """
25 26 _reset_base(migrate_engine)
26 27 from rhodecode.lib.dbmigrate.schema import db_3_7_0_0
27 28
28 29 init_model_encryption(db_3_7_0_0)
29 30
30 31 context = MigrationContext.configure(migrate_engine.connect())
31 32 op = Operations(context)
32 33
33 34 repository = db_3_7_0_0.Repository.__table__
34 35 repo_name_column = repository.columns.repo_name
35 36 clone_uri_column = repository.columns.clone_uri
36 37
37 38 indexes = _get_indexes_list(migrate_engine, repository.name)
38 39 repo_name_indexes = [
39 40 i['name'] for i in indexes if 'repo_name' in i['column_names']]
40 41 constraints = _get_unique_constraint_list(migrate_engine, repository.name)
41 42 repo_name_constraints = [
42 43 c['name'] for c in constraints if 'repo_name' in c['column_names']]
43 44
44 45 with op.batch_alter_table(repository.name) as batch_op:
45 46 repo_name_idx = 'r_repo_name_idx'
46 47 if repo_name_idx in repo_name_indexes:
47 48 batch_op.drop_index(repo_name_idx)
48 49 for name in repo_name_constraints:
49 50 if name: # sqlite can have this empty, then it raises an error
50 51 batch_op.drop_constraint(name, type_='unique')
51 52
52 53 batch_op.alter_column(repo_name_column.name, type_=Text)
53 54 batch_op.alter_column(clone_uri_column.name, type_=Text)
54 55 batch_op.create_index(
55 56 'r_repo_name_idx', ['repo_name'], mysql_length=255)
56 57 batch_op.add_column(Column('repo_name_hash', String(40), unique=False))
57 58
58 59 _generate_repo_name_hashes(db_3_7_0_0, op, meta.Session)
59 60
60 61 with op.batch_alter_table(repository.name) as batch_op:
61 62 batch_op.create_unique_constraint(
62 63 'uq_repo_name_hash', ['repo_name_hash'])
63 64
64 65
65 66 def downgrade(migrate_engine):
66 67 pass
67 68
68 69
69 70 def _generate_repo_name_hashes(models, op, session):
70 71 repositories = models.Repository.get_all()
71 72 for repository in repositories:
72 hash_ = hashlib.sha1(safe_str(repository.repo_name)).hexdigest()
73 hash_ = sha1_safe(repository.repo_name)
73 74 params = {'hash': hash_, 'id': repository.repo_id}
74 query = text(
75 'UPDATE repositories SET repo_name_hash = :hash'
76 ' WHERE repo_id = :id').bindparams(**params)
75 query = text('UPDATE repositories SET repo_name_hash = :hash WHERE repo_id = :id').bindparams(**params)
77 76 op.execute(query)
78 77 session().commit()
79 78
80 79
81 80 def _get_unique_constraint_list(migrate_engine, table_name):
82 81 inspector = reflection.Inspector.from_engine(migrate_engine)
83 82 return inspector.get_unique_constraints(table_name)
84 83
85 84
86 85 def _get_indexes_list(migrate_engine, table_name):
87 86 inspector = reflection.Inspector.from_engine(migrate_engine)
88 87 return inspector.get_indexes(table_name)
@@ -1,150 +1,154 b''
1 1 # Copyright (C) 2014-2023 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 """
21 21 Generic encryption library for RhodeCode
22 22 """
23 23
24 24 import base64
25 25 import logging
26 26
27 27 from Crypto.Cipher import AES
28 28 from Crypto import Random
29 29 from Crypto.Hash import HMAC, SHA256
30 30
31 31 from rhodecode.lib.str_utils import safe_bytes, safe_str
32 32 from rhodecode.lib.exceptions import signature_verification_error
33 33
34 34
35 35 class InvalidDecryptedValue(str):
36 36
37 37 def __new__(cls, content):
38 38 """
39 39 This will generate something like this::
40 40 <InvalidDecryptedValue(QkWusFgLJXR6m42v...)>
41 41 And represent a safe indicator that encryption key is broken
42 42 """
43 43 content = f'<{cls.__name__}({content[:16]}...)>'
44 44 return str.__new__(cls, content)
45 45
46 46 KEY_FORMAT = b'enc$aes_hmac${1}'
47 47
48 48
49 49 class AESCipher(object):
50 50
51 51 def __init__(self, key: bytes, hmac=False, strict_verification=True):
52 52
53 53 if not key:
54 54 raise ValueError('passed key variable is empty')
55 55 self.strict_verification = strict_verification
56 56 self.block_size = 32
57 57 self.hmac_size = 32
58 58 self.hmac = hmac
59 59
60 60 self.key = SHA256.new(safe_bytes(key)).digest()
61 61 self.hmac_key = SHA256.new(self.key).digest()
62 62
63 63 def verify_hmac_signature(self, raw_data):
64 64 org_hmac_signature = raw_data[-self.hmac_size:]
65 65 data_without_sig = raw_data[:-self.hmac_size]
66 66 recomputed_hmac = HMAC.new(
67 67 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
68 68 return org_hmac_signature == recomputed_hmac
69 69
70 70 def encrypt(self, raw: bytes):
71 71 raw = self._pad(raw)
72 72 iv = Random.new().read(AES.block_size)
73 73 cipher = AES.new(self.key, AES.MODE_CBC, iv)
74 74 enc_value = cipher.encrypt(raw)
75 75
76 76 hmac_signature = b''
77 77 if self.hmac:
78 78 # compute hmac+sha256 on iv + enc text, we use
79 79 # encrypt then mac method to create the signature
80 80 hmac_signature = HMAC.new(
81 81 self.hmac_key, iv + enc_value, digestmod=SHA256).digest()
82 82
83 83 return base64.b64encode(iv + enc_value + hmac_signature)
84 84
85 85 def decrypt(self, enc, safe=True) -> bytes | InvalidDecryptedValue:
86 86 enc_org = enc
87 87 try:
88 88 enc = base64.b64decode(enc)
89 89 except Exception:
90 90 logging.exception('Failed Base64 decode')
91 91 raise signature_verification_error('Failed Base64 decode')
92 92
93 93 if self.hmac and len(enc) > self.hmac_size:
94 94 if self.verify_hmac_signature(enc):
95 95 # cut off the HMAC verification digest
96 96 enc = enc[:-self.hmac_size]
97 97 else:
98 98
99 99 decrypt_fail = InvalidDecryptedValue(safe_str(enc_org))
100 100 if safe:
101 101 return decrypt_fail
102 102 raise signature_verification_error(decrypt_fail)
103 103
104 104 iv = enc[:AES.block_size]
105 105 cipher = AES.new(self.key, AES.MODE_CBC, iv)
106 106 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
107 107
108 108 def _pad(self, s):
109 109 block_pad = (self.block_size - len(s) % self.block_size)
110 110 return s + block_pad * safe_bytes(chr(block_pad))
111 111
112 112 @staticmethod
113 113 def _unpad(s):
114 114 return s[:-ord(s[len(s)-1:])]
115 115
116 116
117 117 def validate_and_decrypt_data(enc_data, enc_key, enc_strict_mode=False, safe=True):
118 118 enc_data = safe_str(enc_data)
119 119
120 if '$' not in enc_data:
121 # probably not encrypted values
122 return enc_data
123
120 124 parts = enc_data.split('$', 3)
121 125 if len(parts) != 3:
122 raise ValueError(f'Encrypted Data has invalid format, expected {KEY_FORMAT}, got {parts}')
126 raise ValueError(f'Encrypted Data has invalid format, expected {KEY_FORMAT}, got {parts}, org value: {enc_data}')
123 127
124 128 enc_type = parts[1]
125 129 enc_data_part = parts[2]
126 130
127 131 if parts[0] != 'enc':
128 132 # parts ok but without our header ?
129 133 return enc_data
130 134
131 135 # at that stage we know it's our encryption
132 136 if enc_type == 'aes':
133 137 decrypted_data = AESCipher(enc_key).decrypt(enc_data_part, safe=safe)
134 138 elif enc_type == 'aes_hmac':
135 139 decrypted_data = AESCipher(
136 140 enc_key, hmac=True,
137 141 strict_verification=enc_strict_mode).decrypt(enc_data_part, safe=safe)
138 142
139 143 else:
140 144 raise ValueError(
141 145 f'Encryption type part is wrong, must be `aes` '
142 146 f'or `aes_hmac`, got `{enc_type}` instead')
143 147
144 148 return decrypted_data
145 149
146 150
147 151 def encrypt_data(data, enc_key: bytes):
148 152 enc_key = safe_bytes(enc_key)
149 153 enc_value = AESCipher(enc_key, hmac=True).encrypt(safe_bytes(data))
150 154 return KEY_FORMAT.replace(b'{1}', enc_value)
General Comments 0
You need to be logged in to leave comments. Login now