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