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