##// END OF EJS Templates
users: ensure get_first_superadmin actually gets the 1st created super-admin.
marcink -
r3085:dd0ed7cf default
parent child Browse files
Show More
@@ -1,4700 +1,4704 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import re
26 26 import os
27 27 import time
28 28 import hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37 from sqlalchemy import (
38 38 or_, and_, not_, func, TypeDecorator, event,
39 39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 41 Text, Float, PickleType)
42 42 from sqlalchemy.sql.expression import true, false
43 43 from sqlalchemy.sql.functions import coalesce, count # noqa
44 44 from sqlalchemy.orm import (
45 45 relationship, joinedload, class_mapper, validates, aliased)
46 46 from sqlalchemy.ext.declarative import declared_attr
47 47 from sqlalchemy.ext.hybrid import hybrid_property
48 48 from sqlalchemy.exc import IntegrityError # noqa
49 49 from sqlalchemy.dialects.mysql import LONGTEXT
50 50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 51
52 52 from pyramid.threadlocal import get_current_request
53 53
54 54 from rhodecode.translation import _
55 55 from rhodecode.lib.vcs import get_vcs_instance
56 56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 57 from rhodecode.lib.utils2 import (
58 58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
59 59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 60 glob2re, StrictAttributeDict, cleaned_uri)
61 61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 62 JsonRaw
63 63 from rhodecode.lib.ext_json import json
64 64 from rhodecode.lib.caching_query import FromCache
65 65 from rhodecode.lib.encrypt import AESCipher
66 66
67 67 from rhodecode.model.meta import Base, Session
68 68
69 69 URL_SEP = '/'
70 70 log = logging.getLogger(__name__)
71 71
72 72 # =============================================================================
73 73 # BASE CLASSES
74 74 # =============================================================================
75 75
76 76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 77 # beaker.session.secret if first is not set.
78 78 # and initialized at environment.py
79 79 ENCRYPTION_KEY = None
80 80
81 81 # used to sort permissions by types, '#' used here is not allowed to be in
82 82 # usernames, and it's very early in sorted string.printable table.
83 83 PERMISSION_TYPE_SORT = {
84 84 'admin': '####',
85 85 'write': '###',
86 86 'read': '##',
87 87 'none': '#',
88 88 }
89 89
90 90
91 91 def display_user_sort(obj):
92 92 """
93 93 Sort function used to sort permissions in .permissions() function of
94 94 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 95 of all other resources
96 96 """
97 97
98 98 if obj.username == User.DEFAULT_USER:
99 99 return '#####'
100 100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 101 return prefix + obj.username
102 102
103 103
104 104 def display_user_group_sort(obj):
105 105 """
106 106 Sort function used to sort permissions in .permissions() function of
107 107 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 108 of all other resources
109 109 """
110 110
111 111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 112 return prefix + obj.users_group_name
113 113
114 114
115 115 def _hash_key(k):
116 116 return sha1_safe(k)
117 117
118 118
119 119 def in_filter_generator(qry, items, limit=500):
120 120 """
121 121 Splits IN() into multiple with OR
122 122 e.g.::
123 123 cnt = Repository.query().filter(
124 124 or_(
125 125 *in_filter_generator(Repository.repo_id, range(100000))
126 126 )).count()
127 127 """
128 128 if not items:
129 129 # empty list will cause empty query which might cause security issues
130 130 # this can lead to hidden unpleasant results
131 131 items = [-1]
132 132
133 133 parts = []
134 134 for chunk in xrange(0, len(items), limit):
135 135 parts.append(
136 136 qry.in_(items[chunk: chunk + limit])
137 137 )
138 138
139 139 return parts
140 140
141 141
142 142 base_table_args = {
143 143 'extend_existing': True,
144 144 'mysql_engine': 'InnoDB',
145 145 'mysql_charset': 'utf8',
146 146 'sqlite_autoincrement': True
147 147 }
148 148
149 149
150 150 class EncryptedTextValue(TypeDecorator):
151 151 """
152 152 Special column for encrypted long text data, use like::
153 153
154 154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
155 155
156 156 This column is intelligent so if value is in unencrypted form it return
157 157 unencrypted form, but on save it always encrypts
158 158 """
159 159 impl = Text
160 160
161 161 def process_bind_param(self, value, dialect):
162 162 if not value:
163 163 return value
164 164 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
165 165 # protect against double encrypting if someone manually starts
166 166 # doing
167 167 raise ValueError('value needs to be in unencrypted format, ie. '
168 168 'not starting with enc$aes')
169 169 return 'enc$aes_hmac$%s' % AESCipher(
170 170 ENCRYPTION_KEY, hmac=True).encrypt(value)
171 171
172 172 def process_result_value(self, value, dialect):
173 173 import rhodecode
174 174
175 175 if not value:
176 176 return value
177 177
178 178 parts = value.split('$', 3)
179 179 if not len(parts) == 3:
180 180 # probably not encrypted values
181 181 return value
182 182 else:
183 183 if parts[0] != 'enc':
184 184 # parts ok but without our header ?
185 185 return value
186 186 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
187 187 'rhodecode.encrypted_values.strict') or True)
188 188 # at that stage we know it's our encryption
189 189 if parts[1] == 'aes':
190 190 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
191 191 elif parts[1] == 'aes_hmac':
192 192 decrypted_data = AESCipher(
193 193 ENCRYPTION_KEY, hmac=True,
194 194 strict_verification=enc_strict_mode).decrypt(parts[2])
195 195 else:
196 196 raise ValueError(
197 197 'Encryption type part is wrong, must be `aes` '
198 198 'or `aes_hmac`, got `%s` instead' % (parts[1]))
199 199 return decrypted_data
200 200
201 201
202 202 class BaseModel(object):
203 203 """
204 204 Base Model for all classes
205 205 """
206 206
207 207 @classmethod
208 208 def _get_keys(cls):
209 209 """return column names for this model """
210 210 return class_mapper(cls).c.keys()
211 211
212 212 def get_dict(self):
213 213 """
214 214 return dict with keys and values corresponding
215 215 to this model data """
216 216
217 217 d = {}
218 218 for k in self._get_keys():
219 219 d[k] = getattr(self, k)
220 220
221 221 # also use __json__() if present to get additional fields
222 222 _json_attr = getattr(self, '__json__', None)
223 223 if _json_attr:
224 224 # update with attributes from __json__
225 225 if callable(_json_attr):
226 226 _json_attr = _json_attr()
227 227 for k, val in _json_attr.iteritems():
228 228 d[k] = val
229 229 return d
230 230
231 231 def get_appstruct(self):
232 232 """return list with keys and values tuples corresponding
233 233 to this model data """
234 234
235 235 lst = []
236 236 for k in self._get_keys():
237 237 lst.append((k, getattr(self, k),))
238 238 return lst
239 239
240 240 def populate_obj(self, populate_dict):
241 241 """populate model with data from given populate_dict"""
242 242
243 243 for k in self._get_keys():
244 244 if k in populate_dict:
245 245 setattr(self, k, populate_dict[k])
246 246
247 247 @classmethod
248 248 def query(cls):
249 249 return Session().query(cls)
250 250
251 251 @classmethod
252 252 def get(cls, id_):
253 253 if id_:
254 254 return cls.query().get(id_)
255 255
256 256 @classmethod
257 257 def get_or_404(cls, id_):
258 258 from pyramid.httpexceptions import HTTPNotFound
259 259
260 260 try:
261 261 id_ = int(id_)
262 262 except (TypeError, ValueError):
263 263 raise HTTPNotFound()
264 264
265 265 res = cls.query().get(id_)
266 266 if not res:
267 267 raise HTTPNotFound()
268 268 return res
269 269
270 270 @classmethod
271 271 def getAll(cls):
272 272 # deprecated and left for backward compatibility
273 273 return cls.get_all()
274 274
275 275 @classmethod
276 276 def get_all(cls):
277 277 return cls.query().all()
278 278
279 279 @classmethod
280 280 def delete(cls, id_):
281 281 obj = cls.query().get(id_)
282 282 Session().delete(obj)
283 283
284 284 @classmethod
285 285 def identity_cache(cls, session, attr_name, value):
286 286 exist_in_session = []
287 287 for (item_cls, pkey), instance in session.identity_map.items():
288 288 if cls == item_cls and getattr(instance, attr_name) == value:
289 289 exist_in_session.append(instance)
290 290 if exist_in_session:
291 291 if len(exist_in_session) == 1:
292 292 return exist_in_session[0]
293 293 log.exception(
294 294 'multiple objects with attr %s and '
295 295 'value %s found with same name: %r',
296 296 attr_name, value, exist_in_session)
297 297
298 298 def __repr__(self):
299 299 if hasattr(self, '__unicode__'):
300 300 # python repr needs to return str
301 301 try:
302 302 return safe_str(self.__unicode__())
303 303 except UnicodeDecodeError:
304 304 pass
305 305 return '<DB:%s>' % (self.__class__.__name__)
306 306
307 307
308 308 class RhodeCodeSetting(Base, BaseModel):
309 309 __tablename__ = 'rhodecode_settings'
310 310 __table_args__ = (
311 311 UniqueConstraint('app_settings_name'),
312 312 base_table_args
313 313 )
314 314
315 315 SETTINGS_TYPES = {
316 316 'str': safe_str,
317 317 'int': safe_int,
318 318 'unicode': safe_unicode,
319 319 'bool': str2bool,
320 320 'list': functools.partial(aslist, sep=',')
321 321 }
322 322 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
323 323 GLOBAL_CONF_KEY = 'app_settings'
324 324
325 325 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 326 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
327 327 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
328 328 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
329 329
330 330 def __init__(self, key='', val='', type='unicode'):
331 331 self.app_settings_name = key
332 332 self.app_settings_type = type
333 333 self.app_settings_value = val
334 334
335 335 @validates('_app_settings_value')
336 336 def validate_settings_value(self, key, val):
337 337 assert type(val) == unicode
338 338 return val
339 339
340 340 @hybrid_property
341 341 def app_settings_value(self):
342 342 v = self._app_settings_value
343 343 _type = self.app_settings_type
344 344 if _type:
345 345 _type = self.app_settings_type.split('.')[0]
346 346 # decode the encrypted value
347 347 if 'encrypted' in self.app_settings_type:
348 348 cipher = EncryptedTextValue()
349 349 v = safe_unicode(cipher.process_result_value(v, None))
350 350
351 351 converter = self.SETTINGS_TYPES.get(_type) or \
352 352 self.SETTINGS_TYPES['unicode']
353 353 return converter(v)
354 354
355 355 @app_settings_value.setter
356 356 def app_settings_value(self, val):
357 357 """
358 358 Setter that will always make sure we use unicode in app_settings_value
359 359
360 360 :param val:
361 361 """
362 362 val = safe_unicode(val)
363 363 # encode the encrypted value
364 364 if 'encrypted' in self.app_settings_type:
365 365 cipher = EncryptedTextValue()
366 366 val = safe_unicode(cipher.process_bind_param(val, None))
367 367 self._app_settings_value = val
368 368
369 369 @hybrid_property
370 370 def app_settings_type(self):
371 371 return self._app_settings_type
372 372
373 373 @app_settings_type.setter
374 374 def app_settings_type(self, val):
375 375 if val.split('.')[0] not in self.SETTINGS_TYPES:
376 376 raise Exception('type must be one of %s got %s'
377 377 % (self.SETTINGS_TYPES.keys(), val))
378 378 self._app_settings_type = val
379 379
380 380 def __unicode__(self):
381 381 return u"<%s('%s:%s[%s]')>" % (
382 382 self.__class__.__name__,
383 383 self.app_settings_name, self.app_settings_value,
384 384 self.app_settings_type
385 385 )
386 386
387 387
388 388 class RhodeCodeUi(Base, BaseModel):
389 389 __tablename__ = 'rhodecode_ui'
390 390 __table_args__ = (
391 391 UniqueConstraint('ui_key'),
392 392 base_table_args
393 393 )
394 394
395 395 HOOK_REPO_SIZE = 'changegroup.repo_size'
396 396 # HG
397 397 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
398 398 HOOK_PULL = 'outgoing.pull_logger'
399 399 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
400 400 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
401 401 HOOK_PUSH = 'changegroup.push_logger'
402 402 HOOK_PUSH_KEY = 'pushkey.key_push'
403 403
404 404 # TODO: johbo: Unify way how hooks are configured for git and hg,
405 405 # git part is currently hardcoded.
406 406
407 407 # SVN PATTERNS
408 408 SVN_BRANCH_ID = 'vcs_svn_branch'
409 409 SVN_TAG_ID = 'vcs_svn_tag'
410 410
411 411 ui_id = Column(
412 412 "ui_id", Integer(), nullable=False, unique=True, default=None,
413 413 primary_key=True)
414 414 ui_section = Column(
415 415 "ui_section", String(255), nullable=True, unique=None, default=None)
416 416 ui_key = Column(
417 417 "ui_key", String(255), nullable=True, unique=None, default=None)
418 418 ui_value = Column(
419 419 "ui_value", String(255), nullable=True, unique=None, default=None)
420 420 ui_active = Column(
421 421 "ui_active", Boolean(), nullable=True, unique=None, default=True)
422 422
423 423 def __repr__(self):
424 424 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
425 425 self.ui_key, self.ui_value)
426 426
427 427
428 428 class RepoRhodeCodeSetting(Base, BaseModel):
429 429 __tablename__ = 'repo_rhodecode_settings'
430 430 __table_args__ = (
431 431 UniqueConstraint(
432 432 'app_settings_name', 'repository_id',
433 433 name='uq_repo_rhodecode_setting_name_repo_id'),
434 434 base_table_args
435 435 )
436 436
437 437 repository_id = Column(
438 438 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
439 439 nullable=False)
440 440 app_settings_id = Column(
441 441 "app_settings_id", Integer(), nullable=False, unique=True,
442 442 default=None, primary_key=True)
443 443 app_settings_name = Column(
444 444 "app_settings_name", String(255), nullable=True, unique=None,
445 445 default=None)
446 446 _app_settings_value = Column(
447 447 "app_settings_value", String(4096), nullable=True, unique=None,
448 448 default=None)
449 449 _app_settings_type = Column(
450 450 "app_settings_type", String(255), nullable=True, unique=None,
451 451 default=None)
452 452
453 453 repository = relationship('Repository')
454 454
455 455 def __init__(self, repository_id, key='', val='', type='unicode'):
456 456 self.repository_id = repository_id
457 457 self.app_settings_name = key
458 458 self.app_settings_type = type
459 459 self.app_settings_value = val
460 460
461 461 @validates('_app_settings_value')
462 462 def validate_settings_value(self, key, val):
463 463 assert type(val) == unicode
464 464 return val
465 465
466 466 @hybrid_property
467 467 def app_settings_value(self):
468 468 v = self._app_settings_value
469 469 type_ = self.app_settings_type
470 470 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
471 471 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
472 472 return converter(v)
473 473
474 474 @app_settings_value.setter
475 475 def app_settings_value(self, val):
476 476 """
477 477 Setter that will always make sure we use unicode in app_settings_value
478 478
479 479 :param val:
480 480 """
481 481 self._app_settings_value = safe_unicode(val)
482 482
483 483 @hybrid_property
484 484 def app_settings_type(self):
485 485 return self._app_settings_type
486 486
487 487 @app_settings_type.setter
488 488 def app_settings_type(self, val):
489 489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 490 if val not in SETTINGS_TYPES:
491 491 raise Exception('type must be one of %s got %s'
492 492 % (SETTINGS_TYPES.keys(), val))
493 493 self._app_settings_type = val
494 494
495 495 def __unicode__(self):
496 496 return u"<%s('%s:%s:%s[%s]')>" % (
497 497 self.__class__.__name__, self.repository.repo_name,
498 498 self.app_settings_name, self.app_settings_value,
499 499 self.app_settings_type
500 500 )
501 501
502 502
503 503 class RepoRhodeCodeUi(Base, BaseModel):
504 504 __tablename__ = 'repo_rhodecode_ui'
505 505 __table_args__ = (
506 506 UniqueConstraint(
507 507 'repository_id', 'ui_section', 'ui_key',
508 508 name='uq_repo_rhodecode_ui_repository_id_section_key'),
509 509 base_table_args
510 510 )
511 511
512 512 repository_id = Column(
513 513 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
514 514 nullable=False)
515 515 ui_id = Column(
516 516 "ui_id", Integer(), nullable=False, unique=True, default=None,
517 517 primary_key=True)
518 518 ui_section = Column(
519 519 "ui_section", String(255), nullable=True, unique=None, default=None)
520 520 ui_key = Column(
521 521 "ui_key", String(255), nullable=True, unique=None, default=None)
522 522 ui_value = Column(
523 523 "ui_value", String(255), nullable=True, unique=None, default=None)
524 524 ui_active = Column(
525 525 "ui_active", Boolean(), nullable=True, unique=None, default=True)
526 526
527 527 repository = relationship('Repository')
528 528
529 529 def __repr__(self):
530 530 return '<%s[%s:%s]%s=>%s]>' % (
531 531 self.__class__.__name__, self.repository.repo_name,
532 532 self.ui_section, self.ui_key, self.ui_value)
533 533
534 534
535 535 class User(Base, BaseModel):
536 536 __tablename__ = 'users'
537 537 __table_args__ = (
538 538 UniqueConstraint('username'), UniqueConstraint('email'),
539 539 Index('u_username_idx', 'username'),
540 540 Index('u_email_idx', 'email'),
541 541 base_table_args
542 542 )
543 543
544 544 DEFAULT_USER = 'default'
545 545 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
546 546 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
547 547
548 548 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
549 549 username = Column("username", String(255), nullable=True, unique=None, default=None)
550 550 password = Column("password", String(255), nullable=True, unique=None, default=None)
551 551 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
552 552 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
553 553 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
554 554 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
555 555 _email = Column("email", String(255), nullable=True, unique=None, default=None)
556 556 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
557 557 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
558 558
559 559 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
560 560 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
561 561 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
562 562 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
563 563 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
564 564 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
565 565
566 566 user_log = relationship('UserLog')
567 567 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
568 568
569 569 repositories = relationship('Repository')
570 570 repository_groups = relationship('RepoGroup')
571 571 user_groups = relationship('UserGroup')
572 572
573 573 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
574 574 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
575 575
576 576 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
577 577 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
578 578 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
579 579
580 580 group_member = relationship('UserGroupMember', cascade='all')
581 581
582 582 notifications = relationship('UserNotification', cascade='all')
583 583 # notifications assigned to this user
584 584 user_created_notifications = relationship('Notification', cascade='all')
585 585 # comments created by this user
586 586 user_comments = relationship('ChangesetComment', cascade='all')
587 587 # user profile extra info
588 588 user_emails = relationship('UserEmailMap', cascade='all')
589 589 user_ip_map = relationship('UserIpMap', cascade='all')
590 590 user_auth_tokens = relationship('UserApiKeys', cascade='all')
591 591 user_ssh_keys = relationship('UserSshKeys', cascade='all')
592 592
593 593 # gists
594 594 user_gists = relationship('Gist', cascade='all')
595 595 # user pull requests
596 596 user_pull_requests = relationship('PullRequest', cascade='all')
597 597 # external identities
598 598 extenal_identities = relationship(
599 599 'ExternalIdentity',
600 600 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
601 601 cascade='all')
602 602 # review rules
603 603 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
604 604
605 605 def __unicode__(self):
606 606 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
607 607 self.user_id, self.username)
608 608
609 609 @hybrid_property
610 610 def email(self):
611 611 return self._email
612 612
613 613 @email.setter
614 614 def email(self, val):
615 615 self._email = val.lower() if val else None
616 616
617 617 @hybrid_property
618 618 def first_name(self):
619 619 from rhodecode.lib import helpers as h
620 620 if self.name:
621 621 return h.escape(self.name)
622 622 return self.name
623 623
624 624 @hybrid_property
625 625 def last_name(self):
626 626 from rhodecode.lib import helpers as h
627 627 if self.lastname:
628 628 return h.escape(self.lastname)
629 629 return self.lastname
630 630
631 631 @hybrid_property
632 632 def api_key(self):
633 633 """
634 634 Fetch if exist an auth-token with role ALL connected to this user
635 635 """
636 636 user_auth_token = UserApiKeys.query()\
637 637 .filter(UserApiKeys.user_id == self.user_id)\
638 638 .filter(or_(UserApiKeys.expires == -1,
639 639 UserApiKeys.expires >= time.time()))\
640 640 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
641 641 if user_auth_token:
642 642 user_auth_token = user_auth_token.api_key
643 643
644 644 return user_auth_token
645 645
646 646 @api_key.setter
647 647 def api_key(self, val):
648 648 # don't allow to set API key this is deprecated for now
649 649 self._api_key = None
650 650
651 651 @property
652 652 def reviewer_pull_requests(self):
653 653 return PullRequestReviewers.query() \
654 654 .options(joinedload(PullRequestReviewers.pull_request)) \
655 655 .filter(PullRequestReviewers.user_id == self.user_id) \
656 656 .all()
657 657
658 658 @property
659 659 def firstname(self):
660 660 # alias for future
661 661 return self.name
662 662
663 663 @property
664 664 def emails(self):
665 665 other = UserEmailMap.query()\
666 666 .filter(UserEmailMap.user == self) \
667 667 .order_by(UserEmailMap.email_id.asc()) \
668 668 .all()
669 669 return [self.email] + [x.email for x in other]
670 670
671 671 @property
672 672 def auth_tokens(self):
673 673 auth_tokens = self.get_auth_tokens()
674 674 return [x.api_key for x in auth_tokens]
675 675
676 676 def get_auth_tokens(self):
677 677 return UserApiKeys.query()\
678 678 .filter(UserApiKeys.user == self)\
679 679 .order_by(UserApiKeys.user_api_key_id.asc())\
680 680 .all()
681 681
682 682 @LazyProperty
683 683 def feed_token(self):
684 684 return self.get_feed_token()
685 685
686 686 def get_feed_token(self, cache=True):
687 687 feed_tokens = UserApiKeys.query()\
688 688 .filter(UserApiKeys.user == self)\
689 689 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
690 690 if cache:
691 691 feed_tokens = feed_tokens.options(
692 692 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
693 693
694 694 feed_tokens = feed_tokens.all()
695 695 if feed_tokens:
696 696 return feed_tokens[0].api_key
697 697 return 'NO_FEED_TOKEN_AVAILABLE'
698 698
699 699 @classmethod
700 700 def get(cls, user_id, cache=False):
701 701 if not user_id:
702 702 return
703 703
704 704 user = cls.query()
705 705 if cache:
706 706 user = user.options(
707 707 FromCache("sql_cache_short", "get_users_%s" % user_id))
708 708 return user.get(user_id)
709 709
710 710 @classmethod
711 711 def extra_valid_auth_tokens(cls, user, role=None):
712 712 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
713 713 .filter(or_(UserApiKeys.expires == -1,
714 714 UserApiKeys.expires >= time.time()))
715 715 if role:
716 716 tokens = tokens.filter(or_(UserApiKeys.role == role,
717 717 UserApiKeys.role == UserApiKeys.ROLE_ALL))
718 718 return tokens.all()
719 719
720 720 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
721 721 from rhodecode.lib import auth
722 722
723 723 log.debug('Trying to authenticate user: %s via auth-token, '
724 724 'and roles: %s', self, roles)
725 725
726 726 if not auth_token:
727 727 return False
728 728
729 729 crypto_backend = auth.crypto_backend()
730 730
731 731 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
732 732 tokens_q = UserApiKeys.query()\
733 733 .filter(UserApiKeys.user_id == self.user_id)\
734 734 .filter(or_(UserApiKeys.expires == -1,
735 735 UserApiKeys.expires >= time.time()))
736 736
737 737 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
738 738
739 739 plain_tokens = []
740 740 hash_tokens = []
741 741
742 742 user_tokens = tokens_q.all()
743 743 log.debug('Found %s user tokens to check for authentication', len(user_tokens))
744 744 for token in user_tokens:
745 745 log.debug('AUTH_TOKEN: checking if user token with id `%s` matches',
746 746 token.user_api_key_id)
747 747 # verify scope first, since it's way faster than hash calculation of
748 748 # encrypted tokens
749 749 if token.repo_id:
750 750 # token has a scope, we need to verify it
751 751 if scope_repo_id != token.repo_id:
752 752 log.debug(
753 753 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
754 754 'and calling scope is:%s, skipping further checks',
755 755 token.repo, scope_repo_id)
756 756 # token has a scope, and it doesn't match, skip token
757 757 continue
758 758
759 759 if token.api_key.startswith(crypto_backend.ENC_PREF):
760 760 hash_tokens.append(token.api_key)
761 761 else:
762 762 plain_tokens.append(token.api_key)
763 763
764 764 is_plain_match = auth_token in plain_tokens
765 765 if is_plain_match:
766 766 return True
767 767
768 768 for hashed in hash_tokens:
769 769 # NOTE(marcink): this is expensive to calculate, but most secure
770 770 match = crypto_backend.hash_check(auth_token, hashed)
771 771 if match:
772 772 return True
773 773
774 774 return False
775 775
776 776 @property
777 777 def ip_addresses(self):
778 778 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
779 779 return [x.ip_addr for x in ret]
780 780
781 781 @property
782 782 def username_and_name(self):
783 783 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
784 784
785 785 @property
786 786 def username_or_name_or_email(self):
787 787 full_name = self.full_name if self.full_name is not ' ' else None
788 788 return self.username or full_name or self.email
789 789
790 790 @property
791 791 def full_name(self):
792 792 return '%s %s' % (self.first_name, self.last_name)
793 793
794 794 @property
795 795 def full_name_or_username(self):
796 796 return ('%s %s' % (self.first_name, self.last_name)
797 797 if (self.first_name and self.last_name) else self.username)
798 798
799 799 @property
800 800 def full_contact(self):
801 801 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
802 802
803 803 @property
804 804 def short_contact(self):
805 805 return '%s %s' % (self.first_name, self.last_name)
806 806
807 807 @property
808 808 def is_admin(self):
809 809 return self.admin
810 810
811 811 def AuthUser(self, **kwargs):
812 812 """
813 813 Returns instance of AuthUser for this user
814 814 """
815 815 from rhodecode.lib.auth import AuthUser
816 816 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
817 817
818 818 @hybrid_property
819 819 def user_data(self):
820 820 if not self._user_data:
821 821 return {}
822 822
823 823 try:
824 824 return json.loads(self._user_data)
825 825 except TypeError:
826 826 return {}
827 827
828 828 @user_data.setter
829 829 def user_data(self, val):
830 830 if not isinstance(val, dict):
831 831 raise Exception('user_data must be dict, got %s' % type(val))
832 832 try:
833 833 self._user_data = json.dumps(val)
834 834 except Exception:
835 835 log.error(traceback.format_exc())
836 836
837 837 @classmethod
838 838 def get_by_username(cls, username, case_insensitive=False,
839 839 cache=False, identity_cache=False):
840 840 session = Session()
841 841
842 842 if case_insensitive:
843 843 q = cls.query().filter(
844 844 func.lower(cls.username) == func.lower(username))
845 845 else:
846 846 q = cls.query().filter(cls.username == username)
847 847
848 848 if cache:
849 849 if identity_cache:
850 850 val = cls.identity_cache(session, 'username', username)
851 851 if val:
852 852 return val
853 853 else:
854 854 cache_key = "get_user_by_name_%s" % _hash_key(username)
855 855 q = q.options(
856 856 FromCache("sql_cache_short", cache_key))
857 857
858 858 return q.scalar()
859 859
860 860 @classmethod
861 861 def get_by_auth_token(cls, auth_token, cache=False):
862 862 q = UserApiKeys.query()\
863 863 .filter(UserApiKeys.api_key == auth_token)\
864 864 .filter(or_(UserApiKeys.expires == -1,
865 865 UserApiKeys.expires >= time.time()))
866 866 if cache:
867 867 q = q.options(
868 868 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
869 869
870 870 match = q.first()
871 871 if match:
872 872 return match.user
873 873
874 874 @classmethod
875 875 def get_by_email(cls, email, case_insensitive=False, cache=False):
876 876
877 877 if case_insensitive:
878 878 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
879 879
880 880 else:
881 881 q = cls.query().filter(cls.email == email)
882 882
883 883 email_key = _hash_key(email)
884 884 if cache:
885 885 q = q.options(
886 886 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
887 887
888 888 ret = q.scalar()
889 889 if ret is None:
890 890 q = UserEmailMap.query()
891 891 # try fetching in alternate email map
892 892 if case_insensitive:
893 893 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
894 894 else:
895 895 q = q.filter(UserEmailMap.email == email)
896 896 q = q.options(joinedload(UserEmailMap.user))
897 897 if cache:
898 898 q = q.options(
899 899 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
900 900 ret = getattr(q.scalar(), 'user', None)
901 901
902 902 return ret
903 903
904 904 @classmethod
905 905 def get_from_cs_author(cls, author):
906 906 """
907 907 Tries to get User objects out of commit author string
908 908
909 909 :param author:
910 910 """
911 911 from rhodecode.lib.helpers import email, author_name
912 912 # Valid email in the attribute passed, see if they're in the system
913 913 _email = email(author)
914 914 if _email:
915 915 user = cls.get_by_email(_email, case_insensitive=True)
916 916 if user:
917 917 return user
918 918 # Maybe we can match by username?
919 919 _author = author_name(author)
920 920 user = cls.get_by_username(_author, case_insensitive=True)
921 921 if user:
922 922 return user
923 923
924 924 def update_userdata(self, **kwargs):
925 925 usr = self
926 926 old = usr.user_data
927 927 old.update(**kwargs)
928 928 usr.user_data = old
929 929 Session().add(usr)
930 930 log.debug('updated userdata with ', kwargs)
931 931
932 932 def update_lastlogin(self):
933 933 """Update user lastlogin"""
934 934 self.last_login = datetime.datetime.now()
935 935 Session().add(self)
936 936 log.debug('updated user %s lastlogin', self.username)
937 937
938 938 def update_password(self, new_password):
939 939 from rhodecode.lib.auth import get_crypt_password
940 940
941 941 self.password = get_crypt_password(new_password)
942 942 Session().add(self)
943 943
944 944 @classmethod
945 945 def get_first_super_admin(cls):
946 user = User.query().filter(User.admin == true()).first()
946 user = User.query()\
947 .filter(User.admin == true()) \
948 .order_by(User.user_id.asc()) \
949 .first()
950
947 951 if user is None:
948 952 raise Exception('FATAL: Missing administrative account!')
949 953 return user
950 954
951 955 @classmethod
952 956 def get_all_super_admins(cls):
953 957 """
954 958 Returns all admin accounts sorted by username
955 959 """
956 960 return User.query().filter(User.admin == true())\
957 961 .order_by(User.username.asc()).all()
958 962
959 963 @classmethod
960 964 def get_default_user(cls, cache=False, refresh=False):
961 965 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
962 966 if user is None:
963 967 raise Exception('FATAL: Missing default account!')
964 968 if refresh:
965 969 # The default user might be based on outdated state which
966 970 # has been loaded from the cache.
967 971 # A call to refresh() ensures that the
968 972 # latest state from the database is used.
969 973 Session().refresh(user)
970 974 return user
971 975
972 976 def _get_default_perms(self, user, suffix=''):
973 977 from rhodecode.model.permission import PermissionModel
974 978 return PermissionModel().get_default_perms(user.user_perms, suffix)
975 979
976 980 def get_default_perms(self, suffix=''):
977 981 return self._get_default_perms(self, suffix)
978 982
979 983 def get_api_data(self, include_secrets=False, details='full'):
980 984 """
981 985 Common function for generating user related data for API
982 986
983 987 :param include_secrets: By default secrets in the API data will be replaced
984 988 by a placeholder value to prevent exposing this data by accident. In case
985 989 this data shall be exposed, set this flag to ``True``.
986 990
987 991 :param details: details can be 'basic|full' basic gives only a subset of
988 992 the available user information that includes user_id, name and emails.
989 993 """
990 994 user = self
991 995 user_data = self.user_data
992 996 data = {
993 997 'user_id': user.user_id,
994 998 'username': user.username,
995 999 'firstname': user.name,
996 1000 'lastname': user.lastname,
997 1001 'email': user.email,
998 1002 'emails': user.emails,
999 1003 }
1000 1004 if details == 'basic':
1001 1005 return data
1002 1006
1003 1007 auth_token_length = 40
1004 1008 auth_token_replacement = '*' * auth_token_length
1005 1009
1006 1010 extras = {
1007 1011 'auth_tokens': [auth_token_replacement],
1008 1012 'active': user.active,
1009 1013 'admin': user.admin,
1010 1014 'extern_type': user.extern_type,
1011 1015 'extern_name': user.extern_name,
1012 1016 'last_login': user.last_login,
1013 1017 'last_activity': user.last_activity,
1014 1018 'ip_addresses': user.ip_addresses,
1015 1019 'language': user_data.get('language')
1016 1020 }
1017 1021 data.update(extras)
1018 1022
1019 1023 if include_secrets:
1020 1024 data['auth_tokens'] = user.auth_tokens
1021 1025 return data
1022 1026
1023 1027 def __json__(self):
1024 1028 data = {
1025 1029 'full_name': self.full_name,
1026 1030 'full_name_or_username': self.full_name_or_username,
1027 1031 'short_contact': self.short_contact,
1028 1032 'full_contact': self.full_contact,
1029 1033 }
1030 1034 data.update(self.get_api_data())
1031 1035 return data
1032 1036
1033 1037
1034 1038 class UserApiKeys(Base, BaseModel):
1035 1039 __tablename__ = 'user_api_keys'
1036 1040 __table_args__ = (
1037 1041 Index('uak_api_key_idx', 'api_key', unique=True),
1038 1042 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1039 1043 base_table_args
1040 1044 )
1041 1045 __mapper_args__ = {}
1042 1046
1043 1047 # ApiKey role
1044 1048 ROLE_ALL = 'token_role_all'
1045 1049 ROLE_HTTP = 'token_role_http'
1046 1050 ROLE_VCS = 'token_role_vcs'
1047 1051 ROLE_API = 'token_role_api'
1048 1052 ROLE_FEED = 'token_role_feed'
1049 1053 ROLE_PASSWORD_RESET = 'token_password_reset'
1050 1054
1051 1055 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1052 1056
1053 1057 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1054 1058 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1055 1059 api_key = Column("api_key", String(255), nullable=False, unique=True)
1056 1060 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1057 1061 expires = Column('expires', Float(53), nullable=False)
1058 1062 role = Column('role', String(255), nullable=True)
1059 1063 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1060 1064
1061 1065 # scope columns
1062 1066 repo_id = Column(
1063 1067 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1064 1068 nullable=True, unique=None, default=None)
1065 1069 repo = relationship('Repository', lazy='joined')
1066 1070
1067 1071 repo_group_id = Column(
1068 1072 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1069 1073 nullable=True, unique=None, default=None)
1070 1074 repo_group = relationship('RepoGroup', lazy='joined')
1071 1075
1072 1076 user = relationship('User', lazy='joined')
1073 1077
1074 1078 def __unicode__(self):
1075 1079 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1076 1080
1077 1081 def __json__(self):
1078 1082 data = {
1079 1083 'auth_token': self.api_key,
1080 1084 'role': self.role,
1081 1085 'scope': self.scope_humanized,
1082 1086 'expired': self.expired
1083 1087 }
1084 1088 return data
1085 1089
1086 1090 def get_api_data(self, include_secrets=False):
1087 1091 data = self.__json__()
1088 1092 if include_secrets:
1089 1093 return data
1090 1094 else:
1091 1095 data['auth_token'] = self.token_obfuscated
1092 1096 return data
1093 1097
1094 1098 @hybrid_property
1095 1099 def description_safe(self):
1096 1100 from rhodecode.lib import helpers as h
1097 1101 return h.escape(self.description)
1098 1102
1099 1103 @property
1100 1104 def expired(self):
1101 1105 if self.expires == -1:
1102 1106 return False
1103 1107 return time.time() > self.expires
1104 1108
1105 1109 @classmethod
1106 1110 def _get_role_name(cls, role):
1107 1111 return {
1108 1112 cls.ROLE_ALL: _('all'),
1109 1113 cls.ROLE_HTTP: _('http/web interface'),
1110 1114 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1111 1115 cls.ROLE_API: _('api calls'),
1112 1116 cls.ROLE_FEED: _('feed access'),
1113 1117 }.get(role, role)
1114 1118
1115 1119 @property
1116 1120 def role_humanized(self):
1117 1121 return self._get_role_name(self.role)
1118 1122
1119 1123 def _get_scope(self):
1120 1124 if self.repo:
1121 1125 return repr(self.repo)
1122 1126 if self.repo_group:
1123 1127 return repr(self.repo_group) + ' (recursive)'
1124 1128 return 'global'
1125 1129
1126 1130 @property
1127 1131 def scope_humanized(self):
1128 1132 return self._get_scope()
1129 1133
1130 1134 @property
1131 1135 def token_obfuscated(self):
1132 1136 if self.api_key:
1133 1137 return self.api_key[:4] + "****"
1134 1138
1135 1139
1136 1140 class UserEmailMap(Base, BaseModel):
1137 1141 __tablename__ = 'user_email_map'
1138 1142 __table_args__ = (
1139 1143 Index('uem_email_idx', 'email'),
1140 1144 UniqueConstraint('email'),
1141 1145 base_table_args
1142 1146 )
1143 1147 __mapper_args__ = {}
1144 1148
1145 1149 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 1150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 1151 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 1152 user = relationship('User', lazy='joined')
1149 1153
1150 1154 @validates('_email')
1151 1155 def validate_email(self, key, email):
1152 1156 # check if this email is not main one
1153 1157 main_email = Session().query(User).filter(User.email == email).scalar()
1154 1158 if main_email is not None:
1155 1159 raise AttributeError('email %s is present is user table' % email)
1156 1160 return email
1157 1161
1158 1162 @hybrid_property
1159 1163 def email(self):
1160 1164 return self._email
1161 1165
1162 1166 @email.setter
1163 1167 def email(self, val):
1164 1168 self._email = val.lower() if val else None
1165 1169
1166 1170
1167 1171 class UserIpMap(Base, BaseModel):
1168 1172 __tablename__ = 'user_ip_map'
1169 1173 __table_args__ = (
1170 1174 UniqueConstraint('user_id', 'ip_addr'),
1171 1175 base_table_args
1172 1176 )
1173 1177 __mapper_args__ = {}
1174 1178
1175 1179 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 1180 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 1181 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 1182 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 1183 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 1184 user = relationship('User', lazy='joined')
1181 1185
1182 1186 @hybrid_property
1183 1187 def description_safe(self):
1184 1188 from rhodecode.lib import helpers as h
1185 1189 return h.escape(self.description)
1186 1190
1187 1191 @classmethod
1188 1192 def _get_ip_range(cls, ip_addr):
1189 1193 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 1194 return [str(net.network_address), str(net.broadcast_address)]
1191 1195
1192 1196 def __json__(self):
1193 1197 return {
1194 1198 'ip_addr': self.ip_addr,
1195 1199 'ip_range': self._get_ip_range(self.ip_addr),
1196 1200 }
1197 1201
1198 1202 def __unicode__(self):
1199 1203 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 1204 self.user_id, self.ip_addr)
1201 1205
1202 1206
1203 1207 class UserSshKeys(Base, BaseModel):
1204 1208 __tablename__ = 'user_ssh_keys'
1205 1209 __table_args__ = (
1206 1210 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 1211
1208 1212 UniqueConstraint('ssh_key_fingerprint'),
1209 1213
1210 1214 base_table_args
1211 1215 )
1212 1216 __mapper_args__ = {}
1213 1217
1214 1218 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1215 1219 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1216 1220 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1217 1221
1218 1222 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1219 1223
1220 1224 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1221 1225 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1222 1226 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1223 1227
1224 1228 user = relationship('User', lazy='joined')
1225 1229
1226 1230 def __json__(self):
1227 1231 data = {
1228 1232 'ssh_fingerprint': self.ssh_key_fingerprint,
1229 1233 'description': self.description,
1230 1234 'created_on': self.created_on
1231 1235 }
1232 1236 return data
1233 1237
1234 1238 def get_api_data(self):
1235 1239 data = self.__json__()
1236 1240 return data
1237 1241
1238 1242
1239 1243 class UserLog(Base, BaseModel):
1240 1244 __tablename__ = 'user_logs'
1241 1245 __table_args__ = (
1242 1246 base_table_args,
1243 1247 )
1244 1248
1245 1249 VERSION_1 = 'v1'
1246 1250 VERSION_2 = 'v2'
1247 1251 VERSIONS = [VERSION_1, VERSION_2]
1248 1252
1249 1253 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1250 1254 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1251 1255 username = Column("username", String(255), nullable=True, unique=None, default=None)
1252 1256 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 1257 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1254 1258 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1255 1259 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1256 1260 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1257 1261
1258 1262 version = Column("version", String(255), nullable=True, default=VERSION_1)
1259 1263 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1260 1264 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 1265
1262 1266 def __unicode__(self):
1263 1267 return u"<%s('id:%s:%s')>" % (
1264 1268 self.__class__.__name__, self.repository_name, self.action)
1265 1269
1266 1270 def __json__(self):
1267 1271 return {
1268 1272 'user_id': self.user_id,
1269 1273 'username': self.username,
1270 1274 'repository_id': self.repository_id,
1271 1275 'repository_name': self.repository_name,
1272 1276 'user_ip': self.user_ip,
1273 1277 'action_date': self.action_date,
1274 1278 'action': self.action,
1275 1279 }
1276 1280
1277 1281 @hybrid_property
1278 1282 def entry_id(self):
1279 1283 return self.user_log_id
1280 1284
1281 1285 @property
1282 1286 def action_as_day(self):
1283 1287 return datetime.date(*self.action_date.timetuple()[:3])
1284 1288
1285 1289 user = relationship('User')
1286 1290 repository = relationship('Repository', cascade='')
1287 1291
1288 1292
1289 1293 class UserGroup(Base, BaseModel):
1290 1294 __tablename__ = 'users_groups'
1291 1295 __table_args__ = (
1292 1296 base_table_args,
1293 1297 )
1294 1298
1295 1299 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1296 1300 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1297 1301 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1298 1302 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1299 1303 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1300 1304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1301 1305 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1302 1306 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1303 1307
1304 1308 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1305 1309 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1306 1310 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1307 1311 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1308 1312 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1309 1313 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1310 1314
1311 1315 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1312 1316 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1313 1317
1314 1318 @classmethod
1315 1319 def _load_group_data(cls, column):
1316 1320 if not column:
1317 1321 return {}
1318 1322
1319 1323 try:
1320 1324 return json.loads(column) or {}
1321 1325 except TypeError:
1322 1326 return {}
1323 1327
1324 1328 @hybrid_property
1325 1329 def description_safe(self):
1326 1330 from rhodecode.lib import helpers as h
1327 1331 return h.escape(self.user_group_description)
1328 1332
1329 1333 @hybrid_property
1330 1334 def group_data(self):
1331 1335 return self._load_group_data(self._group_data)
1332 1336
1333 1337 @group_data.expression
1334 1338 def group_data(self, **kwargs):
1335 1339 return self._group_data
1336 1340
1337 1341 @group_data.setter
1338 1342 def group_data(self, val):
1339 1343 try:
1340 1344 self._group_data = json.dumps(val)
1341 1345 except Exception:
1342 1346 log.error(traceback.format_exc())
1343 1347
1344 1348 @classmethod
1345 1349 def _load_sync(cls, group_data):
1346 1350 if group_data:
1347 1351 return group_data.get('extern_type')
1348 1352
1349 1353 @property
1350 1354 def sync(self):
1351 1355 return self._load_sync(self.group_data)
1352 1356
1353 1357 def __unicode__(self):
1354 1358 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1355 1359 self.users_group_id,
1356 1360 self.users_group_name)
1357 1361
1358 1362 @classmethod
1359 1363 def get_by_group_name(cls, group_name, cache=False,
1360 1364 case_insensitive=False):
1361 1365 if case_insensitive:
1362 1366 q = cls.query().filter(func.lower(cls.users_group_name) ==
1363 1367 func.lower(group_name))
1364 1368
1365 1369 else:
1366 1370 q = cls.query().filter(cls.users_group_name == group_name)
1367 1371 if cache:
1368 1372 q = q.options(
1369 1373 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1370 1374 return q.scalar()
1371 1375
1372 1376 @classmethod
1373 1377 def get(cls, user_group_id, cache=False):
1374 1378 if not user_group_id:
1375 1379 return
1376 1380
1377 1381 user_group = cls.query()
1378 1382 if cache:
1379 1383 user_group = user_group.options(
1380 1384 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1381 1385 return user_group.get(user_group_id)
1382 1386
1383 1387 def permissions(self, with_admins=True, with_owner=True):
1384 1388 """
1385 1389 Permissions for user groups
1386 1390 """
1387 1391 _admin_perm = 'usergroup.admin'
1388 1392
1389 1393 owner_row = []
1390 1394 if with_owner:
1391 1395 usr = AttributeDict(self.user.get_dict())
1392 1396 usr.owner_row = True
1393 1397 usr.permission = _admin_perm
1394 1398 owner_row.append(usr)
1395 1399
1396 1400 super_admin_ids = []
1397 1401 super_admin_rows = []
1398 1402 if with_admins:
1399 1403 for usr in User.get_all_super_admins():
1400 1404 super_admin_ids.append(usr.user_id)
1401 1405 # if this admin is also owner, don't double the record
1402 1406 if usr.user_id == owner_row[0].user_id:
1403 1407 owner_row[0].admin_row = True
1404 1408 else:
1405 1409 usr = AttributeDict(usr.get_dict())
1406 1410 usr.admin_row = True
1407 1411 usr.permission = _admin_perm
1408 1412 super_admin_rows.append(usr)
1409 1413
1410 1414 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1411 1415 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1412 1416 joinedload(UserUserGroupToPerm.user),
1413 1417 joinedload(UserUserGroupToPerm.permission),)
1414 1418
1415 1419 # get owners and admins and permissions. We do a trick of re-writing
1416 1420 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1417 1421 # has a global reference and changing one object propagates to all
1418 1422 # others. This means if admin is also an owner admin_row that change
1419 1423 # would propagate to both objects
1420 1424 perm_rows = []
1421 1425 for _usr in q.all():
1422 1426 usr = AttributeDict(_usr.user.get_dict())
1423 1427 # if this user is also owner/admin, mark as duplicate record
1424 1428 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1425 1429 usr.duplicate_perm = True
1426 1430 usr.permission = _usr.permission.permission_name
1427 1431 perm_rows.append(usr)
1428 1432
1429 1433 # filter the perm rows by 'default' first and then sort them by
1430 1434 # admin,write,read,none permissions sorted again alphabetically in
1431 1435 # each group
1432 1436 perm_rows = sorted(perm_rows, key=display_user_sort)
1433 1437
1434 1438 return super_admin_rows + owner_row + perm_rows
1435 1439
1436 1440 def permission_user_groups(self):
1437 1441 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1438 1442 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1439 1443 joinedload(UserGroupUserGroupToPerm.target_user_group),
1440 1444 joinedload(UserGroupUserGroupToPerm.permission),)
1441 1445
1442 1446 perm_rows = []
1443 1447 for _user_group in q.all():
1444 1448 usr = AttributeDict(_user_group.user_group.get_dict())
1445 1449 usr.permission = _user_group.permission.permission_name
1446 1450 perm_rows.append(usr)
1447 1451
1448 1452 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1449 1453 return perm_rows
1450 1454
1451 1455 def _get_default_perms(self, user_group, suffix=''):
1452 1456 from rhodecode.model.permission import PermissionModel
1453 1457 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1454 1458
1455 1459 def get_default_perms(self, suffix=''):
1456 1460 return self._get_default_perms(self, suffix)
1457 1461
1458 1462 def get_api_data(self, with_group_members=True, include_secrets=False):
1459 1463 """
1460 1464 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1461 1465 basically forwarded.
1462 1466
1463 1467 """
1464 1468 user_group = self
1465 1469 data = {
1466 1470 'users_group_id': user_group.users_group_id,
1467 1471 'group_name': user_group.users_group_name,
1468 1472 'group_description': user_group.user_group_description,
1469 1473 'active': user_group.users_group_active,
1470 1474 'owner': user_group.user.username,
1471 1475 'sync': user_group.sync,
1472 1476 'owner_email': user_group.user.email,
1473 1477 }
1474 1478
1475 1479 if with_group_members:
1476 1480 users = []
1477 1481 for user in user_group.members:
1478 1482 user = user.user
1479 1483 users.append(user.get_api_data(include_secrets=include_secrets))
1480 1484 data['users'] = users
1481 1485
1482 1486 return data
1483 1487
1484 1488
1485 1489 class UserGroupMember(Base, BaseModel):
1486 1490 __tablename__ = 'users_groups_members'
1487 1491 __table_args__ = (
1488 1492 base_table_args,
1489 1493 )
1490 1494
1491 1495 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1492 1496 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1493 1497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1494 1498
1495 1499 user = relationship('User', lazy='joined')
1496 1500 users_group = relationship('UserGroup')
1497 1501
1498 1502 def __init__(self, gr_id='', u_id=''):
1499 1503 self.users_group_id = gr_id
1500 1504 self.user_id = u_id
1501 1505
1502 1506
1503 1507 class RepositoryField(Base, BaseModel):
1504 1508 __tablename__ = 'repositories_fields'
1505 1509 __table_args__ = (
1506 1510 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1507 1511 base_table_args,
1508 1512 )
1509 1513
1510 1514 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1511 1515
1512 1516 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1513 1517 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514 1518 field_key = Column("field_key", String(250))
1515 1519 field_label = Column("field_label", String(1024), nullable=False)
1516 1520 field_value = Column("field_value", String(10000), nullable=False)
1517 1521 field_desc = Column("field_desc", String(1024), nullable=False)
1518 1522 field_type = Column("field_type", String(255), nullable=False, unique=None)
1519 1523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1520 1524
1521 1525 repository = relationship('Repository')
1522 1526
1523 1527 @property
1524 1528 def field_key_prefixed(self):
1525 1529 return 'ex_%s' % self.field_key
1526 1530
1527 1531 @classmethod
1528 1532 def un_prefix_key(cls, key):
1529 1533 if key.startswith(cls.PREFIX):
1530 1534 return key[len(cls.PREFIX):]
1531 1535 return key
1532 1536
1533 1537 @classmethod
1534 1538 def get_by_key_name(cls, key, repo):
1535 1539 row = cls.query()\
1536 1540 .filter(cls.repository == repo)\
1537 1541 .filter(cls.field_key == key).scalar()
1538 1542 return row
1539 1543
1540 1544
1541 1545 class Repository(Base, BaseModel):
1542 1546 __tablename__ = 'repositories'
1543 1547 __table_args__ = (
1544 1548 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1545 1549 base_table_args,
1546 1550 )
1547 1551 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1548 1552 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1549 1553 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1550 1554
1551 1555 STATE_CREATED = 'repo_state_created'
1552 1556 STATE_PENDING = 'repo_state_pending'
1553 1557 STATE_ERROR = 'repo_state_error'
1554 1558
1555 1559 LOCK_AUTOMATIC = 'lock_auto'
1556 1560 LOCK_API = 'lock_api'
1557 1561 LOCK_WEB = 'lock_web'
1558 1562 LOCK_PULL = 'lock_pull'
1559 1563
1560 1564 NAME_SEP = URL_SEP
1561 1565
1562 1566 repo_id = Column(
1563 1567 "repo_id", Integer(), nullable=False, unique=True, default=None,
1564 1568 primary_key=True)
1565 1569 _repo_name = Column(
1566 1570 "repo_name", Text(), nullable=False, default=None)
1567 1571 _repo_name_hash = Column(
1568 1572 "repo_name_hash", String(255), nullable=False, unique=True)
1569 1573 repo_state = Column("repo_state", String(255), nullable=True)
1570 1574
1571 1575 clone_uri = Column(
1572 1576 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1573 1577 default=None)
1574 1578 push_uri = Column(
1575 1579 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1576 1580 default=None)
1577 1581 repo_type = Column(
1578 1582 "repo_type", String(255), nullable=False, unique=False, default=None)
1579 1583 user_id = Column(
1580 1584 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1581 1585 unique=False, default=None)
1582 1586 private = Column(
1583 1587 "private", Boolean(), nullable=True, unique=None, default=None)
1584 1588 enable_statistics = Column(
1585 1589 "statistics", Boolean(), nullable=True, unique=None, default=True)
1586 1590 enable_downloads = Column(
1587 1591 "downloads", Boolean(), nullable=True, unique=None, default=True)
1588 1592 description = Column(
1589 1593 "description", String(10000), nullable=True, unique=None, default=None)
1590 1594 created_on = Column(
1591 1595 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1592 1596 default=datetime.datetime.now)
1593 1597 updated_on = Column(
1594 1598 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1595 1599 default=datetime.datetime.now)
1596 1600 _landing_revision = Column(
1597 1601 "landing_revision", String(255), nullable=False, unique=False,
1598 1602 default=None)
1599 1603 enable_locking = Column(
1600 1604 "enable_locking", Boolean(), nullable=False, unique=None,
1601 1605 default=False)
1602 1606 _locked = Column(
1603 1607 "locked", String(255), nullable=True, unique=False, default=None)
1604 1608 _changeset_cache = Column(
1605 1609 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1606 1610
1607 1611 fork_id = Column(
1608 1612 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1609 1613 nullable=True, unique=False, default=None)
1610 1614 group_id = Column(
1611 1615 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1612 1616 unique=False, default=None)
1613 1617
1614 1618 user = relationship('User', lazy='joined')
1615 1619 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1616 1620 group = relationship('RepoGroup', lazy='joined')
1617 1621 repo_to_perm = relationship(
1618 1622 'UserRepoToPerm', cascade='all',
1619 1623 order_by='UserRepoToPerm.repo_to_perm_id')
1620 1624 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1621 1625 stats = relationship('Statistics', cascade='all', uselist=False)
1622 1626
1623 1627 followers = relationship(
1624 1628 'UserFollowing',
1625 1629 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1626 1630 cascade='all')
1627 1631 extra_fields = relationship(
1628 1632 'RepositoryField', cascade="all, delete, delete-orphan")
1629 1633 logs = relationship('UserLog')
1630 1634 comments = relationship(
1631 1635 'ChangesetComment', cascade="all, delete, delete-orphan")
1632 1636 pull_requests_source = relationship(
1633 1637 'PullRequest',
1634 1638 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1635 1639 cascade="all, delete, delete-orphan")
1636 1640 pull_requests_target = relationship(
1637 1641 'PullRequest',
1638 1642 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1639 1643 cascade="all, delete, delete-orphan")
1640 1644 ui = relationship('RepoRhodeCodeUi', cascade="all")
1641 1645 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1642 1646 integrations = relationship('Integration',
1643 1647 cascade="all, delete, delete-orphan")
1644 1648
1645 1649 scoped_tokens = relationship('UserApiKeys', cascade="all")
1646 1650
1647 1651 def __unicode__(self):
1648 1652 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1649 1653 safe_unicode(self.repo_name))
1650 1654
1651 1655 @hybrid_property
1652 1656 def description_safe(self):
1653 1657 from rhodecode.lib import helpers as h
1654 1658 return h.escape(self.description)
1655 1659
1656 1660 @hybrid_property
1657 1661 def landing_rev(self):
1658 1662 # always should return [rev_type, rev]
1659 1663 if self._landing_revision:
1660 1664 _rev_info = self._landing_revision.split(':')
1661 1665 if len(_rev_info) < 2:
1662 1666 _rev_info.insert(0, 'rev')
1663 1667 return [_rev_info[0], _rev_info[1]]
1664 1668 return [None, None]
1665 1669
1666 1670 @landing_rev.setter
1667 1671 def landing_rev(self, val):
1668 1672 if ':' not in val:
1669 1673 raise ValueError('value must be delimited with `:` and consist '
1670 1674 'of <rev_type>:<rev>, got %s instead' % val)
1671 1675 self._landing_revision = val
1672 1676
1673 1677 @hybrid_property
1674 1678 def locked(self):
1675 1679 if self._locked:
1676 1680 user_id, timelocked, reason = self._locked.split(':')
1677 1681 lock_values = int(user_id), timelocked, reason
1678 1682 else:
1679 1683 lock_values = [None, None, None]
1680 1684 return lock_values
1681 1685
1682 1686 @locked.setter
1683 1687 def locked(self, val):
1684 1688 if val and isinstance(val, (list, tuple)):
1685 1689 self._locked = ':'.join(map(str, val))
1686 1690 else:
1687 1691 self._locked = None
1688 1692
1689 1693 @hybrid_property
1690 1694 def changeset_cache(self):
1691 1695 from rhodecode.lib.vcs.backends.base import EmptyCommit
1692 1696 dummy = EmptyCommit().__json__()
1693 1697 if not self._changeset_cache:
1694 1698 return dummy
1695 1699 try:
1696 1700 return json.loads(self._changeset_cache)
1697 1701 except TypeError:
1698 1702 return dummy
1699 1703 except Exception:
1700 1704 log.error(traceback.format_exc())
1701 1705 return dummy
1702 1706
1703 1707 @changeset_cache.setter
1704 1708 def changeset_cache(self, val):
1705 1709 try:
1706 1710 self._changeset_cache = json.dumps(val)
1707 1711 except Exception:
1708 1712 log.error(traceback.format_exc())
1709 1713
1710 1714 @hybrid_property
1711 1715 def repo_name(self):
1712 1716 return self._repo_name
1713 1717
1714 1718 @repo_name.setter
1715 1719 def repo_name(self, value):
1716 1720 self._repo_name = value
1717 1721 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1718 1722
1719 1723 @classmethod
1720 1724 def normalize_repo_name(cls, repo_name):
1721 1725 """
1722 1726 Normalizes os specific repo_name to the format internally stored inside
1723 1727 database using URL_SEP
1724 1728
1725 1729 :param cls:
1726 1730 :param repo_name:
1727 1731 """
1728 1732 return cls.NAME_SEP.join(repo_name.split(os.sep))
1729 1733
1730 1734 @classmethod
1731 1735 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1732 1736 session = Session()
1733 1737 q = session.query(cls).filter(cls.repo_name == repo_name)
1734 1738
1735 1739 if cache:
1736 1740 if identity_cache:
1737 1741 val = cls.identity_cache(session, 'repo_name', repo_name)
1738 1742 if val:
1739 1743 return val
1740 1744 else:
1741 1745 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1742 1746 q = q.options(
1743 1747 FromCache("sql_cache_short", cache_key))
1744 1748
1745 1749 return q.scalar()
1746 1750
1747 1751 @classmethod
1748 1752 def get_by_id_or_repo_name(cls, repoid):
1749 1753 if isinstance(repoid, (int, long)):
1750 1754 try:
1751 1755 repo = cls.get(repoid)
1752 1756 except ValueError:
1753 1757 repo = None
1754 1758 else:
1755 1759 repo = cls.get_by_repo_name(repoid)
1756 1760 return repo
1757 1761
1758 1762 @classmethod
1759 1763 def get_by_full_path(cls, repo_full_path):
1760 1764 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1761 1765 repo_name = cls.normalize_repo_name(repo_name)
1762 1766 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1763 1767
1764 1768 @classmethod
1765 1769 def get_repo_forks(cls, repo_id):
1766 1770 return cls.query().filter(Repository.fork_id == repo_id)
1767 1771
1768 1772 @classmethod
1769 1773 def base_path(cls):
1770 1774 """
1771 1775 Returns base path when all repos are stored
1772 1776
1773 1777 :param cls:
1774 1778 """
1775 1779 q = Session().query(RhodeCodeUi)\
1776 1780 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1777 1781 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1778 1782 return q.one().ui_value
1779 1783
1780 1784 @classmethod
1781 1785 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1782 1786 case_insensitive=True):
1783 1787 q = Repository.query()
1784 1788
1785 1789 if not isinstance(user_id, Optional):
1786 1790 q = q.filter(Repository.user_id == user_id)
1787 1791
1788 1792 if not isinstance(group_id, Optional):
1789 1793 q = q.filter(Repository.group_id == group_id)
1790 1794
1791 1795 if case_insensitive:
1792 1796 q = q.order_by(func.lower(Repository.repo_name))
1793 1797 else:
1794 1798 q = q.order_by(Repository.repo_name)
1795 1799 return q.all()
1796 1800
1797 1801 @property
1798 1802 def forks(self):
1799 1803 """
1800 1804 Return forks of this repo
1801 1805 """
1802 1806 return Repository.get_repo_forks(self.repo_id)
1803 1807
1804 1808 @property
1805 1809 def parent(self):
1806 1810 """
1807 1811 Returns fork parent
1808 1812 """
1809 1813 return self.fork
1810 1814
1811 1815 @property
1812 1816 def just_name(self):
1813 1817 return self.repo_name.split(self.NAME_SEP)[-1]
1814 1818
1815 1819 @property
1816 1820 def groups_with_parents(self):
1817 1821 groups = []
1818 1822 if self.group is None:
1819 1823 return groups
1820 1824
1821 1825 cur_gr = self.group
1822 1826 groups.insert(0, cur_gr)
1823 1827 while 1:
1824 1828 gr = getattr(cur_gr, 'parent_group', None)
1825 1829 cur_gr = cur_gr.parent_group
1826 1830 if gr is None:
1827 1831 break
1828 1832 groups.insert(0, gr)
1829 1833
1830 1834 return groups
1831 1835
1832 1836 @property
1833 1837 def groups_and_repo(self):
1834 1838 return self.groups_with_parents, self
1835 1839
1836 1840 @LazyProperty
1837 1841 def repo_path(self):
1838 1842 """
1839 1843 Returns base full path for that repository means where it actually
1840 1844 exists on a filesystem
1841 1845 """
1842 1846 q = Session().query(RhodeCodeUi).filter(
1843 1847 RhodeCodeUi.ui_key == self.NAME_SEP)
1844 1848 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1845 1849 return q.one().ui_value
1846 1850
1847 1851 @property
1848 1852 def repo_full_path(self):
1849 1853 p = [self.repo_path]
1850 1854 # we need to split the name by / since this is how we store the
1851 1855 # names in the database, but that eventually needs to be converted
1852 1856 # into a valid system path
1853 1857 p += self.repo_name.split(self.NAME_SEP)
1854 1858 return os.path.join(*map(safe_unicode, p))
1855 1859
1856 1860 @property
1857 1861 def cache_keys(self):
1858 1862 """
1859 1863 Returns associated cache keys for that repo
1860 1864 """
1861 1865 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1862 1866 repo_id=self.repo_id)
1863 1867 return CacheKey.query()\
1864 1868 .filter(CacheKey.cache_args == invalidation_namespace)\
1865 1869 .order_by(CacheKey.cache_key)\
1866 1870 .all()
1867 1871
1868 1872 @property
1869 1873 def cached_diffs_relative_dir(self):
1870 1874 """
1871 1875 Return a relative to the repository store path of cached diffs
1872 1876 used for safe display for users, who shouldn't know the absolute store
1873 1877 path
1874 1878 """
1875 1879 return os.path.join(
1876 1880 os.path.dirname(self.repo_name),
1877 1881 self.cached_diffs_dir.split(os.path.sep)[-1])
1878 1882
1879 1883 @property
1880 1884 def cached_diffs_dir(self):
1881 1885 path = self.repo_full_path
1882 1886 return os.path.join(
1883 1887 os.path.dirname(path),
1884 1888 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1885 1889
1886 1890 def cached_diffs(self):
1887 1891 diff_cache_dir = self.cached_diffs_dir
1888 1892 if os.path.isdir(diff_cache_dir):
1889 1893 return os.listdir(diff_cache_dir)
1890 1894 return []
1891 1895
1892 1896 def shadow_repos(self):
1893 1897 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1894 1898 return [
1895 1899 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1896 1900 if x.startswith(shadow_repos_pattern)]
1897 1901
1898 1902 def get_new_name(self, repo_name):
1899 1903 """
1900 1904 returns new full repository name based on assigned group and new new
1901 1905
1902 1906 :param group_name:
1903 1907 """
1904 1908 path_prefix = self.group.full_path_splitted if self.group else []
1905 1909 return self.NAME_SEP.join(path_prefix + [repo_name])
1906 1910
1907 1911 @property
1908 1912 def _config(self):
1909 1913 """
1910 1914 Returns db based config object.
1911 1915 """
1912 1916 from rhodecode.lib.utils import make_db_config
1913 1917 return make_db_config(clear_session=False, repo=self)
1914 1918
1915 1919 def permissions(self, with_admins=True, with_owner=True):
1916 1920 """
1917 1921 Permissions for repositories
1918 1922 """
1919 1923 _admin_perm = 'repository.admin'
1920 1924
1921 1925 owner_row = []
1922 1926 if with_owner:
1923 1927 usr = AttributeDict(self.user.get_dict())
1924 1928 usr.owner_row = True
1925 1929 usr.permission = _admin_perm
1926 1930 usr.permission_id = None
1927 1931 owner_row.append(usr)
1928 1932
1929 1933 super_admin_ids = []
1930 1934 super_admin_rows = []
1931 1935 if with_admins:
1932 1936 for usr in User.get_all_super_admins():
1933 1937 super_admin_ids.append(usr.user_id)
1934 1938 # if this admin is also owner, don't double the record
1935 1939 if usr.user_id == owner_row[0].user_id:
1936 1940 owner_row[0].admin_row = True
1937 1941 else:
1938 1942 usr = AttributeDict(usr.get_dict())
1939 1943 usr.admin_row = True
1940 1944 usr.permission = _admin_perm
1941 1945 usr.permission_id = None
1942 1946 super_admin_rows.append(usr)
1943 1947
1944 1948 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1945 1949 q = q.options(joinedload(UserRepoToPerm.repository),
1946 1950 joinedload(UserRepoToPerm.user),
1947 1951 joinedload(UserRepoToPerm.permission),)
1948 1952
1949 1953 # get owners and admins and permissions. We do a trick of re-writing
1950 1954 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1951 1955 # has a global reference and changing one object propagates to all
1952 1956 # others. This means if admin is also an owner admin_row that change
1953 1957 # would propagate to both objects
1954 1958 perm_rows = []
1955 1959 for _usr in q.all():
1956 1960 usr = AttributeDict(_usr.user.get_dict())
1957 1961 # if this user is also owner/admin, mark as duplicate record
1958 1962 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1959 1963 usr.duplicate_perm = True
1960 1964 # also check if this permission is maybe used by branch_permissions
1961 1965 if _usr.branch_perm_entry:
1962 1966 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1963 1967
1964 1968 usr.permission = _usr.permission.permission_name
1965 1969 usr.permission_id = _usr.repo_to_perm_id
1966 1970 perm_rows.append(usr)
1967 1971
1968 1972 # filter the perm rows by 'default' first and then sort them by
1969 1973 # admin,write,read,none permissions sorted again alphabetically in
1970 1974 # each group
1971 1975 perm_rows = sorted(perm_rows, key=display_user_sort)
1972 1976
1973 1977 return super_admin_rows + owner_row + perm_rows
1974 1978
1975 1979 def permission_user_groups(self):
1976 1980 q = UserGroupRepoToPerm.query().filter(
1977 1981 UserGroupRepoToPerm.repository == self)
1978 1982 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1979 1983 joinedload(UserGroupRepoToPerm.users_group),
1980 1984 joinedload(UserGroupRepoToPerm.permission),)
1981 1985
1982 1986 perm_rows = []
1983 1987 for _user_group in q.all():
1984 1988 usr = AttributeDict(_user_group.users_group.get_dict())
1985 1989 usr.permission = _user_group.permission.permission_name
1986 1990 perm_rows.append(usr)
1987 1991
1988 1992 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1989 1993 return perm_rows
1990 1994
1991 1995 def get_api_data(self, include_secrets=False):
1992 1996 """
1993 1997 Common function for generating repo api data
1994 1998
1995 1999 :param include_secrets: See :meth:`User.get_api_data`.
1996 2000
1997 2001 """
1998 2002 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1999 2003 # move this methods on models level.
2000 2004 from rhodecode.model.settings import SettingsModel
2001 2005 from rhodecode.model.repo import RepoModel
2002 2006
2003 2007 repo = self
2004 2008 _user_id, _time, _reason = self.locked
2005 2009
2006 2010 data = {
2007 2011 'repo_id': repo.repo_id,
2008 2012 'repo_name': repo.repo_name,
2009 2013 'repo_type': repo.repo_type,
2010 2014 'clone_uri': repo.clone_uri or '',
2011 2015 'push_uri': repo.push_uri or '',
2012 2016 'url': RepoModel().get_url(self),
2013 2017 'private': repo.private,
2014 2018 'created_on': repo.created_on,
2015 2019 'description': repo.description_safe,
2016 2020 'landing_rev': repo.landing_rev,
2017 2021 'owner': repo.user.username,
2018 2022 'fork_of': repo.fork.repo_name if repo.fork else None,
2019 2023 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2020 2024 'enable_statistics': repo.enable_statistics,
2021 2025 'enable_locking': repo.enable_locking,
2022 2026 'enable_downloads': repo.enable_downloads,
2023 2027 'last_changeset': repo.changeset_cache,
2024 2028 'locked_by': User.get(_user_id).get_api_data(
2025 2029 include_secrets=include_secrets) if _user_id else None,
2026 2030 'locked_date': time_to_datetime(_time) if _time else None,
2027 2031 'lock_reason': _reason if _reason else None,
2028 2032 }
2029 2033
2030 2034 # TODO: mikhail: should be per-repo settings here
2031 2035 rc_config = SettingsModel().get_all_settings()
2032 2036 repository_fields = str2bool(
2033 2037 rc_config.get('rhodecode_repository_fields'))
2034 2038 if repository_fields:
2035 2039 for f in self.extra_fields:
2036 2040 data[f.field_key_prefixed] = f.field_value
2037 2041
2038 2042 return data
2039 2043
2040 2044 @classmethod
2041 2045 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2042 2046 if not lock_time:
2043 2047 lock_time = time.time()
2044 2048 if not lock_reason:
2045 2049 lock_reason = cls.LOCK_AUTOMATIC
2046 2050 repo.locked = [user_id, lock_time, lock_reason]
2047 2051 Session().add(repo)
2048 2052 Session().commit()
2049 2053
2050 2054 @classmethod
2051 2055 def unlock(cls, repo):
2052 2056 repo.locked = None
2053 2057 Session().add(repo)
2054 2058 Session().commit()
2055 2059
2056 2060 @classmethod
2057 2061 def getlock(cls, repo):
2058 2062 return repo.locked
2059 2063
2060 2064 def is_user_lock(self, user_id):
2061 2065 if self.lock[0]:
2062 2066 lock_user_id = safe_int(self.lock[0])
2063 2067 user_id = safe_int(user_id)
2064 2068 # both are ints, and they are equal
2065 2069 return all([lock_user_id, user_id]) and lock_user_id == user_id
2066 2070
2067 2071 return False
2068 2072
2069 2073 def get_locking_state(self, action, user_id, only_when_enabled=True):
2070 2074 """
2071 2075 Checks locking on this repository, if locking is enabled and lock is
2072 2076 present returns a tuple of make_lock, locked, locked_by.
2073 2077 make_lock can have 3 states None (do nothing) True, make lock
2074 2078 False release lock, This value is later propagated to hooks, which
2075 2079 do the locking. Think about this as signals passed to hooks what to do.
2076 2080
2077 2081 """
2078 2082 # TODO: johbo: This is part of the business logic and should be moved
2079 2083 # into the RepositoryModel.
2080 2084
2081 2085 if action not in ('push', 'pull'):
2082 2086 raise ValueError("Invalid action value: %s" % repr(action))
2083 2087
2084 2088 # defines if locked error should be thrown to user
2085 2089 currently_locked = False
2086 2090 # defines if new lock should be made, tri-state
2087 2091 make_lock = None
2088 2092 repo = self
2089 2093 user = User.get(user_id)
2090 2094
2091 2095 lock_info = repo.locked
2092 2096
2093 2097 if repo and (repo.enable_locking or not only_when_enabled):
2094 2098 if action == 'push':
2095 2099 # check if it's already locked !, if it is compare users
2096 2100 locked_by_user_id = lock_info[0]
2097 2101 if user.user_id == locked_by_user_id:
2098 2102 log.debug(
2099 2103 'Got `push` action from user %s, now unlocking', user)
2100 2104 # unlock if we have push from user who locked
2101 2105 make_lock = False
2102 2106 else:
2103 2107 # we're not the same user who locked, ban with
2104 2108 # code defined in settings (default is 423 HTTP Locked) !
2105 2109 log.debug('Repo %s is currently locked by %s', repo, user)
2106 2110 currently_locked = True
2107 2111 elif action == 'pull':
2108 2112 # [0] user [1] date
2109 2113 if lock_info[0] and lock_info[1]:
2110 2114 log.debug('Repo %s is currently locked by %s', repo, user)
2111 2115 currently_locked = True
2112 2116 else:
2113 2117 log.debug('Setting lock on repo %s by %s', repo, user)
2114 2118 make_lock = True
2115 2119
2116 2120 else:
2117 2121 log.debug('Repository %s do not have locking enabled', repo)
2118 2122
2119 2123 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2120 2124 make_lock, currently_locked, lock_info)
2121 2125
2122 2126 from rhodecode.lib.auth import HasRepoPermissionAny
2123 2127 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2124 2128 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2125 2129 # if we don't have at least write permission we cannot make a lock
2126 2130 log.debug('lock state reset back to FALSE due to lack '
2127 2131 'of at least read permission')
2128 2132 make_lock = False
2129 2133
2130 2134 return make_lock, currently_locked, lock_info
2131 2135
2132 2136 @property
2133 2137 def last_db_change(self):
2134 2138 return self.updated_on
2135 2139
2136 2140 @property
2137 2141 def clone_uri_hidden(self):
2138 2142 clone_uri = self.clone_uri
2139 2143 if clone_uri:
2140 2144 import urlobject
2141 2145 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2142 2146 if url_obj.password:
2143 2147 clone_uri = url_obj.with_password('*****')
2144 2148 return clone_uri
2145 2149
2146 2150 @property
2147 2151 def push_uri_hidden(self):
2148 2152 push_uri = self.push_uri
2149 2153 if push_uri:
2150 2154 import urlobject
2151 2155 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2152 2156 if url_obj.password:
2153 2157 push_uri = url_obj.with_password('*****')
2154 2158 return push_uri
2155 2159
2156 2160 def clone_url(self, **override):
2157 2161 from rhodecode.model.settings import SettingsModel
2158 2162
2159 2163 uri_tmpl = None
2160 2164 if 'with_id' in override:
2161 2165 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2162 2166 del override['with_id']
2163 2167
2164 2168 if 'uri_tmpl' in override:
2165 2169 uri_tmpl = override['uri_tmpl']
2166 2170 del override['uri_tmpl']
2167 2171
2168 2172 ssh = False
2169 2173 if 'ssh' in override:
2170 2174 ssh = True
2171 2175 del override['ssh']
2172 2176
2173 2177 # we didn't override our tmpl from **overrides
2174 2178 if not uri_tmpl:
2175 2179 rc_config = SettingsModel().get_all_settings(cache=True)
2176 2180 if ssh:
2177 2181 uri_tmpl = rc_config.get(
2178 2182 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2179 2183 else:
2180 2184 uri_tmpl = rc_config.get(
2181 2185 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2182 2186
2183 2187 request = get_current_request()
2184 2188 return get_clone_url(request=request,
2185 2189 uri_tmpl=uri_tmpl,
2186 2190 repo_name=self.repo_name,
2187 2191 repo_id=self.repo_id, **override)
2188 2192
2189 2193 def set_state(self, state):
2190 2194 self.repo_state = state
2191 2195 Session().add(self)
2192 2196 #==========================================================================
2193 2197 # SCM PROPERTIES
2194 2198 #==========================================================================
2195 2199
2196 2200 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2197 2201 return get_commit_safe(
2198 2202 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2199 2203
2200 2204 def get_changeset(self, rev=None, pre_load=None):
2201 2205 warnings.warn("Use get_commit", DeprecationWarning)
2202 2206 commit_id = None
2203 2207 commit_idx = None
2204 2208 if isinstance(rev, basestring):
2205 2209 commit_id = rev
2206 2210 else:
2207 2211 commit_idx = rev
2208 2212 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2209 2213 pre_load=pre_load)
2210 2214
2211 2215 def get_landing_commit(self):
2212 2216 """
2213 2217 Returns landing commit, or if that doesn't exist returns the tip
2214 2218 """
2215 2219 _rev_type, _rev = self.landing_rev
2216 2220 commit = self.get_commit(_rev)
2217 2221 if isinstance(commit, EmptyCommit):
2218 2222 return self.get_commit()
2219 2223 return commit
2220 2224
2221 2225 def update_commit_cache(self, cs_cache=None, config=None):
2222 2226 """
2223 2227 Update cache of last changeset for repository, keys should be::
2224 2228
2225 2229 short_id
2226 2230 raw_id
2227 2231 revision
2228 2232 parents
2229 2233 message
2230 2234 date
2231 2235 author
2232 2236
2233 2237 :param cs_cache:
2234 2238 """
2235 2239 from rhodecode.lib.vcs.backends.base import BaseChangeset
2236 2240 if cs_cache is None:
2237 2241 # use no-cache version here
2238 2242 scm_repo = self.scm_instance(cache=False, config=config)
2239 2243
2240 2244 empty = scm_repo.is_empty()
2241 2245 if not empty:
2242 2246 cs_cache = scm_repo.get_commit(
2243 2247 pre_load=["author", "date", "message", "parents"])
2244 2248 else:
2245 2249 cs_cache = EmptyCommit()
2246 2250
2247 2251 if isinstance(cs_cache, BaseChangeset):
2248 2252 cs_cache = cs_cache.__json__()
2249 2253
2250 2254 def is_outdated(new_cs_cache):
2251 2255 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2252 2256 new_cs_cache['revision'] != self.changeset_cache['revision']):
2253 2257 return True
2254 2258 return False
2255 2259
2256 2260 # check if we have maybe already latest cached revision
2257 2261 if is_outdated(cs_cache) or not self.changeset_cache:
2258 2262 _default = datetime.datetime.utcnow()
2259 2263 last_change = cs_cache.get('date') or _default
2260 2264 if self.updated_on and self.updated_on > last_change:
2261 2265 # we check if last update is newer than the new value
2262 2266 # if yes, we use the current timestamp instead. Imagine you get
2263 2267 # old commit pushed 1y ago, we'd set last update 1y to ago.
2264 2268 last_change = _default
2265 2269 log.debug('updated repo %s with new cs cache %s',
2266 2270 self.repo_name, cs_cache)
2267 2271 self.updated_on = last_change
2268 2272 self.changeset_cache = cs_cache
2269 2273 Session().add(self)
2270 2274 Session().commit()
2271 2275 else:
2272 2276 log.debug('Skipping update_commit_cache for repo:`%s` '
2273 2277 'commit already with latest changes', self.repo_name)
2274 2278
2275 2279 @property
2276 2280 def tip(self):
2277 2281 return self.get_commit('tip')
2278 2282
2279 2283 @property
2280 2284 def author(self):
2281 2285 return self.tip.author
2282 2286
2283 2287 @property
2284 2288 def last_change(self):
2285 2289 return self.scm_instance().last_change
2286 2290
2287 2291 def get_comments(self, revisions=None):
2288 2292 """
2289 2293 Returns comments for this repository grouped by revisions
2290 2294
2291 2295 :param revisions: filter query by revisions only
2292 2296 """
2293 2297 cmts = ChangesetComment.query()\
2294 2298 .filter(ChangesetComment.repo == self)
2295 2299 if revisions:
2296 2300 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2297 2301 grouped = collections.defaultdict(list)
2298 2302 for cmt in cmts.all():
2299 2303 grouped[cmt.revision].append(cmt)
2300 2304 return grouped
2301 2305
2302 2306 def statuses(self, revisions=None):
2303 2307 """
2304 2308 Returns statuses for this repository
2305 2309
2306 2310 :param revisions: list of revisions to get statuses for
2307 2311 """
2308 2312 statuses = ChangesetStatus.query()\
2309 2313 .filter(ChangesetStatus.repo == self)\
2310 2314 .filter(ChangesetStatus.version == 0)
2311 2315
2312 2316 if revisions:
2313 2317 # Try doing the filtering in chunks to avoid hitting limits
2314 2318 size = 500
2315 2319 status_results = []
2316 2320 for chunk in xrange(0, len(revisions), size):
2317 2321 status_results += statuses.filter(
2318 2322 ChangesetStatus.revision.in_(
2319 2323 revisions[chunk: chunk+size])
2320 2324 ).all()
2321 2325 else:
2322 2326 status_results = statuses.all()
2323 2327
2324 2328 grouped = {}
2325 2329
2326 2330 # maybe we have open new pullrequest without a status?
2327 2331 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2328 2332 status_lbl = ChangesetStatus.get_status_lbl(stat)
2329 2333 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2330 2334 for rev in pr.revisions:
2331 2335 pr_id = pr.pull_request_id
2332 2336 pr_repo = pr.target_repo.repo_name
2333 2337 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2334 2338
2335 2339 for stat in status_results:
2336 2340 pr_id = pr_repo = None
2337 2341 if stat.pull_request:
2338 2342 pr_id = stat.pull_request.pull_request_id
2339 2343 pr_repo = stat.pull_request.target_repo.repo_name
2340 2344 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2341 2345 pr_id, pr_repo]
2342 2346 return grouped
2343 2347
2344 2348 # ==========================================================================
2345 2349 # SCM CACHE INSTANCE
2346 2350 # ==========================================================================
2347 2351
2348 2352 def scm_instance(self, **kwargs):
2349 2353 import rhodecode
2350 2354
2351 2355 # Passing a config will not hit the cache currently only used
2352 2356 # for repo2dbmapper
2353 2357 config = kwargs.pop('config', None)
2354 2358 cache = kwargs.pop('cache', None)
2355 2359 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2356 2360 # if cache is NOT defined use default global, else we have a full
2357 2361 # control over cache behaviour
2358 2362 if cache is None and full_cache and not config:
2359 2363 return self._get_instance_cached()
2360 2364 return self._get_instance(cache=bool(cache), config=config)
2361 2365
2362 2366 def _get_instance_cached(self):
2363 2367 from rhodecode.lib import rc_cache
2364 2368
2365 2369 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2366 2370 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2367 2371 repo_id=self.repo_id)
2368 2372 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2369 2373
2370 2374 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2371 2375 def get_instance_cached(repo_id, context_id):
2372 2376 return self._get_instance()
2373 2377
2374 2378 # we must use thread scoped cache here,
2375 2379 # because each thread of gevent needs it's own not shared connection and cache
2376 2380 # we also alter `args` so the cache key is individual for every green thread.
2377 2381 inv_context_manager = rc_cache.InvalidationContext(
2378 2382 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2379 2383 thread_scoped=True)
2380 2384 with inv_context_manager as invalidation_context:
2381 2385 args = (self.repo_id, inv_context_manager.cache_key)
2382 2386 # re-compute and store cache if we get invalidate signal
2383 2387 if invalidation_context.should_invalidate():
2384 2388 instance = get_instance_cached.refresh(*args)
2385 2389 else:
2386 2390 instance = get_instance_cached(*args)
2387 2391
2388 2392 log.debug(
2389 2393 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2390 2394 return instance
2391 2395
2392 2396 def _get_instance(self, cache=True, config=None):
2393 2397 config = config or self._config
2394 2398 custom_wire = {
2395 2399 'cache': cache # controls the vcs.remote cache
2396 2400 }
2397 2401 repo = get_vcs_instance(
2398 2402 repo_path=safe_str(self.repo_full_path),
2399 2403 config=config,
2400 2404 with_wire=custom_wire,
2401 2405 create=False,
2402 2406 _vcs_alias=self.repo_type)
2403 2407
2404 2408 return repo
2405 2409
2406 2410 def __json__(self):
2407 2411 return {'landing_rev': self.landing_rev}
2408 2412
2409 2413 def get_dict(self):
2410 2414
2411 2415 # Since we transformed `repo_name` to a hybrid property, we need to
2412 2416 # keep compatibility with the code which uses `repo_name` field.
2413 2417
2414 2418 result = super(Repository, self).get_dict()
2415 2419 result['repo_name'] = result.pop('_repo_name', None)
2416 2420 return result
2417 2421
2418 2422
2419 2423 class RepoGroup(Base, BaseModel):
2420 2424 __tablename__ = 'groups'
2421 2425 __table_args__ = (
2422 2426 UniqueConstraint('group_name', 'group_parent_id'),
2423 2427 CheckConstraint('group_id != group_parent_id'),
2424 2428 base_table_args,
2425 2429 )
2426 2430 __mapper_args__ = {'order_by': 'group_name'}
2427 2431
2428 2432 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2429 2433
2430 2434 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2431 2435 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2432 2436 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2433 2437 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2434 2438 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2435 2439 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2436 2440 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2437 2441 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2438 2442 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2439 2443
2440 2444 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2441 2445 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2442 2446 parent_group = relationship('RepoGroup', remote_side=group_id)
2443 2447 user = relationship('User')
2444 2448 integrations = relationship('Integration',
2445 2449 cascade="all, delete, delete-orphan")
2446 2450
2447 2451 def __init__(self, group_name='', parent_group=None):
2448 2452 self.group_name = group_name
2449 2453 self.parent_group = parent_group
2450 2454
2451 2455 def __unicode__(self):
2452 2456 return u"<%s('id:%s:%s')>" % (
2453 2457 self.__class__.__name__, self.group_id, self.group_name)
2454 2458
2455 2459 @hybrid_property
2456 2460 def description_safe(self):
2457 2461 from rhodecode.lib import helpers as h
2458 2462 return h.escape(self.group_description)
2459 2463
2460 2464 @classmethod
2461 2465 def _generate_choice(cls, repo_group):
2462 2466 from webhelpers.html import literal as _literal
2463 2467 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2464 2468 return repo_group.group_id, _name(repo_group.full_path_splitted)
2465 2469
2466 2470 @classmethod
2467 2471 def groups_choices(cls, groups=None, show_empty_group=True):
2468 2472 if not groups:
2469 2473 groups = cls.query().all()
2470 2474
2471 2475 repo_groups = []
2472 2476 if show_empty_group:
2473 2477 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2474 2478
2475 2479 repo_groups.extend([cls._generate_choice(x) for x in groups])
2476 2480
2477 2481 repo_groups = sorted(
2478 2482 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2479 2483 return repo_groups
2480 2484
2481 2485 @classmethod
2482 2486 def url_sep(cls):
2483 2487 return URL_SEP
2484 2488
2485 2489 @classmethod
2486 2490 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2487 2491 if case_insensitive:
2488 2492 gr = cls.query().filter(func.lower(cls.group_name)
2489 2493 == func.lower(group_name))
2490 2494 else:
2491 2495 gr = cls.query().filter(cls.group_name == group_name)
2492 2496 if cache:
2493 2497 name_key = _hash_key(group_name)
2494 2498 gr = gr.options(
2495 2499 FromCache("sql_cache_short", "get_group_%s" % name_key))
2496 2500 return gr.scalar()
2497 2501
2498 2502 @classmethod
2499 2503 def get_user_personal_repo_group(cls, user_id):
2500 2504 user = User.get(user_id)
2501 2505 if user.username == User.DEFAULT_USER:
2502 2506 return None
2503 2507
2504 2508 return cls.query()\
2505 2509 .filter(cls.personal == true()) \
2506 2510 .filter(cls.user == user).scalar()
2507 2511
2508 2512 @classmethod
2509 2513 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2510 2514 case_insensitive=True):
2511 2515 q = RepoGroup.query()
2512 2516
2513 2517 if not isinstance(user_id, Optional):
2514 2518 q = q.filter(RepoGroup.user_id == user_id)
2515 2519
2516 2520 if not isinstance(group_id, Optional):
2517 2521 q = q.filter(RepoGroup.group_parent_id == group_id)
2518 2522
2519 2523 if case_insensitive:
2520 2524 q = q.order_by(func.lower(RepoGroup.group_name))
2521 2525 else:
2522 2526 q = q.order_by(RepoGroup.group_name)
2523 2527 return q.all()
2524 2528
2525 2529 @property
2526 2530 def parents(self):
2527 2531 parents_recursion_limit = 10
2528 2532 groups = []
2529 2533 if self.parent_group is None:
2530 2534 return groups
2531 2535 cur_gr = self.parent_group
2532 2536 groups.insert(0, cur_gr)
2533 2537 cnt = 0
2534 2538 while 1:
2535 2539 cnt += 1
2536 2540 gr = getattr(cur_gr, 'parent_group', None)
2537 2541 cur_gr = cur_gr.parent_group
2538 2542 if gr is None:
2539 2543 break
2540 2544 if cnt == parents_recursion_limit:
2541 2545 # this will prevent accidental infinit loops
2542 2546 log.error('more than %s parents found for group %s, stopping '
2543 2547 'recursive parent fetching', parents_recursion_limit, self)
2544 2548 break
2545 2549
2546 2550 groups.insert(0, gr)
2547 2551 return groups
2548 2552
2549 2553 @property
2550 2554 def last_db_change(self):
2551 2555 return self.updated_on
2552 2556
2553 2557 @property
2554 2558 def children(self):
2555 2559 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2556 2560
2557 2561 @property
2558 2562 def name(self):
2559 2563 return self.group_name.split(RepoGroup.url_sep())[-1]
2560 2564
2561 2565 @property
2562 2566 def full_path(self):
2563 2567 return self.group_name
2564 2568
2565 2569 @property
2566 2570 def full_path_splitted(self):
2567 2571 return self.group_name.split(RepoGroup.url_sep())
2568 2572
2569 2573 @property
2570 2574 def repositories(self):
2571 2575 return Repository.query()\
2572 2576 .filter(Repository.group == self)\
2573 2577 .order_by(Repository.repo_name)
2574 2578
2575 2579 @property
2576 2580 def repositories_recursive_count(self):
2577 2581 cnt = self.repositories.count()
2578 2582
2579 2583 def children_count(group):
2580 2584 cnt = 0
2581 2585 for child in group.children:
2582 2586 cnt += child.repositories.count()
2583 2587 cnt += children_count(child)
2584 2588 return cnt
2585 2589
2586 2590 return cnt + children_count(self)
2587 2591
2588 2592 def _recursive_objects(self, include_repos=True):
2589 2593 all_ = []
2590 2594
2591 2595 def _get_members(root_gr):
2592 2596 if include_repos:
2593 2597 for r in root_gr.repositories:
2594 2598 all_.append(r)
2595 2599 childs = root_gr.children.all()
2596 2600 if childs:
2597 2601 for gr in childs:
2598 2602 all_.append(gr)
2599 2603 _get_members(gr)
2600 2604
2601 2605 _get_members(self)
2602 2606 return [self] + all_
2603 2607
2604 2608 def recursive_groups_and_repos(self):
2605 2609 """
2606 2610 Recursive return all groups, with repositories in those groups
2607 2611 """
2608 2612 return self._recursive_objects()
2609 2613
2610 2614 def recursive_groups(self):
2611 2615 """
2612 2616 Returns all children groups for this group including children of children
2613 2617 """
2614 2618 return self._recursive_objects(include_repos=False)
2615 2619
2616 2620 def get_new_name(self, group_name):
2617 2621 """
2618 2622 returns new full group name based on parent and new name
2619 2623
2620 2624 :param group_name:
2621 2625 """
2622 2626 path_prefix = (self.parent_group.full_path_splitted if
2623 2627 self.parent_group else [])
2624 2628 return RepoGroup.url_sep().join(path_prefix + [group_name])
2625 2629
2626 2630 def permissions(self, with_admins=True, with_owner=True):
2627 2631 """
2628 2632 Permissions for repository groups
2629 2633 """
2630 2634 _admin_perm = 'group.admin'
2631 2635
2632 2636 owner_row = []
2633 2637 if with_owner:
2634 2638 usr = AttributeDict(self.user.get_dict())
2635 2639 usr.owner_row = True
2636 2640 usr.permission = _admin_perm
2637 2641 owner_row.append(usr)
2638 2642
2639 2643 super_admin_ids = []
2640 2644 super_admin_rows = []
2641 2645 if with_admins:
2642 2646 for usr in User.get_all_super_admins():
2643 2647 super_admin_ids.append(usr.user_id)
2644 2648 # if this admin is also owner, don't double the record
2645 2649 if usr.user_id == owner_row[0].user_id:
2646 2650 owner_row[0].admin_row = True
2647 2651 else:
2648 2652 usr = AttributeDict(usr.get_dict())
2649 2653 usr.admin_row = True
2650 2654 usr.permission = _admin_perm
2651 2655 super_admin_rows.append(usr)
2652 2656
2653 2657 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2654 2658 q = q.options(joinedload(UserRepoGroupToPerm.group),
2655 2659 joinedload(UserRepoGroupToPerm.user),
2656 2660 joinedload(UserRepoGroupToPerm.permission),)
2657 2661
2658 2662 # get owners and admins and permissions. We do a trick of re-writing
2659 2663 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2660 2664 # has a global reference and changing one object propagates to all
2661 2665 # others. This means if admin is also an owner admin_row that change
2662 2666 # would propagate to both objects
2663 2667 perm_rows = []
2664 2668 for _usr in q.all():
2665 2669 usr = AttributeDict(_usr.user.get_dict())
2666 2670 # if this user is also owner/admin, mark as duplicate record
2667 2671 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2668 2672 usr.duplicate_perm = True
2669 2673 usr.permission = _usr.permission.permission_name
2670 2674 perm_rows.append(usr)
2671 2675
2672 2676 # filter the perm rows by 'default' first and then sort them by
2673 2677 # admin,write,read,none permissions sorted again alphabetically in
2674 2678 # each group
2675 2679 perm_rows = sorted(perm_rows, key=display_user_sort)
2676 2680
2677 2681 return super_admin_rows + owner_row + perm_rows
2678 2682
2679 2683 def permission_user_groups(self):
2680 2684 q = UserGroupRepoGroupToPerm.query().filter(
2681 2685 UserGroupRepoGroupToPerm.group == self)
2682 2686 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2683 2687 joinedload(UserGroupRepoGroupToPerm.users_group),
2684 2688 joinedload(UserGroupRepoGroupToPerm.permission),)
2685 2689
2686 2690 perm_rows = []
2687 2691 for _user_group in q.all():
2688 2692 usr = AttributeDict(_user_group.users_group.get_dict())
2689 2693 usr.permission = _user_group.permission.permission_name
2690 2694 perm_rows.append(usr)
2691 2695
2692 2696 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2693 2697 return perm_rows
2694 2698
2695 2699 def get_api_data(self):
2696 2700 """
2697 2701 Common function for generating api data
2698 2702
2699 2703 """
2700 2704 group = self
2701 2705 data = {
2702 2706 'group_id': group.group_id,
2703 2707 'group_name': group.group_name,
2704 2708 'group_description': group.description_safe,
2705 2709 'parent_group': group.parent_group.group_name if group.parent_group else None,
2706 2710 'repositories': [x.repo_name for x in group.repositories],
2707 2711 'owner': group.user.username,
2708 2712 }
2709 2713 return data
2710 2714
2711 2715
2712 2716 class Permission(Base, BaseModel):
2713 2717 __tablename__ = 'permissions'
2714 2718 __table_args__ = (
2715 2719 Index('p_perm_name_idx', 'permission_name'),
2716 2720 base_table_args,
2717 2721 )
2718 2722
2719 2723 PERMS = [
2720 2724 ('hg.admin', _('RhodeCode Super Administrator')),
2721 2725
2722 2726 ('repository.none', _('Repository no access')),
2723 2727 ('repository.read', _('Repository read access')),
2724 2728 ('repository.write', _('Repository write access')),
2725 2729 ('repository.admin', _('Repository admin access')),
2726 2730
2727 2731 ('group.none', _('Repository group no access')),
2728 2732 ('group.read', _('Repository group read access')),
2729 2733 ('group.write', _('Repository group write access')),
2730 2734 ('group.admin', _('Repository group admin access')),
2731 2735
2732 2736 ('usergroup.none', _('User group no access')),
2733 2737 ('usergroup.read', _('User group read access')),
2734 2738 ('usergroup.write', _('User group write access')),
2735 2739 ('usergroup.admin', _('User group admin access')),
2736 2740
2737 2741 ('branch.none', _('Branch no permissions')),
2738 2742 ('branch.merge', _('Branch access by web merge')),
2739 2743 ('branch.push', _('Branch access by push')),
2740 2744 ('branch.push_force', _('Branch access by push with force')),
2741 2745
2742 2746 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2743 2747 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2744 2748
2745 2749 ('hg.usergroup.create.false', _('User Group creation disabled')),
2746 2750 ('hg.usergroup.create.true', _('User Group creation enabled')),
2747 2751
2748 2752 ('hg.create.none', _('Repository creation disabled')),
2749 2753 ('hg.create.repository', _('Repository creation enabled')),
2750 2754 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2751 2755 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2752 2756
2753 2757 ('hg.fork.none', _('Repository forking disabled')),
2754 2758 ('hg.fork.repository', _('Repository forking enabled')),
2755 2759
2756 2760 ('hg.register.none', _('Registration disabled')),
2757 2761 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2758 2762 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2759 2763
2760 2764 ('hg.password_reset.enabled', _('Password reset enabled')),
2761 2765 ('hg.password_reset.hidden', _('Password reset hidden')),
2762 2766 ('hg.password_reset.disabled', _('Password reset disabled')),
2763 2767
2764 2768 ('hg.extern_activate.manual', _('Manual activation of external account')),
2765 2769 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2766 2770
2767 2771 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2768 2772 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2769 2773 ]
2770 2774
2771 2775 # definition of system default permissions for DEFAULT user, created on
2772 2776 # system setup
2773 2777 DEFAULT_USER_PERMISSIONS = [
2774 2778 # object perms
2775 2779 'repository.read',
2776 2780 'group.read',
2777 2781 'usergroup.read',
2778 2782 # branch, for backward compat we need same value as before so forced pushed
2779 2783 'branch.push_force',
2780 2784 # global
2781 2785 'hg.create.repository',
2782 2786 'hg.repogroup.create.false',
2783 2787 'hg.usergroup.create.false',
2784 2788 'hg.create.write_on_repogroup.true',
2785 2789 'hg.fork.repository',
2786 2790 'hg.register.manual_activate',
2787 2791 'hg.password_reset.enabled',
2788 2792 'hg.extern_activate.auto',
2789 2793 'hg.inherit_default_perms.true',
2790 2794 ]
2791 2795
2792 2796 # defines which permissions are more important higher the more important
2793 2797 # Weight defines which permissions are more important.
2794 2798 # The higher number the more important.
2795 2799 PERM_WEIGHTS = {
2796 2800 'repository.none': 0,
2797 2801 'repository.read': 1,
2798 2802 'repository.write': 3,
2799 2803 'repository.admin': 4,
2800 2804
2801 2805 'group.none': 0,
2802 2806 'group.read': 1,
2803 2807 'group.write': 3,
2804 2808 'group.admin': 4,
2805 2809
2806 2810 'usergroup.none': 0,
2807 2811 'usergroup.read': 1,
2808 2812 'usergroup.write': 3,
2809 2813 'usergroup.admin': 4,
2810 2814
2811 2815 'branch.none': 0,
2812 2816 'branch.merge': 1,
2813 2817 'branch.push': 3,
2814 2818 'branch.push_force': 4,
2815 2819
2816 2820 'hg.repogroup.create.false': 0,
2817 2821 'hg.repogroup.create.true': 1,
2818 2822
2819 2823 'hg.usergroup.create.false': 0,
2820 2824 'hg.usergroup.create.true': 1,
2821 2825
2822 2826 'hg.fork.none': 0,
2823 2827 'hg.fork.repository': 1,
2824 2828 'hg.create.none': 0,
2825 2829 'hg.create.repository': 1
2826 2830 }
2827 2831
2828 2832 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2829 2833 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2830 2834 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2831 2835
2832 2836 def __unicode__(self):
2833 2837 return u"<%s('%s:%s')>" % (
2834 2838 self.__class__.__name__, self.permission_id, self.permission_name
2835 2839 )
2836 2840
2837 2841 @classmethod
2838 2842 def get_by_key(cls, key):
2839 2843 return cls.query().filter(cls.permission_name == key).scalar()
2840 2844
2841 2845 @classmethod
2842 2846 def get_default_repo_perms(cls, user_id, repo_id=None):
2843 2847 q = Session().query(UserRepoToPerm, Repository, Permission)\
2844 2848 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2845 2849 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2846 2850 .filter(UserRepoToPerm.user_id == user_id)
2847 2851 if repo_id:
2848 2852 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2849 2853 return q.all()
2850 2854
2851 2855 @classmethod
2852 2856 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2853 2857 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2854 2858 .join(
2855 2859 Permission,
2856 2860 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2857 2861 .join(
2858 2862 UserRepoToPerm,
2859 2863 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2860 2864 .filter(UserRepoToPerm.user_id == user_id)
2861 2865
2862 2866 if repo_id:
2863 2867 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2864 2868 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2865 2869
2866 2870 @classmethod
2867 2871 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2868 2872 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2869 2873 .join(
2870 2874 Permission,
2871 2875 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2872 2876 .join(
2873 2877 Repository,
2874 2878 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2875 2879 .join(
2876 2880 UserGroup,
2877 2881 UserGroupRepoToPerm.users_group_id ==
2878 2882 UserGroup.users_group_id)\
2879 2883 .join(
2880 2884 UserGroupMember,
2881 2885 UserGroupRepoToPerm.users_group_id ==
2882 2886 UserGroupMember.users_group_id)\
2883 2887 .filter(
2884 2888 UserGroupMember.user_id == user_id,
2885 2889 UserGroup.users_group_active == true())
2886 2890 if repo_id:
2887 2891 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2888 2892 return q.all()
2889 2893
2890 2894 @classmethod
2891 2895 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2892 2896 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2893 2897 .join(
2894 2898 Permission,
2895 2899 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2896 2900 .join(
2897 2901 UserGroupRepoToPerm,
2898 2902 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2899 2903 .join(
2900 2904 UserGroup,
2901 2905 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2902 2906 .join(
2903 2907 UserGroupMember,
2904 2908 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2905 2909 .filter(
2906 2910 UserGroupMember.user_id == user_id,
2907 2911 UserGroup.users_group_active == true())
2908 2912
2909 2913 if repo_id:
2910 2914 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2911 2915 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2912 2916
2913 2917 @classmethod
2914 2918 def get_default_group_perms(cls, user_id, repo_group_id=None):
2915 2919 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2916 2920 .join(
2917 2921 Permission,
2918 2922 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2919 2923 .join(
2920 2924 RepoGroup,
2921 2925 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2922 2926 .filter(UserRepoGroupToPerm.user_id == user_id)
2923 2927 if repo_group_id:
2924 2928 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2925 2929 return q.all()
2926 2930
2927 2931 @classmethod
2928 2932 def get_default_group_perms_from_user_group(
2929 2933 cls, user_id, repo_group_id=None):
2930 2934 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2931 2935 .join(
2932 2936 Permission,
2933 2937 UserGroupRepoGroupToPerm.permission_id ==
2934 2938 Permission.permission_id)\
2935 2939 .join(
2936 2940 RepoGroup,
2937 2941 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2938 2942 .join(
2939 2943 UserGroup,
2940 2944 UserGroupRepoGroupToPerm.users_group_id ==
2941 2945 UserGroup.users_group_id)\
2942 2946 .join(
2943 2947 UserGroupMember,
2944 2948 UserGroupRepoGroupToPerm.users_group_id ==
2945 2949 UserGroupMember.users_group_id)\
2946 2950 .filter(
2947 2951 UserGroupMember.user_id == user_id,
2948 2952 UserGroup.users_group_active == true())
2949 2953 if repo_group_id:
2950 2954 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2951 2955 return q.all()
2952 2956
2953 2957 @classmethod
2954 2958 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2955 2959 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2956 2960 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2957 2961 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2958 2962 .filter(UserUserGroupToPerm.user_id == user_id)
2959 2963 if user_group_id:
2960 2964 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2961 2965 return q.all()
2962 2966
2963 2967 @classmethod
2964 2968 def get_default_user_group_perms_from_user_group(
2965 2969 cls, user_id, user_group_id=None):
2966 2970 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2967 2971 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2968 2972 .join(
2969 2973 Permission,
2970 2974 UserGroupUserGroupToPerm.permission_id ==
2971 2975 Permission.permission_id)\
2972 2976 .join(
2973 2977 TargetUserGroup,
2974 2978 UserGroupUserGroupToPerm.target_user_group_id ==
2975 2979 TargetUserGroup.users_group_id)\
2976 2980 .join(
2977 2981 UserGroup,
2978 2982 UserGroupUserGroupToPerm.user_group_id ==
2979 2983 UserGroup.users_group_id)\
2980 2984 .join(
2981 2985 UserGroupMember,
2982 2986 UserGroupUserGroupToPerm.user_group_id ==
2983 2987 UserGroupMember.users_group_id)\
2984 2988 .filter(
2985 2989 UserGroupMember.user_id == user_id,
2986 2990 UserGroup.users_group_active == true())
2987 2991 if user_group_id:
2988 2992 q = q.filter(
2989 2993 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2990 2994
2991 2995 return q.all()
2992 2996
2993 2997
2994 2998 class UserRepoToPerm(Base, BaseModel):
2995 2999 __tablename__ = 'repo_to_perm'
2996 3000 __table_args__ = (
2997 3001 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2998 3002 base_table_args
2999 3003 )
3000 3004
3001 3005 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3002 3006 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3003 3007 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3004 3008 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3005 3009
3006 3010 user = relationship('User')
3007 3011 repository = relationship('Repository')
3008 3012 permission = relationship('Permission')
3009 3013
3010 3014 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3011 3015
3012 3016 @classmethod
3013 3017 def create(cls, user, repository, permission):
3014 3018 n = cls()
3015 3019 n.user = user
3016 3020 n.repository = repository
3017 3021 n.permission = permission
3018 3022 Session().add(n)
3019 3023 return n
3020 3024
3021 3025 def __unicode__(self):
3022 3026 return u'<%s => %s >' % (self.user, self.repository)
3023 3027
3024 3028
3025 3029 class UserUserGroupToPerm(Base, BaseModel):
3026 3030 __tablename__ = 'user_user_group_to_perm'
3027 3031 __table_args__ = (
3028 3032 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3029 3033 base_table_args
3030 3034 )
3031 3035
3032 3036 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3033 3037 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3034 3038 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3035 3039 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3036 3040
3037 3041 user = relationship('User')
3038 3042 user_group = relationship('UserGroup')
3039 3043 permission = relationship('Permission')
3040 3044
3041 3045 @classmethod
3042 3046 def create(cls, user, user_group, permission):
3043 3047 n = cls()
3044 3048 n.user = user
3045 3049 n.user_group = user_group
3046 3050 n.permission = permission
3047 3051 Session().add(n)
3048 3052 return n
3049 3053
3050 3054 def __unicode__(self):
3051 3055 return u'<%s => %s >' % (self.user, self.user_group)
3052 3056
3053 3057
3054 3058 class UserToPerm(Base, BaseModel):
3055 3059 __tablename__ = 'user_to_perm'
3056 3060 __table_args__ = (
3057 3061 UniqueConstraint('user_id', 'permission_id'),
3058 3062 base_table_args
3059 3063 )
3060 3064
3061 3065 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3062 3066 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3063 3067 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3064 3068
3065 3069 user = relationship('User')
3066 3070 permission = relationship('Permission', lazy='joined')
3067 3071
3068 3072 def __unicode__(self):
3069 3073 return u'<%s => %s >' % (self.user, self.permission)
3070 3074
3071 3075
3072 3076 class UserGroupRepoToPerm(Base, BaseModel):
3073 3077 __tablename__ = 'users_group_repo_to_perm'
3074 3078 __table_args__ = (
3075 3079 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3076 3080 base_table_args
3077 3081 )
3078 3082
3079 3083 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3080 3084 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3081 3085 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3082 3086 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3083 3087
3084 3088 users_group = relationship('UserGroup')
3085 3089 permission = relationship('Permission')
3086 3090 repository = relationship('Repository')
3087 3091 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3088 3092
3089 3093 @classmethod
3090 3094 def create(cls, users_group, repository, permission):
3091 3095 n = cls()
3092 3096 n.users_group = users_group
3093 3097 n.repository = repository
3094 3098 n.permission = permission
3095 3099 Session().add(n)
3096 3100 return n
3097 3101
3098 3102 def __unicode__(self):
3099 3103 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3100 3104
3101 3105
3102 3106 class UserGroupUserGroupToPerm(Base, BaseModel):
3103 3107 __tablename__ = 'user_group_user_group_to_perm'
3104 3108 __table_args__ = (
3105 3109 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3106 3110 CheckConstraint('target_user_group_id != user_group_id'),
3107 3111 base_table_args
3108 3112 )
3109 3113
3110 3114 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)
3111 3115 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3112 3116 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3113 3117 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3114 3118
3115 3119 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3116 3120 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3117 3121 permission = relationship('Permission')
3118 3122
3119 3123 @classmethod
3120 3124 def create(cls, target_user_group, user_group, permission):
3121 3125 n = cls()
3122 3126 n.target_user_group = target_user_group
3123 3127 n.user_group = user_group
3124 3128 n.permission = permission
3125 3129 Session().add(n)
3126 3130 return n
3127 3131
3128 3132 def __unicode__(self):
3129 3133 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3130 3134
3131 3135
3132 3136 class UserGroupToPerm(Base, BaseModel):
3133 3137 __tablename__ = 'users_group_to_perm'
3134 3138 __table_args__ = (
3135 3139 UniqueConstraint('users_group_id', 'permission_id',),
3136 3140 base_table_args
3137 3141 )
3138 3142
3139 3143 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3140 3144 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3141 3145 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3142 3146
3143 3147 users_group = relationship('UserGroup')
3144 3148 permission = relationship('Permission')
3145 3149
3146 3150
3147 3151 class UserRepoGroupToPerm(Base, BaseModel):
3148 3152 __tablename__ = 'user_repo_group_to_perm'
3149 3153 __table_args__ = (
3150 3154 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3151 3155 base_table_args
3152 3156 )
3153 3157
3154 3158 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3155 3159 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3156 3160 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3157 3161 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3158 3162
3159 3163 user = relationship('User')
3160 3164 group = relationship('RepoGroup')
3161 3165 permission = relationship('Permission')
3162 3166
3163 3167 @classmethod
3164 3168 def create(cls, user, repository_group, permission):
3165 3169 n = cls()
3166 3170 n.user = user
3167 3171 n.group = repository_group
3168 3172 n.permission = permission
3169 3173 Session().add(n)
3170 3174 return n
3171 3175
3172 3176
3173 3177 class UserGroupRepoGroupToPerm(Base, BaseModel):
3174 3178 __tablename__ = 'users_group_repo_group_to_perm'
3175 3179 __table_args__ = (
3176 3180 UniqueConstraint('users_group_id', 'group_id'),
3177 3181 base_table_args
3178 3182 )
3179 3183
3180 3184 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)
3181 3185 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3182 3186 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3183 3187 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3184 3188
3185 3189 users_group = relationship('UserGroup')
3186 3190 permission = relationship('Permission')
3187 3191 group = relationship('RepoGroup')
3188 3192
3189 3193 @classmethod
3190 3194 def create(cls, user_group, repository_group, permission):
3191 3195 n = cls()
3192 3196 n.users_group = user_group
3193 3197 n.group = repository_group
3194 3198 n.permission = permission
3195 3199 Session().add(n)
3196 3200 return n
3197 3201
3198 3202 def __unicode__(self):
3199 3203 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3200 3204
3201 3205
3202 3206 class Statistics(Base, BaseModel):
3203 3207 __tablename__ = 'statistics'
3204 3208 __table_args__ = (
3205 3209 base_table_args
3206 3210 )
3207 3211
3208 3212 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3209 3213 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3210 3214 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3211 3215 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3212 3216 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3213 3217 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3214 3218
3215 3219 repository = relationship('Repository', single_parent=True)
3216 3220
3217 3221
3218 3222 class UserFollowing(Base, BaseModel):
3219 3223 __tablename__ = 'user_followings'
3220 3224 __table_args__ = (
3221 3225 UniqueConstraint('user_id', 'follows_repository_id'),
3222 3226 UniqueConstraint('user_id', 'follows_user_id'),
3223 3227 base_table_args
3224 3228 )
3225 3229
3226 3230 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3227 3231 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3228 3232 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3229 3233 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3230 3234 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3231 3235
3232 3236 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3233 3237
3234 3238 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3235 3239 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3236 3240
3237 3241 @classmethod
3238 3242 def get_repo_followers(cls, repo_id):
3239 3243 return cls.query().filter(cls.follows_repo_id == repo_id)
3240 3244
3241 3245
3242 3246 class CacheKey(Base, BaseModel):
3243 3247 __tablename__ = 'cache_invalidation'
3244 3248 __table_args__ = (
3245 3249 UniqueConstraint('cache_key'),
3246 3250 Index('key_idx', 'cache_key'),
3247 3251 base_table_args,
3248 3252 )
3249 3253
3250 3254 CACHE_TYPE_FEED = 'FEED'
3251 3255 CACHE_TYPE_README = 'README'
3252 3256 # namespaces used to register process/thread aware caches
3253 3257 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3254 3258 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3255 3259
3256 3260 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3257 3261 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3258 3262 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3259 3263 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3260 3264
3261 3265 def __init__(self, cache_key, cache_args=''):
3262 3266 self.cache_key = cache_key
3263 3267 self.cache_args = cache_args
3264 3268 self.cache_active = False
3265 3269
3266 3270 def __unicode__(self):
3267 3271 return u"<%s('%s:%s[%s]')>" % (
3268 3272 self.__class__.__name__,
3269 3273 self.cache_id, self.cache_key, self.cache_active)
3270 3274
3271 3275 def _cache_key_partition(self):
3272 3276 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3273 3277 return prefix, repo_name, suffix
3274 3278
3275 3279 def get_prefix(self):
3276 3280 """
3277 3281 Try to extract prefix from existing cache key. The key could consist
3278 3282 of prefix, repo_name, suffix
3279 3283 """
3280 3284 # this returns prefix, repo_name, suffix
3281 3285 return self._cache_key_partition()[0]
3282 3286
3283 3287 def get_suffix(self):
3284 3288 """
3285 3289 get suffix that might have been used in _get_cache_key to
3286 3290 generate self.cache_key. Only used for informational purposes
3287 3291 in repo_edit.mako.
3288 3292 """
3289 3293 # prefix, repo_name, suffix
3290 3294 return self._cache_key_partition()[2]
3291 3295
3292 3296 @classmethod
3293 3297 def delete_all_cache(cls):
3294 3298 """
3295 3299 Delete all cache keys from database.
3296 3300 Should only be run when all instances are down and all entries
3297 3301 thus stale.
3298 3302 """
3299 3303 cls.query().delete()
3300 3304 Session().commit()
3301 3305
3302 3306 @classmethod
3303 3307 def set_invalidate(cls, cache_uid, delete=False):
3304 3308 """
3305 3309 Mark all caches of a repo as invalid in the database.
3306 3310 """
3307 3311
3308 3312 try:
3309 3313 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3310 3314 if delete:
3311 3315 qry.delete()
3312 3316 log.debug('cache objects deleted for cache args %s',
3313 3317 safe_str(cache_uid))
3314 3318 else:
3315 3319 qry.update({"cache_active": False})
3316 3320 log.debug('cache objects marked as invalid for cache args %s',
3317 3321 safe_str(cache_uid))
3318 3322
3319 3323 Session().commit()
3320 3324 except Exception:
3321 3325 log.exception(
3322 3326 'Cache key invalidation failed for cache args %s',
3323 3327 safe_str(cache_uid))
3324 3328 Session().rollback()
3325 3329
3326 3330 @classmethod
3327 3331 def get_active_cache(cls, cache_key):
3328 3332 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3329 3333 if inv_obj:
3330 3334 return inv_obj
3331 3335 return None
3332 3336
3333 3337
3334 3338 class ChangesetComment(Base, BaseModel):
3335 3339 __tablename__ = 'changeset_comments'
3336 3340 __table_args__ = (
3337 3341 Index('cc_revision_idx', 'revision'),
3338 3342 base_table_args,
3339 3343 )
3340 3344
3341 3345 COMMENT_OUTDATED = u'comment_outdated'
3342 3346 COMMENT_TYPE_NOTE = u'note'
3343 3347 COMMENT_TYPE_TODO = u'todo'
3344 3348 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3345 3349
3346 3350 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3347 3351 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3348 3352 revision = Column('revision', String(40), nullable=True)
3349 3353 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3350 3354 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3351 3355 line_no = Column('line_no', Unicode(10), nullable=True)
3352 3356 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3353 3357 f_path = Column('f_path', Unicode(1000), nullable=True)
3354 3358 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3355 3359 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3356 3360 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3357 3361 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3358 3362 renderer = Column('renderer', Unicode(64), nullable=True)
3359 3363 display_state = Column('display_state', Unicode(128), nullable=True)
3360 3364
3361 3365 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3362 3366 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3363 3367 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3364 3368 author = relationship('User', lazy='joined')
3365 3369 repo = relationship('Repository')
3366 3370 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3367 3371 pull_request = relationship('PullRequest', lazy='joined')
3368 3372 pull_request_version = relationship('PullRequestVersion')
3369 3373
3370 3374 @classmethod
3371 3375 def get_users(cls, revision=None, pull_request_id=None):
3372 3376 """
3373 3377 Returns user associated with this ChangesetComment. ie those
3374 3378 who actually commented
3375 3379
3376 3380 :param cls:
3377 3381 :param revision:
3378 3382 """
3379 3383 q = Session().query(User)\
3380 3384 .join(ChangesetComment.author)
3381 3385 if revision:
3382 3386 q = q.filter(cls.revision == revision)
3383 3387 elif pull_request_id:
3384 3388 q = q.filter(cls.pull_request_id == pull_request_id)
3385 3389 return q.all()
3386 3390
3387 3391 @classmethod
3388 3392 def get_index_from_version(cls, pr_version, versions):
3389 3393 num_versions = [x.pull_request_version_id for x in versions]
3390 3394 try:
3391 3395 return num_versions.index(pr_version) +1
3392 3396 except (IndexError, ValueError):
3393 3397 return
3394 3398
3395 3399 @property
3396 3400 def outdated(self):
3397 3401 return self.display_state == self.COMMENT_OUTDATED
3398 3402
3399 3403 def outdated_at_version(self, version):
3400 3404 """
3401 3405 Checks if comment is outdated for given pull request version
3402 3406 """
3403 3407 return self.outdated and self.pull_request_version_id != version
3404 3408
3405 3409 def older_than_version(self, version):
3406 3410 """
3407 3411 Checks if comment is made from previous version than given
3408 3412 """
3409 3413 if version is None:
3410 3414 return self.pull_request_version_id is not None
3411 3415
3412 3416 return self.pull_request_version_id < version
3413 3417
3414 3418 @property
3415 3419 def resolved(self):
3416 3420 return self.resolved_by[0] if self.resolved_by else None
3417 3421
3418 3422 @property
3419 3423 def is_todo(self):
3420 3424 return self.comment_type == self.COMMENT_TYPE_TODO
3421 3425
3422 3426 @property
3423 3427 def is_inline(self):
3424 3428 return self.line_no and self.f_path
3425 3429
3426 3430 def get_index_version(self, versions):
3427 3431 return self.get_index_from_version(
3428 3432 self.pull_request_version_id, versions)
3429 3433
3430 3434 def __repr__(self):
3431 3435 if self.comment_id:
3432 3436 return '<DB:Comment #%s>' % self.comment_id
3433 3437 else:
3434 3438 return '<DB:Comment at %#x>' % id(self)
3435 3439
3436 3440 def get_api_data(self):
3437 3441 comment = self
3438 3442 data = {
3439 3443 'comment_id': comment.comment_id,
3440 3444 'comment_type': comment.comment_type,
3441 3445 'comment_text': comment.text,
3442 3446 'comment_status': comment.status_change,
3443 3447 'comment_f_path': comment.f_path,
3444 3448 'comment_lineno': comment.line_no,
3445 3449 'comment_author': comment.author,
3446 3450 'comment_created_on': comment.created_on
3447 3451 }
3448 3452 return data
3449 3453
3450 3454 def __json__(self):
3451 3455 data = dict()
3452 3456 data.update(self.get_api_data())
3453 3457 return data
3454 3458
3455 3459
3456 3460 class ChangesetStatus(Base, BaseModel):
3457 3461 __tablename__ = 'changeset_statuses'
3458 3462 __table_args__ = (
3459 3463 Index('cs_revision_idx', 'revision'),
3460 3464 Index('cs_version_idx', 'version'),
3461 3465 UniqueConstraint('repo_id', 'revision', 'version'),
3462 3466 base_table_args
3463 3467 )
3464 3468
3465 3469 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3466 3470 STATUS_APPROVED = 'approved'
3467 3471 STATUS_REJECTED = 'rejected'
3468 3472 STATUS_UNDER_REVIEW = 'under_review'
3469 3473
3470 3474 STATUSES = [
3471 3475 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3472 3476 (STATUS_APPROVED, _("Approved")),
3473 3477 (STATUS_REJECTED, _("Rejected")),
3474 3478 (STATUS_UNDER_REVIEW, _("Under Review")),
3475 3479 ]
3476 3480
3477 3481 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3478 3482 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3479 3483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3480 3484 revision = Column('revision', String(40), nullable=False)
3481 3485 status = Column('status', String(128), nullable=False, default=DEFAULT)
3482 3486 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3483 3487 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3484 3488 version = Column('version', Integer(), nullable=False, default=0)
3485 3489 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3486 3490
3487 3491 author = relationship('User', lazy='joined')
3488 3492 repo = relationship('Repository')
3489 3493 comment = relationship('ChangesetComment', lazy='joined')
3490 3494 pull_request = relationship('PullRequest', lazy='joined')
3491 3495
3492 3496 def __unicode__(self):
3493 3497 return u"<%s('%s[v%s]:%s')>" % (
3494 3498 self.__class__.__name__,
3495 3499 self.status, self.version, self.author
3496 3500 )
3497 3501
3498 3502 @classmethod
3499 3503 def get_status_lbl(cls, value):
3500 3504 return dict(cls.STATUSES).get(value)
3501 3505
3502 3506 @property
3503 3507 def status_lbl(self):
3504 3508 return ChangesetStatus.get_status_lbl(self.status)
3505 3509
3506 3510 def get_api_data(self):
3507 3511 status = self
3508 3512 data = {
3509 3513 'status_id': status.changeset_status_id,
3510 3514 'status': status.status,
3511 3515 }
3512 3516 return data
3513 3517
3514 3518 def __json__(self):
3515 3519 data = dict()
3516 3520 data.update(self.get_api_data())
3517 3521 return data
3518 3522
3519 3523
3520 3524 class _PullRequestBase(BaseModel):
3521 3525 """
3522 3526 Common attributes of pull request and version entries.
3523 3527 """
3524 3528
3525 3529 # .status values
3526 3530 STATUS_NEW = u'new'
3527 3531 STATUS_OPEN = u'open'
3528 3532 STATUS_CLOSED = u'closed'
3529 3533
3530 3534 title = Column('title', Unicode(255), nullable=True)
3531 3535 description = Column(
3532 3536 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3533 3537 nullable=True)
3534 3538 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3535 3539
3536 3540 # new/open/closed status of pull request (not approve/reject/etc)
3537 3541 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3538 3542 created_on = Column(
3539 3543 'created_on', DateTime(timezone=False), nullable=False,
3540 3544 default=datetime.datetime.now)
3541 3545 updated_on = Column(
3542 3546 'updated_on', DateTime(timezone=False), nullable=False,
3543 3547 default=datetime.datetime.now)
3544 3548
3545 3549 @declared_attr
3546 3550 def user_id(cls):
3547 3551 return Column(
3548 3552 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3549 3553 unique=None)
3550 3554
3551 3555 # 500 revisions max
3552 3556 _revisions = Column(
3553 3557 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3554 3558
3555 3559 @declared_attr
3556 3560 def source_repo_id(cls):
3557 3561 # TODO: dan: rename column to source_repo_id
3558 3562 return Column(
3559 3563 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3560 3564 nullable=False)
3561 3565
3562 3566 source_ref = Column('org_ref', Unicode(255), nullable=False)
3563 3567
3564 3568 @declared_attr
3565 3569 def target_repo_id(cls):
3566 3570 # TODO: dan: rename column to target_repo_id
3567 3571 return Column(
3568 3572 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3569 3573 nullable=False)
3570 3574
3571 3575 target_ref = Column('other_ref', Unicode(255), nullable=False)
3572 3576 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3573 3577
3574 3578 # TODO: dan: rename column to last_merge_source_rev
3575 3579 _last_merge_source_rev = Column(
3576 3580 'last_merge_org_rev', String(40), nullable=True)
3577 3581 # TODO: dan: rename column to last_merge_target_rev
3578 3582 _last_merge_target_rev = Column(
3579 3583 'last_merge_other_rev', String(40), nullable=True)
3580 3584 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3581 3585 merge_rev = Column('merge_rev', String(40), nullable=True)
3582 3586
3583 3587 reviewer_data = Column(
3584 3588 'reviewer_data_json', MutationObj.as_mutable(
3585 3589 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3586 3590
3587 3591 @property
3588 3592 def reviewer_data_json(self):
3589 3593 return json.dumps(self.reviewer_data)
3590 3594
3591 3595 @hybrid_property
3592 3596 def description_safe(self):
3593 3597 from rhodecode.lib import helpers as h
3594 3598 return h.escape(self.description)
3595 3599
3596 3600 @hybrid_property
3597 3601 def revisions(self):
3598 3602 return self._revisions.split(':') if self._revisions else []
3599 3603
3600 3604 @revisions.setter
3601 3605 def revisions(self, val):
3602 3606 self._revisions = ':'.join(val)
3603 3607
3604 3608 @hybrid_property
3605 3609 def last_merge_status(self):
3606 3610 return safe_int(self._last_merge_status)
3607 3611
3608 3612 @last_merge_status.setter
3609 3613 def last_merge_status(self, val):
3610 3614 self._last_merge_status = val
3611 3615
3612 3616 @declared_attr
3613 3617 def author(cls):
3614 3618 return relationship('User', lazy='joined')
3615 3619
3616 3620 @declared_attr
3617 3621 def source_repo(cls):
3618 3622 return relationship(
3619 3623 'Repository',
3620 3624 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3621 3625
3622 3626 @property
3623 3627 def source_ref_parts(self):
3624 3628 return self.unicode_to_reference(self.source_ref)
3625 3629
3626 3630 @declared_attr
3627 3631 def target_repo(cls):
3628 3632 return relationship(
3629 3633 'Repository',
3630 3634 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3631 3635
3632 3636 @property
3633 3637 def target_ref_parts(self):
3634 3638 return self.unicode_to_reference(self.target_ref)
3635 3639
3636 3640 @property
3637 3641 def shadow_merge_ref(self):
3638 3642 return self.unicode_to_reference(self._shadow_merge_ref)
3639 3643
3640 3644 @shadow_merge_ref.setter
3641 3645 def shadow_merge_ref(self, ref):
3642 3646 self._shadow_merge_ref = self.reference_to_unicode(ref)
3643 3647
3644 3648 def unicode_to_reference(self, raw):
3645 3649 """
3646 3650 Convert a unicode (or string) to a reference object.
3647 3651 If unicode evaluates to False it returns None.
3648 3652 """
3649 3653 if raw:
3650 3654 refs = raw.split(':')
3651 3655 return Reference(*refs)
3652 3656 else:
3653 3657 return None
3654 3658
3655 3659 def reference_to_unicode(self, ref):
3656 3660 """
3657 3661 Convert a reference object to unicode.
3658 3662 If reference is None it returns None.
3659 3663 """
3660 3664 if ref:
3661 3665 return u':'.join(ref)
3662 3666 else:
3663 3667 return None
3664 3668
3665 3669 def get_api_data(self, with_merge_state=True):
3666 3670 from rhodecode.model.pull_request import PullRequestModel
3667 3671
3668 3672 pull_request = self
3669 3673 if with_merge_state:
3670 3674 merge_status = PullRequestModel().merge_status(pull_request)
3671 3675 merge_state = {
3672 3676 'status': merge_status[0],
3673 3677 'message': safe_unicode(merge_status[1]),
3674 3678 }
3675 3679 else:
3676 3680 merge_state = {'status': 'not_available',
3677 3681 'message': 'not_available'}
3678 3682
3679 3683 merge_data = {
3680 3684 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3681 3685 'reference': (
3682 3686 pull_request.shadow_merge_ref._asdict()
3683 3687 if pull_request.shadow_merge_ref else None),
3684 3688 }
3685 3689
3686 3690 data = {
3687 3691 'pull_request_id': pull_request.pull_request_id,
3688 3692 'url': PullRequestModel().get_url(pull_request),
3689 3693 'title': pull_request.title,
3690 3694 'description': pull_request.description,
3691 3695 'status': pull_request.status,
3692 3696 'created_on': pull_request.created_on,
3693 3697 'updated_on': pull_request.updated_on,
3694 3698 'commit_ids': pull_request.revisions,
3695 3699 'review_status': pull_request.calculated_review_status(),
3696 3700 'mergeable': merge_state,
3697 3701 'source': {
3698 3702 'clone_url': pull_request.source_repo.clone_url(),
3699 3703 'repository': pull_request.source_repo.repo_name,
3700 3704 'reference': {
3701 3705 'name': pull_request.source_ref_parts.name,
3702 3706 'type': pull_request.source_ref_parts.type,
3703 3707 'commit_id': pull_request.source_ref_parts.commit_id,
3704 3708 },
3705 3709 },
3706 3710 'target': {
3707 3711 'clone_url': pull_request.target_repo.clone_url(),
3708 3712 'repository': pull_request.target_repo.repo_name,
3709 3713 'reference': {
3710 3714 'name': pull_request.target_ref_parts.name,
3711 3715 'type': pull_request.target_ref_parts.type,
3712 3716 'commit_id': pull_request.target_ref_parts.commit_id,
3713 3717 },
3714 3718 },
3715 3719 'merge': merge_data,
3716 3720 'author': pull_request.author.get_api_data(include_secrets=False,
3717 3721 details='basic'),
3718 3722 'reviewers': [
3719 3723 {
3720 3724 'user': reviewer.get_api_data(include_secrets=False,
3721 3725 details='basic'),
3722 3726 'reasons': reasons,
3723 3727 'review_status': st[0][1].status if st else 'not_reviewed',
3724 3728 }
3725 3729 for obj, reviewer, reasons, mandatory, st in
3726 3730 pull_request.reviewers_statuses()
3727 3731 ]
3728 3732 }
3729 3733
3730 3734 return data
3731 3735
3732 3736
3733 3737 class PullRequest(Base, _PullRequestBase):
3734 3738 __tablename__ = 'pull_requests'
3735 3739 __table_args__ = (
3736 3740 base_table_args,
3737 3741 )
3738 3742
3739 3743 pull_request_id = Column(
3740 3744 'pull_request_id', Integer(), nullable=False, primary_key=True)
3741 3745
3742 3746 def __repr__(self):
3743 3747 if self.pull_request_id:
3744 3748 return '<DB:PullRequest #%s>' % self.pull_request_id
3745 3749 else:
3746 3750 return '<DB:PullRequest at %#x>' % id(self)
3747 3751
3748 3752 reviewers = relationship('PullRequestReviewers',
3749 3753 cascade="all, delete, delete-orphan")
3750 3754 statuses = relationship('ChangesetStatus',
3751 3755 cascade="all, delete, delete-orphan")
3752 3756 comments = relationship('ChangesetComment',
3753 3757 cascade="all, delete, delete-orphan")
3754 3758 versions = relationship('PullRequestVersion',
3755 3759 cascade="all, delete, delete-orphan",
3756 3760 lazy='dynamic')
3757 3761
3758 3762 @classmethod
3759 3763 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3760 3764 internal_methods=None):
3761 3765
3762 3766 class PullRequestDisplay(object):
3763 3767 """
3764 3768 Special object wrapper for showing PullRequest data via Versions
3765 3769 It mimics PR object as close as possible. This is read only object
3766 3770 just for display
3767 3771 """
3768 3772
3769 3773 def __init__(self, attrs, internal=None):
3770 3774 self.attrs = attrs
3771 3775 # internal have priority over the given ones via attrs
3772 3776 self.internal = internal or ['versions']
3773 3777
3774 3778 def __getattr__(self, item):
3775 3779 if item in self.internal:
3776 3780 return getattr(self, item)
3777 3781 try:
3778 3782 return self.attrs[item]
3779 3783 except KeyError:
3780 3784 raise AttributeError(
3781 3785 '%s object has no attribute %s' % (self, item))
3782 3786
3783 3787 def __repr__(self):
3784 3788 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3785 3789
3786 3790 def versions(self):
3787 3791 return pull_request_obj.versions.order_by(
3788 3792 PullRequestVersion.pull_request_version_id).all()
3789 3793
3790 3794 def is_closed(self):
3791 3795 return pull_request_obj.is_closed()
3792 3796
3793 3797 @property
3794 3798 def pull_request_version_id(self):
3795 3799 return getattr(pull_request_obj, 'pull_request_version_id', None)
3796 3800
3797 3801 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3798 3802
3799 3803 attrs.author = StrictAttributeDict(
3800 3804 pull_request_obj.author.get_api_data())
3801 3805 if pull_request_obj.target_repo:
3802 3806 attrs.target_repo = StrictAttributeDict(
3803 3807 pull_request_obj.target_repo.get_api_data())
3804 3808 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3805 3809
3806 3810 if pull_request_obj.source_repo:
3807 3811 attrs.source_repo = StrictAttributeDict(
3808 3812 pull_request_obj.source_repo.get_api_data())
3809 3813 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3810 3814
3811 3815 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3812 3816 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3813 3817 attrs.revisions = pull_request_obj.revisions
3814 3818
3815 3819 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3816 3820 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3817 3821 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3818 3822
3819 3823 return PullRequestDisplay(attrs, internal=internal_methods)
3820 3824
3821 3825 def is_closed(self):
3822 3826 return self.status == self.STATUS_CLOSED
3823 3827
3824 3828 def __json__(self):
3825 3829 return {
3826 3830 'revisions': self.revisions,
3827 3831 }
3828 3832
3829 3833 def calculated_review_status(self):
3830 3834 from rhodecode.model.changeset_status import ChangesetStatusModel
3831 3835 return ChangesetStatusModel().calculated_review_status(self)
3832 3836
3833 3837 def reviewers_statuses(self):
3834 3838 from rhodecode.model.changeset_status import ChangesetStatusModel
3835 3839 return ChangesetStatusModel().reviewers_statuses(self)
3836 3840
3837 3841 @property
3838 3842 def workspace_id(self):
3839 3843 from rhodecode.model.pull_request import PullRequestModel
3840 3844 return PullRequestModel()._workspace_id(self)
3841 3845
3842 3846 def get_shadow_repo(self):
3843 3847 workspace_id = self.workspace_id
3844 3848 vcs_obj = self.target_repo.scm_instance()
3845 3849 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3846 3850 self.target_repo.repo_id, workspace_id)
3847 3851 if os.path.isdir(shadow_repository_path):
3848 3852 return vcs_obj._get_shadow_instance(shadow_repository_path)
3849 3853
3850 3854
3851 3855 class PullRequestVersion(Base, _PullRequestBase):
3852 3856 __tablename__ = 'pull_request_versions'
3853 3857 __table_args__ = (
3854 3858 base_table_args,
3855 3859 )
3856 3860
3857 3861 pull_request_version_id = Column(
3858 3862 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3859 3863 pull_request_id = Column(
3860 3864 'pull_request_id', Integer(),
3861 3865 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3862 3866 pull_request = relationship('PullRequest')
3863 3867
3864 3868 def __repr__(self):
3865 3869 if self.pull_request_version_id:
3866 3870 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3867 3871 else:
3868 3872 return '<DB:PullRequestVersion at %#x>' % id(self)
3869 3873
3870 3874 @property
3871 3875 def reviewers(self):
3872 3876 return self.pull_request.reviewers
3873 3877
3874 3878 @property
3875 3879 def versions(self):
3876 3880 return self.pull_request.versions
3877 3881
3878 3882 def is_closed(self):
3879 3883 # calculate from original
3880 3884 return self.pull_request.status == self.STATUS_CLOSED
3881 3885
3882 3886 def calculated_review_status(self):
3883 3887 return self.pull_request.calculated_review_status()
3884 3888
3885 3889 def reviewers_statuses(self):
3886 3890 return self.pull_request.reviewers_statuses()
3887 3891
3888 3892
3889 3893 class PullRequestReviewers(Base, BaseModel):
3890 3894 __tablename__ = 'pull_request_reviewers'
3891 3895 __table_args__ = (
3892 3896 base_table_args,
3893 3897 )
3894 3898
3895 3899 @hybrid_property
3896 3900 def reasons(self):
3897 3901 if not self._reasons:
3898 3902 return []
3899 3903 return self._reasons
3900 3904
3901 3905 @reasons.setter
3902 3906 def reasons(self, val):
3903 3907 val = val or []
3904 3908 if any(not isinstance(x, basestring) for x in val):
3905 3909 raise Exception('invalid reasons type, must be list of strings')
3906 3910 self._reasons = val
3907 3911
3908 3912 pull_requests_reviewers_id = Column(
3909 3913 'pull_requests_reviewers_id', Integer(), nullable=False,
3910 3914 primary_key=True)
3911 3915 pull_request_id = Column(
3912 3916 "pull_request_id", Integer(),
3913 3917 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3914 3918 user_id = Column(
3915 3919 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3916 3920 _reasons = Column(
3917 3921 'reason', MutationList.as_mutable(
3918 3922 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3919 3923
3920 3924 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3921 3925 user = relationship('User')
3922 3926 pull_request = relationship('PullRequest')
3923 3927
3924 3928 rule_data = Column(
3925 3929 'rule_data_json',
3926 3930 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3927 3931
3928 3932 def rule_user_group_data(self):
3929 3933 """
3930 3934 Returns the voting user group rule data for this reviewer
3931 3935 """
3932 3936
3933 3937 if self.rule_data and 'vote_rule' in self.rule_data:
3934 3938 user_group_data = {}
3935 3939 if 'rule_user_group_entry_id' in self.rule_data:
3936 3940 # means a group with voting rules !
3937 3941 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3938 3942 user_group_data['name'] = self.rule_data['rule_name']
3939 3943 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3940 3944
3941 3945 return user_group_data
3942 3946
3943 3947 def __unicode__(self):
3944 3948 return u"<%s('id:%s')>" % (self.__class__.__name__,
3945 3949 self.pull_requests_reviewers_id)
3946 3950
3947 3951
3948 3952 class Notification(Base, BaseModel):
3949 3953 __tablename__ = 'notifications'
3950 3954 __table_args__ = (
3951 3955 Index('notification_type_idx', 'type'),
3952 3956 base_table_args,
3953 3957 )
3954 3958
3955 3959 TYPE_CHANGESET_COMMENT = u'cs_comment'
3956 3960 TYPE_MESSAGE = u'message'
3957 3961 TYPE_MENTION = u'mention'
3958 3962 TYPE_REGISTRATION = u'registration'
3959 3963 TYPE_PULL_REQUEST = u'pull_request'
3960 3964 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3961 3965
3962 3966 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3963 3967 subject = Column('subject', Unicode(512), nullable=True)
3964 3968 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3965 3969 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3966 3970 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3967 3971 type_ = Column('type', Unicode(255))
3968 3972
3969 3973 created_by_user = relationship('User')
3970 3974 notifications_to_users = relationship('UserNotification', lazy='joined',
3971 3975 cascade="all, delete, delete-orphan")
3972 3976
3973 3977 @property
3974 3978 def recipients(self):
3975 3979 return [x.user for x in UserNotification.query()\
3976 3980 .filter(UserNotification.notification == self)\
3977 3981 .order_by(UserNotification.user_id.asc()).all()]
3978 3982
3979 3983 @classmethod
3980 3984 def create(cls, created_by, subject, body, recipients, type_=None):
3981 3985 if type_ is None:
3982 3986 type_ = Notification.TYPE_MESSAGE
3983 3987
3984 3988 notification = cls()
3985 3989 notification.created_by_user = created_by
3986 3990 notification.subject = subject
3987 3991 notification.body = body
3988 3992 notification.type_ = type_
3989 3993 notification.created_on = datetime.datetime.now()
3990 3994
3991 3995 # For each recipient link the created notification to his account
3992 3996 for u in recipients:
3993 3997 assoc = UserNotification()
3994 3998 assoc.user_id = u.user_id
3995 3999 assoc.notification = notification
3996 4000
3997 4001 # if created_by is inside recipients mark his notification
3998 4002 # as read
3999 4003 if u.user_id == created_by.user_id:
4000 4004 assoc.read = True
4001 4005 Session().add(assoc)
4002 4006
4003 4007 Session().add(notification)
4004 4008
4005 4009 return notification
4006 4010
4007 4011
4008 4012 class UserNotification(Base, BaseModel):
4009 4013 __tablename__ = 'user_to_notification'
4010 4014 __table_args__ = (
4011 4015 UniqueConstraint('user_id', 'notification_id'),
4012 4016 base_table_args
4013 4017 )
4014 4018
4015 4019 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4016 4020 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4017 4021 read = Column('read', Boolean, default=False)
4018 4022 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4019 4023
4020 4024 user = relationship('User', lazy="joined")
4021 4025 notification = relationship('Notification', lazy="joined",
4022 4026 order_by=lambda: Notification.created_on.desc(),)
4023 4027
4024 4028 def mark_as_read(self):
4025 4029 self.read = True
4026 4030 Session().add(self)
4027 4031
4028 4032
4029 4033 class Gist(Base, BaseModel):
4030 4034 __tablename__ = 'gists'
4031 4035 __table_args__ = (
4032 4036 Index('g_gist_access_id_idx', 'gist_access_id'),
4033 4037 Index('g_created_on_idx', 'created_on'),
4034 4038 base_table_args
4035 4039 )
4036 4040
4037 4041 GIST_PUBLIC = u'public'
4038 4042 GIST_PRIVATE = u'private'
4039 4043 DEFAULT_FILENAME = u'gistfile1.txt'
4040 4044
4041 4045 ACL_LEVEL_PUBLIC = u'acl_public'
4042 4046 ACL_LEVEL_PRIVATE = u'acl_private'
4043 4047
4044 4048 gist_id = Column('gist_id', Integer(), primary_key=True)
4045 4049 gist_access_id = Column('gist_access_id', Unicode(250))
4046 4050 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4047 4051 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4048 4052 gist_expires = Column('gist_expires', Float(53), nullable=False)
4049 4053 gist_type = Column('gist_type', Unicode(128), nullable=False)
4050 4054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4051 4055 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4052 4056 acl_level = Column('acl_level', Unicode(128), nullable=True)
4053 4057
4054 4058 owner = relationship('User')
4055 4059
4056 4060 def __repr__(self):
4057 4061 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4058 4062
4059 4063 @hybrid_property
4060 4064 def description_safe(self):
4061 4065 from rhodecode.lib import helpers as h
4062 4066 return h.escape(self.gist_description)
4063 4067
4064 4068 @classmethod
4065 4069 def get_or_404(cls, id_):
4066 4070 from pyramid.httpexceptions import HTTPNotFound
4067 4071
4068 4072 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4069 4073 if not res:
4070 4074 raise HTTPNotFound()
4071 4075 return res
4072 4076
4073 4077 @classmethod
4074 4078 def get_by_access_id(cls, gist_access_id):
4075 4079 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4076 4080
4077 4081 def gist_url(self):
4078 4082 from rhodecode.model.gist import GistModel
4079 4083 return GistModel().get_url(self)
4080 4084
4081 4085 @classmethod
4082 4086 def base_path(cls):
4083 4087 """
4084 4088 Returns base path when all gists are stored
4085 4089
4086 4090 :param cls:
4087 4091 """
4088 4092 from rhodecode.model.gist import GIST_STORE_LOC
4089 4093 q = Session().query(RhodeCodeUi)\
4090 4094 .filter(RhodeCodeUi.ui_key == URL_SEP)
4091 4095 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4092 4096 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4093 4097
4094 4098 def get_api_data(self):
4095 4099 """
4096 4100 Common function for generating gist related data for API
4097 4101 """
4098 4102 gist = self
4099 4103 data = {
4100 4104 'gist_id': gist.gist_id,
4101 4105 'type': gist.gist_type,
4102 4106 'access_id': gist.gist_access_id,
4103 4107 'description': gist.gist_description,
4104 4108 'url': gist.gist_url(),
4105 4109 'expires': gist.gist_expires,
4106 4110 'created_on': gist.created_on,
4107 4111 'modified_at': gist.modified_at,
4108 4112 'content': None,
4109 4113 'acl_level': gist.acl_level,
4110 4114 }
4111 4115 return data
4112 4116
4113 4117 def __json__(self):
4114 4118 data = dict(
4115 4119 )
4116 4120 data.update(self.get_api_data())
4117 4121 return data
4118 4122 # SCM functions
4119 4123
4120 4124 def scm_instance(self, **kwargs):
4121 4125 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4122 4126 return get_vcs_instance(
4123 4127 repo_path=safe_str(full_repo_path), create=False)
4124 4128
4125 4129
4126 4130 class ExternalIdentity(Base, BaseModel):
4127 4131 __tablename__ = 'external_identities'
4128 4132 __table_args__ = (
4129 4133 Index('local_user_id_idx', 'local_user_id'),
4130 4134 Index('external_id_idx', 'external_id'),
4131 4135 base_table_args
4132 4136 )
4133 4137
4134 4138 external_id = Column('external_id', Unicode(255), default=u'',
4135 4139 primary_key=True)
4136 4140 external_username = Column('external_username', Unicode(1024), default=u'')
4137 4141 local_user_id = Column('local_user_id', Integer(),
4138 4142 ForeignKey('users.user_id'), primary_key=True)
4139 4143 provider_name = Column('provider_name', Unicode(255), default=u'',
4140 4144 primary_key=True)
4141 4145 access_token = Column('access_token', String(1024), default=u'')
4142 4146 alt_token = Column('alt_token', String(1024), default=u'')
4143 4147 token_secret = Column('token_secret', String(1024), default=u'')
4144 4148
4145 4149 @classmethod
4146 4150 def by_external_id_and_provider(cls, external_id, provider_name,
4147 4151 local_user_id=None):
4148 4152 """
4149 4153 Returns ExternalIdentity instance based on search params
4150 4154
4151 4155 :param external_id:
4152 4156 :param provider_name:
4153 4157 :return: ExternalIdentity
4154 4158 """
4155 4159 query = cls.query()
4156 4160 query = query.filter(cls.external_id == external_id)
4157 4161 query = query.filter(cls.provider_name == provider_name)
4158 4162 if local_user_id:
4159 4163 query = query.filter(cls.local_user_id == local_user_id)
4160 4164 return query.first()
4161 4165
4162 4166 @classmethod
4163 4167 def user_by_external_id_and_provider(cls, external_id, provider_name):
4164 4168 """
4165 4169 Returns User instance based on search params
4166 4170
4167 4171 :param external_id:
4168 4172 :param provider_name:
4169 4173 :return: User
4170 4174 """
4171 4175 query = User.query()
4172 4176 query = query.filter(cls.external_id == external_id)
4173 4177 query = query.filter(cls.provider_name == provider_name)
4174 4178 query = query.filter(User.user_id == cls.local_user_id)
4175 4179 return query.first()
4176 4180
4177 4181 @classmethod
4178 4182 def by_local_user_id(cls, local_user_id):
4179 4183 """
4180 4184 Returns all tokens for user
4181 4185
4182 4186 :param local_user_id:
4183 4187 :return: ExternalIdentity
4184 4188 """
4185 4189 query = cls.query()
4186 4190 query = query.filter(cls.local_user_id == local_user_id)
4187 4191 return query
4188 4192
4189 4193
4190 4194 class Integration(Base, BaseModel):
4191 4195 __tablename__ = 'integrations'
4192 4196 __table_args__ = (
4193 4197 base_table_args
4194 4198 )
4195 4199
4196 4200 integration_id = Column('integration_id', Integer(), primary_key=True)
4197 4201 integration_type = Column('integration_type', String(255))
4198 4202 enabled = Column('enabled', Boolean(), nullable=False)
4199 4203 name = Column('name', String(255), nullable=False)
4200 4204 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4201 4205 default=False)
4202 4206
4203 4207 settings = Column(
4204 4208 'settings_json', MutationObj.as_mutable(
4205 4209 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4206 4210 repo_id = Column(
4207 4211 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4208 4212 nullable=True, unique=None, default=None)
4209 4213 repo = relationship('Repository', lazy='joined')
4210 4214
4211 4215 repo_group_id = Column(
4212 4216 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4213 4217 nullable=True, unique=None, default=None)
4214 4218 repo_group = relationship('RepoGroup', lazy='joined')
4215 4219
4216 4220 @property
4217 4221 def scope(self):
4218 4222 if self.repo:
4219 4223 return repr(self.repo)
4220 4224 if self.repo_group:
4221 4225 if self.child_repos_only:
4222 4226 return repr(self.repo_group) + ' (child repos only)'
4223 4227 else:
4224 4228 return repr(self.repo_group) + ' (recursive)'
4225 4229 if self.child_repos_only:
4226 4230 return 'root_repos'
4227 4231 return 'global'
4228 4232
4229 4233 def __repr__(self):
4230 4234 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4231 4235
4232 4236
4233 4237 class RepoReviewRuleUser(Base, BaseModel):
4234 4238 __tablename__ = 'repo_review_rules_users'
4235 4239 __table_args__ = (
4236 4240 base_table_args
4237 4241 )
4238 4242
4239 4243 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4240 4244 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4241 4245 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4242 4246 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4243 4247 user = relationship('User')
4244 4248
4245 4249 def rule_data(self):
4246 4250 return {
4247 4251 'mandatory': self.mandatory
4248 4252 }
4249 4253
4250 4254
4251 4255 class RepoReviewRuleUserGroup(Base, BaseModel):
4252 4256 __tablename__ = 'repo_review_rules_users_groups'
4253 4257 __table_args__ = (
4254 4258 base_table_args
4255 4259 )
4256 4260
4257 4261 VOTE_RULE_ALL = -1
4258 4262
4259 4263 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4260 4264 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4261 4265 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4262 4266 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4263 4267 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4264 4268 users_group = relationship('UserGroup')
4265 4269
4266 4270 def rule_data(self):
4267 4271 return {
4268 4272 'mandatory': self.mandatory,
4269 4273 'vote_rule': self.vote_rule
4270 4274 }
4271 4275
4272 4276 @property
4273 4277 def vote_rule_label(self):
4274 4278 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4275 4279 return 'all must vote'
4276 4280 else:
4277 4281 return 'min. vote {}'.format(self.vote_rule)
4278 4282
4279 4283
4280 4284 class RepoReviewRule(Base, BaseModel):
4281 4285 __tablename__ = 'repo_review_rules'
4282 4286 __table_args__ = (
4283 4287 base_table_args
4284 4288 )
4285 4289
4286 4290 repo_review_rule_id = Column(
4287 4291 'repo_review_rule_id', Integer(), primary_key=True)
4288 4292 repo_id = Column(
4289 4293 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4290 4294 repo = relationship('Repository', backref='review_rules')
4291 4295
4292 4296 review_rule_name = Column('review_rule_name', String(255))
4293 4297 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4294 4298 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4295 4299 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4296 4300
4297 4301 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4298 4302 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4299 4303 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4300 4304 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4301 4305
4302 4306 rule_users = relationship('RepoReviewRuleUser')
4303 4307 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4304 4308
4305 4309 def _validate_pattern(self, value):
4306 4310 re.compile('^' + glob2re(value) + '$')
4307 4311
4308 4312 @hybrid_property
4309 4313 def source_branch_pattern(self):
4310 4314 return self._branch_pattern or '*'
4311 4315
4312 4316 @source_branch_pattern.setter
4313 4317 def source_branch_pattern(self, value):
4314 4318 self._validate_pattern(value)
4315 4319 self._branch_pattern = value or '*'
4316 4320
4317 4321 @hybrid_property
4318 4322 def target_branch_pattern(self):
4319 4323 return self._target_branch_pattern or '*'
4320 4324
4321 4325 @target_branch_pattern.setter
4322 4326 def target_branch_pattern(self, value):
4323 4327 self._validate_pattern(value)
4324 4328 self._target_branch_pattern = value or '*'
4325 4329
4326 4330 @hybrid_property
4327 4331 def file_pattern(self):
4328 4332 return self._file_pattern or '*'
4329 4333
4330 4334 @file_pattern.setter
4331 4335 def file_pattern(self, value):
4332 4336 self._validate_pattern(value)
4333 4337 self._file_pattern = value or '*'
4334 4338
4335 4339 def matches(self, source_branch, target_branch, files_changed):
4336 4340 """
4337 4341 Check if this review rule matches a branch/files in a pull request
4338 4342
4339 4343 :param source_branch: source branch name for the commit
4340 4344 :param target_branch: target branch name for the commit
4341 4345 :param files_changed: list of file paths changed in the pull request
4342 4346 """
4343 4347
4344 4348 source_branch = source_branch or ''
4345 4349 target_branch = target_branch or ''
4346 4350 files_changed = files_changed or []
4347 4351
4348 4352 branch_matches = True
4349 4353 if source_branch or target_branch:
4350 4354 if self.source_branch_pattern == '*':
4351 4355 source_branch_match = True
4352 4356 else:
4353 4357 if self.source_branch_pattern.startswith('re:'):
4354 4358 source_pattern = self.source_branch_pattern[3:]
4355 4359 else:
4356 4360 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4357 4361 source_branch_regex = re.compile(source_pattern)
4358 4362 source_branch_match = bool(source_branch_regex.search(source_branch))
4359 4363 if self.target_branch_pattern == '*':
4360 4364 target_branch_match = True
4361 4365 else:
4362 4366 if self.target_branch_pattern.startswith('re:'):
4363 4367 target_pattern = self.target_branch_pattern[3:]
4364 4368 else:
4365 4369 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4366 4370 target_branch_regex = re.compile(target_pattern)
4367 4371 target_branch_match = bool(target_branch_regex.search(target_branch))
4368 4372
4369 4373 branch_matches = source_branch_match and target_branch_match
4370 4374
4371 4375 files_matches = True
4372 4376 if self.file_pattern != '*':
4373 4377 files_matches = False
4374 4378 if self.file_pattern.startswith('re:'):
4375 4379 file_pattern = self.file_pattern[3:]
4376 4380 else:
4377 4381 file_pattern = glob2re(self.file_pattern)
4378 4382 file_regex = re.compile(file_pattern)
4379 4383 for filename in files_changed:
4380 4384 if file_regex.search(filename):
4381 4385 files_matches = True
4382 4386 break
4383 4387
4384 4388 return branch_matches and files_matches
4385 4389
4386 4390 @property
4387 4391 def review_users(self):
4388 4392 """ Returns the users which this rule applies to """
4389 4393
4390 4394 users = collections.OrderedDict()
4391 4395
4392 4396 for rule_user in self.rule_users:
4393 4397 if rule_user.user.active:
4394 4398 if rule_user.user not in users:
4395 4399 users[rule_user.user.username] = {
4396 4400 'user': rule_user.user,
4397 4401 'source': 'user',
4398 4402 'source_data': {},
4399 4403 'data': rule_user.rule_data()
4400 4404 }
4401 4405
4402 4406 for rule_user_group in self.rule_user_groups:
4403 4407 source_data = {
4404 4408 'user_group_id': rule_user_group.users_group.users_group_id,
4405 4409 'name': rule_user_group.users_group.users_group_name,
4406 4410 'members': len(rule_user_group.users_group.members)
4407 4411 }
4408 4412 for member in rule_user_group.users_group.members:
4409 4413 if member.user.active:
4410 4414 key = member.user.username
4411 4415 if key in users:
4412 4416 # skip this member as we have him already
4413 4417 # this prevents from override the "first" matched
4414 4418 # users with duplicates in multiple groups
4415 4419 continue
4416 4420
4417 4421 users[key] = {
4418 4422 'user': member.user,
4419 4423 'source': 'user_group',
4420 4424 'source_data': source_data,
4421 4425 'data': rule_user_group.rule_data()
4422 4426 }
4423 4427
4424 4428 return users
4425 4429
4426 4430 def user_group_vote_rule(self, user_id):
4427 4431
4428 4432 rules = []
4429 4433 if not self.rule_user_groups:
4430 4434 return rules
4431 4435
4432 4436 for user_group in self.rule_user_groups:
4433 4437 user_group_members = [x.user_id for x in user_group.users_group.members]
4434 4438 if user_id in user_group_members:
4435 4439 rules.append(user_group)
4436 4440 return rules
4437 4441
4438 4442 def __repr__(self):
4439 4443 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4440 4444 self.repo_review_rule_id, self.repo)
4441 4445
4442 4446
4443 4447 class ScheduleEntry(Base, BaseModel):
4444 4448 __tablename__ = 'schedule_entries'
4445 4449 __table_args__ = (
4446 4450 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4447 4451 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4448 4452 base_table_args,
4449 4453 )
4450 4454
4451 4455 schedule_types = ['crontab', 'timedelta', 'integer']
4452 4456 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4453 4457
4454 4458 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4455 4459 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4456 4460 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4457 4461
4458 4462 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4459 4463 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4460 4464
4461 4465 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4462 4466 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4463 4467
4464 4468 # task
4465 4469 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4466 4470 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4467 4471 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4468 4472 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4469 4473
4470 4474 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4471 4475 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4472 4476
4473 4477 @hybrid_property
4474 4478 def schedule_type(self):
4475 4479 return self._schedule_type
4476 4480
4477 4481 @schedule_type.setter
4478 4482 def schedule_type(self, val):
4479 4483 if val not in self.schedule_types:
4480 4484 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4481 4485 val, self.schedule_type))
4482 4486
4483 4487 self._schedule_type = val
4484 4488
4485 4489 @classmethod
4486 4490 def get_uid(cls, obj):
4487 4491 args = obj.task_args
4488 4492 kwargs = obj.task_kwargs
4489 4493 if isinstance(args, JsonRaw):
4490 4494 try:
4491 4495 args = json.loads(args)
4492 4496 except ValueError:
4493 4497 args = tuple()
4494 4498
4495 4499 if isinstance(kwargs, JsonRaw):
4496 4500 try:
4497 4501 kwargs = json.loads(kwargs)
4498 4502 except ValueError:
4499 4503 kwargs = dict()
4500 4504
4501 4505 dot_notation = obj.task_dot_notation
4502 4506 val = '.'.join(map(safe_str, [
4503 4507 sorted(dot_notation), args, sorted(kwargs.items())]))
4504 4508 return hashlib.sha1(val).hexdigest()
4505 4509
4506 4510 @classmethod
4507 4511 def get_by_schedule_name(cls, schedule_name):
4508 4512 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4509 4513
4510 4514 @classmethod
4511 4515 def get_by_schedule_id(cls, schedule_id):
4512 4516 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4513 4517
4514 4518 @property
4515 4519 def task(self):
4516 4520 return self.task_dot_notation
4517 4521
4518 4522 @property
4519 4523 def schedule(self):
4520 4524 from rhodecode.lib.celerylib.utils import raw_2_schedule
4521 4525 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4522 4526 return schedule
4523 4527
4524 4528 @property
4525 4529 def args(self):
4526 4530 try:
4527 4531 return list(self.task_args or [])
4528 4532 except ValueError:
4529 4533 return list()
4530 4534
4531 4535 @property
4532 4536 def kwargs(self):
4533 4537 try:
4534 4538 return dict(self.task_kwargs or {})
4535 4539 except ValueError:
4536 4540 return dict()
4537 4541
4538 4542 def _as_raw(self, val):
4539 4543 if hasattr(val, 'de_coerce'):
4540 4544 val = val.de_coerce()
4541 4545 if val:
4542 4546 val = json.dumps(val)
4543 4547
4544 4548 return val
4545 4549
4546 4550 @property
4547 4551 def schedule_definition_raw(self):
4548 4552 return self._as_raw(self.schedule_definition)
4549 4553
4550 4554 @property
4551 4555 def args_raw(self):
4552 4556 return self._as_raw(self.task_args)
4553 4557
4554 4558 @property
4555 4559 def kwargs_raw(self):
4556 4560 return self._as_raw(self.task_kwargs)
4557 4561
4558 4562 def __repr__(self):
4559 4563 return '<DB:ScheduleEntry({}:{})>'.format(
4560 4564 self.schedule_entry_id, self.schedule_name)
4561 4565
4562 4566
4563 4567 @event.listens_for(ScheduleEntry, 'before_update')
4564 4568 def update_task_uid(mapper, connection, target):
4565 4569 target.task_uid = ScheduleEntry.get_uid(target)
4566 4570
4567 4571
4568 4572 @event.listens_for(ScheduleEntry, 'before_insert')
4569 4573 def set_task_uid(mapper, connection, target):
4570 4574 target.task_uid = ScheduleEntry.get_uid(target)
4571 4575
4572 4576
4573 4577 class _BaseBranchPerms(BaseModel):
4574 4578 @classmethod
4575 4579 def compute_hash(cls, value):
4576 4580 return sha1_safe(value)
4577 4581
4578 4582 @hybrid_property
4579 4583 def branch_pattern(self):
4580 4584 return self._branch_pattern or '*'
4581 4585
4582 4586 @hybrid_property
4583 4587 def branch_hash(self):
4584 4588 return self._branch_hash
4585 4589
4586 4590 def _validate_glob(self, value):
4587 4591 re.compile('^' + glob2re(value) + '$')
4588 4592
4589 4593 @branch_pattern.setter
4590 4594 def branch_pattern(self, value):
4591 4595 self._validate_glob(value)
4592 4596 self._branch_pattern = value or '*'
4593 4597 # set the Hash when setting the branch pattern
4594 4598 self._branch_hash = self.compute_hash(self._branch_pattern)
4595 4599
4596 4600 def matches(self, branch):
4597 4601 """
4598 4602 Check if this the branch matches entry
4599 4603
4600 4604 :param branch: branch name for the commit
4601 4605 """
4602 4606
4603 4607 branch = branch or ''
4604 4608
4605 4609 branch_matches = True
4606 4610 if branch:
4607 4611 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4608 4612 branch_matches = bool(branch_regex.search(branch))
4609 4613
4610 4614 return branch_matches
4611 4615
4612 4616
4613 4617 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4614 4618 __tablename__ = 'user_to_repo_branch_permissions'
4615 4619 __table_args__ = (
4616 4620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4617 4621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4618 4622 )
4619 4623
4620 4624 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4621 4625
4622 4626 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4623 4627 repo = relationship('Repository', backref='user_branch_perms')
4624 4628
4625 4629 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4626 4630 permission = relationship('Permission')
4627 4631
4628 4632 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4629 4633 user_repo_to_perm = relationship('UserRepoToPerm')
4630 4634
4631 4635 rule_order = Column('rule_order', Integer(), nullable=False)
4632 4636 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4633 4637 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4634 4638
4635 4639 def __unicode__(self):
4636 4640 return u'<UserBranchPermission(%s => %r)>' % (
4637 4641 self.user_repo_to_perm, self.branch_pattern)
4638 4642
4639 4643
4640 4644 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4641 4645 __tablename__ = 'user_group_to_repo_branch_permissions'
4642 4646 __table_args__ = (
4643 4647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4644 4648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4645 4649 )
4646 4650
4647 4651 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4648 4652
4649 4653 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4650 4654 repo = relationship('Repository', backref='user_group_branch_perms')
4651 4655
4652 4656 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4653 4657 permission = relationship('Permission')
4654 4658
4655 4659 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4656 4660 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4657 4661
4658 4662 rule_order = Column('rule_order', Integer(), nullable=False)
4659 4663 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4660 4664 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4661 4665
4662 4666 def __unicode__(self):
4663 4667 return u'<UserBranchPermission(%s => %r)>' % (
4664 4668 self.user_group_repo_to_perm, self.branch_pattern)
4665 4669
4666 4670
4667 4671 class DbMigrateVersion(Base, BaseModel):
4668 4672 __tablename__ = 'db_migrate_version'
4669 4673 __table_args__ = (
4670 4674 base_table_args,
4671 4675 )
4672 4676
4673 4677 repository_id = Column('repository_id', String(250), primary_key=True)
4674 4678 repository_path = Column('repository_path', Text)
4675 4679 version = Column('version', Integer)
4676 4680
4677 4681 @classmethod
4678 4682 def set_version(cls, version):
4679 4683 """
4680 4684 Helper for forcing a different version, usually for debugging purposes via ishell.
4681 4685 """
4682 4686 ver = DbMigrateVersion.query().first()
4683 4687 ver.version = version
4684 4688 Session().commit()
4685 4689
4686 4690
4687 4691 class DbSession(Base, BaseModel):
4688 4692 __tablename__ = 'db_session'
4689 4693 __table_args__ = (
4690 4694 base_table_args,
4691 4695 )
4692 4696
4693 4697 def __repr__(self):
4694 4698 return '<DB:DbSession({})>'.format(self.id)
4695 4699
4696 4700 id = Column('id', Integer())
4697 4701 namespace = Column('namespace', String(255), primary_key=True)
4698 4702 accessed = Column('accessed', DateTime, nullable=False)
4699 4703 created = Column('created', DateTime, nullable=False)
4700 4704 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now