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