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