##// END OF EJS Templates
hooks: added new pretx hook to allow mercurial checks such as protected branches, or force push.
marcink -
r1461:0ab605bc default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (3909 lines changed) Show them Hide them
@@ -0,0 +1,3909 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Database Models for RhodeCode Enterprise
23 """
24
25 import re
26 import os
27 import time
28 import hashlib
29 import logging
30 import datetime
31 import warnings
32 import ipaddress
33 import functools
34 import traceback
35 import collections
36
37
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
50
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
61
62 from rhodecode.model.meta import Base, Session
63
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
66
67 # =============================================================================
68 # BASE CLASSES
69 # =============================================================================
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
80 'write': '###',
81 'read': '##',
82 'none': '#',
83 }
84
85
86 def display_sort(obj):
87 """
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
91 """
92
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
97
98
99 def _hash_key(k):
100 return md5_safe(k)
101
102
103 class EncryptedTextValue(TypeDecorator):
104 """
105 Special column for encrypted long text data, use like::
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
111 """
112 impl = Text
113
114 def process_bind_param(self, value, dialect):
115 if not value:
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
125 def process_result_value(self, value, dialect):
126 import rhodecode
127
128 if not value:
129 return value
130
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
133 # probably not encrypted values
134 return value
135 else:
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
153
154
155 class BaseModel(object):
156 """
157 Base Model for all classes
158 """
159
160 @classmethod
161 def _get_keys(cls):
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
164
165 def get_dict(self):
166 """
167 return dict with keys and values corresponding
168 to this model data """
169
170 d = {}
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
173
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
177 # update with attributes from __json__
178 if callable(_json_attr):
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
181 d[k] = val
182 return d
183
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
186 to this model data """
187
188 l = []
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
191 return l
192
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
195
196 for k in self._get_keys():
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
199
200 @classmethod
201 def query(cls):
202 return Session().query(cls)
203
204 @classmethod
205 def get(cls, id_):
206 if id_:
207 return cls.query().get(id_)
208
209 @classmethod
210 def get_or_404(cls, id_):
211 try:
212 id_ = int(id_)
213 except (TypeError, ValueError):
214 raise HTTPNotFound
215
216 res = cls.query().get(id_)
217 if not res:
218 raise HTTPNotFound
219 return res
220
221 @classmethod
222 def getAll(cls):
223 # deprecated and left for backward compatibility
224 return cls.get_all()
225
226 @classmethod
227 def get_all(cls):
228 return cls.query().all()
229
230 @classmethod
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
233 Session().delete(obj)
234
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
241 if exist_in_session:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
244 log.exception(
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
248
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
252 try:
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
257
258
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
266
267 SETTINGS_TYPES = {
268 'str': safe_str,
269 'int': safe_int,
270 'unicode': safe_unicode,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
284 self.app_settings_type = type
285 self.app_settings_value = val
286
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
290 return val
291
292 @hybrid_property
293 def app_settings_value(self):
294 v = self._app_settings_value
295 _type = self.app_settings_type
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
306
307 @app_settings_value.setter
308 def app_settings_value(self, val):
309 """
310 Setter that will always make sure we use unicode in app_settings_value
311
312 :param val:
313 """
314 val = safe_unicode(val)
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
320
321 @hybrid_property
322 def app_settings_type(self):
323 return self._app_settings_type
324
325 @app_settings_type.setter
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
331
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
337 )
338
339
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
358
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
378
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
389
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
405
406 repository = relationship('Repository')
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
410 self.app_settings_name = key
411 self.app_settings_type = type
412 self.app_settings_value = val
413
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
417 return val
418
419 @hybrid_property
420 def app_settings_value(self):
421 v = self._app_settings_value
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
426
427 @app_settings_value.setter
428 def app_settings_value(self, val):
429 """
430 Setter that will always make sure we use unicode in app_settings_value
431
432 :param val:
433 """
434 self._app_settings_value = safe_unicode(val)
435
436 @hybrid_property
437 def app_settings_type(self):
438 return self._app_settings_type
439
440 @app_settings_type.setter
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
447
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
453 )
454
455
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
465
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
481 repository = relationship('Repository')
482
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
487
488
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
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)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517
518 user_log = relationship('UserLog')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520
521 repositories = relationship('Repository')
522 repository_groups = relationship('RepoGroup')
523 user_groups = relationship('UserGroup')
524
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531
532 group_member = relationship('UserGroupMember', cascade='all')
533
534 notifications = relationship('UserNotification', cascade='all')
535 # notifications assigned to this user
536 user_created_notifications = relationship('Notification', cascade='all')
537 # comments created by this user
538 user_comments = relationship('ChangesetComment', cascade='all')
539 # user profile extra info
540 user_emails = relationship('UserEmailMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 # gists
544 user_gists = relationship('Gist', cascade='all')
545 # user pull requests
546 user_pull_requests = relationship('PullRequest', cascade='all')
547 # external identities
548 extenal_identities = relationship(
549 'ExternalIdentity',
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 cascade='all')
552
553 def __unicode__(self):
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 self.user_id, self.username)
556
557 @hybrid_property
558 def email(self):
559 return self._email
560
561 @email.setter
562 def email(self, val):
563 self._email = val.lower() if val else None
564
565 @property
566 def firstname(self):
567 # alias for future
568 return self.name
569
570 @property
571 def emails(self):
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 return [self.email] + [x.email for x in other]
574
575 @property
576 def auth_tokens(self):
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578
579 @property
580 def extra_auth_tokens(self):
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582
583 @property
584 def feed_token(self):
585 return self.get_feed_token()
586
587 def get_feed_token(self):
588 feed_tokens = UserApiKeys.query()\
589 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .all()
592 if feed_tokens:
593 return feed_tokens[0].api_key
594 return 'NO_FEED_TOKEN_AVAILABLE'
595
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
605
606 def authenticate_by_token(self, auth_token, roles=None,
607 include_builtin_token=False):
608 from rhodecode.lib import auth
609
610 log.debug('Trying to authenticate user: %s via auth-token, '
611 'and roles: %s', self, roles)
612
613 if not auth_token:
614 return False
615
616 crypto_backend = auth.crypto_backend()
617
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
619 tokens_q = UserApiKeys.query()\
620 .filter(UserApiKeys.user_id == self.user_id)\
621 .filter(or_(UserApiKeys.expires == -1,
622 UserApiKeys.expires >= time.time()))
623
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
625
626 maybe_builtin = []
627 if include_builtin_token:
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
629
630 plain_tokens = []
631 hash_tokens = []
632
633 for token in tokens_q.all() + maybe_builtin:
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
635 hash_tokens.append(token.api_key)
636 else:
637 plain_tokens.append(token.api_key)
638
639 is_plain_match = auth_token in plain_tokens
640 if is_plain_match:
641 return True
642
643 for hashed in hash_tokens:
644 # marcink: this is expensive to calculate, but the most secure
645 match = crypto_backend.hash_check(auth_token, hashed)
646 if match:
647 return True
648
649 return False
650
651 @property
652 def builtin_token_roles(self):
653 roles = [
654 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
655 ]
656 return map(UserApiKeys._get_role_name, roles)
657
658 @property
659 def ip_addresses(self):
660 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
661 return [x.ip_addr for x in ret]
662
663 @property
664 def username_and_name(self):
665 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
666
667 @property
668 def username_or_name_or_email(self):
669 full_name = self.full_name if self.full_name is not ' ' else None
670 return self.username or full_name or self.email
671
672 @property
673 def full_name(self):
674 return '%s %s' % (self.firstname, self.lastname)
675
676 @property
677 def full_name_or_username(self):
678 return ('%s %s' % (self.firstname, self.lastname)
679 if (self.firstname and self.lastname) else self.username)
680
681 @property
682 def full_contact(self):
683 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
684
685 @property
686 def short_contact(self):
687 return '%s %s' % (self.firstname, self.lastname)
688
689 @property
690 def is_admin(self):
691 return self.admin
692
693 @property
694 def AuthUser(self):
695 """
696 Returns instance of AuthUser for this user
697 """
698 from rhodecode.lib.auth import AuthUser
699 return AuthUser(user_id=self.user_id, api_key=self.api_key,
700 username=self.username)
701
702 @hybrid_property
703 def user_data(self):
704 if not self._user_data:
705 return {}
706
707 try:
708 return json.loads(self._user_data)
709 except TypeError:
710 return {}
711
712 @user_data.setter
713 def user_data(self, val):
714 if not isinstance(val, dict):
715 raise Exception('user_data must be dict, got %s' % type(val))
716 try:
717 self._user_data = json.dumps(val)
718 except Exception:
719 log.error(traceback.format_exc())
720
721 @classmethod
722 def get_by_username(cls, username, case_insensitive=False,
723 cache=False, identity_cache=False):
724 session = Session()
725
726 if case_insensitive:
727 q = cls.query().filter(
728 func.lower(cls.username) == func.lower(username))
729 else:
730 q = cls.query().filter(cls.username == username)
731
732 if cache:
733 if identity_cache:
734 val = cls.identity_cache(session, 'username', username)
735 if val:
736 return val
737 else:
738 q = q.options(
739 FromCache("sql_cache_short",
740 "get_user_by_name_%s" % _hash_key(username)))
741
742 return q.scalar()
743
744 @classmethod
745 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
746 q = cls.query().filter(cls.api_key == auth_token)
747
748 if cache:
749 q = q.options(FromCache("sql_cache_short",
750 "get_auth_token_%s" % auth_token))
751 res = q.scalar()
752
753 if fallback and not res:
754 #fallback to additional keys
755 _res = UserApiKeys.query()\
756 .filter(UserApiKeys.api_key == auth_token)\
757 .filter(or_(UserApiKeys.expires == -1,
758 UserApiKeys.expires >= time.time()))\
759 .first()
760 if _res:
761 res = _res.user
762 return res
763
764 @classmethod
765 def get_by_email(cls, email, case_insensitive=False, cache=False):
766
767 if case_insensitive:
768 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
769
770 else:
771 q = cls.query().filter(cls.email == email)
772
773 if cache:
774 q = q.options(FromCache("sql_cache_short",
775 "get_email_key_%s" % _hash_key(email)))
776
777 ret = q.scalar()
778 if ret is None:
779 q = UserEmailMap.query()
780 # try fetching in alternate email map
781 if case_insensitive:
782 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
783 else:
784 q = q.filter(UserEmailMap.email == email)
785 q = q.options(joinedload(UserEmailMap.user))
786 if cache:
787 q = q.options(FromCache("sql_cache_short",
788 "get_email_map_key_%s" % email))
789 ret = getattr(q.scalar(), 'user', None)
790
791 return ret
792
793 @classmethod
794 def get_from_cs_author(cls, author):
795 """
796 Tries to get User objects out of commit author string
797
798 :param author:
799 """
800 from rhodecode.lib.helpers import email, author_name
801 # Valid email in the attribute passed, see if they're in the system
802 _email = email(author)
803 if _email:
804 user = cls.get_by_email(_email, case_insensitive=True)
805 if user:
806 return user
807 # Maybe we can match by username?
808 _author = author_name(author)
809 user = cls.get_by_username(_author, case_insensitive=True)
810 if user:
811 return user
812
813 def update_userdata(self, **kwargs):
814 usr = self
815 old = usr.user_data
816 old.update(**kwargs)
817 usr.user_data = old
818 Session().add(usr)
819 log.debug('updated userdata with ', kwargs)
820
821 def update_lastlogin(self):
822 """Update user lastlogin"""
823 self.last_login = datetime.datetime.now()
824 Session().add(self)
825 log.debug('updated user %s lastlogin', self.username)
826
827 def update_lastactivity(self):
828 """Update user lastactivity"""
829 usr = self
830 old = usr.user_data
831 old.update({'last_activity': time.time()})
832 usr.user_data = old
833 Session().add(usr)
834 log.debug('updated user %s lastactivity', usr.username)
835
836 def update_password(self, new_password, change_api_key=False):
837 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
838
839 self.password = get_crypt_password(new_password)
840 if change_api_key:
841 self.api_key = generate_auth_token(self.username)
842 Session().add(self)
843
844 @classmethod
845 def get_first_super_admin(cls):
846 user = User.query().filter(User.admin == true()).first()
847 if user is None:
848 raise Exception('FATAL: Missing administrative account!')
849 return user
850
851 @classmethod
852 def get_all_super_admins(cls):
853 """
854 Returns all admin accounts sorted by username
855 """
856 return User.query().filter(User.admin == true())\
857 .order_by(User.username.asc()).all()
858
859 @classmethod
860 def get_default_user(cls, cache=False):
861 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
862 if user is None:
863 raise Exception('FATAL: Missing default account!')
864 return user
865
866 def _get_default_perms(self, user, suffix=''):
867 from rhodecode.model.permission import PermissionModel
868 return PermissionModel().get_default_perms(user.user_perms, suffix)
869
870 def get_default_perms(self, suffix=''):
871 return self._get_default_perms(self, suffix)
872
873 def get_api_data(self, include_secrets=False, details='full'):
874 """
875 Common function for generating user related data for API
876
877 :param include_secrets: By default secrets in the API data will be replaced
878 by a placeholder value to prevent exposing this data by accident. In case
879 this data shall be exposed, set this flag to ``True``.
880
881 :param details: details can be 'basic|full' basic gives only a subset of
882 the available user information that includes user_id, name and emails.
883 """
884 user = self
885 user_data = self.user_data
886 data = {
887 'user_id': user.user_id,
888 'username': user.username,
889 'firstname': user.name,
890 'lastname': user.lastname,
891 'email': user.email,
892 'emails': user.emails,
893 }
894 if details == 'basic':
895 return data
896
897 api_key_length = 40
898 api_key_replacement = '*' * api_key_length
899
900 extras = {
901 'api_key': api_key_replacement,
902 'api_keys': [api_key_replacement],
903 'active': user.active,
904 'admin': user.admin,
905 'extern_type': user.extern_type,
906 'extern_name': user.extern_name,
907 'last_login': user.last_login,
908 'ip_addresses': user.ip_addresses,
909 'language': user_data.get('language')
910 }
911 data.update(extras)
912
913 if include_secrets:
914 data['api_key'] = user.api_key
915 data['api_keys'] = user.auth_tokens
916 return data
917
918 def __json__(self):
919 data = {
920 'full_name': self.full_name,
921 'full_name_or_username': self.full_name_or_username,
922 'short_contact': self.short_contact,
923 'full_contact': self.full_contact,
924 }
925 data.update(self.get_api_data())
926 return data
927
928
929 class UserApiKeys(Base, BaseModel):
930 __tablename__ = 'user_api_keys'
931 __table_args__ = (
932 Index('uak_api_key_idx', 'api_key'),
933 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
934 UniqueConstraint('api_key'),
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 )
938 __mapper_args__ = {}
939
940 # ApiKey role
941 ROLE_ALL = 'token_role_all'
942 ROLE_HTTP = 'token_role_http'
943 ROLE_VCS = 'token_role_vcs'
944 ROLE_API = 'token_role_api'
945 ROLE_FEED = 'token_role_feed'
946 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
947
948 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
950 api_key = Column("api_key", String(255), nullable=False, unique=True)
951 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
952 expires = Column('expires', Float(53), nullable=False)
953 role = Column('role', String(255), nullable=True)
954 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
955
956 user = relationship('User', lazy='joined')
957
958 @classmethod
959 def _get_role_name(cls, role):
960 return {
961 cls.ROLE_ALL: _('all'),
962 cls.ROLE_HTTP: _('http/web interface'),
963 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
964 cls.ROLE_API: _('api calls'),
965 cls.ROLE_FEED: _('feed access'),
966 }.get(role, role)
967
968 @property
969 def expired(self):
970 if self.expires == -1:
971 return False
972 return time.time() > self.expires
973
974 @property
975 def role_humanized(self):
976 return self._get_role_name(self.role)
977
978
979 class UserEmailMap(Base, BaseModel):
980 __tablename__ = 'user_email_map'
981 __table_args__ = (
982 Index('uem_email_idx', 'email'),
983 UniqueConstraint('email'),
984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
986 )
987 __mapper_args__ = {}
988
989 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
990 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
991 _email = Column("email", String(255), nullable=True, unique=False, default=None)
992 user = relationship('User', lazy='joined')
993
994 @validates('_email')
995 def validate_email(self, key, email):
996 # check if this email is not main one
997 main_email = Session().query(User).filter(User.email == email).scalar()
998 if main_email is not None:
999 raise AttributeError('email %s is present is user table' % email)
1000 return email
1001
1002 @hybrid_property
1003 def email(self):
1004 return self._email
1005
1006 @email.setter
1007 def email(self, val):
1008 self._email = val.lower() if val else None
1009
1010
1011 class UserIpMap(Base, BaseModel):
1012 __tablename__ = 'user_ip_map'
1013 __table_args__ = (
1014 UniqueConstraint('user_id', 'ip_addr'),
1015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1016 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1017 )
1018 __mapper_args__ = {}
1019
1020 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1022 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1023 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1024 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1025 user = relationship('User', lazy='joined')
1026
1027 @classmethod
1028 def _get_ip_range(cls, ip_addr):
1029 net = ipaddress.ip_network(ip_addr, strict=False)
1030 return [str(net.network_address), str(net.broadcast_address)]
1031
1032 def __json__(self):
1033 return {
1034 'ip_addr': self.ip_addr,
1035 'ip_range': self._get_ip_range(self.ip_addr),
1036 }
1037
1038 def __unicode__(self):
1039 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1040 self.user_id, self.ip_addr)
1041
1042 class UserLog(Base, BaseModel):
1043 __tablename__ = 'user_logs'
1044 __table_args__ = (
1045 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1046 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1047 )
1048 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 username = Column("username", String(255), nullable=True, unique=None, default=None)
1051 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1052 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1053 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1054 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1055 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1056
1057 def __unicode__(self):
1058 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1059 self.repository_name,
1060 self.action)
1061
1062 @property
1063 def action_as_day(self):
1064 return datetime.date(*self.action_date.timetuple()[:3])
1065
1066 user = relationship('User')
1067 repository = relationship('Repository', cascade='')
1068
1069
1070 class UserGroup(Base, BaseModel):
1071 __tablename__ = 'users_groups'
1072 __table_args__ = (
1073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1075 )
1076
1077 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1078 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1079 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1080 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1081 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1084 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1085
1086 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1087 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1088 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1089 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1090 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1091 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1092
1093 user = relationship('User')
1094
1095 @hybrid_property
1096 def group_data(self):
1097 if not self._group_data:
1098 return {}
1099
1100 try:
1101 return json.loads(self._group_data)
1102 except TypeError:
1103 return {}
1104
1105 @group_data.setter
1106 def group_data(self, val):
1107 try:
1108 self._group_data = json.dumps(val)
1109 except Exception:
1110 log.error(traceback.format_exc())
1111
1112 def __unicode__(self):
1113 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1114 self.users_group_id,
1115 self.users_group_name)
1116
1117 @classmethod
1118 def get_by_group_name(cls, group_name, cache=False,
1119 case_insensitive=False):
1120 if case_insensitive:
1121 q = cls.query().filter(func.lower(cls.users_group_name) ==
1122 func.lower(group_name))
1123
1124 else:
1125 q = cls.query().filter(cls.users_group_name == group_name)
1126 if cache:
1127 q = q.options(FromCache(
1128 "sql_cache_short",
1129 "get_group_%s" % _hash_key(group_name)))
1130 return q.scalar()
1131
1132 @classmethod
1133 def get(cls, user_group_id, cache=False):
1134 user_group = cls.query()
1135 if cache:
1136 user_group = user_group.options(FromCache("sql_cache_short",
1137 "get_users_group_%s" % user_group_id))
1138 return user_group.get(user_group_id)
1139
1140 def permissions(self, with_admins=True, with_owner=True):
1141 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1142 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1143 joinedload(UserUserGroupToPerm.user),
1144 joinedload(UserUserGroupToPerm.permission),)
1145
1146 # get owners and admins and permissions. We do a trick of re-writing
1147 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1148 # has a global reference and changing one object propagates to all
1149 # others. This means if admin is also an owner admin_row that change
1150 # would propagate to both objects
1151 perm_rows = []
1152 for _usr in q.all():
1153 usr = AttributeDict(_usr.user.get_dict())
1154 usr.permission = _usr.permission.permission_name
1155 perm_rows.append(usr)
1156
1157 # filter the perm rows by 'default' first and then sort them by
1158 # admin,write,read,none permissions sorted again alphabetically in
1159 # each group
1160 perm_rows = sorted(perm_rows, key=display_sort)
1161
1162 _admin_perm = 'usergroup.admin'
1163 owner_row = []
1164 if with_owner:
1165 usr = AttributeDict(self.user.get_dict())
1166 usr.owner_row = True
1167 usr.permission = _admin_perm
1168 owner_row.append(usr)
1169
1170 super_admin_rows = []
1171 if with_admins:
1172 for usr in User.get_all_super_admins():
1173 # if this admin is also owner, don't double the record
1174 if usr.user_id == owner_row[0].user_id:
1175 owner_row[0].admin_row = True
1176 else:
1177 usr = AttributeDict(usr.get_dict())
1178 usr.admin_row = True
1179 usr.permission = _admin_perm
1180 super_admin_rows.append(usr)
1181
1182 return super_admin_rows + owner_row + perm_rows
1183
1184 def permission_user_groups(self):
1185 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1186 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1187 joinedload(UserGroupUserGroupToPerm.target_user_group),
1188 joinedload(UserGroupUserGroupToPerm.permission),)
1189
1190 perm_rows = []
1191 for _user_group in q.all():
1192 usr = AttributeDict(_user_group.user_group.get_dict())
1193 usr.permission = _user_group.permission.permission_name
1194 perm_rows.append(usr)
1195
1196 return perm_rows
1197
1198 def _get_default_perms(self, user_group, suffix=''):
1199 from rhodecode.model.permission import PermissionModel
1200 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1201
1202 def get_default_perms(self, suffix=''):
1203 return self._get_default_perms(self, suffix)
1204
1205 def get_api_data(self, with_group_members=True, include_secrets=False):
1206 """
1207 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1208 basically forwarded.
1209
1210 """
1211 user_group = self
1212
1213 data = {
1214 'users_group_id': user_group.users_group_id,
1215 'group_name': user_group.users_group_name,
1216 'group_description': user_group.user_group_description,
1217 'active': user_group.users_group_active,
1218 'owner': user_group.user.username,
1219 }
1220 if with_group_members:
1221 users = []
1222 for user in user_group.members:
1223 user = user.user
1224 users.append(user.get_api_data(include_secrets=include_secrets))
1225 data['users'] = users
1226
1227 return data
1228
1229
1230 class UserGroupMember(Base, BaseModel):
1231 __tablename__ = 'users_groups_members'
1232 __table_args__ = (
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1235 )
1236
1237 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1240
1241 user = relationship('User', lazy='joined')
1242 users_group = relationship('UserGroup')
1243
1244 def __init__(self, gr_id='', u_id=''):
1245 self.users_group_id = gr_id
1246 self.user_id = u_id
1247
1248
1249 class RepositoryField(Base, BaseModel):
1250 __tablename__ = 'repositories_fields'
1251 __table_args__ = (
1252 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1253 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1254 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1255 )
1256 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1257
1258 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1259 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1260 field_key = Column("field_key", String(250))
1261 field_label = Column("field_label", String(1024), nullable=False)
1262 field_value = Column("field_value", String(10000), nullable=False)
1263 field_desc = Column("field_desc", String(1024), nullable=False)
1264 field_type = Column("field_type", String(255), nullable=False, unique=None)
1265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1266
1267 repository = relationship('Repository')
1268
1269 @property
1270 def field_key_prefixed(self):
1271 return 'ex_%s' % self.field_key
1272
1273 @classmethod
1274 def un_prefix_key(cls, key):
1275 if key.startswith(cls.PREFIX):
1276 return key[len(cls.PREFIX):]
1277 return key
1278
1279 @classmethod
1280 def get_by_key_name(cls, key, repo):
1281 row = cls.query()\
1282 .filter(cls.repository == repo)\
1283 .filter(cls.field_key == key).scalar()
1284 return row
1285
1286
1287 class Repository(Base, BaseModel):
1288 __tablename__ = 'repositories'
1289 __table_args__ = (
1290 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1292 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1293 )
1294 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1295 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1296
1297 STATE_CREATED = 'repo_state_created'
1298 STATE_PENDING = 'repo_state_pending'
1299 STATE_ERROR = 'repo_state_error'
1300
1301 LOCK_AUTOMATIC = 'lock_auto'
1302 LOCK_API = 'lock_api'
1303 LOCK_WEB = 'lock_web'
1304 LOCK_PULL = 'lock_pull'
1305
1306 NAME_SEP = URL_SEP
1307
1308 repo_id = Column(
1309 "repo_id", Integer(), nullable=False, unique=True, default=None,
1310 primary_key=True)
1311 _repo_name = Column(
1312 "repo_name", Text(), nullable=False, default=None)
1313 _repo_name_hash = Column(
1314 "repo_name_hash", String(255), nullable=False, unique=True)
1315 repo_state = Column("repo_state", String(255), nullable=True)
1316
1317 clone_uri = Column(
1318 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1319 default=None)
1320 repo_type = Column(
1321 "repo_type", String(255), nullable=False, unique=False, default=None)
1322 user_id = Column(
1323 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1324 unique=False, default=None)
1325 private = Column(
1326 "private", Boolean(), nullable=True, unique=None, default=None)
1327 enable_statistics = Column(
1328 "statistics", Boolean(), nullable=True, unique=None, default=True)
1329 enable_downloads = Column(
1330 "downloads", Boolean(), nullable=True, unique=None, default=True)
1331 description = Column(
1332 "description", String(10000), nullable=True, unique=None, default=None)
1333 created_on = Column(
1334 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1335 default=datetime.datetime.now)
1336 updated_on = Column(
1337 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1338 default=datetime.datetime.now)
1339 _landing_revision = Column(
1340 "landing_revision", String(255), nullable=False, unique=False,
1341 default=None)
1342 enable_locking = Column(
1343 "enable_locking", Boolean(), nullable=False, unique=None,
1344 default=False)
1345 _locked = Column(
1346 "locked", String(255), nullable=True, unique=False, default=None)
1347 _changeset_cache = Column(
1348 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1349
1350 fork_id = Column(
1351 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1352 nullable=True, unique=False, default=None)
1353 group_id = Column(
1354 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1355 unique=False, default=None)
1356
1357 user = relationship('User', lazy='joined')
1358 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1359 group = relationship('RepoGroup', lazy='joined')
1360 repo_to_perm = relationship(
1361 'UserRepoToPerm', cascade='all',
1362 order_by='UserRepoToPerm.repo_to_perm_id')
1363 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1364 stats = relationship('Statistics', cascade='all', uselist=False)
1365
1366 followers = relationship(
1367 'UserFollowing',
1368 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1369 cascade='all')
1370 extra_fields = relationship(
1371 'RepositoryField', cascade="all, delete, delete-orphan")
1372 logs = relationship('UserLog')
1373 comments = relationship(
1374 'ChangesetComment', cascade="all, delete, delete-orphan")
1375 pull_requests_source = relationship(
1376 'PullRequest',
1377 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1378 cascade="all, delete, delete-orphan")
1379 pull_requests_target = relationship(
1380 'PullRequest',
1381 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1382 cascade="all, delete, delete-orphan")
1383 ui = relationship('RepoRhodeCodeUi', cascade="all")
1384 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1385 integrations = relationship('Integration',
1386 cascade="all, delete, delete-orphan")
1387
1388 def __unicode__(self):
1389 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1390 safe_unicode(self.repo_name))
1391
1392 @hybrid_property
1393 def landing_rev(self):
1394 # always should return [rev_type, rev]
1395 if self._landing_revision:
1396 _rev_info = self._landing_revision.split(':')
1397 if len(_rev_info) < 2:
1398 _rev_info.insert(0, 'rev')
1399 return [_rev_info[0], _rev_info[1]]
1400 return [None, None]
1401
1402 @landing_rev.setter
1403 def landing_rev(self, val):
1404 if ':' not in val:
1405 raise ValueError('value must be delimited with `:` and consist '
1406 'of <rev_type>:<rev>, got %s instead' % val)
1407 self._landing_revision = val
1408
1409 @hybrid_property
1410 def locked(self):
1411 if self._locked:
1412 user_id, timelocked, reason = self._locked.split(':')
1413 lock_values = int(user_id), timelocked, reason
1414 else:
1415 lock_values = [None, None, None]
1416 return lock_values
1417
1418 @locked.setter
1419 def locked(self, val):
1420 if val and isinstance(val, (list, tuple)):
1421 self._locked = ':'.join(map(str, val))
1422 else:
1423 self._locked = None
1424
1425 @hybrid_property
1426 def changeset_cache(self):
1427 from rhodecode.lib.vcs.backends.base import EmptyCommit
1428 dummy = EmptyCommit().__json__()
1429 if not self._changeset_cache:
1430 return dummy
1431 try:
1432 return json.loads(self._changeset_cache)
1433 except TypeError:
1434 return dummy
1435 except Exception:
1436 log.error(traceback.format_exc())
1437 return dummy
1438
1439 @changeset_cache.setter
1440 def changeset_cache(self, val):
1441 try:
1442 self._changeset_cache = json.dumps(val)
1443 except Exception:
1444 log.error(traceback.format_exc())
1445
1446 @hybrid_property
1447 def repo_name(self):
1448 return self._repo_name
1449
1450 @repo_name.setter
1451 def repo_name(self, value):
1452 self._repo_name = value
1453 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1454
1455 @classmethod
1456 def normalize_repo_name(cls, repo_name):
1457 """
1458 Normalizes os specific repo_name to the format internally stored inside
1459 database using URL_SEP
1460
1461 :param cls:
1462 :param repo_name:
1463 """
1464 return cls.NAME_SEP.join(repo_name.split(os.sep))
1465
1466 @classmethod
1467 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1468 session = Session()
1469 q = session.query(cls).filter(cls.repo_name == repo_name)
1470
1471 if cache:
1472 if identity_cache:
1473 val = cls.identity_cache(session, 'repo_name', repo_name)
1474 if val:
1475 return val
1476 else:
1477 q = q.options(
1478 FromCache("sql_cache_short",
1479 "get_repo_by_name_%s" % _hash_key(repo_name)))
1480
1481 return q.scalar()
1482
1483 @classmethod
1484 def get_by_full_path(cls, repo_full_path):
1485 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1486 repo_name = cls.normalize_repo_name(repo_name)
1487 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1488
1489 @classmethod
1490 def get_repo_forks(cls, repo_id):
1491 return cls.query().filter(Repository.fork_id == repo_id)
1492
1493 @classmethod
1494 def base_path(cls):
1495 """
1496 Returns base path when all repos are stored
1497
1498 :param cls:
1499 """
1500 q = Session().query(RhodeCodeUi)\
1501 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1502 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1503 return q.one().ui_value
1504
1505 @classmethod
1506 def is_valid(cls, repo_name):
1507 """
1508 returns True if given repo name is a valid filesystem repository
1509
1510 :param cls:
1511 :param repo_name:
1512 """
1513 from rhodecode.lib.utils import is_valid_repo
1514
1515 return is_valid_repo(repo_name, cls.base_path())
1516
1517 @classmethod
1518 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1519 case_insensitive=True):
1520 q = Repository.query()
1521
1522 if not isinstance(user_id, Optional):
1523 q = q.filter(Repository.user_id == user_id)
1524
1525 if not isinstance(group_id, Optional):
1526 q = q.filter(Repository.group_id == group_id)
1527
1528 if case_insensitive:
1529 q = q.order_by(func.lower(Repository.repo_name))
1530 else:
1531 q = q.order_by(Repository.repo_name)
1532 return q.all()
1533
1534 @property
1535 def forks(self):
1536 """
1537 Return forks of this repo
1538 """
1539 return Repository.get_repo_forks(self.repo_id)
1540
1541 @property
1542 def parent(self):
1543 """
1544 Returns fork parent
1545 """
1546 return self.fork
1547
1548 @property
1549 def just_name(self):
1550 return self.repo_name.split(self.NAME_SEP)[-1]
1551
1552 @property
1553 def groups_with_parents(self):
1554 groups = []
1555 if self.group is None:
1556 return groups
1557
1558 cur_gr = self.group
1559 groups.insert(0, cur_gr)
1560 while 1:
1561 gr = getattr(cur_gr, 'parent_group', None)
1562 cur_gr = cur_gr.parent_group
1563 if gr is None:
1564 break
1565 groups.insert(0, gr)
1566
1567 return groups
1568
1569 @property
1570 def groups_and_repo(self):
1571 return self.groups_with_parents, self
1572
1573 @LazyProperty
1574 def repo_path(self):
1575 """
1576 Returns base full path for that repository means where it actually
1577 exists on a filesystem
1578 """
1579 q = Session().query(RhodeCodeUi).filter(
1580 RhodeCodeUi.ui_key == self.NAME_SEP)
1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1582 return q.one().ui_value
1583
1584 @property
1585 def repo_full_path(self):
1586 p = [self.repo_path]
1587 # we need to split the name by / since this is how we store the
1588 # names in the database, but that eventually needs to be converted
1589 # into a valid system path
1590 p += self.repo_name.split(self.NAME_SEP)
1591 return os.path.join(*map(safe_unicode, p))
1592
1593 @property
1594 def cache_keys(self):
1595 """
1596 Returns associated cache keys for that repo
1597 """
1598 return CacheKey.query()\
1599 .filter(CacheKey.cache_args == self.repo_name)\
1600 .order_by(CacheKey.cache_key)\
1601 .all()
1602
1603 def get_new_name(self, repo_name):
1604 """
1605 returns new full repository name based on assigned group and new new
1606
1607 :param group_name:
1608 """
1609 path_prefix = self.group.full_path_splitted if self.group else []
1610 return self.NAME_SEP.join(path_prefix + [repo_name])
1611
1612 @property
1613 def _config(self):
1614 """
1615 Returns db based config object.
1616 """
1617 from rhodecode.lib.utils import make_db_config
1618 return make_db_config(clear_session=False, repo=self)
1619
1620 def permissions(self, with_admins=True, with_owner=True):
1621 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1622 q = q.options(joinedload(UserRepoToPerm.repository),
1623 joinedload(UserRepoToPerm.user),
1624 joinedload(UserRepoToPerm.permission),)
1625
1626 # get owners and admins and permissions. We do a trick of re-writing
1627 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1628 # has a global reference and changing one object propagates to all
1629 # others. This means if admin is also an owner admin_row that change
1630 # would propagate to both objects
1631 perm_rows = []
1632 for _usr in q.all():
1633 usr = AttributeDict(_usr.user.get_dict())
1634 usr.permission = _usr.permission.permission_name
1635 perm_rows.append(usr)
1636
1637 # filter the perm rows by 'default' first and then sort them by
1638 # admin,write,read,none permissions sorted again alphabetically in
1639 # each group
1640 perm_rows = sorted(perm_rows, key=display_sort)
1641
1642 _admin_perm = 'repository.admin'
1643 owner_row = []
1644 if with_owner:
1645 usr = AttributeDict(self.user.get_dict())
1646 usr.owner_row = True
1647 usr.permission = _admin_perm
1648 owner_row.append(usr)
1649
1650 super_admin_rows = []
1651 if with_admins:
1652 for usr in User.get_all_super_admins():
1653 # if this admin is also owner, don't double the record
1654 if usr.user_id == owner_row[0].user_id:
1655 owner_row[0].admin_row = True
1656 else:
1657 usr = AttributeDict(usr.get_dict())
1658 usr.admin_row = True
1659 usr.permission = _admin_perm
1660 super_admin_rows.append(usr)
1661
1662 return super_admin_rows + owner_row + perm_rows
1663
1664 def permission_user_groups(self):
1665 q = UserGroupRepoToPerm.query().filter(
1666 UserGroupRepoToPerm.repository == self)
1667 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1668 joinedload(UserGroupRepoToPerm.users_group),
1669 joinedload(UserGroupRepoToPerm.permission),)
1670
1671 perm_rows = []
1672 for _user_group in q.all():
1673 usr = AttributeDict(_user_group.users_group.get_dict())
1674 usr.permission = _user_group.permission.permission_name
1675 perm_rows.append(usr)
1676
1677 return perm_rows
1678
1679 def get_api_data(self, include_secrets=False):
1680 """
1681 Common function for generating repo api data
1682
1683 :param include_secrets: See :meth:`User.get_api_data`.
1684
1685 """
1686 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1687 # move this methods on models level.
1688 from rhodecode.model.settings import SettingsModel
1689
1690 repo = self
1691 _user_id, _time, _reason = self.locked
1692
1693 data = {
1694 'repo_id': repo.repo_id,
1695 'repo_name': repo.repo_name,
1696 'repo_type': repo.repo_type,
1697 'clone_uri': repo.clone_uri or '',
1698 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1699 'private': repo.private,
1700 'created_on': repo.created_on,
1701 'description': repo.description,
1702 'landing_rev': repo.landing_rev,
1703 'owner': repo.user.username,
1704 'fork_of': repo.fork.repo_name if repo.fork else None,
1705 'enable_statistics': repo.enable_statistics,
1706 'enable_locking': repo.enable_locking,
1707 'enable_downloads': repo.enable_downloads,
1708 'last_changeset': repo.changeset_cache,
1709 'locked_by': User.get(_user_id).get_api_data(
1710 include_secrets=include_secrets) if _user_id else None,
1711 'locked_date': time_to_datetime(_time) if _time else None,
1712 'lock_reason': _reason if _reason else None,
1713 }
1714
1715 # TODO: mikhail: should be per-repo settings here
1716 rc_config = SettingsModel().get_all_settings()
1717 repository_fields = str2bool(
1718 rc_config.get('rhodecode_repository_fields'))
1719 if repository_fields:
1720 for f in self.extra_fields:
1721 data[f.field_key_prefixed] = f.field_value
1722
1723 return data
1724
1725 @classmethod
1726 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1727 if not lock_time:
1728 lock_time = time.time()
1729 if not lock_reason:
1730 lock_reason = cls.LOCK_AUTOMATIC
1731 repo.locked = [user_id, lock_time, lock_reason]
1732 Session().add(repo)
1733 Session().commit()
1734
1735 @classmethod
1736 def unlock(cls, repo):
1737 repo.locked = None
1738 Session().add(repo)
1739 Session().commit()
1740
1741 @classmethod
1742 def getlock(cls, repo):
1743 return repo.locked
1744
1745 def is_user_lock(self, user_id):
1746 if self.lock[0]:
1747 lock_user_id = safe_int(self.lock[0])
1748 user_id = safe_int(user_id)
1749 # both are ints, and they are equal
1750 return all([lock_user_id, user_id]) and lock_user_id == user_id
1751
1752 return False
1753
1754 def get_locking_state(self, action, user_id, only_when_enabled=True):
1755 """
1756 Checks locking on this repository, if locking is enabled and lock is
1757 present returns a tuple of make_lock, locked, locked_by.
1758 make_lock can have 3 states None (do nothing) True, make lock
1759 False release lock, This value is later propagated to hooks, which
1760 do the locking. Think about this as signals passed to hooks what to do.
1761
1762 """
1763 # TODO: johbo: This is part of the business logic and should be moved
1764 # into the RepositoryModel.
1765
1766 if action not in ('push', 'pull'):
1767 raise ValueError("Invalid action value: %s" % repr(action))
1768
1769 # defines if locked error should be thrown to user
1770 currently_locked = False
1771 # defines if new lock should be made, tri-state
1772 make_lock = None
1773 repo = self
1774 user = User.get(user_id)
1775
1776 lock_info = repo.locked
1777
1778 if repo and (repo.enable_locking or not only_when_enabled):
1779 if action == 'push':
1780 # check if it's already locked !, if it is compare users
1781 locked_by_user_id = lock_info[0]
1782 if user.user_id == locked_by_user_id:
1783 log.debug(
1784 'Got `push` action from user %s, now unlocking', user)
1785 # unlock if we have push from user who locked
1786 make_lock = False
1787 else:
1788 # we're not the same user who locked, ban with
1789 # code defined in settings (default is 423 HTTP Locked) !
1790 log.debug('Repo %s is currently locked by %s', repo, user)
1791 currently_locked = True
1792 elif action == 'pull':
1793 # [0] user [1] date
1794 if lock_info[0] and lock_info[1]:
1795 log.debug('Repo %s is currently locked by %s', repo, user)
1796 currently_locked = True
1797 else:
1798 log.debug('Setting lock on repo %s by %s', repo, user)
1799 make_lock = True
1800
1801 else:
1802 log.debug('Repository %s do not have locking enabled', repo)
1803
1804 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1805 make_lock, currently_locked, lock_info)
1806
1807 from rhodecode.lib.auth import HasRepoPermissionAny
1808 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1809 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1810 # if we don't have at least write permission we cannot make a lock
1811 log.debug('lock state reset back to FALSE due to lack '
1812 'of at least read permission')
1813 make_lock = False
1814
1815 return make_lock, currently_locked, lock_info
1816
1817 @property
1818 def last_db_change(self):
1819 return self.updated_on
1820
1821 @property
1822 def clone_uri_hidden(self):
1823 clone_uri = self.clone_uri
1824 if clone_uri:
1825 import urlobject
1826 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1827 if url_obj.password:
1828 clone_uri = url_obj.with_password('*****')
1829 return clone_uri
1830
1831 def clone_url(self, **override):
1832 qualified_home_url = url('home', qualified=True)
1833
1834 uri_tmpl = None
1835 if 'with_id' in override:
1836 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1837 del override['with_id']
1838
1839 if 'uri_tmpl' in override:
1840 uri_tmpl = override['uri_tmpl']
1841 del override['uri_tmpl']
1842
1843 # we didn't override our tmpl from **overrides
1844 if not uri_tmpl:
1845 uri_tmpl = self.DEFAULT_CLONE_URI
1846 try:
1847 from pylons import tmpl_context as c
1848 uri_tmpl = c.clone_uri_tmpl
1849 except Exception:
1850 # in any case if we call this outside of request context,
1851 # ie, not having tmpl_context set up
1852 pass
1853
1854 return get_clone_url(uri_tmpl=uri_tmpl,
1855 qualifed_home_url=qualified_home_url,
1856 repo_name=self.repo_name,
1857 repo_id=self.repo_id, **override)
1858
1859 def set_state(self, state):
1860 self.repo_state = state
1861 Session().add(self)
1862 #==========================================================================
1863 # SCM PROPERTIES
1864 #==========================================================================
1865
1866 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1867 return get_commit_safe(
1868 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1869
1870 def get_changeset(self, rev=None, pre_load=None):
1871 warnings.warn("Use get_commit", DeprecationWarning)
1872 commit_id = None
1873 commit_idx = None
1874 if isinstance(rev, basestring):
1875 commit_id = rev
1876 else:
1877 commit_idx = rev
1878 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1879 pre_load=pre_load)
1880
1881 def get_landing_commit(self):
1882 """
1883 Returns landing commit, or if that doesn't exist returns the tip
1884 """
1885 _rev_type, _rev = self.landing_rev
1886 commit = self.get_commit(_rev)
1887 if isinstance(commit, EmptyCommit):
1888 return self.get_commit()
1889 return commit
1890
1891 def update_commit_cache(self, cs_cache=None, config=None):
1892 """
1893 Update cache of last changeset for repository, keys should be::
1894
1895 short_id
1896 raw_id
1897 revision
1898 parents
1899 message
1900 date
1901 author
1902
1903 :param cs_cache:
1904 """
1905 from rhodecode.lib.vcs.backends.base import BaseChangeset
1906 if cs_cache is None:
1907 # use no-cache version here
1908 scm_repo = self.scm_instance(cache=False, config=config)
1909 if scm_repo:
1910 cs_cache = scm_repo.get_commit(
1911 pre_load=["author", "date", "message", "parents"])
1912 else:
1913 cs_cache = EmptyCommit()
1914
1915 if isinstance(cs_cache, BaseChangeset):
1916 cs_cache = cs_cache.__json__()
1917
1918 def is_outdated(new_cs_cache):
1919 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1920 new_cs_cache['revision'] != self.changeset_cache['revision']):
1921 return True
1922 return False
1923
1924 # check if we have maybe already latest cached revision
1925 if is_outdated(cs_cache) or not self.changeset_cache:
1926 _default = datetime.datetime.fromtimestamp(0)
1927 last_change = cs_cache.get('date') or _default
1928 log.debug('updated repo %s with new cs cache %s',
1929 self.repo_name, cs_cache)
1930 self.updated_on = last_change
1931 self.changeset_cache = cs_cache
1932 Session().add(self)
1933 Session().commit()
1934 else:
1935 log.debug('Skipping update_commit_cache for repo:`%s` '
1936 'commit already with latest changes', self.repo_name)
1937
1938 @property
1939 def tip(self):
1940 return self.get_commit('tip')
1941
1942 @property
1943 def author(self):
1944 return self.tip.author
1945
1946 @property
1947 def last_change(self):
1948 return self.scm_instance().last_change
1949
1950 def get_comments(self, revisions=None):
1951 """
1952 Returns comments for this repository grouped by revisions
1953
1954 :param revisions: filter query by revisions only
1955 """
1956 cmts = ChangesetComment.query()\
1957 .filter(ChangesetComment.repo == self)
1958 if revisions:
1959 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1960 grouped = collections.defaultdict(list)
1961 for cmt in cmts.all():
1962 grouped[cmt.revision].append(cmt)
1963 return grouped
1964
1965 def statuses(self, revisions=None):
1966 """
1967 Returns statuses for this repository
1968
1969 :param revisions: list of revisions to get statuses for
1970 """
1971 statuses = ChangesetStatus.query()\
1972 .filter(ChangesetStatus.repo == self)\
1973 .filter(ChangesetStatus.version == 0)
1974
1975 if revisions:
1976 # Try doing the filtering in chunks to avoid hitting limits
1977 size = 500
1978 status_results = []
1979 for chunk in xrange(0, len(revisions), size):
1980 status_results += statuses.filter(
1981 ChangesetStatus.revision.in_(
1982 revisions[chunk: chunk+size])
1983 ).all()
1984 else:
1985 status_results = statuses.all()
1986
1987 grouped = {}
1988
1989 # maybe we have open new pullrequest without a status?
1990 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1991 status_lbl = ChangesetStatus.get_status_lbl(stat)
1992 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1993 for rev in pr.revisions:
1994 pr_id = pr.pull_request_id
1995 pr_repo = pr.target_repo.repo_name
1996 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1997
1998 for stat in status_results:
1999 pr_id = pr_repo = None
2000 if stat.pull_request:
2001 pr_id = stat.pull_request.pull_request_id
2002 pr_repo = stat.pull_request.target_repo.repo_name
2003 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2004 pr_id, pr_repo]
2005 return grouped
2006
2007 # ==========================================================================
2008 # SCM CACHE INSTANCE
2009 # ==========================================================================
2010
2011 def scm_instance(self, **kwargs):
2012 import rhodecode
2013
2014 # Passing a config will not hit the cache currently only used
2015 # for repo2dbmapper
2016 config = kwargs.pop('config', None)
2017 cache = kwargs.pop('cache', None)
2018 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2019 # if cache is NOT defined use default global, else we have a full
2020 # control over cache behaviour
2021 if cache is None and full_cache and not config:
2022 return self._get_instance_cached()
2023 return self._get_instance(cache=bool(cache), config=config)
2024
2025 def _get_instance_cached(self):
2026 @cache_region('long_term')
2027 def _get_repo(cache_key):
2028 return self._get_instance()
2029
2030 invalidator_context = CacheKey.repo_context_cache(
2031 _get_repo, self.repo_name, None, thread_scoped=True)
2032
2033 with invalidator_context as context:
2034 context.invalidate()
2035 repo = context.compute()
2036
2037 return repo
2038
2039 def _get_instance(self, cache=True, config=None):
2040 config = config or self._config
2041 custom_wire = {
2042 'cache': cache # controls the vcs.remote cache
2043 }
2044 repo = get_vcs_instance(
2045 repo_path=safe_str(self.repo_full_path),
2046 config=config,
2047 with_wire=custom_wire,
2048 create=False,
2049 _vcs_alias=self.repo_type)
2050
2051 return repo
2052
2053 def __json__(self):
2054 return {'landing_rev': self.landing_rev}
2055
2056 def get_dict(self):
2057
2058 # Since we transformed `repo_name` to a hybrid property, we need to
2059 # keep compatibility with the code which uses `repo_name` field.
2060
2061 result = super(Repository, self).get_dict()
2062 result['repo_name'] = result.pop('_repo_name', None)
2063 return result
2064
2065
2066 class RepoGroup(Base, BaseModel):
2067 __tablename__ = 'groups'
2068 __table_args__ = (
2069 UniqueConstraint('group_name', 'group_parent_id'),
2070 CheckConstraint('group_id != group_parent_id'),
2071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2072 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2073 )
2074 __mapper_args__ = {'order_by': 'group_name'}
2075
2076 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2077
2078 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2079 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2080 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2081 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2082 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2083 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2084 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2085 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2086
2087 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2088 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2089 parent_group = relationship('RepoGroup', remote_side=group_id)
2090 user = relationship('User')
2091 integrations = relationship('Integration',
2092 cascade="all, delete, delete-orphan")
2093
2094 def __init__(self, group_name='', parent_group=None):
2095 self.group_name = group_name
2096 self.parent_group = parent_group
2097
2098 def __unicode__(self):
2099 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2100 self.group_name)
2101
2102 @classmethod
2103 def _generate_choice(cls, repo_group):
2104 from webhelpers.html import literal as _literal
2105 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2106 return repo_group.group_id, _name(repo_group.full_path_splitted)
2107
2108 @classmethod
2109 def groups_choices(cls, groups=None, show_empty_group=True):
2110 if not groups:
2111 groups = cls.query().all()
2112
2113 repo_groups = []
2114 if show_empty_group:
2115 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2116
2117 repo_groups.extend([cls._generate_choice(x) for x in groups])
2118
2119 repo_groups = sorted(
2120 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2121 return repo_groups
2122
2123 @classmethod
2124 def url_sep(cls):
2125 return URL_SEP
2126
2127 @classmethod
2128 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2129 if case_insensitive:
2130 gr = cls.query().filter(func.lower(cls.group_name)
2131 == func.lower(group_name))
2132 else:
2133 gr = cls.query().filter(cls.group_name == group_name)
2134 if cache:
2135 gr = gr.options(FromCache(
2136 "sql_cache_short",
2137 "get_group_%s" % _hash_key(group_name)))
2138 return gr.scalar()
2139
2140 @classmethod
2141 def get_user_personal_repo_group(cls, user_id):
2142 user = User.get(user_id)
2143 return cls.query()\
2144 .filter(cls.personal == true())\
2145 .filter(cls.user == user).scalar()
2146
2147 @classmethod
2148 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2149 case_insensitive=True):
2150 q = RepoGroup.query()
2151
2152 if not isinstance(user_id, Optional):
2153 q = q.filter(RepoGroup.user_id == user_id)
2154
2155 if not isinstance(group_id, Optional):
2156 q = q.filter(RepoGroup.group_parent_id == group_id)
2157
2158 if case_insensitive:
2159 q = q.order_by(func.lower(RepoGroup.group_name))
2160 else:
2161 q = q.order_by(RepoGroup.group_name)
2162 return q.all()
2163
2164 @property
2165 def parents(self):
2166 parents_recursion_limit = 10
2167 groups = []
2168 if self.parent_group is None:
2169 return groups
2170 cur_gr = self.parent_group
2171 groups.insert(0, cur_gr)
2172 cnt = 0
2173 while 1:
2174 cnt += 1
2175 gr = getattr(cur_gr, 'parent_group', None)
2176 cur_gr = cur_gr.parent_group
2177 if gr is None:
2178 break
2179 if cnt == parents_recursion_limit:
2180 # this will prevent accidental infinit loops
2181 log.error(('more than %s parents found for group %s, stopping '
2182 'recursive parent fetching' % (parents_recursion_limit, self)))
2183 break
2184
2185 groups.insert(0, gr)
2186 return groups
2187
2188 @property
2189 def children(self):
2190 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2191
2192 @property
2193 def name(self):
2194 return self.group_name.split(RepoGroup.url_sep())[-1]
2195
2196 @property
2197 def full_path(self):
2198 return self.group_name
2199
2200 @property
2201 def full_path_splitted(self):
2202 return self.group_name.split(RepoGroup.url_sep())
2203
2204 @property
2205 def repositories(self):
2206 return Repository.query()\
2207 .filter(Repository.group == self)\
2208 .order_by(Repository.repo_name)
2209
2210 @property
2211 def repositories_recursive_count(self):
2212 cnt = self.repositories.count()
2213
2214 def children_count(group):
2215 cnt = 0
2216 for child in group.children:
2217 cnt += child.repositories.count()
2218 cnt += children_count(child)
2219 return cnt
2220
2221 return cnt + children_count(self)
2222
2223 def _recursive_objects(self, include_repos=True):
2224 all_ = []
2225
2226 def _get_members(root_gr):
2227 if include_repos:
2228 for r in root_gr.repositories:
2229 all_.append(r)
2230 childs = root_gr.children.all()
2231 if childs:
2232 for gr in childs:
2233 all_.append(gr)
2234 _get_members(gr)
2235
2236 _get_members(self)
2237 return [self] + all_
2238
2239 def recursive_groups_and_repos(self):
2240 """
2241 Recursive return all groups, with repositories in those groups
2242 """
2243 return self._recursive_objects()
2244
2245 def recursive_groups(self):
2246 """
2247 Returns all children groups for this group including children of children
2248 """
2249 return self._recursive_objects(include_repos=False)
2250
2251 def get_new_name(self, group_name):
2252 """
2253 returns new full group name based on parent and new name
2254
2255 :param group_name:
2256 """
2257 path_prefix = (self.parent_group.full_path_splitted if
2258 self.parent_group else [])
2259 return RepoGroup.url_sep().join(path_prefix + [group_name])
2260
2261 def permissions(self, with_admins=True, with_owner=True):
2262 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2263 q = q.options(joinedload(UserRepoGroupToPerm.group),
2264 joinedload(UserRepoGroupToPerm.user),
2265 joinedload(UserRepoGroupToPerm.permission),)
2266
2267 # get owners and admins and permissions. We do a trick of re-writing
2268 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2269 # has a global reference and changing one object propagates to all
2270 # others. This means if admin is also an owner admin_row that change
2271 # would propagate to both objects
2272 perm_rows = []
2273 for _usr in q.all():
2274 usr = AttributeDict(_usr.user.get_dict())
2275 usr.permission = _usr.permission.permission_name
2276 perm_rows.append(usr)
2277
2278 # filter the perm rows by 'default' first and then sort them by
2279 # admin,write,read,none permissions sorted again alphabetically in
2280 # each group
2281 perm_rows = sorted(perm_rows, key=display_sort)
2282
2283 _admin_perm = 'group.admin'
2284 owner_row = []
2285 if with_owner:
2286 usr = AttributeDict(self.user.get_dict())
2287 usr.owner_row = True
2288 usr.permission = _admin_perm
2289 owner_row.append(usr)
2290
2291 super_admin_rows = []
2292 if with_admins:
2293 for usr in User.get_all_super_admins():
2294 # if this admin is also owner, don't double the record
2295 if usr.user_id == owner_row[0].user_id:
2296 owner_row[0].admin_row = True
2297 else:
2298 usr = AttributeDict(usr.get_dict())
2299 usr.admin_row = True
2300 usr.permission = _admin_perm
2301 super_admin_rows.append(usr)
2302
2303 return super_admin_rows + owner_row + perm_rows
2304
2305 def permission_user_groups(self):
2306 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2307 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2308 joinedload(UserGroupRepoGroupToPerm.users_group),
2309 joinedload(UserGroupRepoGroupToPerm.permission),)
2310
2311 perm_rows = []
2312 for _user_group in q.all():
2313 usr = AttributeDict(_user_group.users_group.get_dict())
2314 usr.permission = _user_group.permission.permission_name
2315 perm_rows.append(usr)
2316
2317 return perm_rows
2318
2319 def get_api_data(self):
2320 """
2321 Common function for generating api data
2322
2323 """
2324 group = self
2325 data = {
2326 'group_id': group.group_id,
2327 'group_name': group.group_name,
2328 'group_description': group.group_description,
2329 'parent_group': group.parent_group.group_name if group.parent_group else None,
2330 'repositories': [x.repo_name for x in group.repositories],
2331 'owner': group.user.username,
2332 }
2333 return data
2334
2335
2336 class Permission(Base, BaseModel):
2337 __tablename__ = 'permissions'
2338 __table_args__ = (
2339 Index('p_perm_name_idx', 'permission_name'),
2340 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2341 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2342 )
2343 PERMS = [
2344 ('hg.admin', _('RhodeCode Super Administrator')),
2345
2346 ('repository.none', _('Repository no access')),
2347 ('repository.read', _('Repository read access')),
2348 ('repository.write', _('Repository write access')),
2349 ('repository.admin', _('Repository admin access')),
2350
2351 ('group.none', _('Repository group no access')),
2352 ('group.read', _('Repository group read access')),
2353 ('group.write', _('Repository group write access')),
2354 ('group.admin', _('Repository group admin access')),
2355
2356 ('usergroup.none', _('User group no access')),
2357 ('usergroup.read', _('User group read access')),
2358 ('usergroup.write', _('User group write access')),
2359 ('usergroup.admin', _('User group admin access')),
2360
2361 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2362 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2363
2364 ('hg.usergroup.create.false', _('User Group creation disabled')),
2365 ('hg.usergroup.create.true', _('User Group creation enabled')),
2366
2367 ('hg.create.none', _('Repository creation disabled')),
2368 ('hg.create.repository', _('Repository creation enabled')),
2369 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2370 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2371
2372 ('hg.fork.none', _('Repository forking disabled')),
2373 ('hg.fork.repository', _('Repository forking enabled')),
2374
2375 ('hg.register.none', _('Registration disabled')),
2376 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2377 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2378
2379 ('hg.password_reset.enabled', _('Password reset enabled')),
2380 ('hg.password_reset.hidden', _('Password reset hidden')),
2381 ('hg.password_reset.disabled', _('Password reset disabled')),
2382
2383 ('hg.extern_activate.manual', _('Manual activation of external account')),
2384 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2385
2386 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2387 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2388 ]
2389
2390 # definition of system default permissions for DEFAULT user
2391 DEFAULT_USER_PERMISSIONS = [
2392 'repository.read',
2393 'group.read',
2394 'usergroup.read',
2395 'hg.create.repository',
2396 'hg.repogroup.create.false',
2397 'hg.usergroup.create.false',
2398 'hg.create.write_on_repogroup.true',
2399 'hg.fork.repository',
2400 'hg.register.manual_activate',
2401 'hg.password_reset.enabled',
2402 'hg.extern_activate.auto',
2403 'hg.inherit_default_perms.true',
2404 ]
2405
2406 # defines which permissions are more important higher the more important
2407 # Weight defines which permissions are more important.
2408 # The higher number the more important.
2409 PERM_WEIGHTS = {
2410 'repository.none': 0,
2411 'repository.read': 1,
2412 'repository.write': 3,
2413 'repository.admin': 4,
2414
2415 'group.none': 0,
2416 'group.read': 1,
2417 'group.write': 3,
2418 'group.admin': 4,
2419
2420 'usergroup.none': 0,
2421 'usergroup.read': 1,
2422 'usergroup.write': 3,
2423 'usergroup.admin': 4,
2424
2425 'hg.repogroup.create.false': 0,
2426 'hg.repogroup.create.true': 1,
2427
2428 'hg.usergroup.create.false': 0,
2429 'hg.usergroup.create.true': 1,
2430
2431 'hg.fork.none': 0,
2432 'hg.fork.repository': 1,
2433 'hg.create.none': 0,
2434 'hg.create.repository': 1
2435 }
2436
2437 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2438 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2439 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2440
2441 def __unicode__(self):
2442 return u"<%s('%s:%s')>" % (
2443 self.__class__.__name__, self.permission_id, self.permission_name
2444 )
2445
2446 @classmethod
2447 def get_by_key(cls, key):
2448 return cls.query().filter(cls.permission_name == key).scalar()
2449
2450 @classmethod
2451 def get_default_repo_perms(cls, user_id, repo_id=None):
2452 q = Session().query(UserRepoToPerm, Repository, Permission)\
2453 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2454 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2455 .filter(UserRepoToPerm.user_id == user_id)
2456 if repo_id:
2457 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2458 return q.all()
2459
2460 @classmethod
2461 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2462 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2463 .join(
2464 Permission,
2465 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2466 .join(
2467 Repository,
2468 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2469 .join(
2470 UserGroup,
2471 UserGroupRepoToPerm.users_group_id ==
2472 UserGroup.users_group_id)\
2473 .join(
2474 UserGroupMember,
2475 UserGroupRepoToPerm.users_group_id ==
2476 UserGroupMember.users_group_id)\
2477 .filter(
2478 UserGroupMember.user_id == user_id,
2479 UserGroup.users_group_active == true())
2480 if repo_id:
2481 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2482 return q.all()
2483
2484 @classmethod
2485 def get_default_group_perms(cls, user_id, repo_group_id=None):
2486 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2487 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2488 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2489 .filter(UserRepoGroupToPerm.user_id == user_id)
2490 if repo_group_id:
2491 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2492 return q.all()
2493
2494 @classmethod
2495 def get_default_group_perms_from_user_group(
2496 cls, user_id, repo_group_id=None):
2497 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2498 .join(
2499 Permission,
2500 UserGroupRepoGroupToPerm.permission_id ==
2501 Permission.permission_id)\
2502 .join(
2503 RepoGroup,
2504 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2505 .join(
2506 UserGroup,
2507 UserGroupRepoGroupToPerm.users_group_id ==
2508 UserGroup.users_group_id)\
2509 .join(
2510 UserGroupMember,
2511 UserGroupRepoGroupToPerm.users_group_id ==
2512 UserGroupMember.users_group_id)\
2513 .filter(
2514 UserGroupMember.user_id == user_id,
2515 UserGroup.users_group_active == true())
2516 if repo_group_id:
2517 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2518 return q.all()
2519
2520 @classmethod
2521 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2522 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2523 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2524 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2525 .filter(UserUserGroupToPerm.user_id == user_id)
2526 if user_group_id:
2527 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2528 return q.all()
2529
2530 @classmethod
2531 def get_default_user_group_perms_from_user_group(
2532 cls, user_id, user_group_id=None):
2533 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2534 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2535 .join(
2536 Permission,
2537 UserGroupUserGroupToPerm.permission_id ==
2538 Permission.permission_id)\
2539 .join(
2540 TargetUserGroup,
2541 UserGroupUserGroupToPerm.target_user_group_id ==
2542 TargetUserGroup.users_group_id)\
2543 .join(
2544 UserGroup,
2545 UserGroupUserGroupToPerm.user_group_id ==
2546 UserGroup.users_group_id)\
2547 .join(
2548 UserGroupMember,
2549 UserGroupUserGroupToPerm.user_group_id ==
2550 UserGroupMember.users_group_id)\
2551 .filter(
2552 UserGroupMember.user_id == user_id,
2553 UserGroup.users_group_active == true())
2554 if user_group_id:
2555 q = q.filter(
2556 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2557
2558 return q.all()
2559
2560
2561 class UserRepoToPerm(Base, BaseModel):
2562 __tablename__ = 'repo_to_perm'
2563 __table_args__ = (
2564 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2567 )
2568 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2571 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2572
2573 user = relationship('User')
2574 repository = relationship('Repository')
2575 permission = relationship('Permission')
2576
2577 @classmethod
2578 def create(cls, user, repository, permission):
2579 n = cls()
2580 n.user = user
2581 n.repository = repository
2582 n.permission = permission
2583 Session().add(n)
2584 return n
2585
2586 def __unicode__(self):
2587 return u'<%s => %s >' % (self.user, self.repository)
2588
2589
2590 class UserUserGroupToPerm(Base, BaseModel):
2591 __tablename__ = 'user_user_group_to_perm'
2592 __table_args__ = (
2593 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2595 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2596 )
2597 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2598 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2599 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2600 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2601
2602 user = relationship('User')
2603 user_group = relationship('UserGroup')
2604 permission = relationship('Permission')
2605
2606 @classmethod
2607 def create(cls, user, user_group, permission):
2608 n = cls()
2609 n.user = user
2610 n.user_group = user_group
2611 n.permission = permission
2612 Session().add(n)
2613 return n
2614
2615 def __unicode__(self):
2616 return u'<%s => %s >' % (self.user, self.user_group)
2617
2618
2619 class UserToPerm(Base, BaseModel):
2620 __tablename__ = 'user_to_perm'
2621 __table_args__ = (
2622 UniqueConstraint('user_id', 'permission_id'),
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2625 )
2626 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2627 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2629
2630 user = relationship('User')
2631 permission = relationship('Permission', lazy='joined')
2632
2633 def __unicode__(self):
2634 return u'<%s => %s >' % (self.user, self.permission)
2635
2636
2637 class UserGroupRepoToPerm(Base, BaseModel):
2638 __tablename__ = 'users_group_repo_to_perm'
2639 __table_args__ = (
2640 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 )
2644 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2646 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2647 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2648
2649 users_group = relationship('UserGroup')
2650 permission = relationship('Permission')
2651 repository = relationship('Repository')
2652
2653 @classmethod
2654 def create(cls, users_group, repository, permission):
2655 n = cls()
2656 n.users_group = users_group
2657 n.repository = repository
2658 n.permission = permission
2659 Session().add(n)
2660 return n
2661
2662 def __unicode__(self):
2663 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2664
2665
2666 class UserGroupUserGroupToPerm(Base, BaseModel):
2667 __tablename__ = 'user_group_user_group_to_perm'
2668 __table_args__ = (
2669 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2670 CheckConstraint('target_user_group_id != user_group_id'),
2671 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2672 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2673 )
2674 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)
2675 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2677 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2678
2679 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2680 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2681 permission = relationship('Permission')
2682
2683 @classmethod
2684 def create(cls, target_user_group, user_group, permission):
2685 n = cls()
2686 n.target_user_group = target_user_group
2687 n.user_group = user_group
2688 n.permission = permission
2689 Session().add(n)
2690 return n
2691
2692 def __unicode__(self):
2693 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2694
2695
2696 class UserGroupToPerm(Base, BaseModel):
2697 __tablename__ = 'users_group_to_perm'
2698 __table_args__ = (
2699 UniqueConstraint('users_group_id', 'permission_id',),
2700 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2701 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2702 )
2703 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2704 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2705 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2706
2707 users_group = relationship('UserGroup')
2708 permission = relationship('Permission')
2709
2710
2711 class UserRepoGroupToPerm(Base, BaseModel):
2712 __tablename__ = 'user_repo_group_to_perm'
2713 __table_args__ = (
2714 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2717 )
2718
2719 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2721 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2722 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2723
2724 user = relationship('User')
2725 group = relationship('RepoGroup')
2726 permission = relationship('Permission')
2727
2728 @classmethod
2729 def create(cls, user, repository_group, permission):
2730 n = cls()
2731 n.user = user
2732 n.group = repository_group
2733 n.permission = permission
2734 Session().add(n)
2735 return n
2736
2737
2738 class UserGroupRepoGroupToPerm(Base, BaseModel):
2739 __tablename__ = 'users_group_repo_group_to_perm'
2740 __table_args__ = (
2741 UniqueConstraint('users_group_id', 'group_id'),
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2744 )
2745
2746 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)
2747 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2748 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2749 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2750
2751 users_group = relationship('UserGroup')
2752 permission = relationship('Permission')
2753 group = relationship('RepoGroup')
2754
2755 @classmethod
2756 def create(cls, user_group, repository_group, permission):
2757 n = cls()
2758 n.users_group = user_group
2759 n.group = repository_group
2760 n.permission = permission
2761 Session().add(n)
2762 return n
2763
2764 def __unicode__(self):
2765 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2766
2767
2768 class Statistics(Base, BaseModel):
2769 __tablename__ = 'statistics'
2770 __table_args__ = (
2771 UniqueConstraint('repository_id'),
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2774 )
2775 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2776 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2777 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2778 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2779 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2780 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2781
2782 repository = relationship('Repository', single_parent=True)
2783
2784
2785 class UserFollowing(Base, BaseModel):
2786 __tablename__ = 'user_followings'
2787 __table_args__ = (
2788 UniqueConstraint('user_id', 'follows_repository_id'),
2789 UniqueConstraint('user_id', 'follows_user_id'),
2790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2792 )
2793
2794 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2796 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2797 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2798 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2799
2800 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2801
2802 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2803 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2804
2805 @classmethod
2806 def get_repo_followers(cls, repo_id):
2807 return cls.query().filter(cls.follows_repo_id == repo_id)
2808
2809
2810 class CacheKey(Base, BaseModel):
2811 __tablename__ = 'cache_invalidation'
2812 __table_args__ = (
2813 UniqueConstraint('cache_key'),
2814 Index('key_idx', 'cache_key'),
2815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2817 )
2818 CACHE_TYPE_ATOM = 'ATOM'
2819 CACHE_TYPE_RSS = 'RSS'
2820 CACHE_TYPE_README = 'README'
2821
2822 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2823 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2824 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2825 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2826
2827 def __init__(self, cache_key, cache_args=''):
2828 self.cache_key = cache_key
2829 self.cache_args = cache_args
2830 self.cache_active = False
2831
2832 def __unicode__(self):
2833 return u"<%s('%s:%s[%s]')>" % (
2834 self.__class__.__name__,
2835 self.cache_id, self.cache_key, self.cache_active)
2836
2837 def _cache_key_partition(self):
2838 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2839 return prefix, repo_name, suffix
2840
2841 def get_prefix(self):
2842 """
2843 Try to extract prefix from existing cache key. The key could consist
2844 of prefix, repo_name, suffix
2845 """
2846 # this returns prefix, repo_name, suffix
2847 return self._cache_key_partition()[0]
2848
2849 def get_suffix(self):
2850 """
2851 get suffix that might have been used in _get_cache_key to
2852 generate self.cache_key. Only used for informational purposes
2853 in repo_edit.mako.
2854 """
2855 # prefix, repo_name, suffix
2856 return self._cache_key_partition()[2]
2857
2858 @classmethod
2859 def delete_all_cache(cls):
2860 """
2861 Delete all cache keys from database.
2862 Should only be run when all instances are down and all entries
2863 thus stale.
2864 """
2865 cls.query().delete()
2866 Session().commit()
2867
2868 @classmethod
2869 def get_cache_key(cls, repo_name, cache_type):
2870 """
2871
2872 Generate a cache key for this process of RhodeCode instance.
2873 Prefix most likely will be process id or maybe explicitly set
2874 instance_id from .ini file.
2875 """
2876 import rhodecode
2877 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2878
2879 repo_as_unicode = safe_unicode(repo_name)
2880 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2881 if cache_type else repo_as_unicode
2882
2883 return u'{}{}'.format(prefix, key)
2884
2885 @classmethod
2886 def set_invalidate(cls, repo_name, delete=False):
2887 """
2888 Mark all caches of a repo as invalid in the database.
2889 """
2890
2891 try:
2892 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2893 if delete:
2894 log.debug('cache objects deleted for repo %s',
2895 safe_str(repo_name))
2896 qry.delete()
2897 else:
2898 log.debug('cache objects marked as invalid for repo %s',
2899 safe_str(repo_name))
2900 qry.update({"cache_active": False})
2901
2902 Session().commit()
2903 except Exception:
2904 log.exception(
2905 'Cache key invalidation failed for repository %s',
2906 safe_str(repo_name))
2907 Session().rollback()
2908
2909 @classmethod
2910 def get_active_cache(cls, cache_key):
2911 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2912 if inv_obj:
2913 return inv_obj
2914 return None
2915
2916 @classmethod
2917 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2918 thread_scoped=False):
2919 """
2920 @cache_region('long_term')
2921 def _heavy_calculation(cache_key):
2922 return 'result'
2923
2924 cache_context = CacheKey.repo_context_cache(
2925 _heavy_calculation, repo_name, cache_type)
2926
2927 with cache_context as context:
2928 context.invalidate()
2929 computed = context.compute()
2930
2931 assert computed == 'result'
2932 """
2933 from rhodecode.lib import caches
2934 return caches.InvalidationContext(
2935 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2936
2937
2938 class ChangesetComment(Base, BaseModel):
2939 __tablename__ = 'changeset_comments'
2940 __table_args__ = (
2941 Index('cc_revision_idx', 'revision'),
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2944 )
2945
2946 COMMENT_OUTDATED = u'comment_outdated'
2947 COMMENT_TYPE_NOTE = u'note'
2948 COMMENT_TYPE_TODO = u'todo'
2949 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2950
2951 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2952 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2953 revision = Column('revision', String(40), nullable=True)
2954 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2955 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2956 line_no = Column('line_no', Unicode(10), nullable=True)
2957 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2958 f_path = Column('f_path', Unicode(1000), nullable=True)
2959 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2960 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2961 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2963 renderer = Column('renderer', Unicode(64), nullable=True)
2964 display_state = Column('display_state', Unicode(128), nullable=True)
2965
2966 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2967 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2968 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2969 author = relationship('User', lazy='joined')
2970 repo = relationship('Repository')
2971 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2972 pull_request = relationship('PullRequest', lazy='joined')
2973 pull_request_version = relationship('PullRequestVersion')
2974
2975 @classmethod
2976 def get_users(cls, revision=None, pull_request_id=None):
2977 """
2978 Returns user associated with this ChangesetComment. ie those
2979 who actually commented
2980
2981 :param cls:
2982 :param revision:
2983 """
2984 q = Session().query(User)\
2985 .join(ChangesetComment.author)
2986 if revision:
2987 q = q.filter(cls.revision == revision)
2988 elif pull_request_id:
2989 q = q.filter(cls.pull_request_id == pull_request_id)
2990 return q.all()
2991
2992 @classmethod
2993 def get_index_from_version(cls, pr_version, versions):
2994 num_versions = [x.pull_request_version_id for x in versions]
2995 try:
2996 return num_versions.index(pr_version) +1
2997 except (IndexError, ValueError):
2998 return
2999
3000 @property
3001 def outdated(self):
3002 return self.display_state == self.COMMENT_OUTDATED
3003
3004 def outdated_at_version(self, version):
3005 """
3006 Checks if comment is outdated for given pull request version
3007 """
3008 return self.outdated and self.pull_request_version_id != version
3009
3010 def older_than_version(self, version):
3011 """
3012 Checks if comment is made from previous version than given
3013 """
3014 if version is None:
3015 return self.pull_request_version_id is not None
3016
3017 return self.pull_request_version_id < version
3018
3019 @property
3020 def resolved(self):
3021 return self.resolved_by[0] if self.resolved_by else None
3022
3023 @property
3024 def is_todo(self):
3025 return self.comment_type == self.COMMENT_TYPE_TODO
3026
3027 def get_index_version(self, versions):
3028 return self.get_index_from_version(
3029 self.pull_request_version_id, versions)
3030
3031 def render(self, mentions=False):
3032 from rhodecode.lib import helpers as h
3033 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3034
3035 def __repr__(self):
3036 if self.comment_id:
3037 return '<DB:Comment #%s>' % self.comment_id
3038 else:
3039 return '<DB:Comment at %#x>' % id(self)
3040
3041
3042 class ChangesetStatus(Base, BaseModel):
3043 __tablename__ = 'changeset_statuses'
3044 __table_args__ = (
3045 Index('cs_revision_idx', 'revision'),
3046 Index('cs_version_idx', 'version'),
3047 UniqueConstraint('repo_id', 'revision', 'version'),
3048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3049 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3050 )
3051 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3052 STATUS_APPROVED = 'approved'
3053 STATUS_REJECTED = 'rejected'
3054 STATUS_UNDER_REVIEW = 'under_review'
3055
3056 STATUSES = [
3057 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3058 (STATUS_APPROVED, _("Approved")),
3059 (STATUS_REJECTED, _("Rejected")),
3060 (STATUS_UNDER_REVIEW, _("Under Review")),
3061 ]
3062
3063 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3064 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3066 revision = Column('revision', String(40), nullable=False)
3067 status = Column('status', String(128), nullable=False, default=DEFAULT)
3068 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3069 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3070 version = Column('version', Integer(), nullable=False, default=0)
3071 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3072
3073 author = relationship('User', lazy='joined')
3074 repo = relationship('Repository')
3075 comment = relationship('ChangesetComment', lazy='joined')
3076 pull_request = relationship('PullRequest', lazy='joined')
3077
3078 def __unicode__(self):
3079 return u"<%s('%s[v%s]:%s')>" % (
3080 self.__class__.__name__,
3081 self.status, self.version, self.author
3082 )
3083
3084 @classmethod
3085 def get_status_lbl(cls, value):
3086 return dict(cls.STATUSES).get(value)
3087
3088 @property
3089 def status_lbl(self):
3090 return ChangesetStatus.get_status_lbl(self.status)
3091
3092
3093 class _PullRequestBase(BaseModel):
3094 """
3095 Common attributes of pull request and version entries.
3096 """
3097
3098 # .status values
3099 STATUS_NEW = u'new'
3100 STATUS_OPEN = u'open'
3101 STATUS_CLOSED = u'closed'
3102
3103 title = Column('title', Unicode(255), nullable=True)
3104 description = Column(
3105 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3106 nullable=True)
3107 # new/open/closed status of pull request (not approve/reject/etc)
3108 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3109 created_on = Column(
3110 'created_on', DateTime(timezone=False), nullable=False,
3111 default=datetime.datetime.now)
3112 updated_on = Column(
3113 'updated_on', DateTime(timezone=False), nullable=False,
3114 default=datetime.datetime.now)
3115
3116 @declared_attr
3117 def user_id(cls):
3118 return Column(
3119 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3120 unique=None)
3121
3122 # 500 revisions max
3123 _revisions = Column(
3124 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3125
3126 @declared_attr
3127 def source_repo_id(cls):
3128 # TODO: dan: rename column to source_repo_id
3129 return Column(
3130 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3131 nullable=False)
3132
3133 source_ref = Column('org_ref', Unicode(255), nullable=False)
3134
3135 @declared_attr
3136 def target_repo_id(cls):
3137 # TODO: dan: rename column to target_repo_id
3138 return Column(
3139 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3140 nullable=False)
3141
3142 target_ref = Column('other_ref', Unicode(255), nullable=False)
3143 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3144
3145 # TODO: dan: rename column to last_merge_source_rev
3146 _last_merge_source_rev = Column(
3147 'last_merge_org_rev', String(40), nullable=True)
3148 # TODO: dan: rename column to last_merge_target_rev
3149 _last_merge_target_rev = Column(
3150 'last_merge_other_rev', String(40), nullable=True)
3151 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3152 merge_rev = Column('merge_rev', String(40), nullable=True)
3153
3154 @hybrid_property
3155 def revisions(self):
3156 return self._revisions.split(':') if self._revisions else []
3157
3158 @revisions.setter
3159 def revisions(self, val):
3160 self._revisions = ':'.join(val)
3161
3162 @declared_attr
3163 def author(cls):
3164 return relationship('User', lazy='joined')
3165
3166 @declared_attr
3167 def source_repo(cls):
3168 return relationship(
3169 'Repository',
3170 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3171
3172 @property
3173 def source_ref_parts(self):
3174 return self.unicode_to_reference(self.source_ref)
3175
3176 @declared_attr
3177 def target_repo(cls):
3178 return relationship(
3179 'Repository',
3180 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3181
3182 @property
3183 def target_ref_parts(self):
3184 return self.unicode_to_reference(self.target_ref)
3185
3186 @property
3187 def shadow_merge_ref(self):
3188 return self.unicode_to_reference(self._shadow_merge_ref)
3189
3190 @shadow_merge_ref.setter
3191 def shadow_merge_ref(self, ref):
3192 self._shadow_merge_ref = self.reference_to_unicode(ref)
3193
3194 def unicode_to_reference(self, raw):
3195 """
3196 Convert a unicode (or string) to a reference object.
3197 If unicode evaluates to False it returns None.
3198 """
3199 if raw:
3200 refs = raw.split(':')
3201 return Reference(*refs)
3202 else:
3203 return None
3204
3205 def reference_to_unicode(self, ref):
3206 """
3207 Convert a reference object to unicode.
3208 If reference is None it returns None.
3209 """
3210 if ref:
3211 return u':'.join(ref)
3212 else:
3213 return None
3214
3215 def get_api_data(self):
3216 from rhodecode.model.pull_request import PullRequestModel
3217 pull_request = self
3218 merge_status = PullRequestModel().merge_status(pull_request)
3219
3220 pull_request_url = url(
3221 'pullrequest_show', repo_name=self.target_repo.repo_name,
3222 pull_request_id=self.pull_request_id, qualified=True)
3223
3224 merge_data = {
3225 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3226 'reference': (
3227 pull_request.shadow_merge_ref._asdict()
3228 if pull_request.shadow_merge_ref else None),
3229 }
3230
3231 data = {
3232 'pull_request_id': pull_request.pull_request_id,
3233 'url': pull_request_url,
3234 'title': pull_request.title,
3235 'description': pull_request.description,
3236 'status': pull_request.status,
3237 'created_on': pull_request.created_on,
3238 'updated_on': pull_request.updated_on,
3239 'commit_ids': pull_request.revisions,
3240 'review_status': pull_request.calculated_review_status(),
3241 'mergeable': {
3242 'status': merge_status[0],
3243 'message': unicode(merge_status[1]),
3244 },
3245 'source': {
3246 'clone_url': pull_request.source_repo.clone_url(),
3247 'repository': pull_request.source_repo.repo_name,
3248 'reference': {
3249 'name': pull_request.source_ref_parts.name,
3250 'type': pull_request.source_ref_parts.type,
3251 'commit_id': pull_request.source_ref_parts.commit_id,
3252 },
3253 },
3254 'target': {
3255 'clone_url': pull_request.target_repo.clone_url(),
3256 'repository': pull_request.target_repo.repo_name,
3257 'reference': {
3258 'name': pull_request.target_ref_parts.name,
3259 'type': pull_request.target_ref_parts.type,
3260 'commit_id': pull_request.target_ref_parts.commit_id,
3261 },
3262 },
3263 'merge': merge_data,
3264 'author': pull_request.author.get_api_data(include_secrets=False,
3265 details='basic'),
3266 'reviewers': [
3267 {
3268 'user': reviewer.get_api_data(include_secrets=False,
3269 details='basic'),
3270 'reasons': reasons,
3271 'review_status': st[0][1].status if st else 'not_reviewed',
3272 }
3273 for reviewer, reasons, st in pull_request.reviewers_statuses()
3274 ]
3275 }
3276
3277 return data
3278
3279
3280 class PullRequest(Base, _PullRequestBase):
3281 __tablename__ = 'pull_requests'
3282 __table_args__ = (
3283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3285 )
3286
3287 pull_request_id = Column(
3288 'pull_request_id', Integer(), nullable=False, primary_key=True)
3289
3290 def __repr__(self):
3291 if self.pull_request_id:
3292 return '<DB:PullRequest #%s>' % self.pull_request_id
3293 else:
3294 return '<DB:PullRequest at %#x>' % id(self)
3295
3296 reviewers = relationship('PullRequestReviewers',
3297 cascade="all, delete, delete-orphan")
3298 statuses = relationship('ChangesetStatus')
3299 comments = relationship('ChangesetComment',
3300 cascade="all, delete, delete-orphan")
3301 versions = relationship('PullRequestVersion',
3302 cascade="all, delete, delete-orphan",
3303 lazy='dynamic')
3304
3305 @classmethod
3306 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3307 internal_methods=None):
3308
3309 class PullRequestDisplay(object):
3310 """
3311 Special object wrapper for showing PullRequest data via Versions
3312 It mimics PR object as close as possible. This is read only object
3313 just for display
3314 """
3315
3316 def __init__(self, attrs, internal=None):
3317 self.attrs = attrs
3318 # internal have priority over the given ones via attrs
3319 self.internal = internal or ['versions']
3320
3321 def __getattr__(self, item):
3322 if item in self.internal:
3323 return getattr(self, item)
3324 try:
3325 return self.attrs[item]
3326 except KeyError:
3327 raise AttributeError(
3328 '%s object has no attribute %s' % (self, item))
3329
3330 def __repr__(self):
3331 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3332
3333 def versions(self):
3334 return pull_request_obj.versions.order_by(
3335 PullRequestVersion.pull_request_version_id).all()
3336
3337 def is_closed(self):
3338 return pull_request_obj.is_closed()
3339
3340 @property
3341 def pull_request_version_id(self):
3342 return getattr(pull_request_obj, 'pull_request_version_id', None)
3343
3344 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3345
3346 attrs.author = StrictAttributeDict(
3347 pull_request_obj.author.get_api_data())
3348 if pull_request_obj.target_repo:
3349 attrs.target_repo = StrictAttributeDict(
3350 pull_request_obj.target_repo.get_api_data())
3351 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3352
3353 if pull_request_obj.source_repo:
3354 attrs.source_repo = StrictAttributeDict(
3355 pull_request_obj.source_repo.get_api_data())
3356 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3357
3358 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3359 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3360 attrs.revisions = pull_request_obj.revisions
3361
3362 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3363
3364 return PullRequestDisplay(attrs, internal=internal_methods)
3365
3366 def is_closed(self):
3367 return self.status == self.STATUS_CLOSED
3368
3369 def __json__(self):
3370 return {
3371 'revisions': self.revisions,
3372 }
3373
3374 def calculated_review_status(self):
3375 from rhodecode.model.changeset_status import ChangesetStatusModel
3376 return ChangesetStatusModel().calculated_review_status(self)
3377
3378 def reviewers_statuses(self):
3379 from rhodecode.model.changeset_status import ChangesetStatusModel
3380 return ChangesetStatusModel().reviewers_statuses(self)
3381
3382 @property
3383 def workspace_id(self):
3384 from rhodecode.model.pull_request import PullRequestModel
3385 return PullRequestModel()._workspace_id(self)
3386
3387 def get_shadow_repo(self):
3388 workspace_id = self.workspace_id
3389 vcs_obj = self.target_repo.scm_instance()
3390 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3391 workspace_id)
3392 return vcs_obj._get_shadow_instance(shadow_repository_path)
3393
3394
3395 class PullRequestVersion(Base, _PullRequestBase):
3396 __tablename__ = 'pull_request_versions'
3397 __table_args__ = (
3398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3400 )
3401
3402 pull_request_version_id = Column(
3403 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3404 pull_request_id = Column(
3405 'pull_request_id', Integer(),
3406 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3407 pull_request = relationship('PullRequest')
3408
3409 def __repr__(self):
3410 if self.pull_request_version_id:
3411 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3412 else:
3413 return '<DB:PullRequestVersion at %#x>' % id(self)
3414
3415 @property
3416 def reviewers(self):
3417 return self.pull_request.reviewers
3418
3419 @property
3420 def versions(self):
3421 return self.pull_request.versions
3422
3423 def is_closed(self):
3424 # calculate from original
3425 return self.pull_request.status == self.STATUS_CLOSED
3426
3427 def calculated_review_status(self):
3428 return self.pull_request.calculated_review_status()
3429
3430 def reviewers_statuses(self):
3431 return self.pull_request.reviewers_statuses()
3432
3433
3434 class PullRequestReviewers(Base, BaseModel):
3435 __tablename__ = 'pull_request_reviewers'
3436 __table_args__ = (
3437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3439 )
3440
3441 def __init__(self, user=None, pull_request=None, reasons=None):
3442 self.user = user
3443 self.pull_request = pull_request
3444 self.reasons = reasons or []
3445
3446 @hybrid_property
3447 def reasons(self):
3448 if not self._reasons:
3449 return []
3450 return self._reasons
3451
3452 @reasons.setter
3453 def reasons(self, val):
3454 val = val or []
3455 if any(not isinstance(x, basestring) for x in val):
3456 raise Exception('invalid reasons type, must be list of strings')
3457 self._reasons = val
3458
3459 pull_requests_reviewers_id = Column(
3460 'pull_requests_reviewers_id', Integer(), nullable=False,
3461 primary_key=True)
3462 pull_request_id = Column(
3463 "pull_request_id", Integer(),
3464 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3465 user_id = Column(
3466 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3467 _reasons = Column(
3468 'reason', MutationList.as_mutable(
3469 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3470
3471 user = relationship('User')
3472 pull_request = relationship('PullRequest')
3473
3474
3475 class Notification(Base, BaseModel):
3476 __tablename__ = 'notifications'
3477 __table_args__ = (
3478 Index('notification_type_idx', 'type'),
3479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3481 )
3482
3483 TYPE_CHANGESET_COMMENT = u'cs_comment'
3484 TYPE_MESSAGE = u'message'
3485 TYPE_MENTION = u'mention'
3486 TYPE_REGISTRATION = u'registration'
3487 TYPE_PULL_REQUEST = u'pull_request'
3488 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3489
3490 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3491 subject = Column('subject', Unicode(512), nullable=True)
3492 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3493 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3494 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3495 type_ = Column('type', Unicode(255))
3496
3497 created_by_user = relationship('User')
3498 notifications_to_users = relationship('UserNotification', lazy='joined',
3499 cascade="all, delete, delete-orphan")
3500
3501 @property
3502 def recipients(self):
3503 return [x.user for x in UserNotification.query()\
3504 .filter(UserNotification.notification == self)\
3505 .order_by(UserNotification.user_id.asc()).all()]
3506
3507 @classmethod
3508 def create(cls, created_by, subject, body, recipients, type_=None):
3509 if type_ is None:
3510 type_ = Notification.TYPE_MESSAGE
3511
3512 notification = cls()
3513 notification.created_by_user = created_by
3514 notification.subject = subject
3515 notification.body = body
3516 notification.type_ = type_
3517 notification.created_on = datetime.datetime.now()
3518
3519 for u in recipients:
3520 assoc = UserNotification()
3521 assoc.notification = notification
3522
3523 # if created_by is inside recipients mark his notification
3524 # as read
3525 if u.user_id == created_by.user_id:
3526 assoc.read = True
3527
3528 u.notifications.append(assoc)
3529 Session().add(notification)
3530
3531 return notification
3532
3533 @property
3534 def description(self):
3535 from rhodecode.model.notification import NotificationModel
3536 return NotificationModel().make_description(self)
3537
3538
3539 class UserNotification(Base, BaseModel):
3540 __tablename__ = 'user_to_notification'
3541 __table_args__ = (
3542 UniqueConstraint('user_id', 'notification_id'),
3543 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3544 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3545 )
3546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3547 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3548 read = Column('read', Boolean, default=False)
3549 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3550
3551 user = relationship('User', lazy="joined")
3552 notification = relationship('Notification', lazy="joined",
3553 order_by=lambda: Notification.created_on.desc(),)
3554
3555 def mark_as_read(self):
3556 self.read = True
3557 Session().add(self)
3558
3559
3560 class Gist(Base, BaseModel):
3561 __tablename__ = 'gists'
3562 __table_args__ = (
3563 Index('g_gist_access_id_idx', 'gist_access_id'),
3564 Index('g_created_on_idx', 'created_on'),
3565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3567 )
3568 GIST_PUBLIC = u'public'
3569 GIST_PRIVATE = u'private'
3570 DEFAULT_FILENAME = u'gistfile1.txt'
3571
3572 ACL_LEVEL_PUBLIC = u'acl_public'
3573 ACL_LEVEL_PRIVATE = u'acl_private'
3574
3575 gist_id = Column('gist_id', Integer(), primary_key=True)
3576 gist_access_id = Column('gist_access_id', Unicode(250))
3577 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3578 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3579 gist_expires = Column('gist_expires', Float(53), nullable=False)
3580 gist_type = Column('gist_type', Unicode(128), nullable=False)
3581 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3582 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3583 acl_level = Column('acl_level', Unicode(128), nullable=True)
3584
3585 owner = relationship('User')
3586
3587 def __repr__(self):
3588 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3589
3590 @classmethod
3591 def get_or_404(cls, id_):
3592 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3593 if not res:
3594 raise HTTPNotFound
3595 return res
3596
3597 @classmethod
3598 def get_by_access_id(cls, gist_access_id):
3599 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3600
3601 def gist_url(self):
3602 import rhodecode
3603 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3604 if alias_url:
3605 return alias_url.replace('{gistid}', self.gist_access_id)
3606
3607 return url('gist', gist_id=self.gist_access_id, qualified=True)
3608
3609 @classmethod
3610 def base_path(cls):
3611 """
3612 Returns base path when all gists are stored
3613
3614 :param cls:
3615 """
3616 from rhodecode.model.gist import GIST_STORE_LOC
3617 q = Session().query(RhodeCodeUi)\
3618 .filter(RhodeCodeUi.ui_key == URL_SEP)
3619 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3620 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3621
3622 def get_api_data(self):
3623 """
3624 Common function for generating gist related data for API
3625 """
3626 gist = self
3627 data = {
3628 'gist_id': gist.gist_id,
3629 'type': gist.gist_type,
3630 'access_id': gist.gist_access_id,
3631 'description': gist.gist_description,
3632 'url': gist.gist_url(),
3633 'expires': gist.gist_expires,
3634 'created_on': gist.created_on,
3635 'modified_at': gist.modified_at,
3636 'content': None,
3637 'acl_level': gist.acl_level,
3638 }
3639 return data
3640
3641 def __json__(self):
3642 data = dict(
3643 )
3644 data.update(self.get_api_data())
3645 return data
3646 # SCM functions
3647
3648 def scm_instance(self, **kwargs):
3649 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3650 return get_vcs_instance(
3651 repo_path=safe_str(full_repo_path), create=False)
3652
3653
3654 class ExternalIdentity(Base, BaseModel):
3655 __tablename__ = 'external_identities'
3656 __table_args__ = (
3657 Index('local_user_id_idx', 'local_user_id'),
3658 Index('external_id_idx', 'external_id'),
3659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3660 'mysql_charset': 'utf8'})
3661
3662 external_id = Column('external_id', Unicode(255), default=u'',
3663 primary_key=True)
3664 external_username = Column('external_username', Unicode(1024), default=u'')
3665 local_user_id = Column('local_user_id', Integer(),
3666 ForeignKey('users.user_id'), primary_key=True)
3667 provider_name = Column('provider_name', Unicode(255), default=u'',
3668 primary_key=True)
3669 access_token = Column('access_token', String(1024), default=u'')
3670 alt_token = Column('alt_token', String(1024), default=u'')
3671 token_secret = Column('token_secret', String(1024), default=u'')
3672
3673 @classmethod
3674 def by_external_id_and_provider(cls, external_id, provider_name,
3675 local_user_id=None):
3676 """
3677 Returns ExternalIdentity instance based on search params
3678
3679 :param external_id:
3680 :param provider_name:
3681 :return: ExternalIdentity
3682 """
3683 query = cls.query()
3684 query = query.filter(cls.external_id == external_id)
3685 query = query.filter(cls.provider_name == provider_name)
3686 if local_user_id:
3687 query = query.filter(cls.local_user_id == local_user_id)
3688 return query.first()
3689
3690 @classmethod
3691 def user_by_external_id_and_provider(cls, external_id, provider_name):
3692 """
3693 Returns User instance based on search params
3694
3695 :param external_id:
3696 :param provider_name:
3697 :return: User
3698 """
3699 query = User.query()
3700 query = query.filter(cls.external_id == external_id)
3701 query = query.filter(cls.provider_name == provider_name)
3702 query = query.filter(User.user_id == cls.local_user_id)
3703 return query.first()
3704
3705 @classmethod
3706 def by_local_user_id(cls, local_user_id):
3707 """
3708 Returns all tokens for user
3709
3710 :param local_user_id:
3711 :return: ExternalIdentity
3712 """
3713 query = cls.query()
3714 query = query.filter(cls.local_user_id == local_user_id)
3715 return query
3716
3717
3718 class Integration(Base, BaseModel):
3719 __tablename__ = 'integrations'
3720 __table_args__ = (
3721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3723 )
3724
3725 integration_id = Column('integration_id', Integer(), primary_key=True)
3726 integration_type = Column('integration_type', String(255))
3727 enabled = Column('enabled', Boolean(), nullable=False)
3728 name = Column('name', String(255), nullable=False)
3729 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3730 default=False)
3731
3732 settings = Column(
3733 'settings_json', MutationObj.as_mutable(
3734 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3735 repo_id = Column(
3736 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3737 nullable=True, unique=None, default=None)
3738 repo = relationship('Repository', lazy='joined')
3739
3740 repo_group_id = Column(
3741 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3742 nullable=True, unique=None, default=None)
3743 repo_group = relationship('RepoGroup', lazy='joined')
3744
3745 @property
3746 def scope(self):
3747 if self.repo:
3748 return repr(self.repo)
3749 if self.repo_group:
3750 if self.child_repos_only:
3751 return repr(self.repo_group) + ' (child repos only)'
3752 else:
3753 return repr(self.repo_group) + ' (recursive)'
3754 if self.child_repos_only:
3755 return 'root_repos'
3756 return 'global'
3757
3758 def __repr__(self):
3759 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3760
3761
3762 class RepoReviewRuleUser(Base, BaseModel):
3763 __tablename__ = 'repo_review_rules_users'
3764 __table_args__ = (
3765 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3766 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3767 )
3768 repo_review_rule_user_id = Column(
3769 'repo_review_rule_user_id', Integer(), primary_key=True)
3770 repo_review_rule_id = Column("repo_review_rule_id",
3771 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3773 nullable=False)
3774 user = relationship('User')
3775
3776
3777 class RepoReviewRuleUserGroup(Base, BaseModel):
3778 __tablename__ = 'repo_review_rules_users_groups'
3779 __table_args__ = (
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3782 )
3783 repo_review_rule_users_group_id = Column(
3784 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3785 repo_review_rule_id = Column("repo_review_rule_id",
3786 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3787 users_group_id = Column("users_group_id", Integer(),
3788 ForeignKey('users_groups.users_group_id'), nullable=False)
3789 users_group = relationship('UserGroup')
3790
3791
3792 class RepoReviewRule(Base, BaseModel):
3793 __tablename__ = 'repo_review_rules'
3794 __table_args__ = (
3795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3797 )
3798
3799 repo_review_rule_id = Column(
3800 'repo_review_rule_id', Integer(), primary_key=True)
3801 repo_id = Column(
3802 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3803 repo = relationship('Repository', backref='review_rules')
3804
3805 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3806 default=u'*') # glob
3807 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3808 default=u'*') # glob
3809
3810 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3811 nullable=False, default=False)
3812 rule_users = relationship('RepoReviewRuleUser')
3813 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3814
3815 @hybrid_property
3816 def branch_pattern(self):
3817 return self._branch_pattern or '*'
3818
3819 def _validate_glob(self, value):
3820 re.compile('^' + glob2re(value) + '$')
3821
3822 @branch_pattern.setter
3823 def branch_pattern(self, value):
3824 self._validate_glob(value)
3825 self._branch_pattern = value or '*'
3826
3827 @hybrid_property
3828 def file_pattern(self):
3829 return self._file_pattern or '*'
3830
3831 @file_pattern.setter
3832 def file_pattern(self, value):
3833 self._validate_glob(value)
3834 self._file_pattern = value or '*'
3835
3836 def matches(self, branch, files_changed):
3837 """
3838 Check if this review rule matches a branch/files in a pull request
3839
3840 :param branch: branch name for the commit
3841 :param files_changed: list of file paths changed in the pull request
3842 """
3843
3844 branch = branch or ''
3845 files_changed = files_changed or []
3846
3847 branch_matches = True
3848 if branch:
3849 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3850 branch_matches = bool(branch_regex.search(branch))
3851
3852 files_matches = True
3853 if self.file_pattern != '*':
3854 files_matches = False
3855 file_regex = re.compile(glob2re(self.file_pattern))
3856 for filename in files_changed:
3857 if file_regex.search(filename):
3858 files_matches = True
3859 break
3860
3861 return branch_matches and files_matches
3862
3863 @property
3864 def review_users(self):
3865 """ Returns the users which this rule applies to """
3866
3867 users = set()
3868 users |= set([
3869 rule_user.user for rule_user in self.rule_users
3870 if rule_user.user.active])
3871 users |= set(
3872 member.user
3873 for rule_user_group in self.rule_user_groups
3874 for member in rule_user_group.users_group.members
3875 if member.user.active
3876 )
3877 return users
3878
3879 def __repr__(self):
3880 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3881 self.repo_review_rule_id, self.repo)
3882
3883
3884 class DbMigrateVersion(Base, BaseModel):
3885 __tablename__ = 'db_migrate_version'
3886 __table_args__ = (
3887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3889 )
3890 repository_id = Column('repository_id', String(250), primary_key=True)
3891 repository_path = Column('repository_path', Text)
3892 version = Column('version', Integer)
3893
3894
3895 class DbSession(Base, BaseModel):
3896 __tablename__ = 'db_session'
3897 __table_args__ = (
3898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3900 )
3901
3902 def __repr__(self):
3903 return '<DB:DbSession({})>'.format(self.id)
3904
3905 id = Column('id', Integer())
3906 namespace = Column('namespace', String(255), primary_key=True)
3907 accessed = Column('accessed', DateTime, nullable=False)
3908 created = Column('created', DateTime, nullable=False)
3909 data = Column('data', PickleType, nullable=False)
@@ -0,0 +1,63 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def get_by_key(cls, key):
11 return cls.query().filter(cls.ui_key == key).scalar()
12
13
14 def create_or_update_hook(cls, key, val, SESSION):
15 new_ui = get_by_key(cls, key) or cls()
16 new_ui.ui_section = 'hooks'
17 new_ui.ui_active = True
18 new_ui.ui_key = key
19 new_ui.ui_value = val
20
21 SESSION().add(new_ui)
22
23
24 def upgrade(migrate_engine):
25 """
26 Upgrade operations go here.
27 Don't create your own engine; bind migrate_engine to your metadata
28 """
29 _reset_base(migrate_engine)
30 from rhodecode.lib.dbmigrate.schema import db_4_7_0_0 as db
31
32 # issue fixups
33 fixups(db, meta.Session)
34
35
36 def downgrade(migrate_engine):
37 meta = MetaData()
38 meta.bind = migrate_engine
39
40
41 def fixups(models, _SESSION):
42
43 cleanup_if_present = (
44 models.RhodeCodeUi.HOOK_PRETX_PUSH,
45 )
46
47 for hook in cleanup_if_present:
48 ui_cfg = models.RhodeCodeUi.query().filter(
49 models.RhodeCodeUi.ui_key == hook).scalar()
50 if ui_cfg is not None:
51 log.info('Removing RhodeCodeUI for hook "%s".', hook)
52 _SESSION().delete(ui_cfg)
53
54 to_add = [
55 (models.RhodeCodeUi.HOOK_PRETX_PUSH,
56 'python:vcsserver.hooks.pre_push'),
57 ]
58
59 for hook, value in to_add:
60 log.info('Adding RhodeCodeUI for hook "%s".', hook)
61 create_or_update_hook(models.RhodeCodeUi, hook, value, _SESSION)
62
63 _SESSION().commit() No newline at end of file
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 64 # defines current db version for migrations
54 __dbversion__ = 65 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,597 +1,598 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args={}):
63 SESSION=None, cli_args={}):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args
70 self.cli_args = cli_args
71 self.init_db(SESSION=SESSION)
71 self.init_db(SESSION=SESSION)
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73
73
74 def get_ask_ok_func(self, param):
74 def get_ask_ok_func(self, param):
75 if param not in [None]:
75 if param not in [None]:
76 # return a function lambda that has a default set to param
76 # return a function lambda that has a default set to param
77 return lambda *args, **kwargs: param
77 return lambda *args, **kwargs: param
78 else:
78 else:
79 from rhodecode.lib.utils import ask_ok
79 from rhodecode.lib.utils import ask_ok
80 return ask_ok
80 return ask_ok
81
81
82 def init_db(self, SESSION=None):
82 def init_db(self, SESSION=None):
83 if SESSION:
83 if SESSION:
84 self.sa = SESSION
84 self.sa = SESSION
85 else:
85 else:
86 # init new sessions
86 # init new sessions
87 engine = create_engine(self.dburi, echo=self.log_sql)
87 engine = create_engine(self.dburi, echo=self.log_sql)
88 init_model(engine)
88 init_model(engine)
89 self.sa = Session()
89 self.sa = Session()
90
90
91 def create_tables(self, override=False):
91 def create_tables(self, override=False):
92 """
92 """
93 Create a auth database
93 Create a auth database
94 """
94 """
95
95
96 log.info("Existing database with the same name is going to be destroyed.")
96 log.info("Existing database with the same name is going to be destroyed.")
97 log.info("Setup command will run DROP ALL command on that database.")
97 log.info("Setup command will run DROP ALL command on that database.")
98 if self.tests:
98 if self.tests:
99 destroy = True
99 destroy = True
100 else:
100 else:
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 if not destroy:
102 if not destroy:
103 log.info('Nothing done.')
103 log.info('Nothing done.')
104 sys.exit(0)
104 sys.exit(0)
105 if destroy:
105 if destroy:
106 Base.metadata.drop_all()
106 Base.metadata.drop_all()
107
107
108 checkfirst = not override
108 checkfirst = not override
109 Base.metadata.create_all(checkfirst=checkfirst)
109 Base.metadata.create_all(checkfirst=checkfirst)
110 log.info('Created tables for %s' % self.dbname)
110 log.info('Created tables for %s' % self.dbname)
111
111
112 def set_db_version(self):
112 def set_db_version(self):
113 ver = DbMigrateVersion()
113 ver = DbMigrateVersion()
114 ver.version = __dbversion__
114 ver.version = __dbversion__
115 ver.repository_id = 'rhodecode_db_migrations'
115 ver.repository_id = 'rhodecode_db_migrations'
116 ver.repository_path = 'versions'
116 ver.repository_path = 'versions'
117 self.sa.add(ver)
117 self.sa.add(ver)
118 log.info('db version set to: %s' % __dbversion__)
118 log.info('db version set to: %s' % __dbversion__)
119
119
120 def run_pre_migration_tasks(self):
120 def run_pre_migration_tasks(self):
121 """
121 """
122 Run various tasks before actually doing migrations
122 Run various tasks before actually doing migrations
123 """
123 """
124 # delete cache keys on each upgrade
124 # delete cache keys on each upgrade
125 total = CacheKey.query().count()
125 total = CacheKey.query().count()
126 log.info("Deleting (%s) cache keys now...", total)
126 log.info("Deleting (%s) cache keys now...", total)
127 CacheKey.delete_all_cache()
127 CacheKey.delete_all_cache()
128
128
129 def upgrade(self):
129 def upgrade(self):
130 """
130 """
131 Upgrades given database schema to given revision following
131 Upgrades given database schema to given revision following
132 all needed steps, to perform the upgrade
132 all needed steps, to perform the upgrade
133
133
134 """
134 """
135
135
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 DatabaseNotControlledError
138 DatabaseNotControlledError
139
139
140 if 'sqlite' in self.dburi:
140 if 'sqlite' in self.dburi:
141 print (
141 print (
142 '********************** WARNING **********************\n'
142 '********************** WARNING **********************\n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 'Earlier versions are known to fail on some migrations\n'
144 'Earlier versions are known to fail on some migrations\n'
145 '*****************************************************\n')
145 '*****************************************************\n')
146
146
147 upgrade = self.ask_ok(
147 upgrade = self.ask_ok(
148 'You are about to perform a database upgrade. Make '
148 'You are about to perform a database upgrade. Make '
149 'sure you have backed up your database. '
149 'sure you have backed up your database. '
150 'Continue ? [y/n]')
150 'Continue ? [y/n]')
151 if not upgrade:
151 if not upgrade:
152 log.info('No upgrade performed')
152 log.info('No upgrade performed')
153 sys.exit(0)
153 sys.exit(0)
154
154
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 'rhodecode/lib/dbmigrate')
156 'rhodecode/lib/dbmigrate')
157 db_uri = self.dburi
157 db_uri = self.dburi
158
158
159 try:
159 try:
160 curr_version = api.db_version(db_uri, repository_path)
160 curr_version = api.db_version(db_uri, repository_path)
161 msg = ('Found current database under version '
161 msg = ('Found current database under version '
162 'control with version %s' % curr_version)
162 'control with version %s' % curr_version)
163
163
164 except (RuntimeError, DatabaseNotControlledError):
164 except (RuntimeError, DatabaseNotControlledError):
165 curr_version = 1
165 curr_version = 1
166 msg = ('Current database is not under version control. Setting '
166 msg = ('Current database is not under version control. Setting '
167 'as version %s' % curr_version)
167 'as version %s' % curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
169
169
170 notify(msg)
170 notify(msg)
171
171
172 self.run_pre_migration_tasks()
172 self.run_pre_migration_tasks()
173
173
174 if curr_version == __dbversion__:
174 if curr_version == __dbversion__:
175 log.info('This database is already at the newest version')
175 log.info('This database is already at the newest version')
176 sys.exit(0)
176 sys.exit(0)
177
177
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 notify('attempting to upgrade database from '
179 notify('attempting to upgrade database from '
180 'version %s to version %s' % (curr_version, __dbversion__))
180 'version %s to version %s' % (curr_version, __dbversion__))
181
181
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 _step = None
183 _step = None
184 for step in upgrade_steps:
184 for step in upgrade_steps:
185 notify('performing upgrade step %s' % step)
185 notify('performing upgrade step %s' % step)
186 time.sleep(0.5)
186 time.sleep(0.5)
187
187
188 api.upgrade(db_uri, repository_path, step)
188 api.upgrade(db_uri, repository_path, step)
189 self.sa.rollback()
189 self.sa.rollback()
190 notify('schema upgrade for step %s completed' % (step,))
190 notify('schema upgrade for step %s completed' % (step,))
191
191
192 _step = step
192 _step = step
193
193
194 notify('upgrade to version %s successful' % _step)
194 notify('upgrade to version %s successful' % _step)
195
195
196 def fix_repo_paths(self):
196 def fix_repo_paths(self):
197 """
197 """
198 Fixes an old RhodeCode version path into new one without a '*'
198 Fixes an old RhodeCode version path into new one without a '*'
199 """
199 """
200
200
201 paths = self.sa.query(RhodeCodeUi)\
201 paths = self.sa.query(RhodeCodeUi)\
202 .filter(RhodeCodeUi.ui_key == '/')\
202 .filter(RhodeCodeUi.ui_key == '/')\
203 .scalar()
203 .scalar()
204
204
205 paths.ui_value = paths.ui_value.replace('*', '')
205 paths.ui_value = paths.ui_value.replace('*', '')
206
206
207 try:
207 try:
208 self.sa.add(paths)
208 self.sa.add(paths)
209 self.sa.commit()
209 self.sa.commit()
210 except Exception:
210 except Exception:
211 self.sa.rollback()
211 self.sa.rollback()
212 raise
212 raise
213
213
214 def fix_default_user(self):
214 def fix_default_user(self):
215 """
215 """
216 Fixes an old default user with some 'nicer' default values,
216 Fixes an old default user with some 'nicer' default values,
217 used mostly for anonymous access
217 used mostly for anonymous access
218 """
218 """
219 def_user = self.sa.query(User)\
219 def_user = self.sa.query(User)\
220 .filter(User.username == User.DEFAULT_USER)\
220 .filter(User.username == User.DEFAULT_USER)\
221 .one()
221 .one()
222
222
223 def_user.name = 'Anonymous'
223 def_user.name = 'Anonymous'
224 def_user.lastname = 'User'
224 def_user.lastname = 'User'
225 def_user.email = User.DEFAULT_USER_EMAIL
225 def_user.email = User.DEFAULT_USER_EMAIL
226
226
227 try:
227 try:
228 self.sa.add(def_user)
228 self.sa.add(def_user)
229 self.sa.commit()
229 self.sa.commit()
230 except Exception:
230 except Exception:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def fix_settings(self):
234 def fix_settings(self):
235 """
235 """
236 Fixes rhodecode settings and adds ga_code key for google analytics
236 Fixes rhodecode settings and adds ga_code key for google analytics
237 """
237 """
238
238
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240
240
241 try:
241 try:
242 self.sa.add(hgsettings3)
242 self.sa.add(hgsettings3)
243 self.sa.commit()
243 self.sa.commit()
244 except Exception:
244 except Exception:
245 self.sa.rollback()
245 self.sa.rollback()
246 raise
246 raise
247
247
248 def create_admin_and_prompt(self):
248 def create_admin_and_prompt(self):
249
249
250 # defaults
250 # defaults
251 defaults = self.cli_args
251 defaults = self.cli_args
252 username = defaults.get('username')
252 username = defaults.get('username')
253 password = defaults.get('password')
253 password = defaults.get('password')
254 email = defaults.get('email')
254 email = defaults.get('email')
255
255
256 if username is None:
256 if username is None:
257 username = raw_input('Specify admin username:')
257 username = raw_input('Specify admin username:')
258 if password is None:
258 if password is None:
259 password = self._get_admin_password()
259 password = self._get_admin_password()
260 if not password:
260 if not password:
261 # second try
261 # second try
262 password = self._get_admin_password()
262 password = self._get_admin_password()
263 if not password:
263 if not password:
264 sys.exit()
264 sys.exit()
265 if email is None:
265 if email is None:
266 email = raw_input('Specify admin email:')
266 email = raw_input('Specify admin email:')
267 api_key = self.cli_args.get('api_key')
267 api_key = self.cli_args.get('api_key')
268 self.create_user(username, password, email, True,
268 self.create_user(username, password, email, True,
269 strict_creation_check=False,
269 strict_creation_check=False,
270 api_key=api_key)
270 api_key=api_key)
271
271
272 def _get_admin_password(self):
272 def _get_admin_password(self):
273 password = getpass.getpass('Specify admin password '
273 password = getpass.getpass('Specify admin password '
274 '(min 6 chars):')
274 '(min 6 chars):')
275 confirm = getpass.getpass('Confirm password:')
275 confirm = getpass.getpass('Confirm password:')
276
276
277 if password != confirm:
277 if password != confirm:
278 log.error('passwords mismatch')
278 log.error('passwords mismatch')
279 return False
279 return False
280 if len(password) < 6:
280 if len(password) < 6:
281 log.error('password is too short - use at least 6 characters')
281 log.error('password is too short - use at least 6 characters')
282 return False
282 return False
283
283
284 return password
284 return password
285
285
286 def create_test_admin_and_users(self):
286 def create_test_admin_and_users(self):
287 log.info('creating admin and regular test users')
287 log.info('creating admin and regular test users')
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True)
295 TEST_USER_ADMIN_EMAIL, True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False)
298 TEST_USER_REGULAR_EMAIL, False)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False)
301 TEST_USER_REGULAR2_EMAIL, False)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
305 Creates ui settings, fills out hooks
305 Creates ui settings, fills out hooks
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309
309
310 # Build HOOKS
310 # Build HOOKS
311 hooks = [
311 hooks = [
312 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
312 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
313
313
314 # HG
314 # HG
315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
318 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
318 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
319 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
319
320
320 ]
321 ]
321
322
322 for key, value in hooks:
323 for key, value in hooks:
323 hook_obj = settings_model.get_ui_by_key(key)
324 hook_obj = settings_model.get_ui_by_key(key)
324 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
325 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
325 hooks2.ui_section = 'hooks'
326 hooks2.ui_section = 'hooks'
326 hooks2.ui_key = key
327 hooks2.ui_key = key
327 hooks2.ui_value = value
328 hooks2.ui_value = value
328 self.sa.add(hooks2)
329 self.sa.add(hooks2)
329
330
330 # enable largefiles
331 # enable largefiles
331 largefiles = RhodeCodeUi()
332 largefiles = RhodeCodeUi()
332 largefiles.ui_section = 'extensions'
333 largefiles.ui_section = 'extensions'
333 largefiles.ui_key = 'largefiles'
334 largefiles.ui_key = 'largefiles'
334 largefiles.ui_value = ''
335 largefiles.ui_value = ''
335 self.sa.add(largefiles)
336 self.sa.add(largefiles)
336
337
337 # set default largefiles cache dir, defaults to
338 # set default largefiles cache dir, defaults to
338 # /repo location/.cache/largefiles
339 # /repo location/.cache/largefiles
339 largefiles = RhodeCodeUi()
340 largefiles = RhodeCodeUi()
340 largefiles.ui_section = 'largefiles'
341 largefiles.ui_section = 'largefiles'
341 largefiles.ui_key = 'usercache'
342 largefiles.ui_key = 'usercache'
342 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
343 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
343 'largefiles')
344 'largefiles')
344 self.sa.add(largefiles)
345 self.sa.add(largefiles)
345
346
346 # enable hgsubversion disabled by default
347 # enable hgsubversion disabled by default
347 hgsubversion = RhodeCodeUi()
348 hgsubversion = RhodeCodeUi()
348 hgsubversion.ui_section = 'extensions'
349 hgsubversion.ui_section = 'extensions'
349 hgsubversion.ui_key = 'hgsubversion'
350 hgsubversion.ui_key = 'hgsubversion'
350 hgsubversion.ui_value = ''
351 hgsubversion.ui_value = ''
351 hgsubversion.ui_active = False
352 hgsubversion.ui_active = False
352 self.sa.add(hgsubversion)
353 self.sa.add(hgsubversion)
353
354
354 # enable hggit disabled by default
355 # enable hggit disabled by default
355 hggit = RhodeCodeUi()
356 hggit = RhodeCodeUi()
356 hggit.ui_section = 'extensions'
357 hggit.ui_section = 'extensions'
357 hggit.ui_key = 'hggit'
358 hggit.ui_key = 'hggit'
358 hggit.ui_value = ''
359 hggit.ui_value = ''
359 hggit.ui_active = False
360 hggit.ui_active = False
360 self.sa.add(hggit)
361 self.sa.add(hggit)
361
362
362 # set svn branch defaults
363 # set svn branch defaults
363 branches = ["/branches/*", "/trunk"]
364 branches = ["/branches/*", "/trunk"]
364 tags = ["/tags/*"]
365 tags = ["/tags/*"]
365
366
366 for branch in branches:
367 for branch in branches:
367 settings_model.create_ui_section_value(
368 settings_model.create_ui_section_value(
368 RhodeCodeUi.SVN_BRANCH_ID, branch)
369 RhodeCodeUi.SVN_BRANCH_ID, branch)
369
370
370 for tag in tags:
371 for tag in tags:
371 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
372 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
372
373
373 def create_auth_plugin_options(self, skip_existing=False):
374 def create_auth_plugin_options(self, skip_existing=False):
374 """
375 """
375 Create default auth plugin settings, and make it active
376 Create default auth plugin settings, and make it active
376
377
377 :param skip_existing:
378 :param skip_existing:
378 """
379 """
379
380
380 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
381 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
381 ('auth_rhodecode_enabled', 'True', 'bool')]:
382 ('auth_rhodecode_enabled', 'True', 'bool')]:
382 if (skip_existing and
383 if (skip_existing and
383 SettingsModel().get_setting_by_name(k) is not None):
384 SettingsModel().get_setting_by_name(k) is not None):
384 log.debug('Skipping option %s' % k)
385 log.debug('Skipping option %s' % k)
385 continue
386 continue
386 setting = RhodeCodeSetting(k, v, t)
387 setting = RhodeCodeSetting(k, v, t)
387 self.sa.add(setting)
388 self.sa.add(setting)
388
389
389 def create_default_options(self, skip_existing=False):
390 def create_default_options(self, skip_existing=False):
390 """Creates default settings"""
391 """Creates default settings"""
391
392
392 for k, v, t in [
393 for k, v, t in [
393 ('default_repo_enable_locking', False, 'bool'),
394 ('default_repo_enable_locking', False, 'bool'),
394 ('default_repo_enable_downloads', False, 'bool'),
395 ('default_repo_enable_downloads', False, 'bool'),
395 ('default_repo_enable_statistics', False, 'bool'),
396 ('default_repo_enable_statistics', False, 'bool'),
396 ('default_repo_private', False, 'bool'),
397 ('default_repo_private', False, 'bool'),
397 ('default_repo_type', 'hg', 'unicode')]:
398 ('default_repo_type', 'hg', 'unicode')]:
398
399
399 if (skip_existing and
400 if (skip_existing and
400 SettingsModel().get_setting_by_name(k) is not None):
401 SettingsModel().get_setting_by_name(k) is not None):
401 log.debug('Skipping option %s' % k)
402 log.debug('Skipping option %s' % k)
402 continue
403 continue
403 setting = RhodeCodeSetting(k, v, t)
404 setting = RhodeCodeSetting(k, v, t)
404 self.sa.add(setting)
405 self.sa.add(setting)
405
406
406 def fixup_groups(self):
407 def fixup_groups(self):
407 def_usr = User.get_default_user()
408 def_usr = User.get_default_user()
408 for g in RepoGroup.query().all():
409 for g in RepoGroup.query().all():
409 g.group_name = g.get_new_name(g.name)
410 g.group_name = g.get_new_name(g.name)
410 self.sa.add(g)
411 self.sa.add(g)
411 # get default perm
412 # get default perm
412 default = UserRepoGroupToPerm.query()\
413 default = UserRepoGroupToPerm.query()\
413 .filter(UserRepoGroupToPerm.group == g)\
414 .filter(UserRepoGroupToPerm.group == g)\
414 .filter(UserRepoGroupToPerm.user == def_usr)\
415 .filter(UserRepoGroupToPerm.user == def_usr)\
415 .scalar()
416 .scalar()
416
417
417 if default is None:
418 if default is None:
418 log.debug('missing default permission for group %s adding' % g)
419 log.debug('missing default permission for group %s adding' % g)
419 perm_obj = RepoGroupModel()._create_default_perms(g)
420 perm_obj = RepoGroupModel()._create_default_perms(g)
420 self.sa.add(perm_obj)
421 self.sa.add(perm_obj)
421
422
422 def reset_permissions(self, username):
423 def reset_permissions(self, username):
423 """
424 """
424 Resets permissions to default state, useful when old systems had
425 Resets permissions to default state, useful when old systems had
425 bad permissions, we must clean them up
426 bad permissions, we must clean them up
426
427
427 :param username:
428 :param username:
428 """
429 """
429 default_user = User.get_by_username(username)
430 default_user = User.get_by_username(username)
430 if not default_user:
431 if not default_user:
431 return
432 return
432
433
433 u2p = UserToPerm.query()\
434 u2p = UserToPerm.query()\
434 .filter(UserToPerm.user == default_user).all()
435 .filter(UserToPerm.user == default_user).all()
435 fixed = False
436 fixed = False
436 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
437 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
437 for p in u2p:
438 for p in u2p:
438 Session().delete(p)
439 Session().delete(p)
439 fixed = True
440 fixed = True
440 self.populate_default_permissions()
441 self.populate_default_permissions()
441 return fixed
442 return fixed
442
443
443 def update_repo_info(self):
444 def update_repo_info(self):
444 RepoModel.update_repoinfo()
445 RepoModel.update_repoinfo()
445
446
446 def config_prompt(self, test_repo_path='', retries=3):
447 def config_prompt(self, test_repo_path='', retries=3):
447 defaults = self.cli_args
448 defaults = self.cli_args
448 _path = defaults.get('repos_location')
449 _path = defaults.get('repos_location')
449 if retries == 3:
450 if retries == 3:
450 log.info('Setting up repositories config')
451 log.info('Setting up repositories config')
451
452
452 if _path is not None:
453 if _path is not None:
453 path = _path
454 path = _path
454 elif not self.tests and not test_repo_path:
455 elif not self.tests and not test_repo_path:
455 path = raw_input(
456 path = raw_input(
456 'Enter a valid absolute path to store repositories. '
457 'Enter a valid absolute path to store repositories. '
457 'All repositories in that path will be added automatically:'
458 'All repositories in that path will be added automatically:'
458 )
459 )
459 else:
460 else:
460 path = test_repo_path
461 path = test_repo_path
461 path_ok = True
462 path_ok = True
462
463
463 # check proper dir
464 # check proper dir
464 if not os.path.isdir(path):
465 if not os.path.isdir(path):
465 path_ok = False
466 path_ok = False
466 log.error('Given path %s is not a valid directory' % (path,))
467 log.error('Given path %s is not a valid directory' % (path,))
467
468
468 elif not os.path.isabs(path):
469 elif not os.path.isabs(path):
469 path_ok = False
470 path_ok = False
470 log.error('Given path %s is not an absolute path' % (path,))
471 log.error('Given path %s is not an absolute path' % (path,))
471
472
472 # check if path is at least readable.
473 # check if path is at least readable.
473 if not os.access(path, os.R_OK):
474 if not os.access(path, os.R_OK):
474 path_ok = False
475 path_ok = False
475 log.error('Given path %s is not readable' % (path,))
476 log.error('Given path %s is not readable' % (path,))
476
477
477 # check write access, warn user about non writeable paths
478 # check write access, warn user about non writeable paths
478 elif not os.access(path, os.W_OK) and path_ok:
479 elif not os.access(path, os.W_OK) and path_ok:
479 log.warning('No write permission to given path %s' % (path,))
480 log.warning('No write permission to given path %s' % (path,))
480
481
481 q = ('Given path %s is not writeable, do you want to '
482 q = ('Given path %s is not writeable, do you want to '
482 'continue with read only mode ? [y/n]' % (path,))
483 'continue with read only mode ? [y/n]' % (path,))
483 if not self.ask_ok(q):
484 if not self.ask_ok(q):
484 log.error('Canceled by user')
485 log.error('Canceled by user')
485 sys.exit(-1)
486 sys.exit(-1)
486
487
487 if retries == 0:
488 if retries == 0:
488 sys.exit('max retries reached')
489 sys.exit('max retries reached')
489 if not path_ok:
490 if not path_ok:
490 retries -= 1
491 retries -= 1
491 return self.config_prompt(test_repo_path, retries)
492 return self.config_prompt(test_repo_path, retries)
492
493
493 real_path = os.path.normpath(os.path.realpath(path))
494 real_path = os.path.normpath(os.path.realpath(path))
494
495
495 if real_path != os.path.normpath(path):
496 if real_path != os.path.normpath(path):
496 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
497 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
497 'given path as %s ? [y/n]') % (real_path,)
498 'given path as %s ? [y/n]') % (real_path,)
498 if not self.ask_ok(q):
499 if not self.ask_ok(q):
499 log.error('Canceled by user')
500 log.error('Canceled by user')
500 sys.exit(-1)
501 sys.exit(-1)
501
502
502 return real_path
503 return real_path
503
504
504 def create_settings(self, path):
505 def create_settings(self, path):
505
506
506 self.create_ui_settings(path)
507 self.create_ui_settings(path)
507
508
508 ui_config = [
509 ui_config = [
509 ('web', 'push_ssl', 'false'),
510 ('web', 'push_ssl', 'false'),
510 ('web', 'allow_archive', 'gz zip bz2'),
511 ('web', 'allow_archive', 'gz zip bz2'),
511 ('web', 'allow_push', '*'),
512 ('web', 'allow_push', '*'),
512 ('web', 'baseurl', '/'),
513 ('web', 'baseurl', '/'),
513 ('paths', '/', path),
514 ('paths', '/', path),
514 ('phases', 'publish', 'true')
515 ('phases', 'publish', 'true')
515 ]
516 ]
516 for section, key, value in ui_config:
517 for section, key, value in ui_config:
517 ui_conf = RhodeCodeUi()
518 ui_conf = RhodeCodeUi()
518 setattr(ui_conf, 'ui_section', section)
519 setattr(ui_conf, 'ui_section', section)
519 setattr(ui_conf, 'ui_key', key)
520 setattr(ui_conf, 'ui_key', key)
520 setattr(ui_conf, 'ui_value', value)
521 setattr(ui_conf, 'ui_value', value)
521 self.sa.add(ui_conf)
522 self.sa.add(ui_conf)
522
523
523 # rhodecode app settings
524 # rhodecode app settings
524 settings = [
525 settings = [
525 ('realm', 'RhodeCode', 'unicode'),
526 ('realm', 'RhodeCode', 'unicode'),
526 ('title', '', 'unicode'),
527 ('title', '', 'unicode'),
527 ('pre_code', '', 'unicode'),
528 ('pre_code', '', 'unicode'),
528 ('post_code', '', 'unicode'),
529 ('post_code', '', 'unicode'),
529 ('show_public_icon', True, 'bool'),
530 ('show_public_icon', True, 'bool'),
530 ('show_private_icon', True, 'bool'),
531 ('show_private_icon', True, 'bool'),
531 ('stylify_metatags', False, 'bool'),
532 ('stylify_metatags', False, 'bool'),
532 ('dashboard_items', 100, 'int'),
533 ('dashboard_items', 100, 'int'),
533 ('admin_grid_items', 25, 'int'),
534 ('admin_grid_items', 25, 'int'),
534 ('show_version', True, 'bool'),
535 ('show_version', True, 'bool'),
535 ('use_gravatar', False, 'bool'),
536 ('use_gravatar', False, 'bool'),
536 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
537 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
537 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
538 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
538 ('support_url', '', 'unicode'),
539 ('support_url', '', 'unicode'),
539 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
540 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
540 ('show_revision_number', True, 'bool'),
541 ('show_revision_number', True, 'bool'),
541 ('show_sha_length', 12, 'int'),
542 ('show_sha_length', 12, 'int'),
542 ]
543 ]
543
544
544 for key, val, type_ in settings:
545 for key, val, type_ in settings:
545 sett = RhodeCodeSetting(key, val, type_)
546 sett = RhodeCodeSetting(key, val, type_)
546 self.sa.add(sett)
547 self.sa.add(sett)
547
548
548 self.create_auth_plugin_options()
549 self.create_auth_plugin_options()
549 self.create_default_options()
550 self.create_default_options()
550
551
551 log.info('created ui config')
552 log.info('created ui config')
552
553
553 def create_user(self, username, password, email='', admin=False,
554 def create_user(self, username, password, email='', admin=False,
554 strict_creation_check=True, api_key=None):
555 strict_creation_check=True, api_key=None):
555 log.info('creating user %s' % username)
556 log.info('creating user %s' % username)
556 user = UserModel().create_or_update(
557 user = UserModel().create_or_update(
557 username, password, email, firstname='RhodeCode', lastname='Admin',
558 username, password, email, firstname='RhodeCode', lastname='Admin',
558 active=True, admin=admin, extern_type="rhodecode",
559 active=True, admin=admin, extern_type="rhodecode",
559 strict_creation_check=strict_creation_check)
560 strict_creation_check=strict_creation_check)
560
561
561 if api_key:
562 if api_key:
562 log.info('setting a provided api key for the user %s', username)
563 log.info('setting a provided api key for the user %s', username)
563 user.api_key = api_key
564 user.api_key = api_key
564
565
565 def create_default_user(self):
566 def create_default_user(self):
566 log.info('creating default user')
567 log.info('creating default user')
567 # create default user for handling default permissions.
568 # create default user for handling default permissions.
568 user = UserModel().create_or_update(username=User.DEFAULT_USER,
569 user = UserModel().create_or_update(username=User.DEFAULT_USER,
569 password=str(uuid.uuid1())[:20],
570 password=str(uuid.uuid1())[:20],
570 email=User.DEFAULT_USER_EMAIL,
571 email=User.DEFAULT_USER_EMAIL,
571 firstname='Anonymous',
572 firstname='Anonymous',
572 lastname='User',
573 lastname='User',
573 strict_creation_check=False)
574 strict_creation_check=False)
574 # based on configuration options activate/deactive this user which
575 # based on configuration options activate/deactive this user which
575 # controlls anonymous access
576 # controlls anonymous access
576 if self.cli_args.get('public_access') is False:
577 if self.cli_args.get('public_access') is False:
577 log.info('Public access disabled')
578 log.info('Public access disabled')
578 user.active = False
579 user.active = False
579 Session().add(user)
580 Session().add(user)
580 Session().commit()
581 Session().commit()
581
582
582 def create_permissions(self):
583 def create_permissions(self):
583 """
584 """
584 Creates all permissions defined in the system
585 Creates all permissions defined in the system
585 """
586 """
586 # module.(access|create|change|delete)_[name]
587 # module.(access|create|change|delete)_[name]
587 # module.(none|read|write|admin)
588 # module.(none|read|write|admin)
588 log.info('creating permissions')
589 log.info('creating permissions')
589 PermissionModel(self.sa).create_permissions()
590 PermissionModel(self.sa).create_permissions()
590
591
591 def populate_default_permissions(self):
592 def populate_default_permissions(self):
592 """
593 """
593 Populate default permissions. It will create only the default
594 Populate default permissions. It will create only the default
594 permissions that are missing, and not alter already defined ones
595 permissions that are missing, and not alter already defined ones
595 """
596 """
596 log.info('creating default user permissions')
597 log.info('creating default user permissions')
597 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
598 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,1020 +1,1021 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45
45
46 from rhodecode.lib.fakemod import create_module
46 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.vcs.backends.base import Config
47 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.utils2 import (
50 from rhodecode.lib.utils2 import (
51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import (
53 from rhodecode.model.db import (
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
61
62 # String which contains characters that are not allowed in slug names for
62 # String which contains characters that are not allowed in slug names for
63 # repositories or repository groups. It is properly escaped to use it in
63 # repositories or repository groups. It is properly escaped to use it in
64 # regular expressions.
64 # regular expressions.
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66
66
67 # Regex that matches forbidden characters in repo/group slugs.
67 # Regex that matches forbidden characters in repo/group slugs.
68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69
69
70 # Regex that matches allowed characters in repo/group slugs.
70 # Regex that matches allowed characters in repo/group slugs.
71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72
72
73 # Regex that matches whole repo/group slugs.
73 # Regex that matches whole repo/group slugs.
74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75
75
76 _license_cache = None
76 _license_cache = None
77
77
78
78
79 def repo_name_slug(value):
79 def repo_name_slug(value):
80 """
80 """
81 Return slug of name of repository
81 Return slug of name of repository
82 This function is called on each creation/modification
82 This function is called on each creation/modification
83 of repository to prevent bad names in repo
83 of repository to prevent bad names in repo
84 """
84 """
85 replacement_char = '-'
85 replacement_char = '-'
86
86
87 slug = remove_formatting(value)
87 slug = remove_formatting(value)
88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = re.sub('[\s]+', '-', slug)
89 slug = re.sub('[\s]+', '-', slug)
90 slug = collapse(slug, replacement_char)
90 slug = collapse(slug, replacement_char)
91 return slug
91 return slug
92
92
93
93
94 #==============================================================================
94 #==============================================================================
95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 #==============================================================================
96 #==============================================================================
97 def get_repo_slug(request):
97 def get_repo_slug(request):
98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
99 if _repo:
99 if _repo:
100 _repo = _repo.rstrip('/')
100 _repo = _repo.rstrip('/')
101 return _repo
101 return _repo
102
102
103
103
104 def get_repo_group_slug(request):
104 def get_repo_group_slug(request):
105 _group = request.environ['pylons.routes_dict'].get('group_name')
105 _group = request.environ['pylons.routes_dict'].get('group_name')
106 if _group:
106 if _group:
107 _group = _group.rstrip('/')
107 _group = _group.rstrip('/')
108 return _group
108 return _group
109
109
110
110
111 def get_user_group_slug(request):
111 def get_user_group_slug(request):
112 _group = request.environ['pylons.routes_dict'].get('user_group_id')
112 _group = request.environ['pylons.routes_dict'].get('user_group_id')
113 try:
113 try:
114 _group = UserGroup.get(_group)
114 _group = UserGroup.get(_group)
115 if _group:
115 if _group:
116 _group = _group.users_group_name
116 _group = _group.users_group_name
117 except Exception:
117 except Exception:
118 log.debug(traceback.format_exc())
118 log.debug(traceback.format_exc())
119 #catch all failures here
119 #catch all failures here
120 pass
120 pass
121
121
122 return _group
122 return _group
123
123
124
124
125 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
125 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
126 """
126 """
127 Action logger for various actions made by users
127 Action logger for various actions made by users
128
128
129 :param user: user that made this action, can be a unique username string or
129 :param user: user that made this action, can be a unique username string or
130 object containing user_id attribute
130 object containing user_id attribute
131 :param action: action to log, should be on of predefined unique actions for
131 :param action: action to log, should be on of predefined unique actions for
132 easy translations
132 easy translations
133 :param repo: string name of repository or object containing repo_id,
133 :param repo: string name of repository or object containing repo_id,
134 that action was made on
134 that action was made on
135 :param ipaddr: optional ip address from what the action was made
135 :param ipaddr: optional ip address from what the action was made
136 :param sa: optional sqlalchemy session
136 :param sa: optional sqlalchemy session
137
137
138 """
138 """
139
139
140 if not sa:
140 if not sa:
141 sa = meta.Session()
141 sa = meta.Session()
142 # if we don't get explicit IP address try to get one from registered user
142 # if we don't get explicit IP address try to get one from registered user
143 # in tmpl context var
143 # in tmpl context var
144 if not ipaddr:
144 if not ipaddr:
145 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
145 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
146
146
147 try:
147 try:
148 if getattr(user, 'user_id', None):
148 if getattr(user, 'user_id', None):
149 user_obj = User.get(user.user_id)
149 user_obj = User.get(user.user_id)
150 elif isinstance(user, basestring):
150 elif isinstance(user, basestring):
151 user_obj = User.get_by_username(user)
151 user_obj = User.get_by_username(user)
152 else:
152 else:
153 raise Exception('You have to provide a user object or a username')
153 raise Exception('You have to provide a user object or a username')
154
154
155 if getattr(repo, 'repo_id', None):
155 if getattr(repo, 'repo_id', None):
156 repo_obj = Repository.get(repo.repo_id)
156 repo_obj = Repository.get(repo.repo_id)
157 repo_name = repo_obj.repo_name
157 repo_name = repo_obj.repo_name
158 elif isinstance(repo, basestring):
158 elif isinstance(repo, basestring):
159 repo_name = repo.lstrip('/')
159 repo_name = repo.lstrip('/')
160 repo_obj = Repository.get_by_repo_name(repo_name)
160 repo_obj = Repository.get_by_repo_name(repo_name)
161 else:
161 else:
162 repo_obj = None
162 repo_obj = None
163 repo_name = ''
163 repo_name = ''
164
164
165 user_log = UserLog()
165 user_log = UserLog()
166 user_log.user_id = user_obj.user_id
166 user_log.user_id = user_obj.user_id
167 user_log.username = user_obj.username
167 user_log.username = user_obj.username
168 action = safe_unicode(action)
168 action = safe_unicode(action)
169 user_log.action = action[:1200000]
169 user_log.action = action[:1200000]
170
170
171 user_log.repository = repo_obj
171 user_log.repository = repo_obj
172 user_log.repository_name = repo_name
172 user_log.repository_name = repo_name
173
173
174 user_log.action_date = datetime.datetime.now()
174 user_log.action_date = datetime.datetime.now()
175 user_log.user_ip = ipaddr
175 user_log.user_ip = ipaddr
176 sa.add(user_log)
176 sa.add(user_log)
177
177
178 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
178 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
179 action, safe_unicode(repo), user_obj, ipaddr)
179 action, safe_unicode(repo), user_obj, ipaddr)
180 if commit:
180 if commit:
181 sa.commit()
181 sa.commit()
182 except Exception:
182 except Exception:
183 log.error(traceback.format_exc())
183 log.error(traceback.format_exc())
184 raise
184 raise
185
185
186
186
187 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
187 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
188 """
188 """
189 Scans given path for repos and return (name,(type,path)) tuple
189 Scans given path for repos and return (name,(type,path)) tuple
190
190
191 :param path: path to scan for repositories
191 :param path: path to scan for repositories
192 :param recursive: recursive search and return names with subdirs in front
192 :param recursive: recursive search and return names with subdirs in front
193 """
193 """
194
194
195 # remove ending slash for better results
195 # remove ending slash for better results
196 path = path.rstrip(os.sep)
196 path = path.rstrip(os.sep)
197 log.debug('now scanning in %s location recursive:%s...', path, recursive)
197 log.debug('now scanning in %s location recursive:%s...', path, recursive)
198
198
199 def _get_repos(p):
199 def _get_repos(p):
200 dirpaths = _get_dirpaths(p)
200 dirpaths = _get_dirpaths(p)
201 if not _is_dir_writable(p):
201 if not _is_dir_writable(p):
202 log.warning('repo path without write access: %s', p)
202 log.warning('repo path without write access: %s', p)
203
203
204 for dirpath in dirpaths:
204 for dirpath in dirpaths:
205 if os.path.isfile(os.path.join(p, dirpath)):
205 if os.path.isfile(os.path.join(p, dirpath)):
206 continue
206 continue
207 cur_path = os.path.join(p, dirpath)
207 cur_path = os.path.join(p, dirpath)
208
208
209 # skip removed repos
209 # skip removed repos
210 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
210 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
211 continue
211 continue
212
212
213 #skip .<somethin> dirs
213 #skip .<somethin> dirs
214 if dirpath.startswith('.'):
214 if dirpath.startswith('.'):
215 continue
215 continue
216
216
217 try:
217 try:
218 scm_info = get_scm(cur_path)
218 scm_info = get_scm(cur_path)
219 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
219 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
220 except VCSError:
220 except VCSError:
221 if not recursive:
221 if not recursive:
222 continue
222 continue
223 #check if this dir containts other repos for recursive scan
223 #check if this dir containts other repos for recursive scan
224 rec_path = os.path.join(p, dirpath)
224 rec_path = os.path.join(p, dirpath)
225 if os.path.isdir(rec_path):
225 if os.path.isdir(rec_path):
226 for inner_scm in _get_repos(rec_path):
226 for inner_scm in _get_repos(rec_path):
227 yield inner_scm
227 yield inner_scm
228
228
229 return _get_repos(path)
229 return _get_repos(path)
230
230
231
231
232 def _get_dirpaths(p):
232 def _get_dirpaths(p):
233 try:
233 try:
234 # OS-independable way of checking if we have at least read-only
234 # OS-independable way of checking if we have at least read-only
235 # access or not.
235 # access or not.
236 dirpaths = os.listdir(p)
236 dirpaths = os.listdir(p)
237 except OSError:
237 except OSError:
238 log.warning('ignoring repo path without read access: %s', p)
238 log.warning('ignoring repo path without read access: %s', p)
239 return []
239 return []
240
240
241 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
241 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
242 # decode paths and suddenly returns unicode objects itself. The items it
242 # decode paths and suddenly returns unicode objects itself. The items it
243 # cannot decode are returned as strings and cause issues.
243 # cannot decode are returned as strings and cause issues.
244 #
244 #
245 # Those paths are ignored here until a solid solution for path handling has
245 # Those paths are ignored here until a solid solution for path handling has
246 # been built.
246 # been built.
247 expected_type = type(p)
247 expected_type = type(p)
248
248
249 def _has_correct_type(item):
249 def _has_correct_type(item):
250 if type(item) is not expected_type:
250 if type(item) is not expected_type:
251 log.error(
251 log.error(
252 u"Ignoring path %s since it cannot be decoded into unicode.",
252 u"Ignoring path %s since it cannot be decoded into unicode.",
253 # Using "repr" to make sure that we see the byte value in case
253 # Using "repr" to make sure that we see the byte value in case
254 # of support.
254 # of support.
255 repr(item))
255 repr(item))
256 return False
256 return False
257 return True
257 return True
258
258
259 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
259 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
260
260
261 return dirpaths
261 return dirpaths
262
262
263
263
264 def _is_dir_writable(path):
264 def _is_dir_writable(path):
265 """
265 """
266 Probe if `path` is writable.
266 Probe if `path` is writable.
267
267
268 Due to trouble on Cygwin / Windows, this is actually probing if it is
268 Due to trouble on Cygwin / Windows, this is actually probing if it is
269 possible to create a file inside of `path`, stat does not produce reliable
269 possible to create a file inside of `path`, stat does not produce reliable
270 results in this case.
270 results in this case.
271 """
271 """
272 try:
272 try:
273 with tempfile.TemporaryFile(dir=path):
273 with tempfile.TemporaryFile(dir=path):
274 pass
274 pass
275 except OSError:
275 except OSError:
276 return False
276 return False
277 return True
277 return True
278
278
279
279
280 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
280 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
281 """
281 """
282 Returns True if given path is a valid repository False otherwise.
282 Returns True if given path is a valid repository False otherwise.
283 If expect_scm param is given also, compare if given scm is the same
283 If expect_scm param is given also, compare if given scm is the same
284 as expected from scm parameter. If explicit_scm is given don't try to
284 as expected from scm parameter. If explicit_scm is given don't try to
285 detect the scm, just use the given one to check if repo is valid
285 detect the scm, just use the given one to check if repo is valid
286
286
287 :param repo_name:
287 :param repo_name:
288 :param base_path:
288 :param base_path:
289 :param expect_scm:
289 :param expect_scm:
290 :param explicit_scm:
290 :param explicit_scm:
291
291
292 :return True: if given path is a valid repository
292 :return True: if given path is a valid repository
293 """
293 """
294 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
294 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
295 log.debug('Checking if `%s` is a valid path for repository. '
295 log.debug('Checking if `%s` is a valid path for repository. '
296 'Explicit type: %s', repo_name, explicit_scm)
296 'Explicit type: %s', repo_name, explicit_scm)
297
297
298 try:
298 try:
299 if explicit_scm:
299 if explicit_scm:
300 detected_scms = [get_scm_backend(explicit_scm)]
300 detected_scms = [get_scm_backend(explicit_scm)]
301 else:
301 else:
302 detected_scms = get_scm(full_path)
302 detected_scms = get_scm(full_path)
303
303
304 if expect_scm:
304 if expect_scm:
305 return detected_scms[0] == expect_scm
305 return detected_scms[0] == expect_scm
306 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
306 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
307 return True
307 return True
308 except VCSError:
308 except VCSError:
309 log.debug('path: %s is not a valid repo !', full_path)
309 log.debug('path: %s is not a valid repo !', full_path)
310 return False
310 return False
311
311
312
312
313 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
313 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
314 """
314 """
315 Returns True if given path is a repository group, False otherwise
315 Returns True if given path is a repository group, False otherwise
316
316
317 :param repo_name:
317 :param repo_name:
318 :param base_path:
318 :param base_path:
319 """
319 """
320 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
320 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
321 log.debug('Checking if `%s` is a valid path for repository group',
321 log.debug('Checking if `%s` is a valid path for repository group',
322 repo_group_name)
322 repo_group_name)
323
323
324 # check if it's not a repo
324 # check if it's not a repo
325 if is_valid_repo(repo_group_name, base_path):
325 if is_valid_repo(repo_group_name, base_path):
326 log.debug('Repo called %s exist, it is not a valid '
326 log.debug('Repo called %s exist, it is not a valid '
327 'repo group' % repo_group_name)
327 'repo group' % repo_group_name)
328 return False
328 return False
329
329
330 try:
330 try:
331 # we need to check bare git repos at higher level
331 # we need to check bare git repos at higher level
332 # since we might match branches/hooks/info/objects or possible
332 # since we might match branches/hooks/info/objects or possible
333 # other things inside bare git repo
333 # other things inside bare git repo
334 scm_ = get_scm(os.path.dirname(full_path))
334 scm_ = get_scm(os.path.dirname(full_path))
335 log.debug('path: %s is a vcs object:%s, not valid '
335 log.debug('path: %s is a vcs object:%s, not valid '
336 'repo group' % (full_path, scm_))
336 'repo group' % (full_path, scm_))
337 return False
337 return False
338 except VCSError:
338 except VCSError:
339 pass
339 pass
340
340
341 # check if it's a valid path
341 # check if it's a valid path
342 if skip_path_check or os.path.isdir(full_path):
342 if skip_path_check or os.path.isdir(full_path):
343 log.debug('path: %s is a valid repo group !', full_path)
343 log.debug('path: %s is a valid repo group !', full_path)
344 return True
344 return True
345
345
346 log.debug('path: %s is not a valid repo group !', full_path)
346 log.debug('path: %s is not a valid repo group !', full_path)
347 return False
347 return False
348
348
349
349
350 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
350 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
351 while True:
351 while True:
352 ok = raw_input(prompt)
352 ok = raw_input(prompt)
353 if ok.lower() in ('y', 'ye', 'yes'):
353 if ok.lower() in ('y', 'ye', 'yes'):
354 return True
354 return True
355 if ok.lower() in ('n', 'no', 'nop', 'nope'):
355 if ok.lower() in ('n', 'no', 'nop', 'nope'):
356 return False
356 return False
357 retries = retries - 1
357 retries = retries - 1
358 if retries < 0:
358 if retries < 0:
359 raise IOError
359 raise IOError
360 print(complaint)
360 print(complaint)
361
361
362 # propagated from mercurial documentation
362 # propagated from mercurial documentation
363 ui_sections = [
363 ui_sections = [
364 'alias', 'auth',
364 'alias', 'auth',
365 'decode/encode', 'defaults',
365 'decode/encode', 'defaults',
366 'diff', 'email',
366 'diff', 'email',
367 'extensions', 'format',
367 'extensions', 'format',
368 'merge-patterns', 'merge-tools',
368 'merge-patterns', 'merge-tools',
369 'hooks', 'http_proxy',
369 'hooks', 'http_proxy',
370 'smtp', 'patch',
370 'smtp', 'patch',
371 'paths', 'profiling',
371 'paths', 'profiling',
372 'server', 'trusted',
372 'server', 'trusted',
373 'ui', 'web', ]
373 'ui', 'web', ]
374
374
375
375
376 def config_data_from_db(clear_session=True, repo=None):
376 def config_data_from_db(clear_session=True, repo=None):
377 """
377 """
378 Read the configuration data from the database and return configuration
378 Read the configuration data from the database and return configuration
379 tuples.
379 tuples.
380 """
380 """
381 from rhodecode.model.settings import VcsSettingsModel
381 from rhodecode.model.settings import VcsSettingsModel
382
382
383 config = []
383 config = []
384
384
385 sa = meta.Session()
385 sa = meta.Session()
386 settings_model = VcsSettingsModel(repo=repo, sa=sa)
386 settings_model = VcsSettingsModel(repo=repo, sa=sa)
387
387
388 ui_settings = settings_model.get_ui_settings()
388 ui_settings = settings_model.get_ui_settings()
389
389
390 for setting in ui_settings:
390 for setting in ui_settings:
391 if setting.active:
391 if setting.active:
392 log.debug(
392 log.debug(
393 'settings ui from db: [%s] %s=%s',
393 'settings ui from db: [%s] %s=%s',
394 setting.section, setting.key, setting.value)
394 setting.section, setting.key, setting.value)
395 config.append((
395 config.append((
396 safe_str(setting.section), safe_str(setting.key),
396 safe_str(setting.section), safe_str(setting.key),
397 safe_str(setting.value)))
397 safe_str(setting.value)))
398 if setting.key == 'push_ssl':
398 if setting.key == 'push_ssl':
399 # force set push_ssl requirement to False, rhodecode
399 # force set push_ssl requirement to False, rhodecode
400 # handles that
400 # handles that
401 config.append((
401 config.append((
402 safe_str(setting.section), safe_str(setting.key), False))
402 safe_str(setting.section), safe_str(setting.key), False))
403 if clear_session:
403 if clear_session:
404 meta.Session.remove()
404 meta.Session.remove()
405
405
406 # TODO: mikhail: probably it makes no sense to re-read hooks information.
406 # TODO: mikhail: probably it makes no sense to re-read hooks information.
407 # It's already there and activated/deactivated
407 # It's already there and activated/deactivated
408 skip_entries = []
408 skip_entries = []
409 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
409 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
410 if 'pull' not in enabled_hook_classes:
410 if 'pull' not in enabled_hook_classes:
411 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
411 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
412 if 'push' not in enabled_hook_classes:
412 if 'push' not in enabled_hook_classes:
413 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
413 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
414 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
414
415
415 config = [entry for entry in config if entry[:2] not in skip_entries]
416 config = [entry for entry in config if entry[:2] not in skip_entries]
416
417
417 return config
418 return config
418
419
419
420
420 def make_db_config(clear_session=True, repo=None):
421 def make_db_config(clear_session=True, repo=None):
421 """
422 """
422 Create a :class:`Config` instance based on the values in the database.
423 Create a :class:`Config` instance based on the values in the database.
423 """
424 """
424 config = Config()
425 config = Config()
425 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
426 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
426 for section, option, value in config_data:
427 for section, option, value in config_data:
427 config.set(section, option, value)
428 config.set(section, option, value)
428 return config
429 return config
429
430
430
431
431 def get_enabled_hook_classes(ui_settings):
432 def get_enabled_hook_classes(ui_settings):
432 """
433 """
433 Return the enabled hook classes.
434 Return the enabled hook classes.
434
435
435 :param ui_settings: List of ui_settings as returned
436 :param ui_settings: List of ui_settings as returned
436 by :meth:`VcsSettingsModel.get_ui_settings`
437 by :meth:`VcsSettingsModel.get_ui_settings`
437
438
438 :return: a list with the enabled hook classes. The order is not guaranteed.
439 :return: a list with the enabled hook classes. The order is not guaranteed.
439 :rtype: list
440 :rtype: list
440 """
441 """
441 enabled_hooks = []
442 enabled_hooks = []
442 active_hook_keys = [
443 active_hook_keys = [
443 key for section, key, value, active in ui_settings
444 key for section, key, value, active in ui_settings
444 if section == 'hooks' and active]
445 if section == 'hooks' and active]
445
446
446 hook_names = {
447 hook_names = {
447 RhodeCodeUi.HOOK_PUSH: 'push',
448 RhodeCodeUi.HOOK_PUSH: 'push',
448 RhodeCodeUi.HOOK_PULL: 'pull',
449 RhodeCodeUi.HOOK_PULL: 'pull',
449 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
450 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
450 }
451 }
451
452
452 for key in active_hook_keys:
453 for key in active_hook_keys:
453 hook = hook_names.get(key)
454 hook = hook_names.get(key)
454 if hook:
455 if hook:
455 enabled_hooks.append(hook)
456 enabled_hooks.append(hook)
456
457
457 return enabled_hooks
458 return enabled_hooks
458
459
459
460
460 def set_rhodecode_config(config):
461 def set_rhodecode_config(config):
461 """
462 """
462 Updates pylons config with new settings from database
463 Updates pylons config with new settings from database
463
464
464 :param config:
465 :param config:
465 """
466 """
466 from rhodecode.model.settings import SettingsModel
467 from rhodecode.model.settings import SettingsModel
467 app_settings = SettingsModel().get_all_settings()
468 app_settings = SettingsModel().get_all_settings()
468
469
469 for k, v in app_settings.items():
470 for k, v in app_settings.items():
470 config[k] = v
471 config[k] = v
471
472
472
473
473 def get_rhodecode_realm():
474 def get_rhodecode_realm():
474 """
475 """
475 Return the rhodecode realm from database.
476 Return the rhodecode realm from database.
476 """
477 """
477 from rhodecode.model.settings import SettingsModel
478 from rhodecode.model.settings import SettingsModel
478 realm = SettingsModel().get_setting_by_name('realm')
479 realm = SettingsModel().get_setting_by_name('realm')
479 return safe_str(realm.app_settings_value)
480 return safe_str(realm.app_settings_value)
480
481
481
482
482 def get_rhodecode_base_path():
483 def get_rhodecode_base_path():
483 """
484 """
484 Returns the base path. The base path is the filesystem path which points
485 Returns the base path. The base path is the filesystem path which points
485 to the repository store.
486 to the repository store.
486 """
487 """
487 from rhodecode.model.settings import SettingsModel
488 from rhodecode.model.settings import SettingsModel
488 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
489 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
489 return safe_str(paths_ui.ui_value)
490 return safe_str(paths_ui.ui_value)
490
491
491
492
492 def map_groups(path):
493 def map_groups(path):
493 """
494 """
494 Given a full path to a repository, create all nested groups that this
495 Given a full path to a repository, create all nested groups that this
495 repo is inside. This function creates parent-child relationships between
496 repo is inside. This function creates parent-child relationships between
496 groups and creates default perms for all new groups.
497 groups and creates default perms for all new groups.
497
498
498 :param paths: full path to repository
499 :param paths: full path to repository
499 """
500 """
500 from rhodecode.model.repo_group import RepoGroupModel
501 from rhodecode.model.repo_group import RepoGroupModel
501 sa = meta.Session()
502 sa = meta.Session()
502 groups = path.split(Repository.NAME_SEP)
503 groups = path.split(Repository.NAME_SEP)
503 parent = None
504 parent = None
504 group = None
505 group = None
505
506
506 # last element is repo in nested groups structure
507 # last element is repo in nested groups structure
507 groups = groups[:-1]
508 groups = groups[:-1]
508 rgm = RepoGroupModel(sa)
509 rgm = RepoGroupModel(sa)
509 owner = User.get_first_super_admin()
510 owner = User.get_first_super_admin()
510 for lvl, group_name in enumerate(groups):
511 for lvl, group_name in enumerate(groups):
511 group_name = '/'.join(groups[:lvl] + [group_name])
512 group_name = '/'.join(groups[:lvl] + [group_name])
512 group = RepoGroup.get_by_group_name(group_name)
513 group = RepoGroup.get_by_group_name(group_name)
513 desc = '%s group' % group_name
514 desc = '%s group' % group_name
514
515
515 # skip folders that are now removed repos
516 # skip folders that are now removed repos
516 if REMOVED_REPO_PAT.match(group_name):
517 if REMOVED_REPO_PAT.match(group_name):
517 break
518 break
518
519
519 if group is None:
520 if group is None:
520 log.debug('creating group level: %s group_name: %s',
521 log.debug('creating group level: %s group_name: %s',
521 lvl, group_name)
522 lvl, group_name)
522 group = RepoGroup(group_name, parent)
523 group = RepoGroup(group_name, parent)
523 group.group_description = desc
524 group.group_description = desc
524 group.user = owner
525 group.user = owner
525 sa.add(group)
526 sa.add(group)
526 perm_obj = rgm._create_default_perms(group)
527 perm_obj = rgm._create_default_perms(group)
527 sa.add(perm_obj)
528 sa.add(perm_obj)
528 sa.flush()
529 sa.flush()
529
530
530 parent = group
531 parent = group
531 return group
532 return group
532
533
533
534
534 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
535 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
535 """
536 """
536 maps all repos given in initial_repo_list, non existing repositories
537 maps all repos given in initial_repo_list, non existing repositories
537 are created, if remove_obsolete is True it also checks for db entries
538 are created, if remove_obsolete is True it also checks for db entries
538 that are not in initial_repo_list and removes them.
539 that are not in initial_repo_list and removes them.
539
540
540 :param initial_repo_list: list of repositories found by scanning methods
541 :param initial_repo_list: list of repositories found by scanning methods
541 :param remove_obsolete: check for obsolete entries in database
542 :param remove_obsolete: check for obsolete entries in database
542 """
543 """
543 from rhodecode.model.repo import RepoModel
544 from rhodecode.model.repo import RepoModel
544 from rhodecode.model.scm import ScmModel
545 from rhodecode.model.scm import ScmModel
545 from rhodecode.model.repo_group import RepoGroupModel
546 from rhodecode.model.repo_group import RepoGroupModel
546 from rhodecode.model.settings import SettingsModel
547 from rhodecode.model.settings import SettingsModel
547
548
548 sa = meta.Session()
549 sa = meta.Session()
549 repo_model = RepoModel()
550 repo_model = RepoModel()
550 user = User.get_first_super_admin()
551 user = User.get_first_super_admin()
551 added = []
552 added = []
552
553
553 # creation defaults
554 # creation defaults
554 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
555 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
555 enable_statistics = defs.get('repo_enable_statistics')
556 enable_statistics = defs.get('repo_enable_statistics')
556 enable_locking = defs.get('repo_enable_locking')
557 enable_locking = defs.get('repo_enable_locking')
557 enable_downloads = defs.get('repo_enable_downloads')
558 enable_downloads = defs.get('repo_enable_downloads')
558 private = defs.get('repo_private')
559 private = defs.get('repo_private')
559
560
560 for name, repo in initial_repo_list.items():
561 for name, repo in initial_repo_list.items():
561 group = map_groups(name)
562 group = map_groups(name)
562 unicode_name = safe_unicode(name)
563 unicode_name = safe_unicode(name)
563 db_repo = repo_model.get_by_repo_name(unicode_name)
564 db_repo = repo_model.get_by_repo_name(unicode_name)
564 # found repo that is on filesystem not in RhodeCode database
565 # found repo that is on filesystem not in RhodeCode database
565 if not db_repo:
566 if not db_repo:
566 log.info('repository %s not found, creating now', name)
567 log.info('repository %s not found, creating now', name)
567 added.append(name)
568 added.append(name)
568 desc = (repo.description
569 desc = (repo.description
569 if repo.description != 'unknown'
570 if repo.description != 'unknown'
570 else '%s repository' % name)
571 else '%s repository' % name)
571
572
572 db_repo = repo_model._create_repo(
573 db_repo = repo_model._create_repo(
573 repo_name=name,
574 repo_name=name,
574 repo_type=repo.alias,
575 repo_type=repo.alias,
575 description=desc,
576 description=desc,
576 repo_group=getattr(group, 'group_id', None),
577 repo_group=getattr(group, 'group_id', None),
577 owner=user,
578 owner=user,
578 enable_locking=enable_locking,
579 enable_locking=enable_locking,
579 enable_downloads=enable_downloads,
580 enable_downloads=enable_downloads,
580 enable_statistics=enable_statistics,
581 enable_statistics=enable_statistics,
581 private=private,
582 private=private,
582 state=Repository.STATE_CREATED
583 state=Repository.STATE_CREATED
583 )
584 )
584 sa.commit()
585 sa.commit()
585 # we added that repo just now, and make sure we updated server info
586 # we added that repo just now, and make sure we updated server info
586 if db_repo.repo_type == 'git':
587 if db_repo.repo_type == 'git':
587 git_repo = db_repo.scm_instance()
588 git_repo = db_repo.scm_instance()
588 # update repository server-info
589 # update repository server-info
589 log.debug('Running update server info')
590 log.debug('Running update server info')
590 git_repo._update_server_info()
591 git_repo._update_server_info()
591
592
592 db_repo.update_commit_cache()
593 db_repo.update_commit_cache()
593
594
594 config = db_repo._config
595 config = db_repo._config
595 config.set('extensions', 'largefiles', '')
596 config.set('extensions', 'largefiles', '')
596 ScmModel().install_hooks(
597 ScmModel().install_hooks(
597 db_repo.scm_instance(config=config),
598 db_repo.scm_instance(config=config),
598 repo_type=db_repo.repo_type)
599 repo_type=db_repo.repo_type)
599
600
600 removed = []
601 removed = []
601 if remove_obsolete:
602 if remove_obsolete:
602 # remove from database those repositories that are not in the filesystem
603 # remove from database those repositories that are not in the filesystem
603 for repo in sa.query(Repository).all():
604 for repo in sa.query(Repository).all():
604 if repo.repo_name not in initial_repo_list.keys():
605 if repo.repo_name not in initial_repo_list.keys():
605 log.debug("Removing non-existing repository found in db `%s`",
606 log.debug("Removing non-existing repository found in db `%s`",
606 repo.repo_name)
607 repo.repo_name)
607 try:
608 try:
608 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
609 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
609 sa.commit()
610 sa.commit()
610 removed.append(repo.repo_name)
611 removed.append(repo.repo_name)
611 except Exception:
612 except Exception:
612 # don't hold further removals on error
613 # don't hold further removals on error
613 log.error(traceback.format_exc())
614 log.error(traceback.format_exc())
614 sa.rollback()
615 sa.rollback()
615
616
616 def splitter(full_repo_name):
617 def splitter(full_repo_name):
617 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
618 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
618 gr_name = None
619 gr_name = None
619 if len(_parts) == 2:
620 if len(_parts) == 2:
620 gr_name = _parts[0]
621 gr_name = _parts[0]
621 return gr_name
622 return gr_name
622
623
623 initial_repo_group_list = [splitter(x) for x in
624 initial_repo_group_list = [splitter(x) for x in
624 initial_repo_list.keys() if splitter(x)]
625 initial_repo_list.keys() if splitter(x)]
625
626
626 # remove from database those repository groups that are not in the
627 # remove from database those repository groups that are not in the
627 # filesystem due to parent child relationships we need to delete them
628 # filesystem due to parent child relationships we need to delete them
628 # in a specific order of most nested first
629 # in a specific order of most nested first
629 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
630 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
630 nested_sort = lambda gr: len(gr.split('/'))
631 nested_sort = lambda gr: len(gr.split('/'))
631 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
632 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
632 if group_name not in initial_repo_group_list:
633 if group_name not in initial_repo_group_list:
633 repo_group = RepoGroup.get_by_group_name(group_name)
634 repo_group = RepoGroup.get_by_group_name(group_name)
634 if (repo_group.children.all() or
635 if (repo_group.children.all() or
635 not RepoGroupModel().check_exist_filesystem(
636 not RepoGroupModel().check_exist_filesystem(
636 group_name=group_name, exc_on_failure=False)):
637 group_name=group_name, exc_on_failure=False)):
637 continue
638 continue
638
639
639 log.info(
640 log.info(
640 'Removing non-existing repository group found in db `%s`',
641 'Removing non-existing repository group found in db `%s`',
641 group_name)
642 group_name)
642 try:
643 try:
643 RepoGroupModel(sa).delete(group_name, fs_remove=False)
644 RepoGroupModel(sa).delete(group_name, fs_remove=False)
644 sa.commit()
645 sa.commit()
645 removed.append(group_name)
646 removed.append(group_name)
646 except Exception:
647 except Exception:
647 # don't hold further removals on error
648 # don't hold further removals on error
648 log.exception(
649 log.exception(
649 'Unable to remove repository group `%s`',
650 'Unable to remove repository group `%s`',
650 group_name)
651 group_name)
651 sa.rollback()
652 sa.rollback()
652 raise
653 raise
653
654
654 return added, removed
655 return added, removed
655
656
656
657
657 def get_default_cache_settings(settings):
658 def get_default_cache_settings(settings):
658 cache_settings = {}
659 cache_settings = {}
659 for key in settings.keys():
660 for key in settings.keys():
660 for prefix in ['beaker.cache.', 'cache.']:
661 for prefix in ['beaker.cache.', 'cache.']:
661 if key.startswith(prefix):
662 if key.startswith(prefix):
662 name = key.split(prefix)[1].strip()
663 name = key.split(prefix)[1].strip()
663 cache_settings[name] = settings[key].strip()
664 cache_settings[name] = settings[key].strip()
664 return cache_settings
665 return cache_settings
665
666
666
667
667 # set cache regions for beaker so celery can utilise it
668 # set cache regions for beaker so celery can utilise it
668 def add_cache(settings):
669 def add_cache(settings):
669 from rhodecode.lib import caches
670 from rhodecode.lib import caches
670 cache_settings = {'regions': None}
671 cache_settings = {'regions': None}
671 # main cache settings used as default ...
672 # main cache settings used as default ...
672 cache_settings.update(get_default_cache_settings(settings))
673 cache_settings.update(get_default_cache_settings(settings))
673
674
674 if cache_settings['regions']:
675 if cache_settings['regions']:
675 for region in cache_settings['regions'].split(','):
676 for region in cache_settings['regions'].split(','):
676 region = region.strip()
677 region = region.strip()
677 region_settings = {}
678 region_settings = {}
678 for key, value in cache_settings.items():
679 for key, value in cache_settings.items():
679 if key.startswith(region):
680 if key.startswith(region):
680 region_settings[key.split('.')[1]] = value
681 region_settings[key.split('.')[1]] = value
681
682
682 caches.configure_cache_region(
683 caches.configure_cache_region(
683 region, region_settings, cache_settings)
684 region, region_settings, cache_settings)
684
685
685
686
686 def load_rcextensions(root_path):
687 def load_rcextensions(root_path):
687 import rhodecode
688 import rhodecode
688 from rhodecode.config import conf
689 from rhodecode.config import conf
689
690
690 path = os.path.join(root_path, 'rcextensions', '__init__.py')
691 path = os.path.join(root_path, 'rcextensions', '__init__.py')
691 if os.path.isfile(path):
692 if os.path.isfile(path):
692 rcext = create_module('rc', path)
693 rcext = create_module('rc', path)
693 EXT = rhodecode.EXTENSIONS = rcext
694 EXT = rhodecode.EXTENSIONS = rcext
694 log.debug('Found rcextensions now loading %s...', rcext)
695 log.debug('Found rcextensions now loading %s...', rcext)
695
696
696 # Additional mappings that are not present in the pygments lexers
697 # Additional mappings that are not present in the pygments lexers
697 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
698 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
698
699
699 # auto check if the module is not missing any data, set to default if is
700 # auto check if the module is not missing any data, set to default if is
700 # this will help autoupdate new feature of rcext module
701 # this will help autoupdate new feature of rcext module
701 #from rhodecode.config import rcextensions
702 #from rhodecode.config import rcextensions
702 #for k in dir(rcextensions):
703 #for k in dir(rcextensions):
703 # if not k.startswith('_') and not hasattr(EXT, k):
704 # if not k.startswith('_') and not hasattr(EXT, k):
704 # setattr(EXT, k, getattr(rcextensions, k))
705 # setattr(EXT, k, getattr(rcextensions, k))
705
706
706
707
707 def get_custom_lexer(extension):
708 def get_custom_lexer(extension):
708 """
709 """
709 returns a custom lexer if it is defined in rcextensions module, or None
710 returns a custom lexer if it is defined in rcextensions module, or None
710 if there's no custom lexer defined
711 if there's no custom lexer defined
711 """
712 """
712 import rhodecode
713 import rhodecode
713 from pygments import lexers
714 from pygments import lexers
714 # check if we didn't define this extension as other lexer
715 # check if we didn't define this extension as other lexer
715 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
716 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
716 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
717 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
717 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
718 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
718 return lexers.get_lexer_by_name(_lexer_name)
719 return lexers.get_lexer_by_name(_lexer_name)
719
720
720
721
721 #==============================================================================
722 #==============================================================================
722 # TEST FUNCTIONS AND CREATORS
723 # TEST FUNCTIONS AND CREATORS
723 #==============================================================================
724 #==============================================================================
724 def create_test_index(repo_location, config):
725 def create_test_index(repo_location, config):
725 """
726 """
726 Makes default test index.
727 Makes default test index.
727 """
728 """
728 import rc_testdata
729 import rc_testdata
729
730
730 rc_testdata.extract_search_index(
731 rc_testdata.extract_search_index(
731 'vcs_search_index', os.path.dirname(config['search.location']))
732 'vcs_search_index', os.path.dirname(config['search.location']))
732
733
733
734
734 def create_test_directory(test_path):
735 def create_test_directory(test_path):
735 """
736 """
736 Create test directory if it doesn't exist.
737 Create test directory if it doesn't exist.
737 """
738 """
738 if not os.path.isdir(test_path):
739 if not os.path.isdir(test_path):
739 log.debug('Creating testdir %s', test_path)
740 log.debug('Creating testdir %s', test_path)
740 os.makedirs(test_path)
741 os.makedirs(test_path)
741
742
742
743
743 def create_test_database(test_path, config):
744 def create_test_database(test_path, config):
744 """
745 """
745 Makes a fresh database.
746 Makes a fresh database.
746 """
747 """
747 from rhodecode.lib.db_manage import DbManage
748 from rhodecode.lib.db_manage import DbManage
748
749
749 # PART ONE create db
750 # PART ONE create db
750 dbconf = config['sqlalchemy.db1.url']
751 dbconf = config['sqlalchemy.db1.url']
751 log.debug('making test db %s', dbconf)
752 log.debug('making test db %s', dbconf)
752
753
753 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
754 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
754 tests=True, cli_args={'force_ask': True})
755 tests=True, cli_args={'force_ask': True})
755 dbmanage.create_tables(override=True)
756 dbmanage.create_tables(override=True)
756 dbmanage.set_db_version()
757 dbmanage.set_db_version()
757 # for tests dynamically set new root paths based on generated content
758 # for tests dynamically set new root paths based on generated content
758 dbmanage.create_settings(dbmanage.config_prompt(test_path))
759 dbmanage.create_settings(dbmanage.config_prompt(test_path))
759 dbmanage.create_default_user()
760 dbmanage.create_default_user()
760 dbmanage.create_test_admin_and_users()
761 dbmanage.create_test_admin_and_users()
761 dbmanage.create_permissions()
762 dbmanage.create_permissions()
762 dbmanage.populate_default_permissions()
763 dbmanage.populate_default_permissions()
763 Session().commit()
764 Session().commit()
764
765
765
766
766 def create_test_repositories(test_path, config):
767 def create_test_repositories(test_path, config):
767 """
768 """
768 Creates test repositories in the temporary directory. Repositories are
769 Creates test repositories in the temporary directory. Repositories are
769 extracted from archives within the rc_testdata package.
770 extracted from archives within the rc_testdata package.
770 """
771 """
771 import rc_testdata
772 import rc_testdata
772 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
773 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
773
774
774 log.debug('making test vcs repositories')
775 log.debug('making test vcs repositories')
775
776
776 idx_path = config['search.location']
777 idx_path = config['search.location']
777 data_path = config['cache_dir']
778 data_path = config['cache_dir']
778
779
779 # clean index and data
780 # clean index and data
780 if idx_path and os.path.exists(idx_path):
781 if idx_path and os.path.exists(idx_path):
781 log.debug('remove %s', idx_path)
782 log.debug('remove %s', idx_path)
782 shutil.rmtree(idx_path)
783 shutil.rmtree(idx_path)
783
784
784 if data_path and os.path.exists(data_path):
785 if data_path and os.path.exists(data_path):
785 log.debug('remove %s', data_path)
786 log.debug('remove %s', data_path)
786 shutil.rmtree(data_path)
787 shutil.rmtree(data_path)
787
788
788 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
789 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
789 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
790 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
790
791
791 # Note: Subversion is in the process of being integrated with the system,
792 # Note: Subversion is in the process of being integrated with the system,
792 # until we have a properly packed version of the test svn repository, this
793 # until we have a properly packed version of the test svn repository, this
793 # tries to copy over the repo from a package "rc_testdata"
794 # tries to copy over the repo from a package "rc_testdata"
794 svn_repo_path = rc_testdata.get_svn_repo_archive()
795 svn_repo_path = rc_testdata.get_svn_repo_archive()
795 with tarfile.open(svn_repo_path) as tar:
796 with tarfile.open(svn_repo_path) as tar:
796 tar.extractall(jn(test_path, SVN_REPO))
797 tar.extractall(jn(test_path, SVN_REPO))
797
798
798
799
799 #==============================================================================
800 #==============================================================================
800 # PASTER COMMANDS
801 # PASTER COMMANDS
801 #==============================================================================
802 #==============================================================================
802 class BasePasterCommand(Command):
803 class BasePasterCommand(Command):
803 """
804 """
804 Abstract Base Class for paster commands.
805 Abstract Base Class for paster commands.
805
806
806 The celery commands are somewhat aggressive about loading
807 The celery commands are somewhat aggressive about loading
807 celery.conf, and since our module sets the `CELERY_LOADER`
808 celery.conf, and since our module sets the `CELERY_LOADER`
808 environment variable to our loader, we have to bootstrap a bit and
809 environment variable to our loader, we have to bootstrap a bit and
809 make sure we've had a chance to load the pylons config off of the
810 make sure we've had a chance to load the pylons config off of the
810 command line, otherwise everything fails.
811 command line, otherwise everything fails.
811 """
812 """
812 min_args = 1
813 min_args = 1
813 min_args_error = "Please provide a paster config file as an argument."
814 min_args_error = "Please provide a paster config file as an argument."
814 takes_config_file = 1
815 takes_config_file = 1
815 requires_config_file = True
816 requires_config_file = True
816
817
817 def notify_msg(self, msg, log=False):
818 def notify_msg(self, msg, log=False):
818 """Make a notification to user, additionally if logger is passed
819 """Make a notification to user, additionally if logger is passed
819 it logs this action using given logger
820 it logs this action using given logger
820
821
821 :param msg: message that will be printed to user
822 :param msg: message that will be printed to user
822 :param log: logging instance, to use to additionally log this message
823 :param log: logging instance, to use to additionally log this message
823
824
824 """
825 """
825 if log and isinstance(log, logging):
826 if log and isinstance(log, logging):
826 log(msg)
827 log(msg)
827
828
828 def run(self, args):
829 def run(self, args):
829 """
830 """
830 Overrides Command.run
831 Overrides Command.run
831
832
832 Checks for a config file argument and loads it.
833 Checks for a config file argument and loads it.
833 """
834 """
834 if len(args) < self.min_args:
835 if len(args) < self.min_args:
835 raise BadCommand(
836 raise BadCommand(
836 self.min_args_error % {'min_args': self.min_args,
837 self.min_args_error % {'min_args': self.min_args,
837 'actual_args': len(args)})
838 'actual_args': len(args)})
838
839
839 # Decrement because we're going to lob off the first argument.
840 # Decrement because we're going to lob off the first argument.
840 # @@ This is hacky
841 # @@ This is hacky
841 self.min_args -= 1
842 self.min_args -= 1
842 self.bootstrap_config(args[0])
843 self.bootstrap_config(args[0])
843 self.update_parser()
844 self.update_parser()
844 return super(BasePasterCommand, self).run(args[1:])
845 return super(BasePasterCommand, self).run(args[1:])
845
846
846 def update_parser(self):
847 def update_parser(self):
847 """
848 """
848 Abstract method. Allows for the class' parser to be updated
849 Abstract method. Allows for the class' parser to be updated
849 before the superclass' `run` method is called. Necessary to
850 before the superclass' `run` method is called. Necessary to
850 allow options/arguments to be passed through to the underlying
851 allow options/arguments to be passed through to the underlying
851 celery command.
852 celery command.
852 """
853 """
853 raise NotImplementedError("Abstract Method.")
854 raise NotImplementedError("Abstract Method.")
854
855
855 def bootstrap_config(self, conf):
856 def bootstrap_config(self, conf):
856 """
857 """
857 Loads the pylons configuration.
858 Loads the pylons configuration.
858 """
859 """
859 from pylons import config as pylonsconfig
860 from pylons import config as pylonsconfig
860
861
861 self.path_to_ini_file = os.path.realpath(conf)
862 self.path_to_ini_file = os.path.realpath(conf)
862 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
863 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
863 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
864 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
864
865
865 def _init_session(self):
866 def _init_session(self):
866 """
867 """
867 Inits SqlAlchemy Session
868 Inits SqlAlchemy Session
868 """
869 """
869 logging.config.fileConfig(self.path_to_ini_file)
870 logging.config.fileConfig(self.path_to_ini_file)
870 from pylons import config
871 from pylons import config
871 from rhodecode.config.utils import initialize_database
872 from rhodecode.config.utils import initialize_database
872
873
873 # get to remove repos !!
874 # get to remove repos !!
874 add_cache(config)
875 add_cache(config)
875 initialize_database(config)
876 initialize_database(config)
876
877
877
878
878 @decorator.decorator
879 @decorator.decorator
879 def jsonify(func, *args, **kwargs):
880 def jsonify(func, *args, **kwargs):
880 """Action decorator that formats output for JSON
881 """Action decorator that formats output for JSON
881
882
882 Given a function that will return content, this decorator will turn
883 Given a function that will return content, this decorator will turn
883 the result into JSON, with a content-type of 'application/json' and
884 the result into JSON, with a content-type of 'application/json' and
884 output it.
885 output it.
885
886
886 """
887 """
887 from pylons.decorators.util import get_pylons
888 from pylons.decorators.util import get_pylons
888 from rhodecode.lib.ext_json import json
889 from rhodecode.lib.ext_json import json
889 pylons = get_pylons(args)
890 pylons = get_pylons(args)
890 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
891 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
891 data = func(*args, **kwargs)
892 data = func(*args, **kwargs)
892 if isinstance(data, (list, tuple)):
893 if isinstance(data, (list, tuple)):
893 msg = "JSON responses with Array envelopes are susceptible to " \
894 msg = "JSON responses with Array envelopes are susceptible to " \
894 "cross-site data leak attacks, see " \
895 "cross-site data leak attacks, see " \
895 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
896 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
896 warnings.warn(msg, Warning, 2)
897 warnings.warn(msg, Warning, 2)
897 log.warning(msg)
898 log.warning(msg)
898 log.debug("Returning JSON wrapped action output")
899 log.debug("Returning JSON wrapped action output")
899 return json.dumps(data, encoding='utf-8')
900 return json.dumps(data, encoding='utf-8')
900
901
901
902
902 class PartialRenderer(object):
903 class PartialRenderer(object):
903 """
904 """
904 Partial renderer used to render chunks of html used in datagrids
905 Partial renderer used to render chunks of html used in datagrids
905 use like::
906 use like::
906
907
907 _render = PartialRenderer('data_table/_dt_elements.mako')
908 _render = PartialRenderer('data_table/_dt_elements.mako')
908 _render('quick_menu', args, kwargs)
909 _render('quick_menu', args, kwargs)
909 PartialRenderer.h,
910 PartialRenderer.h,
910 c,
911 c,
911 _,
912 _,
912 ungettext
913 ungettext
913 are the template stuff initialized inside and can be re-used later
914 are the template stuff initialized inside and can be re-used later
914
915
915 :param tmpl_name: template path relate to /templates/ dir
916 :param tmpl_name: template path relate to /templates/ dir
916 """
917 """
917
918
918 def __init__(self, tmpl_name):
919 def __init__(self, tmpl_name):
919 import rhodecode
920 import rhodecode
920 from pylons import request, tmpl_context as c
921 from pylons import request, tmpl_context as c
921 from pylons.i18n.translation import _, ungettext
922 from pylons.i18n.translation import _, ungettext
922 from rhodecode.lib import helpers as h
923 from rhodecode.lib import helpers as h
923
924
924 self.tmpl_name = tmpl_name
925 self.tmpl_name = tmpl_name
925 self.rhodecode = rhodecode
926 self.rhodecode = rhodecode
926 self.c = c
927 self.c = c
927 self._ = _
928 self._ = _
928 self.ungettext = ungettext
929 self.ungettext = ungettext
929 self.h = h
930 self.h = h
930 self.request = request
931 self.request = request
931
932
932 def _mako_lookup(self):
933 def _mako_lookup(self):
933 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
934 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
934 return _tmpl_lookup.get_template(self.tmpl_name)
935 return _tmpl_lookup.get_template(self.tmpl_name)
935
936
936 def _update_kwargs_for_render(self, kwargs):
937 def _update_kwargs_for_render(self, kwargs):
937 """
938 """
938 Inject params required for Mako rendering
939 Inject params required for Mako rendering
939 """
940 """
940 _kwargs = {
941 _kwargs = {
941 '_': self._,
942 '_': self._,
942 'h': self.h,
943 'h': self.h,
943 'c': self.c,
944 'c': self.c,
944 'request': self.request,
945 'request': self.request,
945 'ungettext': self.ungettext,
946 'ungettext': self.ungettext,
946 }
947 }
947 _kwargs.update(kwargs)
948 _kwargs.update(kwargs)
948 return _kwargs
949 return _kwargs
949
950
950 def _render_with_exc(self, render_func, args, kwargs):
951 def _render_with_exc(self, render_func, args, kwargs):
951 try:
952 try:
952 return render_func.render(*args, **kwargs)
953 return render_func.render(*args, **kwargs)
953 except:
954 except:
954 log.error(exceptions.text_error_template().render())
955 log.error(exceptions.text_error_template().render())
955 raise
956 raise
956
957
957 def _get_template(self, template_obj, def_name):
958 def _get_template(self, template_obj, def_name):
958 if def_name:
959 if def_name:
959 tmpl = template_obj.get_def(def_name)
960 tmpl = template_obj.get_def(def_name)
960 else:
961 else:
961 tmpl = template_obj
962 tmpl = template_obj
962 return tmpl
963 return tmpl
963
964
964 def render(self, def_name, *args, **kwargs):
965 def render(self, def_name, *args, **kwargs):
965 lookup_obj = self._mako_lookup()
966 lookup_obj = self._mako_lookup()
966 tmpl = self._get_template(lookup_obj, def_name=def_name)
967 tmpl = self._get_template(lookup_obj, def_name=def_name)
967 kwargs = self._update_kwargs_for_render(kwargs)
968 kwargs = self._update_kwargs_for_render(kwargs)
968 return self._render_with_exc(tmpl, args, kwargs)
969 return self._render_with_exc(tmpl, args, kwargs)
969
970
970 def __call__(self, tmpl, *args, **kwargs):
971 def __call__(self, tmpl, *args, **kwargs):
971 return self.render(tmpl, *args, **kwargs)
972 return self.render(tmpl, *args, **kwargs)
972
973
973
974
974 def password_changed(auth_user, session):
975 def password_changed(auth_user, session):
975 # Never report password change in case of default user or anonymous user.
976 # Never report password change in case of default user or anonymous user.
976 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
977 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
977 return False
978 return False
978
979
979 password_hash = md5(auth_user.password) if auth_user.password else None
980 password_hash = md5(auth_user.password) if auth_user.password else None
980 rhodecode_user = session.get('rhodecode_user', {})
981 rhodecode_user = session.get('rhodecode_user', {})
981 session_password_hash = rhodecode_user.get('password', '')
982 session_password_hash = rhodecode_user.get('password', '')
982 return password_hash != session_password_hash
983 return password_hash != session_password_hash
983
984
984
985
985 def read_opensource_licenses():
986 def read_opensource_licenses():
986 global _license_cache
987 global _license_cache
987
988
988 if not _license_cache:
989 if not _license_cache:
989 licenses = pkg_resources.resource_string(
990 licenses = pkg_resources.resource_string(
990 'rhodecode', 'config/licenses.json')
991 'rhodecode', 'config/licenses.json')
991 _license_cache = json.loads(licenses)
992 _license_cache = json.loads(licenses)
992
993
993 return _license_cache
994 return _license_cache
994
995
995
996
996 def get_registry(request):
997 def get_registry(request):
997 """
998 """
998 Utility to get the pyramid registry from a request. During migration to
999 Utility to get the pyramid registry from a request. During migration to
999 pyramid we sometimes want to use the pyramid registry from pylons context.
1000 pyramid we sometimes want to use the pyramid registry from pylons context.
1000 Therefore this utility returns `request.registry` for pyramid requests and
1001 Therefore this utility returns `request.registry` for pyramid requests and
1001 uses `get_current_registry()` for pylons requests.
1002 uses `get_current_registry()` for pylons requests.
1002 """
1003 """
1003 try:
1004 try:
1004 return request.registry
1005 return request.registry
1005 except AttributeError:
1006 except AttributeError:
1006 return get_current_registry()
1007 return get_current_registry()
1007
1008
1008
1009
1009 def generate_platform_uuid():
1010 def generate_platform_uuid():
1010 """
1011 """
1011 Generates platform UUID based on it's name
1012 Generates platform UUID based on it's name
1012 """
1013 """
1013 import platform
1014 import platform
1014
1015
1015 try:
1016 try:
1016 uuid_list = [platform.platform()]
1017 uuid_list = [platform.platform()]
1017 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1018 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1018 except Exception as e:
1019 except Exception as e:
1019 log.error('Failed to generate host uuid: %s' % e)
1020 log.error('Failed to generate host uuid: %s' % e)
1020 return 'UNDEFINED'
1021 return 'UNDEFINED'
@@ -1,3908 +1,3909 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
353 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
354
355
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # git part is currently hardcoded.
357 # git part is currently hardcoded.
357
358
358 # SVN PATTERNS
359 # SVN PATTERNS
359 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_TAG_ID = 'vcs_svn_tag'
361 SVN_TAG_ID = 'vcs_svn_tag'
361
362
362 ui_id = Column(
363 ui_id = Column(
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 primary_key=True)
365 primary_key=True)
365 ui_section = Column(
366 ui_section = Column(
366 "ui_section", String(255), nullable=True, unique=None, default=None)
367 "ui_section", String(255), nullable=True, unique=None, default=None)
367 ui_key = Column(
368 ui_key = Column(
368 "ui_key", String(255), nullable=True, unique=None, default=None)
369 "ui_key", String(255), nullable=True, unique=None, default=None)
369 ui_value = Column(
370 ui_value = Column(
370 "ui_value", String(255), nullable=True, unique=None, default=None)
371 "ui_value", String(255), nullable=True, unique=None, default=None)
371 ui_active = Column(
372 ui_active = Column(
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373
374
374 def __repr__(self):
375 def __repr__(self):
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 self.ui_key, self.ui_value)
377 self.ui_key, self.ui_value)
377
378
378
379
379 class RepoRhodeCodeSetting(Base, BaseModel):
380 class RepoRhodeCodeSetting(Base, BaseModel):
380 __tablename__ = 'repo_rhodecode_settings'
381 __tablename__ = 'repo_rhodecode_settings'
381 __table_args__ = (
382 __table_args__ = (
382 UniqueConstraint(
383 UniqueConstraint(
383 'app_settings_name', 'repository_id',
384 'app_settings_name', 'repository_id',
384 name='uq_repo_rhodecode_setting_name_repo_id'),
385 name='uq_repo_rhodecode_setting_name_repo_id'),
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 )
388 )
388
389
389 repository_id = Column(
390 repository_id = Column(
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 nullable=False)
392 nullable=False)
392 app_settings_id = Column(
393 app_settings_id = Column(
393 "app_settings_id", Integer(), nullable=False, unique=True,
394 "app_settings_id", Integer(), nullable=False, unique=True,
394 default=None, primary_key=True)
395 default=None, primary_key=True)
395 app_settings_name = Column(
396 app_settings_name = Column(
396 "app_settings_name", String(255), nullable=True, unique=None,
397 "app_settings_name", String(255), nullable=True, unique=None,
397 default=None)
398 default=None)
398 _app_settings_value = Column(
399 _app_settings_value = Column(
399 "app_settings_value", String(4096), nullable=True, unique=None,
400 "app_settings_value", String(4096), nullable=True, unique=None,
400 default=None)
401 default=None)
401 _app_settings_type = Column(
402 _app_settings_type = Column(
402 "app_settings_type", String(255), nullable=True, unique=None,
403 "app_settings_type", String(255), nullable=True, unique=None,
403 default=None)
404 default=None)
404
405
405 repository = relationship('Repository')
406 repository = relationship('Repository')
406
407
407 def __init__(self, repository_id, key='', val='', type='unicode'):
408 def __init__(self, repository_id, key='', val='', type='unicode'):
408 self.repository_id = repository_id
409 self.repository_id = repository_id
409 self.app_settings_name = key
410 self.app_settings_name = key
410 self.app_settings_type = type
411 self.app_settings_type = type
411 self.app_settings_value = val
412 self.app_settings_value = val
412
413
413 @validates('_app_settings_value')
414 @validates('_app_settings_value')
414 def validate_settings_value(self, key, val):
415 def validate_settings_value(self, key, val):
415 assert type(val) == unicode
416 assert type(val) == unicode
416 return val
417 return val
417
418
418 @hybrid_property
419 @hybrid_property
419 def app_settings_value(self):
420 def app_settings_value(self):
420 v = self._app_settings_value
421 v = self._app_settings_value
421 type_ = self.app_settings_type
422 type_ = self.app_settings_type
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 return converter(v)
425 return converter(v)
425
426
426 @app_settings_value.setter
427 @app_settings_value.setter
427 def app_settings_value(self, val):
428 def app_settings_value(self, val):
428 """
429 """
429 Setter that will always make sure we use unicode in app_settings_value
430 Setter that will always make sure we use unicode in app_settings_value
430
431
431 :param val:
432 :param val:
432 """
433 """
433 self._app_settings_value = safe_unicode(val)
434 self._app_settings_value = safe_unicode(val)
434
435
435 @hybrid_property
436 @hybrid_property
436 def app_settings_type(self):
437 def app_settings_type(self):
437 return self._app_settings_type
438 return self._app_settings_type
438
439
439 @app_settings_type.setter
440 @app_settings_type.setter
440 def app_settings_type(self, val):
441 def app_settings_type(self, val):
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 if val not in SETTINGS_TYPES:
443 if val not in SETTINGS_TYPES:
443 raise Exception('type must be one of %s got %s'
444 raise Exception('type must be one of %s got %s'
444 % (SETTINGS_TYPES.keys(), val))
445 % (SETTINGS_TYPES.keys(), val))
445 self._app_settings_type = val
446 self._app_settings_type = val
446
447
447 def __unicode__(self):
448 def __unicode__(self):
448 return u"<%s('%s:%s:%s[%s]')>" % (
449 return u"<%s('%s:%s:%s[%s]')>" % (
449 self.__class__.__name__, self.repository.repo_name,
450 self.__class__.__name__, self.repository.repo_name,
450 self.app_settings_name, self.app_settings_value,
451 self.app_settings_name, self.app_settings_value,
451 self.app_settings_type
452 self.app_settings_type
452 )
453 )
453
454
454
455
455 class RepoRhodeCodeUi(Base, BaseModel):
456 class RepoRhodeCodeUi(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_ui'
457 __tablename__ = 'repo_rhodecode_ui'
457 __table_args__ = (
458 __table_args__ = (
458 UniqueConstraint(
459 UniqueConstraint(
459 'repository_id', 'ui_section', 'ui_key',
460 'repository_id', 'ui_section', 'ui_key',
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 )
464 )
464
465
465 repository_id = Column(
466 repository_id = Column(
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 nullable=False)
468 nullable=False)
468 ui_id = Column(
469 ui_id = Column(
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 primary_key=True)
471 primary_key=True)
471 ui_section = Column(
472 ui_section = Column(
472 "ui_section", String(255), nullable=True, unique=None, default=None)
473 "ui_section", String(255), nullable=True, unique=None, default=None)
473 ui_key = Column(
474 ui_key = Column(
474 "ui_key", String(255), nullable=True, unique=None, default=None)
475 "ui_key", String(255), nullable=True, unique=None, default=None)
475 ui_value = Column(
476 ui_value = Column(
476 "ui_value", String(255), nullable=True, unique=None, default=None)
477 "ui_value", String(255), nullable=True, unique=None, default=None)
477 ui_active = Column(
478 ui_active = Column(
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479
480
480 repository = relationship('Repository')
481 repository = relationship('Repository')
481
482
482 def __repr__(self):
483 def __repr__(self):
483 return '<%s[%s:%s]%s=>%s]>' % (
484 return '<%s[%s:%s]%s=>%s]>' % (
484 self.__class__.__name__, self.repository.repo_name,
485 self.__class__.__name__, self.repository.repo_name,
485 self.ui_section, self.ui_key, self.ui_value)
486 self.ui_section, self.ui_key, self.ui_value)
486
487
487
488
488 class User(Base, BaseModel):
489 class User(Base, BaseModel):
489 __tablename__ = 'users'
490 __tablename__ = 'users'
490 __table_args__ = (
491 __table_args__ = (
491 UniqueConstraint('username'), UniqueConstraint('email'),
492 UniqueConstraint('username'), UniqueConstraint('email'),
492 Index('u_username_idx', 'username'),
493 Index('u_username_idx', 'username'),
493 Index('u_email_idx', 'email'),
494 Index('u_email_idx', 'email'),
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 )
497 )
497 DEFAULT_USER = 'default'
498 DEFAULT_USER = 'default'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500
501
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 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)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516
517
517 user_log = relationship('UserLog')
518 user_log = relationship('UserLog')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519
520
520 repositories = relationship('Repository')
521 repositories = relationship('Repository')
521 repository_groups = relationship('RepoGroup')
522 repository_groups = relationship('RepoGroup')
522 user_groups = relationship('UserGroup')
523 user_groups = relationship('UserGroup')
523
524
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526
527
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530
531
531 group_member = relationship('UserGroupMember', cascade='all')
532 group_member = relationship('UserGroupMember', cascade='all')
532
533
533 notifications = relationship('UserNotification', cascade='all')
534 notifications = relationship('UserNotification', cascade='all')
534 # notifications assigned to this user
535 # notifications assigned to this user
535 user_created_notifications = relationship('Notification', cascade='all')
536 user_created_notifications = relationship('Notification', cascade='all')
536 # comments created by this user
537 # comments created by this user
537 user_comments = relationship('ChangesetComment', cascade='all')
538 user_comments = relationship('ChangesetComment', cascade='all')
538 # user profile extra info
539 # user profile extra info
539 user_emails = relationship('UserEmailMap', cascade='all')
540 user_emails = relationship('UserEmailMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 # gists
543 # gists
543 user_gists = relationship('Gist', cascade='all')
544 user_gists = relationship('Gist', cascade='all')
544 # user pull requests
545 # user pull requests
545 user_pull_requests = relationship('PullRequest', cascade='all')
546 user_pull_requests = relationship('PullRequest', cascade='all')
546 # external identities
547 # external identities
547 extenal_identities = relationship(
548 extenal_identities = relationship(
548 'ExternalIdentity',
549 'ExternalIdentity',
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 cascade='all')
551 cascade='all')
551
552
552 def __unicode__(self):
553 def __unicode__(self):
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 self.user_id, self.username)
555 self.user_id, self.username)
555
556
556 @hybrid_property
557 @hybrid_property
557 def email(self):
558 def email(self):
558 return self._email
559 return self._email
559
560
560 @email.setter
561 @email.setter
561 def email(self, val):
562 def email(self, val):
562 self._email = val.lower() if val else None
563 self._email = val.lower() if val else None
563
564
564 @property
565 @property
565 def firstname(self):
566 def firstname(self):
566 # alias for future
567 # alias for future
567 return self.name
568 return self.name
568
569
569 @property
570 @property
570 def emails(self):
571 def emails(self):
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 return [self.email] + [x.email for x in other]
573 return [self.email] + [x.email for x in other]
573
574
574 @property
575 @property
575 def auth_tokens(self):
576 def auth_tokens(self):
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577
578
578 @property
579 @property
579 def extra_auth_tokens(self):
580 def extra_auth_tokens(self):
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581
582
582 @property
583 @property
583 def feed_token(self):
584 def feed_token(self):
584 return self.get_feed_token()
585 return self.get_feed_token()
585
586
586 def get_feed_token(self):
587 def get_feed_token(self):
587 feed_tokens = UserApiKeys.query()\
588 feed_tokens = UserApiKeys.query()\
588 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .all()
591 .all()
591 if feed_tokens:
592 if feed_tokens:
592 return feed_tokens[0].api_key
593 return feed_tokens[0].api_key
593 return 'NO_FEED_TOKEN_AVAILABLE'
594 return 'NO_FEED_TOKEN_AVAILABLE'
594
595
595 @classmethod
596 @classmethod
596 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
597 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
599 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
600 if role:
601 if role:
601 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 return tokens.all()
604 return tokens.all()
604
605
605 def authenticate_by_token(self, auth_token, roles=None,
606 def authenticate_by_token(self, auth_token, roles=None,
606 include_builtin_token=False):
607 include_builtin_token=False):
607 from rhodecode.lib import auth
608 from rhodecode.lib import auth
608
609
609 log.debug('Trying to authenticate user: %s via auth-token, '
610 log.debug('Trying to authenticate user: %s via auth-token, '
610 'and roles: %s', self, roles)
611 'and roles: %s', self, roles)
611
612
612 if not auth_token:
613 if not auth_token:
613 return False
614 return False
614
615
615 crypto_backend = auth.crypto_backend()
616 crypto_backend = auth.crypto_backend()
616
617
617 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 tokens_q = UserApiKeys.query()\
619 tokens_q = UserApiKeys.query()\
619 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(or_(UserApiKeys.expires == -1,
621 .filter(or_(UserApiKeys.expires == -1,
621 UserApiKeys.expires >= time.time()))
622 UserApiKeys.expires >= time.time()))
622
623
623 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624
625
625 maybe_builtin = []
626 maybe_builtin = []
626 if include_builtin_token:
627 if include_builtin_token:
627 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
628
629
629 plain_tokens = []
630 plain_tokens = []
630 hash_tokens = []
631 hash_tokens = []
631
632
632 for token in tokens_q.all() + maybe_builtin:
633 for token in tokens_q.all() + maybe_builtin:
633 if token.api_key.startswith(crypto_backend.ENC_PREF):
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
634 hash_tokens.append(token.api_key)
635 hash_tokens.append(token.api_key)
635 else:
636 else:
636 plain_tokens.append(token.api_key)
637 plain_tokens.append(token.api_key)
637
638
638 is_plain_match = auth_token in plain_tokens
639 is_plain_match = auth_token in plain_tokens
639 if is_plain_match:
640 if is_plain_match:
640 return True
641 return True
641
642
642 for hashed in hash_tokens:
643 for hashed in hash_tokens:
643 # marcink: this is expensive to calculate, but the most secure
644 # marcink: this is expensive to calculate, but the most secure
644 match = crypto_backend.hash_check(auth_token, hashed)
645 match = crypto_backend.hash_check(auth_token, hashed)
645 if match:
646 if match:
646 return True
647 return True
647
648
648 return False
649 return False
649
650
650 @property
651 @property
651 def builtin_token_roles(self):
652 def builtin_token_roles(self):
652 roles = [
653 roles = [
653 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
654 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
654 ]
655 ]
655 return map(UserApiKeys._get_role_name, roles)
656 return map(UserApiKeys._get_role_name, roles)
656
657
657 @property
658 @property
658 def ip_addresses(self):
659 def ip_addresses(self):
659 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
660 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
660 return [x.ip_addr for x in ret]
661 return [x.ip_addr for x in ret]
661
662
662 @property
663 @property
663 def username_and_name(self):
664 def username_and_name(self):
664 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
665 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
665
666
666 @property
667 @property
667 def username_or_name_or_email(self):
668 def username_or_name_or_email(self):
668 full_name = self.full_name if self.full_name is not ' ' else None
669 full_name = self.full_name if self.full_name is not ' ' else None
669 return self.username or full_name or self.email
670 return self.username or full_name or self.email
670
671
671 @property
672 @property
672 def full_name(self):
673 def full_name(self):
673 return '%s %s' % (self.firstname, self.lastname)
674 return '%s %s' % (self.firstname, self.lastname)
674
675
675 @property
676 @property
676 def full_name_or_username(self):
677 def full_name_or_username(self):
677 return ('%s %s' % (self.firstname, self.lastname)
678 return ('%s %s' % (self.firstname, self.lastname)
678 if (self.firstname and self.lastname) else self.username)
679 if (self.firstname and self.lastname) else self.username)
679
680
680 @property
681 @property
681 def full_contact(self):
682 def full_contact(self):
682 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
683 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
683
684
684 @property
685 @property
685 def short_contact(self):
686 def short_contact(self):
686 return '%s %s' % (self.firstname, self.lastname)
687 return '%s %s' % (self.firstname, self.lastname)
687
688
688 @property
689 @property
689 def is_admin(self):
690 def is_admin(self):
690 return self.admin
691 return self.admin
691
692
692 @property
693 @property
693 def AuthUser(self):
694 def AuthUser(self):
694 """
695 """
695 Returns instance of AuthUser for this user
696 Returns instance of AuthUser for this user
696 """
697 """
697 from rhodecode.lib.auth import AuthUser
698 from rhodecode.lib.auth import AuthUser
698 return AuthUser(user_id=self.user_id, api_key=self.api_key,
699 return AuthUser(user_id=self.user_id, api_key=self.api_key,
699 username=self.username)
700 username=self.username)
700
701
701 @hybrid_property
702 @hybrid_property
702 def user_data(self):
703 def user_data(self):
703 if not self._user_data:
704 if not self._user_data:
704 return {}
705 return {}
705
706
706 try:
707 try:
707 return json.loads(self._user_data)
708 return json.loads(self._user_data)
708 except TypeError:
709 except TypeError:
709 return {}
710 return {}
710
711
711 @user_data.setter
712 @user_data.setter
712 def user_data(self, val):
713 def user_data(self, val):
713 if not isinstance(val, dict):
714 if not isinstance(val, dict):
714 raise Exception('user_data must be dict, got %s' % type(val))
715 raise Exception('user_data must be dict, got %s' % type(val))
715 try:
716 try:
716 self._user_data = json.dumps(val)
717 self._user_data = json.dumps(val)
717 except Exception:
718 except Exception:
718 log.error(traceback.format_exc())
719 log.error(traceback.format_exc())
719
720
720 @classmethod
721 @classmethod
721 def get_by_username(cls, username, case_insensitive=False,
722 def get_by_username(cls, username, case_insensitive=False,
722 cache=False, identity_cache=False):
723 cache=False, identity_cache=False):
723 session = Session()
724 session = Session()
724
725
725 if case_insensitive:
726 if case_insensitive:
726 q = cls.query().filter(
727 q = cls.query().filter(
727 func.lower(cls.username) == func.lower(username))
728 func.lower(cls.username) == func.lower(username))
728 else:
729 else:
729 q = cls.query().filter(cls.username == username)
730 q = cls.query().filter(cls.username == username)
730
731
731 if cache:
732 if cache:
732 if identity_cache:
733 if identity_cache:
733 val = cls.identity_cache(session, 'username', username)
734 val = cls.identity_cache(session, 'username', username)
734 if val:
735 if val:
735 return val
736 return val
736 else:
737 else:
737 q = q.options(
738 q = q.options(
738 FromCache("sql_cache_short",
739 FromCache("sql_cache_short",
739 "get_user_by_name_%s" % _hash_key(username)))
740 "get_user_by_name_%s" % _hash_key(username)))
740
741
741 return q.scalar()
742 return q.scalar()
742
743
743 @classmethod
744 @classmethod
744 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
745 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
745 q = cls.query().filter(cls.api_key == auth_token)
746 q = cls.query().filter(cls.api_key == auth_token)
746
747
747 if cache:
748 if cache:
748 q = q.options(FromCache("sql_cache_short",
749 q = q.options(FromCache("sql_cache_short",
749 "get_auth_token_%s" % auth_token))
750 "get_auth_token_%s" % auth_token))
750 res = q.scalar()
751 res = q.scalar()
751
752
752 if fallback and not res:
753 if fallback and not res:
753 #fallback to additional keys
754 #fallback to additional keys
754 _res = UserApiKeys.query()\
755 _res = UserApiKeys.query()\
755 .filter(UserApiKeys.api_key == auth_token)\
756 .filter(UserApiKeys.api_key == auth_token)\
756 .filter(or_(UserApiKeys.expires == -1,
757 .filter(or_(UserApiKeys.expires == -1,
757 UserApiKeys.expires >= time.time()))\
758 UserApiKeys.expires >= time.time()))\
758 .first()
759 .first()
759 if _res:
760 if _res:
760 res = _res.user
761 res = _res.user
761 return res
762 return res
762
763
763 @classmethod
764 @classmethod
764 def get_by_email(cls, email, case_insensitive=False, cache=False):
765 def get_by_email(cls, email, case_insensitive=False, cache=False):
765
766
766 if case_insensitive:
767 if case_insensitive:
767 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
768 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
768
769
769 else:
770 else:
770 q = cls.query().filter(cls.email == email)
771 q = cls.query().filter(cls.email == email)
771
772
772 if cache:
773 if cache:
773 q = q.options(FromCache("sql_cache_short",
774 q = q.options(FromCache("sql_cache_short",
774 "get_email_key_%s" % _hash_key(email)))
775 "get_email_key_%s" % _hash_key(email)))
775
776
776 ret = q.scalar()
777 ret = q.scalar()
777 if ret is None:
778 if ret is None:
778 q = UserEmailMap.query()
779 q = UserEmailMap.query()
779 # try fetching in alternate email map
780 # try fetching in alternate email map
780 if case_insensitive:
781 if case_insensitive:
781 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
782 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
782 else:
783 else:
783 q = q.filter(UserEmailMap.email == email)
784 q = q.filter(UserEmailMap.email == email)
784 q = q.options(joinedload(UserEmailMap.user))
785 q = q.options(joinedload(UserEmailMap.user))
785 if cache:
786 if cache:
786 q = q.options(FromCache("sql_cache_short",
787 q = q.options(FromCache("sql_cache_short",
787 "get_email_map_key_%s" % email))
788 "get_email_map_key_%s" % email))
788 ret = getattr(q.scalar(), 'user', None)
789 ret = getattr(q.scalar(), 'user', None)
789
790
790 return ret
791 return ret
791
792
792 @classmethod
793 @classmethod
793 def get_from_cs_author(cls, author):
794 def get_from_cs_author(cls, author):
794 """
795 """
795 Tries to get User objects out of commit author string
796 Tries to get User objects out of commit author string
796
797
797 :param author:
798 :param author:
798 """
799 """
799 from rhodecode.lib.helpers import email, author_name
800 from rhodecode.lib.helpers import email, author_name
800 # Valid email in the attribute passed, see if they're in the system
801 # Valid email in the attribute passed, see if they're in the system
801 _email = email(author)
802 _email = email(author)
802 if _email:
803 if _email:
803 user = cls.get_by_email(_email, case_insensitive=True)
804 user = cls.get_by_email(_email, case_insensitive=True)
804 if user:
805 if user:
805 return user
806 return user
806 # Maybe we can match by username?
807 # Maybe we can match by username?
807 _author = author_name(author)
808 _author = author_name(author)
808 user = cls.get_by_username(_author, case_insensitive=True)
809 user = cls.get_by_username(_author, case_insensitive=True)
809 if user:
810 if user:
810 return user
811 return user
811
812
812 def update_userdata(self, **kwargs):
813 def update_userdata(self, **kwargs):
813 usr = self
814 usr = self
814 old = usr.user_data
815 old = usr.user_data
815 old.update(**kwargs)
816 old.update(**kwargs)
816 usr.user_data = old
817 usr.user_data = old
817 Session().add(usr)
818 Session().add(usr)
818 log.debug('updated userdata with ', kwargs)
819 log.debug('updated userdata with ', kwargs)
819
820
820 def update_lastlogin(self):
821 def update_lastlogin(self):
821 """Update user lastlogin"""
822 """Update user lastlogin"""
822 self.last_login = datetime.datetime.now()
823 self.last_login = datetime.datetime.now()
823 Session().add(self)
824 Session().add(self)
824 log.debug('updated user %s lastlogin', self.username)
825 log.debug('updated user %s lastlogin', self.username)
825
826
826 def update_lastactivity(self):
827 def update_lastactivity(self):
827 """Update user lastactivity"""
828 """Update user lastactivity"""
828 usr = self
829 usr = self
829 old = usr.user_data
830 old = usr.user_data
830 old.update({'last_activity': time.time()})
831 old.update({'last_activity': time.time()})
831 usr.user_data = old
832 usr.user_data = old
832 Session().add(usr)
833 Session().add(usr)
833 log.debug('updated user %s lastactivity', usr.username)
834 log.debug('updated user %s lastactivity', usr.username)
834
835
835 def update_password(self, new_password, change_api_key=False):
836 def update_password(self, new_password, change_api_key=False):
836 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
837 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
837
838
838 self.password = get_crypt_password(new_password)
839 self.password = get_crypt_password(new_password)
839 if change_api_key:
840 if change_api_key:
840 self.api_key = generate_auth_token(self.username)
841 self.api_key = generate_auth_token(self.username)
841 Session().add(self)
842 Session().add(self)
842
843
843 @classmethod
844 @classmethod
844 def get_first_super_admin(cls):
845 def get_first_super_admin(cls):
845 user = User.query().filter(User.admin == true()).first()
846 user = User.query().filter(User.admin == true()).first()
846 if user is None:
847 if user is None:
847 raise Exception('FATAL: Missing administrative account!')
848 raise Exception('FATAL: Missing administrative account!')
848 return user
849 return user
849
850
850 @classmethod
851 @classmethod
851 def get_all_super_admins(cls):
852 def get_all_super_admins(cls):
852 """
853 """
853 Returns all admin accounts sorted by username
854 Returns all admin accounts sorted by username
854 """
855 """
855 return User.query().filter(User.admin == true())\
856 return User.query().filter(User.admin == true())\
856 .order_by(User.username.asc()).all()
857 .order_by(User.username.asc()).all()
857
858
858 @classmethod
859 @classmethod
859 def get_default_user(cls, cache=False):
860 def get_default_user(cls, cache=False):
860 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
861 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
861 if user is None:
862 if user is None:
862 raise Exception('FATAL: Missing default account!')
863 raise Exception('FATAL: Missing default account!')
863 return user
864 return user
864
865
865 def _get_default_perms(self, user, suffix=''):
866 def _get_default_perms(self, user, suffix=''):
866 from rhodecode.model.permission import PermissionModel
867 from rhodecode.model.permission import PermissionModel
867 return PermissionModel().get_default_perms(user.user_perms, suffix)
868 return PermissionModel().get_default_perms(user.user_perms, suffix)
868
869
869 def get_default_perms(self, suffix=''):
870 def get_default_perms(self, suffix=''):
870 return self._get_default_perms(self, suffix)
871 return self._get_default_perms(self, suffix)
871
872
872 def get_api_data(self, include_secrets=False, details='full'):
873 def get_api_data(self, include_secrets=False, details='full'):
873 """
874 """
874 Common function for generating user related data for API
875 Common function for generating user related data for API
875
876
876 :param include_secrets: By default secrets in the API data will be replaced
877 :param include_secrets: By default secrets in the API data will be replaced
877 by a placeholder value to prevent exposing this data by accident. In case
878 by a placeholder value to prevent exposing this data by accident. In case
878 this data shall be exposed, set this flag to ``True``.
879 this data shall be exposed, set this flag to ``True``.
879
880
880 :param details: details can be 'basic|full' basic gives only a subset of
881 :param details: details can be 'basic|full' basic gives only a subset of
881 the available user information that includes user_id, name and emails.
882 the available user information that includes user_id, name and emails.
882 """
883 """
883 user = self
884 user = self
884 user_data = self.user_data
885 user_data = self.user_data
885 data = {
886 data = {
886 'user_id': user.user_id,
887 'user_id': user.user_id,
887 'username': user.username,
888 'username': user.username,
888 'firstname': user.name,
889 'firstname': user.name,
889 'lastname': user.lastname,
890 'lastname': user.lastname,
890 'email': user.email,
891 'email': user.email,
891 'emails': user.emails,
892 'emails': user.emails,
892 }
893 }
893 if details == 'basic':
894 if details == 'basic':
894 return data
895 return data
895
896
896 api_key_length = 40
897 api_key_length = 40
897 api_key_replacement = '*' * api_key_length
898 api_key_replacement = '*' * api_key_length
898
899
899 extras = {
900 extras = {
900 'api_key': api_key_replacement,
901 'api_key': api_key_replacement,
901 'api_keys': [api_key_replacement],
902 'api_keys': [api_key_replacement],
902 'active': user.active,
903 'active': user.active,
903 'admin': user.admin,
904 'admin': user.admin,
904 'extern_type': user.extern_type,
905 'extern_type': user.extern_type,
905 'extern_name': user.extern_name,
906 'extern_name': user.extern_name,
906 'last_login': user.last_login,
907 'last_login': user.last_login,
907 'ip_addresses': user.ip_addresses,
908 'ip_addresses': user.ip_addresses,
908 'language': user_data.get('language')
909 'language': user_data.get('language')
909 }
910 }
910 data.update(extras)
911 data.update(extras)
911
912
912 if include_secrets:
913 if include_secrets:
913 data['api_key'] = user.api_key
914 data['api_key'] = user.api_key
914 data['api_keys'] = user.auth_tokens
915 data['api_keys'] = user.auth_tokens
915 return data
916 return data
916
917
917 def __json__(self):
918 def __json__(self):
918 data = {
919 data = {
919 'full_name': self.full_name,
920 'full_name': self.full_name,
920 'full_name_or_username': self.full_name_or_username,
921 'full_name_or_username': self.full_name_or_username,
921 'short_contact': self.short_contact,
922 'short_contact': self.short_contact,
922 'full_contact': self.full_contact,
923 'full_contact': self.full_contact,
923 }
924 }
924 data.update(self.get_api_data())
925 data.update(self.get_api_data())
925 return data
926 return data
926
927
927
928
928 class UserApiKeys(Base, BaseModel):
929 class UserApiKeys(Base, BaseModel):
929 __tablename__ = 'user_api_keys'
930 __tablename__ = 'user_api_keys'
930 __table_args__ = (
931 __table_args__ = (
931 Index('uak_api_key_idx', 'api_key'),
932 Index('uak_api_key_idx', 'api_key'),
932 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
933 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
933 UniqueConstraint('api_key'),
934 UniqueConstraint('api_key'),
934 {'extend_existing': True, 'mysql_engine': 'InnoDB',
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
935 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
936 )
937 )
937 __mapper_args__ = {}
938 __mapper_args__ = {}
938
939
939 # ApiKey role
940 # ApiKey role
940 ROLE_ALL = 'token_role_all'
941 ROLE_ALL = 'token_role_all'
941 ROLE_HTTP = 'token_role_http'
942 ROLE_HTTP = 'token_role_http'
942 ROLE_VCS = 'token_role_vcs'
943 ROLE_VCS = 'token_role_vcs'
943 ROLE_API = 'token_role_api'
944 ROLE_API = 'token_role_api'
944 ROLE_FEED = 'token_role_feed'
945 ROLE_FEED = 'token_role_feed'
945 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
946 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
946
947
947 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
949 api_key = Column("api_key", String(255), nullable=False, unique=True)
950 api_key = Column("api_key", String(255), nullable=False, unique=True)
950 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
951 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
951 expires = Column('expires', Float(53), nullable=False)
952 expires = Column('expires', Float(53), nullable=False)
952 role = Column('role', String(255), nullable=True)
953 role = Column('role', String(255), nullable=True)
953 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
954 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
954
955
955 user = relationship('User', lazy='joined')
956 user = relationship('User', lazy='joined')
956
957
957 @classmethod
958 @classmethod
958 def _get_role_name(cls, role):
959 def _get_role_name(cls, role):
959 return {
960 return {
960 cls.ROLE_ALL: _('all'),
961 cls.ROLE_ALL: _('all'),
961 cls.ROLE_HTTP: _('http/web interface'),
962 cls.ROLE_HTTP: _('http/web interface'),
962 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
963 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
963 cls.ROLE_API: _('api calls'),
964 cls.ROLE_API: _('api calls'),
964 cls.ROLE_FEED: _('feed access'),
965 cls.ROLE_FEED: _('feed access'),
965 }.get(role, role)
966 }.get(role, role)
966
967
967 @property
968 @property
968 def expired(self):
969 def expired(self):
969 if self.expires == -1:
970 if self.expires == -1:
970 return False
971 return False
971 return time.time() > self.expires
972 return time.time() > self.expires
972
973
973 @property
974 @property
974 def role_humanized(self):
975 def role_humanized(self):
975 return self._get_role_name(self.role)
976 return self._get_role_name(self.role)
976
977
977
978
978 class UserEmailMap(Base, BaseModel):
979 class UserEmailMap(Base, BaseModel):
979 __tablename__ = 'user_email_map'
980 __tablename__ = 'user_email_map'
980 __table_args__ = (
981 __table_args__ = (
981 Index('uem_email_idx', 'email'),
982 Index('uem_email_idx', 'email'),
982 UniqueConstraint('email'),
983 UniqueConstraint('email'),
983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
985 )
986 )
986 __mapper_args__ = {}
987 __mapper_args__ = {}
987
988
988 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
989 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
989 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
990 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
990 _email = Column("email", String(255), nullable=True, unique=False, default=None)
991 _email = Column("email", String(255), nullable=True, unique=False, default=None)
991 user = relationship('User', lazy='joined')
992 user = relationship('User', lazy='joined')
992
993
993 @validates('_email')
994 @validates('_email')
994 def validate_email(self, key, email):
995 def validate_email(self, key, email):
995 # check if this email is not main one
996 # check if this email is not main one
996 main_email = Session().query(User).filter(User.email == email).scalar()
997 main_email = Session().query(User).filter(User.email == email).scalar()
997 if main_email is not None:
998 if main_email is not None:
998 raise AttributeError('email %s is present is user table' % email)
999 raise AttributeError('email %s is present is user table' % email)
999 return email
1000 return email
1000
1001
1001 @hybrid_property
1002 @hybrid_property
1002 def email(self):
1003 def email(self):
1003 return self._email
1004 return self._email
1004
1005
1005 @email.setter
1006 @email.setter
1006 def email(self, val):
1007 def email(self, val):
1007 self._email = val.lower() if val else None
1008 self._email = val.lower() if val else None
1008
1009
1009
1010
1010 class UserIpMap(Base, BaseModel):
1011 class UserIpMap(Base, BaseModel):
1011 __tablename__ = 'user_ip_map'
1012 __tablename__ = 'user_ip_map'
1012 __table_args__ = (
1013 __table_args__ = (
1013 UniqueConstraint('user_id', 'ip_addr'),
1014 UniqueConstraint('user_id', 'ip_addr'),
1014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1015 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1016 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1016 )
1017 )
1017 __mapper_args__ = {}
1018 __mapper_args__ = {}
1018
1019
1019 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1021 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1022 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1022 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1023 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1023 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1024 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1024 user = relationship('User', lazy='joined')
1025 user = relationship('User', lazy='joined')
1025
1026
1026 @classmethod
1027 @classmethod
1027 def _get_ip_range(cls, ip_addr):
1028 def _get_ip_range(cls, ip_addr):
1028 net = ipaddress.ip_network(ip_addr, strict=False)
1029 net = ipaddress.ip_network(ip_addr, strict=False)
1029 return [str(net.network_address), str(net.broadcast_address)]
1030 return [str(net.network_address), str(net.broadcast_address)]
1030
1031
1031 def __json__(self):
1032 def __json__(self):
1032 return {
1033 return {
1033 'ip_addr': self.ip_addr,
1034 'ip_addr': self.ip_addr,
1034 'ip_range': self._get_ip_range(self.ip_addr),
1035 'ip_range': self._get_ip_range(self.ip_addr),
1035 }
1036 }
1036
1037
1037 def __unicode__(self):
1038 def __unicode__(self):
1038 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1039 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1039 self.user_id, self.ip_addr)
1040 self.user_id, self.ip_addr)
1040
1041
1041 class UserLog(Base, BaseModel):
1042 class UserLog(Base, BaseModel):
1042 __tablename__ = 'user_logs'
1043 __tablename__ = 'user_logs'
1043 __table_args__ = (
1044 __table_args__ = (
1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1045 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1046 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1046 )
1047 )
1047 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1049 username = Column("username", String(255), nullable=True, unique=None, default=None)
1050 username = Column("username", String(255), nullable=True, unique=None, default=None)
1050 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1051 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1051 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1052 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1052 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1053 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1053 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1054 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1054 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1055 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1055
1056
1056 def __unicode__(self):
1057 def __unicode__(self):
1057 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1058 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1058 self.repository_name,
1059 self.repository_name,
1059 self.action)
1060 self.action)
1060
1061
1061 @property
1062 @property
1062 def action_as_day(self):
1063 def action_as_day(self):
1063 return datetime.date(*self.action_date.timetuple()[:3])
1064 return datetime.date(*self.action_date.timetuple()[:3])
1064
1065
1065 user = relationship('User')
1066 user = relationship('User')
1066 repository = relationship('Repository', cascade='')
1067 repository = relationship('Repository', cascade='')
1067
1068
1068
1069
1069 class UserGroup(Base, BaseModel):
1070 class UserGroup(Base, BaseModel):
1070 __tablename__ = 'users_groups'
1071 __tablename__ = 'users_groups'
1071 __table_args__ = (
1072 __table_args__ = (
1072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1073 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1074 )
1075 )
1075
1076
1076 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1077 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1077 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1078 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1078 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1079 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1079 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1080 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1080 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1081 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1082 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1083 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1084 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1084
1085
1085 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1086 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1086 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1087 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1087 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1088 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1088 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1089 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1089 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1090 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1090 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1091 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1091
1092
1092 user = relationship('User')
1093 user = relationship('User')
1093
1094
1094 @hybrid_property
1095 @hybrid_property
1095 def group_data(self):
1096 def group_data(self):
1096 if not self._group_data:
1097 if not self._group_data:
1097 return {}
1098 return {}
1098
1099
1099 try:
1100 try:
1100 return json.loads(self._group_data)
1101 return json.loads(self._group_data)
1101 except TypeError:
1102 except TypeError:
1102 return {}
1103 return {}
1103
1104
1104 @group_data.setter
1105 @group_data.setter
1105 def group_data(self, val):
1106 def group_data(self, val):
1106 try:
1107 try:
1107 self._group_data = json.dumps(val)
1108 self._group_data = json.dumps(val)
1108 except Exception:
1109 except Exception:
1109 log.error(traceback.format_exc())
1110 log.error(traceback.format_exc())
1110
1111
1111 def __unicode__(self):
1112 def __unicode__(self):
1112 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1113 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1113 self.users_group_id,
1114 self.users_group_id,
1114 self.users_group_name)
1115 self.users_group_name)
1115
1116
1116 @classmethod
1117 @classmethod
1117 def get_by_group_name(cls, group_name, cache=False,
1118 def get_by_group_name(cls, group_name, cache=False,
1118 case_insensitive=False):
1119 case_insensitive=False):
1119 if case_insensitive:
1120 if case_insensitive:
1120 q = cls.query().filter(func.lower(cls.users_group_name) ==
1121 q = cls.query().filter(func.lower(cls.users_group_name) ==
1121 func.lower(group_name))
1122 func.lower(group_name))
1122
1123
1123 else:
1124 else:
1124 q = cls.query().filter(cls.users_group_name == group_name)
1125 q = cls.query().filter(cls.users_group_name == group_name)
1125 if cache:
1126 if cache:
1126 q = q.options(FromCache(
1127 q = q.options(FromCache(
1127 "sql_cache_short",
1128 "sql_cache_short",
1128 "get_group_%s" % _hash_key(group_name)))
1129 "get_group_%s" % _hash_key(group_name)))
1129 return q.scalar()
1130 return q.scalar()
1130
1131
1131 @classmethod
1132 @classmethod
1132 def get(cls, user_group_id, cache=False):
1133 def get(cls, user_group_id, cache=False):
1133 user_group = cls.query()
1134 user_group = cls.query()
1134 if cache:
1135 if cache:
1135 user_group = user_group.options(FromCache("sql_cache_short",
1136 user_group = user_group.options(FromCache("sql_cache_short",
1136 "get_users_group_%s" % user_group_id))
1137 "get_users_group_%s" % user_group_id))
1137 return user_group.get(user_group_id)
1138 return user_group.get(user_group_id)
1138
1139
1139 def permissions(self, with_admins=True, with_owner=True):
1140 def permissions(self, with_admins=True, with_owner=True):
1140 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1141 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1141 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1142 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1142 joinedload(UserUserGroupToPerm.user),
1143 joinedload(UserUserGroupToPerm.user),
1143 joinedload(UserUserGroupToPerm.permission),)
1144 joinedload(UserUserGroupToPerm.permission),)
1144
1145
1145 # get owners and admins and permissions. We do a trick of re-writing
1146 # get owners and admins and permissions. We do a trick of re-writing
1146 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1147 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1147 # has a global reference and changing one object propagates to all
1148 # has a global reference and changing one object propagates to all
1148 # others. This means if admin is also an owner admin_row that change
1149 # others. This means if admin is also an owner admin_row that change
1149 # would propagate to both objects
1150 # would propagate to both objects
1150 perm_rows = []
1151 perm_rows = []
1151 for _usr in q.all():
1152 for _usr in q.all():
1152 usr = AttributeDict(_usr.user.get_dict())
1153 usr = AttributeDict(_usr.user.get_dict())
1153 usr.permission = _usr.permission.permission_name
1154 usr.permission = _usr.permission.permission_name
1154 perm_rows.append(usr)
1155 perm_rows.append(usr)
1155
1156
1156 # filter the perm rows by 'default' first and then sort them by
1157 # filter the perm rows by 'default' first and then sort them by
1157 # admin,write,read,none permissions sorted again alphabetically in
1158 # admin,write,read,none permissions sorted again alphabetically in
1158 # each group
1159 # each group
1159 perm_rows = sorted(perm_rows, key=display_sort)
1160 perm_rows = sorted(perm_rows, key=display_sort)
1160
1161
1161 _admin_perm = 'usergroup.admin'
1162 _admin_perm = 'usergroup.admin'
1162 owner_row = []
1163 owner_row = []
1163 if with_owner:
1164 if with_owner:
1164 usr = AttributeDict(self.user.get_dict())
1165 usr = AttributeDict(self.user.get_dict())
1165 usr.owner_row = True
1166 usr.owner_row = True
1166 usr.permission = _admin_perm
1167 usr.permission = _admin_perm
1167 owner_row.append(usr)
1168 owner_row.append(usr)
1168
1169
1169 super_admin_rows = []
1170 super_admin_rows = []
1170 if with_admins:
1171 if with_admins:
1171 for usr in User.get_all_super_admins():
1172 for usr in User.get_all_super_admins():
1172 # if this admin is also owner, don't double the record
1173 # if this admin is also owner, don't double the record
1173 if usr.user_id == owner_row[0].user_id:
1174 if usr.user_id == owner_row[0].user_id:
1174 owner_row[0].admin_row = True
1175 owner_row[0].admin_row = True
1175 else:
1176 else:
1176 usr = AttributeDict(usr.get_dict())
1177 usr = AttributeDict(usr.get_dict())
1177 usr.admin_row = True
1178 usr.admin_row = True
1178 usr.permission = _admin_perm
1179 usr.permission = _admin_perm
1179 super_admin_rows.append(usr)
1180 super_admin_rows.append(usr)
1180
1181
1181 return super_admin_rows + owner_row + perm_rows
1182 return super_admin_rows + owner_row + perm_rows
1182
1183
1183 def permission_user_groups(self):
1184 def permission_user_groups(self):
1184 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1185 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1185 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1186 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1186 joinedload(UserGroupUserGroupToPerm.target_user_group),
1187 joinedload(UserGroupUserGroupToPerm.target_user_group),
1187 joinedload(UserGroupUserGroupToPerm.permission),)
1188 joinedload(UserGroupUserGroupToPerm.permission),)
1188
1189
1189 perm_rows = []
1190 perm_rows = []
1190 for _user_group in q.all():
1191 for _user_group in q.all():
1191 usr = AttributeDict(_user_group.user_group.get_dict())
1192 usr = AttributeDict(_user_group.user_group.get_dict())
1192 usr.permission = _user_group.permission.permission_name
1193 usr.permission = _user_group.permission.permission_name
1193 perm_rows.append(usr)
1194 perm_rows.append(usr)
1194
1195
1195 return perm_rows
1196 return perm_rows
1196
1197
1197 def _get_default_perms(self, user_group, suffix=''):
1198 def _get_default_perms(self, user_group, suffix=''):
1198 from rhodecode.model.permission import PermissionModel
1199 from rhodecode.model.permission import PermissionModel
1199 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1200 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1200
1201
1201 def get_default_perms(self, suffix=''):
1202 def get_default_perms(self, suffix=''):
1202 return self._get_default_perms(self, suffix)
1203 return self._get_default_perms(self, suffix)
1203
1204
1204 def get_api_data(self, with_group_members=True, include_secrets=False):
1205 def get_api_data(self, with_group_members=True, include_secrets=False):
1205 """
1206 """
1206 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1207 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1207 basically forwarded.
1208 basically forwarded.
1208
1209
1209 """
1210 """
1210 user_group = self
1211 user_group = self
1211
1212
1212 data = {
1213 data = {
1213 'users_group_id': user_group.users_group_id,
1214 'users_group_id': user_group.users_group_id,
1214 'group_name': user_group.users_group_name,
1215 'group_name': user_group.users_group_name,
1215 'group_description': user_group.user_group_description,
1216 'group_description': user_group.user_group_description,
1216 'active': user_group.users_group_active,
1217 'active': user_group.users_group_active,
1217 'owner': user_group.user.username,
1218 'owner': user_group.user.username,
1218 }
1219 }
1219 if with_group_members:
1220 if with_group_members:
1220 users = []
1221 users = []
1221 for user in user_group.members:
1222 for user in user_group.members:
1222 user = user.user
1223 user = user.user
1223 users.append(user.get_api_data(include_secrets=include_secrets))
1224 users.append(user.get_api_data(include_secrets=include_secrets))
1224 data['users'] = users
1225 data['users'] = users
1225
1226
1226 return data
1227 return data
1227
1228
1228
1229
1229 class UserGroupMember(Base, BaseModel):
1230 class UserGroupMember(Base, BaseModel):
1230 __tablename__ = 'users_groups_members'
1231 __tablename__ = 'users_groups_members'
1231 __table_args__ = (
1232 __table_args__ = (
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1234 )
1235 )
1235
1236
1236 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1238 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1239
1240
1240 user = relationship('User', lazy='joined')
1241 user = relationship('User', lazy='joined')
1241 users_group = relationship('UserGroup')
1242 users_group = relationship('UserGroup')
1242
1243
1243 def __init__(self, gr_id='', u_id=''):
1244 def __init__(self, gr_id='', u_id=''):
1244 self.users_group_id = gr_id
1245 self.users_group_id = gr_id
1245 self.user_id = u_id
1246 self.user_id = u_id
1246
1247
1247
1248
1248 class RepositoryField(Base, BaseModel):
1249 class RepositoryField(Base, BaseModel):
1249 __tablename__ = 'repositories_fields'
1250 __tablename__ = 'repositories_fields'
1250 __table_args__ = (
1251 __table_args__ = (
1251 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1252 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1254 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1254 )
1255 )
1255 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1256 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1256
1257
1257 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1259 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1259 field_key = Column("field_key", String(250))
1260 field_key = Column("field_key", String(250))
1260 field_label = Column("field_label", String(1024), nullable=False)
1261 field_label = Column("field_label", String(1024), nullable=False)
1261 field_value = Column("field_value", String(10000), nullable=False)
1262 field_value = Column("field_value", String(10000), nullable=False)
1262 field_desc = Column("field_desc", String(1024), nullable=False)
1263 field_desc = Column("field_desc", String(1024), nullable=False)
1263 field_type = Column("field_type", String(255), nullable=False, unique=None)
1264 field_type = Column("field_type", String(255), nullable=False, unique=None)
1264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1265
1266
1266 repository = relationship('Repository')
1267 repository = relationship('Repository')
1267
1268
1268 @property
1269 @property
1269 def field_key_prefixed(self):
1270 def field_key_prefixed(self):
1270 return 'ex_%s' % self.field_key
1271 return 'ex_%s' % self.field_key
1271
1272
1272 @classmethod
1273 @classmethod
1273 def un_prefix_key(cls, key):
1274 def un_prefix_key(cls, key):
1274 if key.startswith(cls.PREFIX):
1275 if key.startswith(cls.PREFIX):
1275 return key[len(cls.PREFIX):]
1276 return key[len(cls.PREFIX):]
1276 return key
1277 return key
1277
1278
1278 @classmethod
1279 @classmethod
1279 def get_by_key_name(cls, key, repo):
1280 def get_by_key_name(cls, key, repo):
1280 row = cls.query()\
1281 row = cls.query()\
1281 .filter(cls.repository == repo)\
1282 .filter(cls.repository == repo)\
1282 .filter(cls.field_key == key).scalar()
1283 .filter(cls.field_key == key).scalar()
1283 return row
1284 return row
1284
1285
1285
1286
1286 class Repository(Base, BaseModel):
1287 class Repository(Base, BaseModel):
1287 __tablename__ = 'repositories'
1288 __tablename__ = 'repositories'
1288 __table_args__ = (
1289 __table_args__ = (
1289 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1290 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1292 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1292 )
1293 )
1293 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1294 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1294 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1295 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1295
1296
1296 STATE_CREATED = 'repo_state_created'
1297 STATE_CREATED = 'repo_state_created'
1297 STATE_PENDING = 'repo_state_pending'
1298 STATE_PENDING = 'repo_state_pending'
1298 STATE_ERROR = 'repo_state_error'
1299 STATE_ERROR = 'repo_state_error'
1299
1300
1300 LOCK_AUTOMATIC = 'lock_auto'
1301 LOCK_AUTOMATIC = 'lock_auto'
1301 LOCK_API = 'lock_api'
1302 LOCK_API = 'lock_api'
1302 LOCK_WEB = 'lock_web'
1303 LOCK_WEB = 'lock_web'
1303 LOCK_PULL = 'lock_pull'
1304 LOCK_PULL = 'lock_pull'
1304
1305
1305 NAME_SEP = URL_SEP
1306 NAME_SEP = URL_SEP
1306
1307
1307 repo_id = Column(
1308 repo_id = Column(
1308 "repo_id", Integer(), nullable=False, unique=True, default=None,
1309 "repo_id", Integer(), nullable=False, unique=True, default=None,
1309 primary_key=True)
1310 primary_key=True)
1310 _repo_name = Column(
1311 _repo_name = Column(
1311 "repo_name", Text(), nullable=False, default=None)
1312 "repo_name", Text(), nullable=False, default=None)
1312 _repo_name_hash = Column(
1313 _repo_name_hash = Column(
1313 "repo_name_hash", String(255), nullable=False, unique=True)
1314 "repo_name_hash", String(255), nullable=False, unique=True)
1314 repo_state = Column("repo_state", String(255), nullable=True)
1315 repo_state = Column("repo_state", String(255), nullable=True)
1315
1316
1316 clone_uri = Column(
1317 clone_uri = Column(
1317 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1318 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1318 default=None)
1319 default=None)
1319 repo_type = Column(
1320 repo_type = Column(
1320 "repo_type", String(255), nullable=False, unique=False, default=None)
1321 "repo_type", String(255), nullable=False, unique=False, default=None)
1321 user_id = Column(
1322 user_id = Column(
1322 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1323 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1323 unique=False, default=None)
1324 unique=False, default=None)
1324 private = Column(
1325 private = Column(
1325 "private", Boolean(), nullable=True, unique=None, default=None)
1326 "private", Boolean(), nullable=True, unique=None, default=None)
1326 enable_statistics = Column(
1327 enable_statistics = Column(
1327 "statistics", Boolean(), nullable=True, unique=None, default=True)
1328 "statistics", Boolean(), nullable=True, unique=None, default=True)
1328 enable_downloads = Column(
1329 enable_downloads = Column(
1329 "downloads", Boolean(), nullable=True, unique=None, default=True)
1330 "downloads", Boolean(), nullable=True, unique=None, default=True)
1330 description = Column(
1331 description = Column(
1331 "description", String(10000), nullable=True, unique=None, default=None)
1332 "description", String(10000), nullable=True, unique=None, default=None)
1332 created_on = Column(
1333 created_on = Column(
1333 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1334 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1334 default=datetime.datetime.now)
1335 default=datetime.datetime.now)
1335 updated_on = Column(
1336 updated_on = Column(
1336 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1337 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1337 default=datetime.datetime.now)
1338 default=datetime.datetime.now)
1338 _landing_revision = Column(
1339 _landing_revision = Column(
1339 "landing_revision", String(255), nullable=False, unique=False,
1340 "landing_revision", String(255), nullable=False, unique=False,
1340 default=None)
1341 default=None)
1341 enable_locking = Column(
1342 enable_locking = Column(
1342 "enable_locking", Boolean(), nullable=False, unique=None,
1343 "enable_locking", Boolean(), nullable=False, unique=None,
1343 default=False)
1344 default=False)
1344 _locked = Column(
1345 _locked = Column(
1345 "locked", String(255), nullable=True, unique=False, default=None)
1346 "locked", String(255), nullable=True, unique=False, default=None)
1346 _changeset_cache = Column(
1347 _changeset_cache = Column(
1347 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1348 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1348
1349
1349 fork_id = Column(
1350 fork_id = Column(
1350 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1351 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1351 nullable=True, unique=False, default=None)
1352 nullable=True, unique=False, default=None)
1352 group_id = Column(
1353 group_id = Column(
1353 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1354 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1354 unique=False, default=None)
1355 unique=False, default=None)
1355
1356
1356 user = relationship('User', lazy='joined')
1357 user = relationship('User', lazy='joined')
1357 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1358 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1358 group = relationship('RepoGroup', lazy='joined')
1359 group = relationship('RepoGroup', lazy='joined')
1359 repo_to_perm = relationship(
1360 repo_to_perm = relationship(
1360 'UserRepoToPerm', cascade='all',
1361 'UserRepoToPerm', cascade='all',
1361 order_by='UserRepoToPerm.repo_to_perm_id')
1362 order_by='UserRepoToPerm.repo_to_perm_id')
1362 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1363 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1363 stats = relationship('Statistics', cascade='all', uselist=False)
1364 stats = relationship('Statistics', cascade='all', uselist=False)
1364
1365
1365 followers = relationship(
1366 followers = relationship(
1366 'UserFollowing',
1367 'UserFollowing',
1367 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1368 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1368 cascade='all')
1369 cascade='all')
1369 extra_fields = relationship(
1370 extra_fields = relationship(
1370 'RepositoryField', cascade="all, delete, delete-orphan")
1371 'RepositoryField', cascade="all, delete, delete-orphan")
1371 logs = relationship('UserLog')
1372 logs = relationship('UserLog')
1372 comments = relationship(
1373 comments = relationship(
1373 'ChangesetComment', cascade="all, delete, delete-orphan")
1374 'ChangesetComment', cascade="all, delete, delete-orphan")
1374 pull_requests_source = relationship(
1375 pull_requests_source = relationship(
1375 'PullRequest',
1376 'PullRequest',
1376 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1377 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1377 cascade="all, delete, delete-orphan")
1378 cascade="all, delete, delete-orphan")
1378 pull_requests_target = relationship(
1379 pull_requests_target = relationship(
1379 'PullRequest',
1380 'PullRequest',
1380 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1381 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1381 cascade="all, delete, delete-orphan")
1382 cascade="all, delete, delete-orphan")
1382 ui = relationship('RepoRhodeCodeUi', cascade="all")
1383 ui = relationship('RepoRhodeCodeUi', cascade="all")
1383 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1384 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1384 integrations = relationship('Integration',
1385 integrations = relationship('Integration',
1385 cascade="all, delete, delete-orphan")
1386 cascade="all, delete, delete-orphan")
1386
1387
1387 def __unicode__(self):
1388 def __unicode__(self):
1388 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1389 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1389 safe_unicode(self.repo_name))
1390 safe_unicode(self.repo_name))
1390
1391
1391 @hybrid_property
1392 @hybrid_property
1392 def landing_rev(self):
1393 def landing_rev(self):
1393 # always should return [rev_type, rev]
1394 # always should return [rev_type, rev]
1394 if self._landing_revision:
1395 if self._landing_revision:
1395 _rev_info = self._landing_revision.split(':')
1396 _rev_info = self._landing_revision.split(':')
1396 if len(_rev_info) < 2:
1397 if len(_rev_info) < 2:
1397 _rev_info.insert(0, 'rev')
1398 _rev_info.insert(0, 'rev')
1398 return [_rev_info[0], _rev_info[1]]
1399 return [_rev_info[0], _rev_info[1]]
1399 return [None, None]
1400 return [None, None]
1400
1401
1401 @landing_rev.setter
1402 @landing_rev.setter
1402 def landing_rev(self, val):
1403 def landing_rev(self, val):
1403 if ':' not in val:
1404 if ':' not in val:
1404 raise ValueError('value must be delimited with `:` and consist '
1405 raise ValueError('value must be delimited with `:` and consist '
1405 'of <rev_type>:<rev>, got %s instead' % val)
1406 'of <rev_type>:<rev>, got %s instead' % val)
1406 self._landing_revision = val
1407 self._landing_revision = val
1407
1408
1408 @hybrid_property
1409 @hybrid_property
1409 def locked(self):
1410 def locked(self):
1410 if self._locked:
1411 if self._locked:
1411 user_id, timelocked, reason = self._locked.split(':')
1412 user_id, timelocked, reason = self._locked.split(':')
1412 lock_values = int(user_id), timelocked, reason
1413 lock_values = int(user_id), timelocked, reason
1413 else:
1414 else:
1414 lock_values = [None, None, None]
1415 lock_values = [None, None, None]
1415 return lock_values
1416 return lock_values
1416
1417
1417 @locked.setter
1418 @locked.setter
1418 def locked(self, val):
1419 def locked(self, val):
1419 if val and isinstance(val, (list, tuple)):
1420 if val and isinstance(val, (list, tuple)):
1420 self._locked = ':'.join(map(str, val))
1421 self._locked = ':'.join(map(str, val))
1421 else:
1422 else:
1422 self._locked = None
1423 self._locked = None
1423
1424
1424 @hybrid_property
1425 @hybrid_property
1425 def changeset_cache(self):
1426 def changeset_cache(self):
1426 from rhodecode.lib.vcs.backends.base import EmptyCommit
1427 from rhodecode.lib.vcs.backends.base import EmptyCommit
1427 dummy = EmptyCommit().__json__()
1428 dummy = EmptyCommit().__json__()
1428 if not self._changeset_cache:
1429 if not self._changeset_cache:
1429 return dummy
1430 return dummy
1430 try:
1431 try:
1431 return json.loads(self._changeset_cache)
1432 return json.loads(self._changeset_cache)
1432 except TypeError:
1433 except TypeError:
1433 return dummy
1434 return dummy
1434 except Exception:
1435 except Exception:
1435 log.error(traceback.format_exc())
1436 log.error(traceback.format_exc())
1436 return dummy
1437 return dummy
1437
1438
1438 @changeset_cache.setter
1439 @changeset_cache.setter
1439 def changeset_cache(self, val):
1440 def changeset_cache(self, val):
1440 try:
1441 try:
1441 self._changeset_cache = json.dumps(val)
1442 self._changeset_cache = json.dumps(val)
1442 except Exception:
1443 except Exception:
1443 log.error(traceback.format_exc())
1444 log.error(traceback.format_exc())
1444
1445
1445 @hybrid_property
1446 @hybrid_property
1446 def repo_name(self):
1447 def repo_name(self):
1447 return self._repo_name
1448 return self._repo_name
1448
1449
1449 @repo_name.setter
1450 @repo_name.setter
1450 def repo_name(self, value):
1451 def repo_name(self, value):
1451 self._repo_name = value
1452 self._repo_name = value
1452 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1453 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1453
1454
1454 @classmethod
1455 @classmethod
1455 def normalize_repo_name(cls, repo_name):
1456 def normalize_repo_name(cls, repo_name):
1456 """
1457 """
1457 Normalizes os specific repo_name to the format internally stored inside
1458 Normalizes os specific repo_name to the format internally stored inside
1458 database using URL_SEP
1459 database using URL_SEP
1459
1460
1460 :param cls:
1461 :param cls:
1461 :param repo_name:
1462 :param repo_name:
1462 """
1463 """
1463 return cls.NAME_SEP.join(repo_name.split(os.sep))
1464 return cls.NAME_SEP.join(repo_name.split(os.sep))
1464
1465
1465 @classmethod
1466 @classmethod
1466 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1467 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1467 session = Session()
1468 session = Session()
1468 q = session.query(cls).filter(cls.repo_name == repo_name)
1469 q = session.query(cls).filter(cls.repo_name == repo_name)
1469
1470
1470 if cache:
1471 if cache:
1471 if identity_cache:
1472 if identity_cache:
1472 val = cls.identity_cache(session, 'repo_name', repo_name)
1473 val = cls.identity_cache(session, 'repo_name', repo_name)
1473 if val:
1474 if val:
1474 return val
1475 return val
1475 else:
1476 else:
1476 q = q.options(
1477 q = q.options(
1477 FromCache("sql_cache_short",
1478 FromCache("sql_cache_short",
1478 "get_repo_by_name_%s" % _hash_key(repo_name)))
1479 "get_repo_by_name_%s" % _hash_key(repo_name)))
1479
1480
1480 return q.scalar()
1481 return q.scalar()
1481
1482
1482 @classmethod
1483 @classmethod
1483 def get_by_full_path(cls, repo_full_path):
1484 def get_by_full_path(cls, repo_full_path):
1484 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1485 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1485 repo_name = cls.normalize_repo_name(repo_name)
1486 repo_name = cls.normalize_repo_name(repo_name)
1486 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1487 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1487
1488
1488 @classmethod
1489 @classmethod
1489 def get_repo_forks(cls, repo_id):
1490 def get_repo_forks(cls, repo_id):
1490 return cls.query().filter(Repository.fork_id == repo_id)
1491 return cls.query().filter(Repository.fork_id == repo_id)
1491
1492
1492 @classmethod
1493 @classmethod
1493 def base_path(cls):
1494 def base_path(cls):
1494 """
1495 """
1495 Returns base path when all repos are stored
1496 Returns base path when all repos are stored
1496
1497
1497 :param cls:
1498 :param cls:
1498 """
1499 """
1499 q = Session().query(RhodeCodeUi)\
1500 q = Session().query(RhodeCodeUi)\
1500 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1501 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1501 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1502 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1502 return q.one().ui_value
1503 return q.one().ui_value
1503
1504
1504 @classmethod
1505 @classmethod
1505 def is_valid(cls, repo_name):
1506 def is_valid(cls, repo_name):
1506 """
1507 """
1507 returns True if given repo name is a valid filesystem repository
1508 returns True if given repo name is a valid filesystem repository
1508
1509
1509 :param cls:
1510 :param cls:
1510 :param repo_name:
1511 :param repo_name:
1511 """
1512 """
1512 from rhodecode.lib.utils import is_valid_repo
1513 from rhodecode.lib.utils import is_valid_repo
1513
1514
1514 return is_valid_repo(repo_name, cls.base_path())
1515 return is_valid_repo(repo_name, cls.base_path())
1515
1516
1516 @classmethod
1517 @classmethod
1517 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1518 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1518 case_insensitive=True):
1519 case_insensitive=True):
1519 q = Repository.query()
1520 q = Repository.query()
1520
1521
1521 if not isinstance(user_id, Optional):
1522 if not isinstance(user_id, Optional):
1522 q = q.filter(Repository.user_id == user_id)
1523 q = q.filter(Repository.user_id == user_id)
1523
1524
1524 if not isinstance(group_id, Optional):
1525 if not isinstance(group_id, Optional):
1525 q = q.filter(Repository.group_id == group_id)
1526 q = q.filter(Repository.group_id == group_id)
1526
1527
1527 if case_insensitive:
1528 if case_insensitive:
1528 q = q.order_by(func.lower(Repository.repo_name))
1529 q = q.order_by(func.lower(Repository.repo_name))
1529 else:
1530 else:
1530 q = q.order_by(Repository.repo_name)
1531 q = q.order_by(Repository.repo_name)
1531 return q.all()
1532 return q.all()
1532
1533
1533 @property
1534 @property
1534 def forks(self):
1535 def forks(self):
1535 """
1536 """
1536 Return forks of this repo
1537 Return forks of this repo
1537 """
1538 """
1538 return Repository.get_repo_forks(self.repo_id)
1539 return Repository.get_repo_forks(self.repo_id)
1539
1540
1540 @property
1541 @property
1541 def parent(self):
1542 def parent(self):
1542 """
1543 """
1543 Returns fork parent
1544 Returns fork parent
1544 """
1545 """
1545 return self.fork
1546 return self.fork
1546
1547
1547 @property
1548 @property
1548 def just_name(self):
1549 def just_name(self):
1549 return self.repo_name.split(self.NAME_SEP)[-1]
1550 return self.repo_name.split(self.NAME_SEP)[-1]
1550
1551
1551 @property
1552 @property
1552 def groups_with_parents(self):
1553 def groups_with_parents(self):
1553 groups = []
1554 groups = []
1554 if self.group is None:
1555 if self.group is None:
1555 return groups
1556 return groups
1556
1557
1557 cur_gr = self.group
1558 cur_gr = self.group
1558 groups.insert(0, cur_gr)
1559 groups.insert(0, cur_gr)
1559 while 1:
1560 while 1:
1560 gr = getattr(cur_gr, 'parent_group', None)
1561 gr = getattr(cur_gr, 'parent_group', None)
1561 cur_gr = cur_gr.parent_group
1562 cur_gr = cur_gr.parent_group
1562 if gr is None:
1563 if gr is None:
1563 break
1564 break
1564 groups.insert(0, gr)
1565 groups.insert(0, gr)
1565
1566
1566 return groups
1567 return groups
1567
1568
1568 @property
1569 @property
1569 def groups_and_repo(self):
1570 def groups_and_repo(self):
1570 return self.groups_with_parents, self
1571 return self.groups_with_parents, self
1571
1572
1572 @LazyProperty
1573 @LazyProperty
1573 def repo_path(self):
1574 def repo_path(self):
1574 """
1575 """
1575 Returns base full path for that repository means where it actually
1576 Returns base full path for that repository means where it actually
1576 exists on a filesystem
1577 exists on a filesystem
1577 """
1578 """
1578 q = Session().query(RhodeCodeUi).filter(
1579 q = Session().query(RhodeCodeUi).filter(
1579 RhodeCodeUi.ui_key == self.NAME_SEP)
1580 RhodeCodeUi.ui_key == self.NAME_SEP)
1580 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1581 return q.one().ui_value
1582 return q.one().ui_value
1582
1583
1583 @property
1584 @property
1584 def repo_full_path(self):
1585 def repo_full_path(self):
1585 p = [self.repo_path]
1586 p = [self.repo_path]
1586 # we need to split the name by / since this is how we store the
1587 # we need to split the name by / since this is how we store the
1587 # names in the database, but that eventually needs to be converted
1588 # names in the database, but that eventually needs to be converted
1588 # into a valid system path
1589 # into a valid system path
1589 p += self.repo_name.split(self.NAME_SEP)
1590 p += self.repo_name.split(self.NAME_SEP)
1590 return os.path.join(*map(safe_unicode, p))
1591 return os.path.join(*map(safe_unicode, p))
1591
1592
1592 @property
1593 @property
1593 def cache_keys(self):
1594 def cache_keys(self):
1594 """
1595 """
1595 Returns associated cache keys for that repo
1596 Returns associated cache keys for that repo
1596 """
1597 """
1597 return CacheKey.query()\
1598 return CacheKey.query()\
1598 .filter(CacheKey.cache_args == self.repo_name)\
1599 .filter(CacheKey.cache_args == self.repo_name)\
1599 .order_by(CacheKey.cache_key)\
1600 .order_by(CacheKey.cache_key)\
1600 .all()
1601 .all()
1601
1602
1602 def get_new_name(self, repo_name):
1603 def get_new_name(self, repo_name):
1603 """
1604 """
1604 returns new full repository name based on assigned group and new new
1605 returns new full repository name based on assigned group and new new
1605
1606
1606 :param group_name:
1607 :param group_name:
1607 """
1608 """
1608 path_prefix = self.group.full_path_splitted if self.group else []
1609 path_prefix = self.group.full_path_splitted if self.group else []
1609 return self.NAME_SEP.join(path_prefix + [repo_name])
1610 return self.NAME_SEP.join(path_prefix + [repo_name])
1610
1611
1611 @property
1612 @property
1612 def _config(self):
1613 def _config(self):
1613 """
1614 """
1614 Returns db based config object.
1615 Returns db based config object.
1615 """
1616 """
1616 from rhodecode.lib.utils import make_db_config
1617 from rhodecode.lib.utils import make_db_config
1617 return make_db_config(clear_session=False, repo=self)
1618 return make_db_config(clear_session=False, repo=self)
1618
1619
1619 def permissions(self, with_admins=True, with_owner=True):
1620 def permissions(self, with_admins=True, with_owner=True):
1620 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1621 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1621 q = q.options(joinedload(UserRepoToPerm.repository),
1622 q = q.options(joinedload(UserRepoToPerm.repository),
1622 joinedload(UserRepoToPerm.user),
1623 joinedload(UserRepoToPerm.user),
1623 joinedload(UserRepoToPerm.permission),)
1624 joinedload(UserRepoToPerm.permission),)
1624
1625
1625 # get owners and admins and permissions. We do a trick of re-writing
1626 # get owners and admins and permissions. We do a trick of re-writing
1626 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1627 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1627 # has a global reference and changing one object propagates to all
1628 # has a global reference and changing one object propagates to all
1628 # others. This means if admin is also an owner admin_row that change
1629 # others. This means if admin is also an owner admin_row that change
1629 # would propagate to both objects
1630 # would propagate to both objects
1630 perm_rows = []
1631 perm_rows = []
1631 for _usr in q.all():
1632 for _usr in q.all():
1632 usr = AttributeDict(_usr.user.get_dict())
1633 usr = AttributeDict(_usr.user.get_dict())
1633 usr.permission = _usr.permission.permission_name
1634 usr.permission = _usr.permission.permission_name
1634 perm_rows.append(usr)
1635 perm_rows.append(usr)
1635
1636
1636 # filter the perm rows by 'default' first and then sort them by
1637 # filter the perm rows by 'default' first and then sort them by
1637 # admin,write,read,none permissions sorted again alphabetically in
1638 # admin,write,read,none permissions sorted again alphabetically in
1638 # each group
1639 # each group
1639 perm_rows = sorted(perm_rows, key=display_sort)
1640 perm_rows = sorted(perm_rows, key=display_sort)
1640
1641
1641 _admin_perm = 'repository.admin'
1642 _admin_perm = 'repository.admin'
1642 owner_row = []
1643 owner_row = []
1643 if with_owner:
1644 if with_owner:
1644 usr = AttributeDict(self.user.get_dict())
1645 usr = AttributeDict(self.user.get_dict())
1645 usr.owner_row = True
1646 usr.owner_row = True
1646 usr.permission = _admin_perm
1647 usr.permission = _admin_perm
1647 owner_row.append(usr)
1648 owner_row.append(usr)
1648
1649
1649 super_admin_rows = []
1650 super_admin_rows = []
1650 if with_admins:
1651 if with_admins:
1651 for usr in User.get_all_super_admins():
1652 for usr in User.get_all_super_admins():
1652 # if this admin is also owner, don't double the record
1653 # if this admin is also owner, don't double the record
1653 if usr.user_id == owner_row[0].user_id:
1654 if usr.user_id == owner_row[0].user_id:
1654 owner_row[0].admin_row = True
1655 owner_row[0].admin_row = True
1655 else:
1656 else:
1656 usr = AttributeDict(usr.get_dict())
1657 usr = AttributeDict(usr.get_dict())
1657 usr.admin_row = True
1658 usr.admin_row = True
1658 usr.permission = _admin_perm
1659 usr.permission = _admin_perm
1659 super_admin_rows.append(usr)
1660 super_admin_rows.append(usr)
1660
1661
1661 return super_admin_rows + owner_row + perm_rows
1662 return super_admin_rows + owner_row + perm_rows
1662
1663
1663 def permission_user_groups(self):
1664 def permission_user_groups(self):
1664 q = UserGroupRepoToPerm.query().filter(
1665 q = UserGroupRepoToPerm.query().filter(
1665 UserGroupRepoToPerm.repository == self)
1666 UserGroupRepoToPerm.repository == self)
1666 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1667 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1667 joinedload(UserGroupRepoToPerm.users_group),
1668 joinedload(UserGroupRepoToPerm.users_group),
1668 joinedload(UserGroupRepoToPerm.permission),)
1669 joinedload(UserGroupRepoToPerm.permission),)
1669
1670
1670 perm_rows = []
1671 perm_rows = []
1671 for _user_group in q.all():
1672 for _user_group in q.all():
1672 usr = AttributeDict(_user_group.users_group.get_dict())
1673 usr = AttributeDict(_user_group.users_group.get_dict())
1673 usr.permission = _user_group.permission.permission_name
1674 usr.permission = _user_group.permission.permission_name
1674 perm_rows.append(usr)
1675 perm_rows.append(usr)
1675
1676
1676 return perm_rows
1677 return perm_rows
1677
1678
1678 def get_api_data(self, include_secrets=False):
1679 def get_api_data(self, include_secrets=False):
1679 """
1680 """
1680 Common function for generating repo api data
1681 Common function for generating repo api data
1681
1682
1682 :param include_secrets: See :meth:`User.get_api_data`.
1683 :param include_secrets: See :meth:`User.get_api_data`.
1683
1684
1684 """
1685 """
1685 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1686 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1686 # move this methods on models level.
1687 # move this methods on models level.
1687 from rhodecode.model.settings import SettingsModel
1688 from rhodecode.model.settings import SettingsModel
1688
1689
1689 repo = self
1690 repo = self
1690 _user_id, _time, _reason = self.locked
1691 _user_id, _time, _reason = self.locked
1691
1692
1692 data = {
1693 data = {
1693 'repo_id': repo.repo_id,
1694 'repo_id': repo.repo_id,
1694 'repo_name': repo.repo_name,
1695 'repo_name': repo.repo_name,
1695 'repo_type': repo.repo_type,
1696 'repo_type': repo.repo_type,
1696 'clone_uri': repo.clone_uri or '',
1697 'clone_uri': repo.clone_uri or '',
1697 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1698 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1698 'private': repo.private,
1699 'private': repo.private,
1699 'created_on': repo.created_on,
1700 'created_on': repo.created_on,
1700 'description': repo.description,
1701 'description': repo.description,
1701 'landing_rev': repo.landing_rev,
1702 'landing_rev': repo.landing_rev,
1702 'owner': repo.user.username,
1703 'owner': repo.user.username,
1703 'fork_of': repo.fork.repo_name if repo.fork else None,
1704 'fork_of': repo.fork.repo_name if repo.fork else None,
1704 'enable_statistics': repo.enable_statistics,
1705 'enable_statistics': repo.enable_statistics,
1705 'enable_locking': repo.enable_locking,
1706 'enable_locking': repo.enable_locking,
1706 'enable_downloads': repo.enable_downloads,
1707 'enable_downloads': repo.enable_downloads,
1707 'last_changeset': repo.changeset_cache,
1708 'last_changeset': repo.changeset_cache,
1708 'locked_by': User.get(_user_id).get_api_data(
1709 'locked_by': User.get(_user_id).get_api_data(
1709 include_secrets=include_secrets) if _user_id else None,
1710 include_secrets=include_secrets) if _user_id else None,
1710 'locked_date': time_to_datetime(_time) if _time else None,
1711 'locked_date': time_to_datetime(_time) if _time else None,
1711 'lock_reason': _reason if _reason else None,
1712 'lock_reason': _reason if _reason else None,
1712 }
1713 }
1713
1714
1714 # TODO: mikhail: should be per-repo settings here
1715 # TODO: mikhail: should be per-repo settings here
1715 rc_config = SettingsModel().get_all_settings()
1716 rc_config = SettingsModel().get_all_settings()
1716 repository_fields = str2bool(
1717 repository_fields = str2bool(
1717 rc_config.get('rhodecode_repository_fields'))
1718 rc_config.get('rhodecode_repository_fields'))
1718 if repository_fields:
1719 if repository_fields:
1719 for f in self.extra_fields:
1720 for f in self.extra_fields:
1720 data[f.field_key_prefixed] = f.field_value
1721 data[f.field_key_prefixed] = f.field_value
1721
1722
1722 return data
1723 return data
1723
1724
1724 @classmethod
1725 @classmethod
1725 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1726 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1726 if not lock_time:
1727 if not lock_time:
1727 lock_time = time.time()
1728 lock_time = time.time()
1728 if not lock_reason:
1729 if not lock_reason:
1729 lock_reason = cls.LOCK_AUTOMATIC
1730 lock_reason = cls.LOCK_AUTOMATIC
1730 repo.locked = [user_id, lock_time, lock_reason]
1731 repo.locked = [user_id, lock_time, lock_reason]
1731 Session().add(repo)
1732 Session().add(repo)
1732 Session().commit()
1733 Session().commit()
1733
1734
1734 @classmethod
1735 @classmethod
1735 def unlock(cls, repo):
1736 def unlock(cls, repo):
1736 repo.locked = None
1737 repo.locked = None
1737 Session().add(repo)
1738 Session().add(repo)
1738 Session().commit()
1739 Session().commit()
1739
1740
1740 @classmethod
1741 @classmethod
1741 def getlock(cls, repo):
1742 def getlock(cls, repo):
1742 return repo.locked
1743 return repo.locked
1743
1744
1744 def is_user_lock(self, user_id):
1745 def is_user_lock(self, user_id):
1745 if self.lock[0]:
1746 if self.lock[0]:
1746 lock_user_id = safe_int(self.lock[0])
1747 lock_user_id = safe_int(self.lock[0])
1747 user_id = safe_int(user_id)
1748 user_id = safe_int(user_id)
1748 # both are ints, and they are equal
1749 # both are ints, and they are equal
1749 return all([lock_user_id, user_id]) and lock_user_id == user_id
1750 return all([lock_user_id, user_id]) and lock_user_id == user_id
1750
1751
1751 return False
1752 return False
1752
1753
1753 def get_locking_state(self, action, user_id, only_when_enabled=True):
1754 def get_locking_state(self, action, user_id, only_when_enabled=True):
1754 """
1755 """
1755 Checks locking on this repository, if locking is enabled and lock is
1756 Checks locking on this repository, if locking is enabled and lock is
1756 present returns a tuple of make_lock, locked, locked_by.
1757 present returns a tuple of make_lock, locked, locked_by.
1757 make_lock can have 3 states None (do nothing) True, make lock
1758 make_lock can have 3 states None (do nothing) True, make lock
1758 False release lock, This value is later propagated to hooks, which
1759 False release lock, This value is later propagated to hooks, which
1759 do the locking. Think about this as signals passed to hooks what to do.
1760 do the locking. Think about this as signals passed to hooks what to do.
1760
1761
1761 """
1762 """
1762 # TODO: johbo: This is part of the business logic and should be moved
1763 # TODO: johbo: This is part of the business logic and should be moved
1763 # into the RepositoryModel.
1764 # into the RepositoryModel.
1764
1765
1765 if action not in ('push', 'pull'):
1766 if action not in ('push', 'pull'):
1766 raise ValueError("Invalid action value: %s" % repr(action))
1767 raise ValueError("Invalid action value: %s" % repr(action))
1767
1768
1768 # defines if locked error should be thrown to user
1769 # defines if locked error should be thrown to user
1769 currently_locked = False
1770 currently_locked = False
1770 # defines if new lock should be made, tri-state
1771 # defines if new lock should be made, tri-state
1771 make_lock = None
1772 make_lock = None
1772 repo = self
1773 repo = self
1773 user = User.get(user_id)
1774 user = User.get(user_id)
1774
1775
1775 lock_info = repo.locked
1776 lock_info = repo.locked
1776
1777
1777 if repo and (repo.enable_locking or not only_when_enabled):
1778 if repo and (repo.enable_locking or not only_when_enabled):
1778 if action == 'push':
1779 if action == 'push':
1779 # check if it's already locked !, if it is compare users
1780 # check if it's already locked !, if it is compare users
1780 locked_by_user_id = lock_info[0]
1781 locked_by_user_id = lock_info[0]
1781 if user.user_id == locked_by_user_id:
1782 if user.user_id == locked_by_user_id:
1782 log.debug(
1783 log.debug(
1783 'Got `push` action from user %s, now unlocking', user)
1784 'Got `push` action from user %s, now unlocking', user)
1784 # unlock if we have push from user who locked
1785 # unlock if we have push from user who locked
1785 make_lock = False
1786 make_lock = False
1786 else:
1787 else:
1787 # we're not the same user who locked, ban with
1788 # we're not the same user who locked, ban with
1788 # code defined in settings (default is 423 HTTP Locked) !
1789 # code defined in settings (default is 423 HTTP Locked) !
1789 log.debug('Repo %s is currently locked by %s', repo, user)
1790 log.debug('Repo %s is currently locked by %s', repo, user)
1790 currently_locked = True
1791 currently_locked = True
1791 elif action == 'pull':
1792 elif action == 'pull':
1792 # [0] user [1] date
1793 # [0] user [1] date
1793 if lock_info[0] and lock_info[1]:
1794 if lock_info[0] and lock_info[1]:
1794 log.debug('Repo %s is currently locked by %s', repo, user)
1795 log.debug('Repo %s is currently locked by %s', repo, user)
1795 currently_locked = True
1796 currently_locked = True
1796 else:
1797 else:
1797 log.debug('Setting lock on repo %s by %s', repo, user)
1798 log.debug('Setting lock on repo %s by %s', repo, user)
1798 make_lock = True
1799 make_lock = True
1799
1800
1800 else:
1801 else:
1801 log.debug('Repository %s do not have locking enabled', repo)
1802 log.debug('Repository %s do not have locking enabled', repo)
1802
1803
1803 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1804 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1804 make_lock, currently_locked, lock_info)
1805 make_lock, currently_locked, lock_info)
1805
1806
1806 from rhodecode.lib.auth import HasRepoPermissionAny
1807 from rhodecode.lib.auth import HasRepoPermissionAny
1807 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1808 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1808 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1809 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1809 # if we don't have at least write permission we cannot make a lock
1810 # if we don't have at least write permission we cannot make a lock
1810 log.debug('lock state reset back to FALSE due to lack '
1811 log.debug('lock state reset back to FALSE due to lack '
1811 'of at least read permission')
1812 'of at least read permission')
1812 make_lock = False
1813 make_lock = False
1813
1814
1814 return make_lock, currently_locked, lock_info
1815 return make_lock, currently_locked, lock_info
1815
1816
1816 @property
1817 @property
1817 def last_db_change(self):
1818 def last_db_change(self):
1818 return self.updated_on
1819 return self.updated_on
1819
1820
1820 @property
1821 @property
1821 def clone_uri_hidden(self):
1822 def clone_uri_hidden(self):
1822 clone_uri = self.clone_uri
1823 clone_uri = self.clone_uri
1823 if clone_uri:
1824 if clone_uri:
1824 import urlobject
1825 import urlobject
1825 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1826 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1826 if url_obj.password:
1827 if url_obj.password:
1827 clone_uri = url_obj.with_password('*****')
1828 clone_uri = url_obj.with_password('*****')
1828 return clone_uri
1829 return clone_uri
1829
1830
1830 def clone_url(self, **override):
1831 def clone_url(self, **override):
1831 qualified_home_url = url('home', qualified=True)
1832 qualified_home_url = url('home', qualified=True)
1832
1833
1833 uri_tmpl = None
1834 uri_tmpl = None
1834 if 'with_id' in override:
1835 if 'with_id' in override:
1835 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1836 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1836 del override['with_id']
1837 del override['with_id']
1837
1838
1838 if 'uri_tmpl' in override:
1839 if 'uri_tmpl' in override:
1839 uri_tmpl = override['uri_tmpl']
1840 uri_tmpl = override['uri_tmpl']
1840 del override['uri_tmpl']
1841 del override['uri_tmpl']
1841
1842
1842 # we didn't override our tmpl from **overrides
1843 # we didn't override our tmpl from **overrides
1843 if not uri_tmpl:
1844 if not uri_tmpl:
1844 uri_tmpl = self.DEFAULT_CLONE_URI
1845 uri_tmpl = self.DEFAULT_CLONE_URI
1845 try:
1846 try:
1846 from pylons import tmpl_context as c
1847 from pylons import tmpl_context as c
1847 uri_tmpl = c.clone_uri_tmpl
1848 uri_tmpl = c.clone_uri_tmpl
1848 except Exception:
1849 except Exception:
1849 # in any case if we call this outside of request context,
1850 # in any case if we call this outside of request context,
1850 # ie, not having tmpl_context set up
1851 # ie, not having tmpl_context set up
1851 pass
1852 pass
1852
1853
1853 return get_clone_url(uri_tmpl=uri_tmpl,
1854 return get_clone_url(uri_tmpl=uri_tmpl,
1854 qualifed_home_url=qualified_home_url,
1855 qualifed_home_url=qualified_home_url,
1855 repo_name=self.repo_name,
1856 repo_name=self.repo_name,
1856 repo_id=self.repo_id, **override)
1857 repo_id=self.repo_id, **override)
1857
1858
1858 def set_state(self, state):
1859 def set_state(self, state):
1859 self.repo_state = state
1860 self.repo_state = state
1860 Session().add(self)
1861 Session().add(self)
1861 #==========================================================================
1862 #==========================================================================
1862 # SCM PROPERTIES
1863 # SCM PROPERTIES
1863 #==========================================================================
1864 #==========================================================================
1864
1865
1865 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1866 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1866 return get_commit_safe(
1867 return get_commit_safe(
1867 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1868 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1868
1869
1869 def get_changeset(self, rev=None, pre_load=None):
1870 def get_changeset(self, rev=None, pre_load=None):
1870 warnings.warn("Use get_commit", DeprecationWarning)
1871 warnings.warn("Use get_commit", DeprecationWarning)
1871 commit_id = None
1872 commit_id = None
1872 commit_idx = None
1873 commit_idx = None
1873 if isinstance(rev, basestring):
1874 if isinstance(rev, basestring):
1874 commit_id = rev
1875 commit_id = rev
1875 else:
1876 else:
1876 commit_idx = rev
1877 commit_idx = rev
1877 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1878 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1878 pre_load=pre_load)
1879 pre_load=pre_load)
1879
1880
1880 def get_landing_commit(self):
1881 def get_landing_commit(self):
1881 """
1882 """
1882 Returns landing commit, or if that doesn't exist returns the tip
1883 Returns landing commit, or if that doesn't exist returns the tip
1883 """
1884 """
1884 _rev_type, _rev = self.landing_rev
1885 _rev_type, _rev = self.landing_rev
1885 commit = self.get_commit(_rev)
1886 commit = self.get_commit(_rev)
1886 if isinstance(commit, EmptyCommit):
1887 if isinstance(commit, EmptyCommit):
1887 return self.get_commit()
1888 return self.get_commit()
1888 return commit
1889 return commit
1889
1890
1890 def update_commit_cache(self, cs_cache=None, config=None):
1891 def update_commit_cache(self, cs_cache=None, config=None):
1891 """
1892 """
1892 Update cache of last changeset for repository, keys should be::
1893 Update cache of last changeset for repository, keys should be::
1893
1894
1894 short_id
1895 short_id
1895 raw_id
1896 raw_id
1896 revision
1897 revision
1897 parents
1898 parents
1898 message
1899 message
1899 date
1900 date
1900 author
1901 author
1901
1902
1902 :param cs_cache:
1903 :param cs_cache:
1903 """
1904 """
1904 from rhodecode.lib.vcs.backends.base import BaseChangeset
1905 from rhodecode.lib.vcs.backends.base import BaseChangeset
1905 if cs_cache is None:
1906 if cs_cache is None:
1906 # use no-cache version here
1907 # use no-cache version here
1907 scm_repo = self.scm_instance(cache=False, config=config)
1908 scm_repo = self.scm_instance(cache=False, config=config)
1908 if scm_repo:
1909 if scm_repo:
1909 cs_cache = scm_repo.get_commit(
1910 cs_cache = scm_repo.get_commit(
1910 pre_load=["author", "date", "message", "parents"])
1911 pre_load=["author", "date", "message", "parents"])
1911 else:
1912 else:
1912 cs_cache = EmptyCommit()
1913 cs_cache = EmptyCommit()
1913
1914
1914 if isinstance(cs_cache, BaseChangeset):
1915 if isinstance(cs_cache, BaseChangeset):
1915 cs_cache = cs_cache.__json__()
1916 cs_cache = cs_cache.__json__()
1916
1917
1917 def is_outdated(new_cs_cache):
1918 def is_outdated(new_cs_cache):
1918 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1919 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1919 new_cs_cache['revision'] != self.changeset_cache['revision']):
1920 new_cs_cache['revision'] != self.changeset_cache['revision']):
1920 return True
1921 return True
1921 return False
1922 return False
1922
1923
1923 # check if we have maybe already latest cached revision
1924 # check if we have maybe already latest cached revision
1924 if is_outdated(cs_cache) or not self.changeset_cache:
1925 if is_outdated(cs_cache) or not self.changeset_cache:
1925 _default = datetime.datetime.fromtimestamp(0)
1926 _default = datetime.datetime.fromtimestamp(0)
1926 last_change = cs_cache.get('date') or _default
1927 last_change = cs_cache.get('date') or _default
1927 log.debug('updated repo %s with new cs cache %s',
1928 log.debug('updated repo %s with new cs cache %s',
1928 self.repo_name, cs_cache)
1929 self.repo_name, cs_cache)
1929 self.updated_on = last_change
1930 self.updated_on = last_change
1930 self.changeset_cache = cs_cache
1931 self.changeset_cache = cs_cache
1931 Session().add(self)
1932 Session().add(self)
1932 Session().commit()
1933 Session().commit()
1933 else:
1934 else:
1934 log.debug('Skipping update_commit_cache for repo:`%s` '
1935 log.debug('Skipping update_commit_cache for repo:`%s` '
1935 'commit already with latest changes', self.repo_name)
1936 'commit already with latest changes', self.repo_name)
1936
1937
1937 @property
1938 @property
1938 def tip(self):
1939 def tip(self):
1939 return self.get_commit('tip')
1940 return self.get_commit('tip')
1940
1941
1941 @property
1942 @property
1942 def author(self):
1943 def author(self):
1943 return self.tip.author
1944 return self.tip.author
1944
1945
1945 @property
1946 @property
1946 def last_change(self):
1947 def last_change(self):
1947 return self.scm_instance().last_change
1948 return self.scm_instance().last_change
1948
1949
1949 def get_comments(self, revisions=None):
1950 def get_comments(self, revisions=None):
1950 """
1951 """
1951 Returns comments for this repository grouped by revisions
1952 Returns comments for this repository grouped by revisions
1952
1953
1953 :param revisions: filter query by revisions only
1954 :param revisions: filter query by revisions only
1954 """
1955 """
1955 cmts = ChangesetComment.query()\
1956 cmts = ChangesetComment.query()\
1956 .filter(ChangesetComment.repo == self)
1957 .filter(ChangesetComment.repo == self)
1957 if revisions:
1958 if revisions:
1958 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1959 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1959 grouped = collections.defaultdict(list)
1960 grouped = collections.defaultdict(list)
1960 for cmt in cmts.all():
1961 for cmt in cmts.all():
1961 grouped[cmt.revision].append(cmt)
1962 grouped[cmt.revision].append(cmt)
1962 return grouped
1963 return grouped
1963
1964
1964 def statuses(self, revisions=None):
1965 def statuses(self, revisions=None):
1965 """
1966 """
1966 Returns statuses for this repository
1967 Returns statuses for this repository
1967
1968
1968 :param revisions: list of revisions to get statuses for
1969 :param revisions: list of revisions to get statuses for
1969 """
1970 """
1970 statuses = ChangesetStatus.query()\
1971 statuses = ChangesetStatus.query()\
1971 .filter(ChangesetStatus.repo == self)\
1972 .filter(ChangesetStatus.repo == self)\
1972 .filter(ChangesetStatus.version == 0)
1973 .filter(ChangesetStatus.version == 0)
1973
1974
1974 if revisions:
1975 if revisions:
1975 # Try doing the filtering in chunks to avoid hitting limits
1976 # Try doing the filtering in chunks to avoid hitting limits
1976 size = 500
1977 size = 500
1977 status_results = []
1978 status_results = []
1978 for chunk in xrange(0, len(revisions), size):
1979 for chunk in xrange(0, len(revisions), size):
1979 status_results += statuses.filter(
1980 status_results += statuses.filter(
1980 ChangesetStatus.revision.in_(
1981 ChangesetStatus.revision.in_(
1981 revisions[chunk: chunk+size])
1982 revisions[chunk: chunk+size])
1982 ).all()
1983 ).all()
1983 else:
1984 else:
1984 status_results = statuses.all()
1985 status_results = statuses.all()
1985
1986
1986 grouped = {}
1987 grouped = {}
1987
1988
1988 # maybe we have open new pullrequest without a status?
1989 # maybe we have open new pullrequest without a status?
1989 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1990 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1990 status_lbl = ChangesetStatus.get_status_lbl(stat)
1991 status_lbl = ChangesetStatus.get_status_lbl(stat)
1991 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1992 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1992 for rev in pr.revisions:
1993 for rev in pr.revisions:
1993 pr_id = pr.pull_request_id
1994 pr_id = pr.pull_request_id
1994 pr_repo = pr.target_repo.repo_name
1995 pr_repo = pr.target_repo.repo_name
1995 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1996 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1996
1997
1997 for stat in status_results:
1998 for stat in status_results:
1998 pr_id = pr_repo = None
1999 pr_id = pr_repo = None
1999 if stat.pull_request:
2000 if stat.pull_request:
2000 pr_id = stat.pull_request.pull_request_id
2001 pr_id = stat.pull_request.pull_request_id
2001 pr_repo = stat.pull_request.target_repo.repo_name
2002 pr_repo = stat.pull_request.target_repo.repo_name
2002 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2003 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2003 pr_id, pr_repo]
2004 pr_id, pr_repo]
2004 return grouped
2005 return grouped
2005
2006
2006 # ==========================================================================
2007 # ==========================================================================
2007 # SCM CACHE INSTANCE
2008 # SCM CACHE INSTANCE
2008 # ==========================================================================
2009 # ==========================================================================
2009
2010
2010 def scm_instance(self, **kwargs):
2011 def scm_instance(self, **kwargs):
2011 import rhodecode
2012 import rhodecode
2012
2013
2013 # Passing a config will not hit the cache currently only used
2014 # Passing a config will not hit the cache currently only used
2014 # for repo2dbmapper
2015 # for repo2dbmapper
2015 config = kwargs.pop('config', None)
2016 config = kwargs.pop('config', None)
2016 cache = kwargs.pop('cache', None)
2017 cache = kwargs.pop('cache', None)
2017 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2018 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2018 # if cache is NOT defined use default global, else we have a full
2019 # if cache is NOT defined use default global, else we have a full
2019 # control over cache behaviour
2020 # control over cache behaviour
2020 if cache is None and full_cache and not config:
2021 if cache is None and full_cache and not config:
2021 return self._get_instance_cached()
2022 return self._get_instance_cached()
2022 return self._get_instance(cache=bool(cache), config=config)
2023 return self._get_instance(cache=bool(cache), config=config)
2023
2024
2024 def _get_instance_cached(self):
2025 def _get_instance_cached(self):
2025 @cache_region('long_term')
2026 @cache_region('long_term')
2026 def _get_repo(cache_key):
2027 def _get_repo(cache_key):
2027 return self._get_instance()
2028 return self._get_instance()
2028
2029
2029 invalidator_context = CacheKey.repo_context_cache(
2030 invalidator_context = CacheKey.repo_context_cache(
2030 _get_repo, self.repo_name, None, thread_scoped=True)
2031 _get_repo, self.repo_name, None, thread_scoped=True)
2031
2032
2032 with invalidator_context as context:
2033 with invalidator_context as context:
2033 context.invalidate()
2034 context.invalidate()
2034 repo = context.compute()
2035 repo = context.compute()
2035
2036
2036 return repo
2037 return repo
2037
2038
2038 def _get_instance(self, cache=True, config=None):
2039 def _get_instance(self, cache=True, config=None):
2039 config = config or self._config
2040 config = config or self._config
2040 custom_wire = {
2041 custom_wire = {
2041 'cache': cache # controls the vcs.remote cache
2042 'cache': cache # controls the vcs.remote cache
2042 }
2043 }
2043 repo = get_vcs_instance(
2044 repo = get_vcs_instance(
2044 repo_path=safe_str(self.repo_full_path),
2045 repo_path=safe_str(self.repo_full_path),
2045 config=config,
2046 config=config,
2046 with_wire=custom_wire,
2047 with_wire=custom_wire,
2047 create=False,
2048 create=False,
2048 _vcs_alias=self.repo_type)
2049 _vcs_alias=self.repo_type)
2049
2050
2050 return repo
2051 return repo
2051
2052
2052 def __json__(self):
2053 def __json__(self):
2053 return {'landing_rev': self.landing_rev}
2054 return {'landing_rev': self.landing_rev}
2054
2055
2055 def get_dict(self):
2056 def get_dict(self):
2056
2057
2057 # Since we transformed `repo_name` to a hybrid property, we need to
2058 # Since we transformed `repo_name` to a hybrid property, we need to
2058 # keep compatibility with the code which uses `repo_name` field.
2059 # keep compatibility with the code which uses `repo_name` field.
2059
2060
2060 result = super(Repository, self).get_dict()
2061 result = super(Repository, self).get_dict()
2061 result['repo_name'] = result.pop('_repo_name', None)
2062 result['repo_name'] = result.pop('_repo_name', None)
2062 return result
2063 return result
2063
2064
2064
2065
2065 class RepoGroup(Base, BaseModel):
2066 class RepoGroup(Base, BaseModel):
2066 __tablename__ = 'groups'
2067 __tablename__ = 'groups'
2067 __table_args__ = (
2068 __table_args__ = (
2068 UniqueConstraint('group_name', 'group_parent_id'),
2069 UniqueConstraint('group_name', 'group_parent_id'),
2069 CheckConstraint('group_id != group_parent_id'),
2070 CheckConstraint('group_id != group_parent_id'),
2070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2072 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2072 )
2073 )
2073 __mapper_args__ = {'order_by': 'group_name'}
2074 __mapper_args__ = {'order_by': 'group_name'}
2074
2075
2075 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2076 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2076
2077
2077 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2078 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2078 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2079 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2079 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2080 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2080 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2081 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2081 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2082 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2083 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2084 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2084 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2085 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2085
2086
2086 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2087 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2087 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2088 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2088 parent_group = relationship('RepoGroup', remote_side=group_id)
2089 parent_group = relationship('RepoGroup', remote_side=group_id)
2089 user = relationship('User')
2090 user = relationship('User')
2090 integrations = relationship('Integration',
2091 integrations = relationship('Integration',
2091 cascade="all, delete, delete-orphan")
2092 cascade="all, delete, delete-orphan")
2092
2093
2093 def __init__(self, group_name='', parent_group=None):
2094 def __init__(self, group_name='', parent_group=None):
2094 self.group_name = group_name
2095 self.group_name = group_name
2095 self.parent_group = parent_group
2096 self.parent_group = parent_group
2096
2097
2097 def __unicode__(self):
2098 def __unicode__(self):
2098 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2099 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2099 self.group_name)
2100 self.group_name)
2100
2101
2101 @classmethod
2102 @classmethod
2102 def _generate_choice(cls, repo_group):
2103 def _generate_choice(cls, repo_group):
2103 from webhelpers.html import literal as _literal
2104 from webhelpers.html import literal as _literal
2104 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2105 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2105 return repo_group.group_id, _name(repo_group.full_path_splitted)
2106 return repo_group.group_id, _name(repo_group.full_path_splitted)
2106
2107
2107 @classmethod
2108 @classmethod
2108 def groups_choices(cls, groups=None, show_empty_group=True):
2109 def groups_choices(cls, groups=None, show_empty_group=True):
2109 if not groups:
2110 if not groups:
2110 groups = cls.query().all()
2111 groups = cls.query().all()
2111
2112
2112 repo_groups = []
2113 repo_groups = []
2113 if show_empty_group:
2114 if show_empty_group:
2114 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2115 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2115
2116
2116 repo_groups.extend([cls._generate_choice(x) for x in groups])
2117 repo_groups.extend([cls._generate_choice(x) for x in groups])
2117
2118
2118 repo_groups = sorted(
2119 repo_groups = sorted(
2119 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2120 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2120 return repo_groups
2121 return repo_groups
2121
2122
2122 @classmethod
2123 @classmethod
2123 def url_sep(cls):
2124 def url_sep(cls):
2124 return URL_SEP
2125 return URL_SEP
2125
2126
2126 @classmethod
2127 @classmethod
2127 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2128 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2128 if case_insensitive:
2129 if case_insensitive:
2129 gr = cls.query().filter(func.lower(cls.group_name)
2130 gr = cls.query().filter(func.lower(cls.group_name)
2130 == func.lower(group_name))
2131 == func.lower(group_name))
2131 else:
2132 else:
2132 gr = cls.query().filter(cls.group_name == group_name)
2133 gr = cls.query().filter(cls.group_name == group_name)
2133 if cache:
2134 if cache:
2134 gr = gr.options(FromCache(
2135 gr = gr.options(FromCache(
2135 "sql_cache_short",
2136 "sql_cache_short",
2136 "get_group_%s" % _hash_key(group_name)))
2137 "get_group_%s" % _hash_key(group_name)))
2137 return gr.scalar()
2138 return gr.scalar()
2138
2139
2139 @classmethod
2140 @classmethod
2140 def get_user_personal_repo_group(cls, user_id):
2141 def get_user_personal_repo_group(cls, user_id):
2141 user = User.get(user_id)
2142 user = User.get(user_id)
2142 return cls.query()\
2143 return cls.query()\
2143 .filter(cls.personal == true())\
2144 .filter(cls.personal == true())\
2144 .filter(cls.user == user).scalar()
2145 .filter(cls.user == user).scalar()
2145
2146
2146 @classmethod
2147 @classmethod
2147 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2148 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2148 case_insensitive=True):
2149 case_insensitive=True):
2149 q = RepoGroup.query()
2150 q = RepoGroup.query()
2150
2151
2151 if not isinstance(user_id, Optional):
2152 if not isinstance(user_id, Optional):
2152 q = q.filter(RepoGroup.user_id == user_id)
2153 q = q.filter(RepoGroup.user_id == user_id)
2153
2154
2154 if not isinstance(group_id, Optional):
2155 if not isinstance(group_id, Optional):
2155 q = q.filter(RepoGroup.group_parent_id == group_id)
2156 q = q.filter(RepoGroup.group_parent_id == group_id)
2156
2157
2157 if case_insensitive:
2158 if case_insensitive:
2158 q = q.order_by(func.lower(RepoGroup.group_name))
2159 q = q.order_by(func.lower(RepoGroup.group_name))
2159 else:
2160 else:
2160 q = q.order_by(RepoGroup.group_name)
2161 q = q.order_by(RepoGroup.group_name)
2161 return q.all()
2162 return q.all()
2162
2163
2163 @property
2164 @property
2164 def parents(self):
2165 def parents(self):
2165 parents_recursion_limit = 10
2166 parents_recursion_limit = 10
2166 groups = []
2167 groups = []
2167 if self.parent_group is None:
2168 if self.parent_group is None:
2168 return groups
2169 return groups
2169 cur_gr = self.parent_group
2170 cur_gr = self.parent_group
2170 groups.insert(0, cur_gr)
2171 groups.insert(0, cur_gr)
2171 cnt = 0
2172 cnt = 0
2172 while 1:
2173 while 1:
2173 cnt += 1
2174 cnt += 1
2174 gr = getattr(cur_gr, 'parent_group', None)
2175 gr = getattr(cur_gr, 'parent_group', None)
2175 cur_gr = cur_gr.parent_group
2176 cur_gr = cur_gr.parent_group
2176 if gr is None:
2177 if gr is None:
2177 break
2178 break
2178 if cnt == parents_recursion_limit:
2179 if cnt == parents_recursion_limit:
2179 # this will prevent accidental infinit loops
2180 # this will prevent accidental infinit loops
2180 log.error(('more than %s parents found for group %s, stopping '
2181 log.error(('more than %s parents found for group %s, stopping '
2181 'recursive parent fetching' % (parents_recursion_limit, self)))
2182 'recursive parent fetching' % (parents_recursion_limit, self)))
2182 break
2183 break
2183
2184
2184 groups.insert(0, gr)
2185 groups.insert(0, gr)
2185 return groups
2186 return groups
2186
2187
2187 @property
2188 @property
2188 def children(self):
2189 def children(self):
2189 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2190 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2190
2191
2191 @property
2192 @property
2192 def name(self):
2193 def name(self):
2193 return self.group_name.split(RepoGroup.url_sep())[-1]
2194 return self.group_name.split(RepoGroup.url_sep())[-1]
2194
2195
2195 @property
2196 @property
2196 def full_path(self):
2197 def full_path(self):
2197 return self.group_name
2198 return self.group_name
2198
2199
2199 @property
2200 @property
2200 def full_path_splitted(self):
2201 def full_path_splitted(self):
2201 return self.group_name.split(RepoGroup.url_sep())
2202 return self.group_name.split(RepoGroup.url_sep())
2202
2203
2203 @property
2204 @property
2204 def repositories(self):
2205 def repositories(self):
2205 return Repository.query()\
2206 return Repository.query()\
2206 .filter(Repository.group == self)\
2207 .filter(Repository.group == self)\
2207 .order_by(Repository.repo_name)
2208 .order_by(Repository.repo_name)
2208
2209
2209 @property
2210 @property
2210 def repositories_recursive_count(self):
2211 def repositories_recursive_count(self):
2211 cnt = self.repositories.count()
2212 cnt = self.repositories.count()
2212
2213
2213 def children_count(group):
2214 def children_count(group):
2214 cnt = 0
2215 cnt = 0
2215 for child in group.children:
2216 for child in group.children:
2216 cnt += child.repositories.count()
2217 cnt += child.repositories.count()
2217 cnt += children_count(child)
2218 cnt += children_count(child)
2218 return cnt
2219 return cnt
2219
2220
2220 return cnt + children_count(self)
2221 return cnt + children_count(self)
2221
2222
2222 def _recursive_objects(self, include_repos=True):
2223 def _recursive_objects(self, include_repos=True):
2223 all_ = []
2224 all_ = []
2224
2225
2225 def _get_members(root_gr):
2226 def _get_members(root_gr):
2226 if include_repos:
2227 if include_repos:
2227 for r in root_gr.repositories:
2228 for r in root_gr.repositories:
2228 all_.append(r)
2229 all_.append(r)
2229 childs = root_gr.children.all()
2230 childs = root_gr.children.all()
2230 if childs:
2231 if childs:
2231 for gr in childs:
2232 for gr in childs:
2232 all_.append(gr)
2233 all_.append(gr)
2233 _get_members(gr)
2234 _get_members(gr)
2234
2235
2235 _get_members(self)
2236 _get_members(self)
2236 return [self] + all_
2237 return [self] + all_
2237
2238
2238 def recursive_groups_and_repos(self):
2239 def recursive_groups_and_repos(self):
2239 """
2240 """
2240 Recursive return all groups, with repositories in those groups
2241 Recursive return all groups, with repositories in those groups
2241 """
2242 """
2242 return self._recursive_objects()
2243 return self._recursive_objects()
2243
2244
2244 def recursive_groups(self):
2245 def recursive_groups(self):
2245 """
2246 """
2246 Returns all children groups for this group including children of children
2247 Returns all children groups for this group including children of children
2247 """
2248 """
2248 return self._recursive_objects(include_repos=False)
2249 return self._recursive_objects(include_repos=False)
2249
2250
2250 def get_new_name(self, group_name):
2251 def get_new_name(self, group_name):
2251 """
2252 """
2252 returns new full group name based on parent and new name
2253 returns new full group name based on parent and new name
2253
2254
2254 :param group_name:
2255 :param group_name:
2255 """
2256 """
2256 path_prefix = (self.parent_group.full_path_splitted if
2257 path_prefix = (self.parent_group.full_path_splitted if
2257 self.parent_group else [])
2258 self.parent_group else [])
2258 return RepoGroup.url_sep().join(path_prefix + [group_name])
2259 return RepoGroup.url_sep().join(path_prefix + [group_name])
2259
2260
2260 def permissions(self, with_admins=True, with_owner=True):
2261 def permissions(self, with_admins=True, with_owner=True):
2261 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2262 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2262 q = q.options(joinedload(UserRepoGroupToPerm.group),
2263 q = q.options(joinedload(UserRepoGroupToPerm.group),
2263 joinedload(UserRepoGroupToPerm.user),
2264 joinedload(UserRepoGroupToPerm.user),
2264 joinedload(UserRepoGroupToPerm.permission),)
2265 joinedload(UserRepoGroupToPerm.permission),)
2265
2266
2266 # get owners and admins and permissions. We do a trick of re-writing
2267 # get owners and admins and permissions. We do a trick of re-writing
2267 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2268 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2268 # has a global reference and changing one object propagates to all
2269 # has a global reference and changing one object propagates to all
2269 # others. This means if admin is also an owner admin_row that change
2270 # others. This means if admin is also an owner admin_row that change
2270 # would propagate to both objects
2271 # would propagate to both objects
2271 perm_rows = []
2272 perm_rows = []
2272 for _usr in q.all():
2273 for _usr in q.all():
2273 usr = AttributeDict(_usr.user.get_dict())
2274 usr = AttributeDict(_usr.user.get_dict())
2274 usr.permission = _usr.permission.permission_name
2275 usr.permission = _usr.permission.permission_name
2275 perm_rows.append(usr)
2276 perm_rows.append(usr)
2276
2277
2277 # filter the perm rows by 'default' first and then sort them by
2278 # filter the perm rows by 'default' first and then sort them by
2278 # admin,write,read,none permissions sorted again alphabetically in
2279 # admin,write,read,none permissions sorted again alphabetically in
2279 # each group
2280 # each group
2280 perm_rows = sorted(perm_rows, key=display_sort)
2281 perm_rows = sorted(perm_rows, key=display_sort)
2281
2282
2282 _admin_perm = 'group.admin'
2283 _admin_perm = 'group.admin'
2283 owner_row = []
2284 owner_row = []
2284 if with_owner:
2285 if with_owner:
2285 usr = AttributeDict(self.user.get_dict())
2286 usr = AttributeDict(self.user.get_dict())
2286 usr.owner_row = True
2287 usr.owner_row = True
2287 usr.permission = _admin_perm
2288 usr.permission = _admin_perm
2288 owner_row.append(usr)
2289 owner_row.append(usr)
2289
2290
2290 super_admin_rows = []
2291 super_admin_rows = []
2291 if with_admins:
2292 if with_admins:
2292 for usr in User.get_all_super_admins():
2293 for usr in User.get_all_super_admins():
2293 # if this admin is also owner, don't double the record
2294 # if this admin is also owner, don't double the record
2294 if usr.user_id == owner_row[0].user_id:
2295 if usr.user_id == owner_row[0].user_id:
2295 owner_row[0].admin_row = True
2296 owner_row[0].admin_row = True
2296 else:
2297 else:
2297 usr = AttributeDict(usr.get_dict())
2298 usr = AttributeDict(usr.get_dict())
2298 usr.admin_row = True
2299 usr.admin_row = True
2299 usr.permission = _admin_perm
2300 usr.permission = _admin_perm
2300 super_admin_rows.append(usr)
2301 super_admin_rows.append(usr)
2301
2302
2302 return super_admin_rows + owner_row + perm_rows
2303 return super_admin_rows + owner_row + perm_rows
2303
2304
2304 def permission_user_groups(self):
2305 def permission_user_groups(self):
2305 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2306 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2306 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2307 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2307 joinedload(UserGroupRepoGroupToPerm.users_group),
2308 joinedload(UserGroupRepoGroupToPerm.users_group),
2308 joinedload(UserGroupRepoGroupToPerm.permission),)
2309 joinedload(UserGroupRepoGroupToPerm.permission),)
2309
2310
2310 perm_rows = []
2311 perm_rows = []
2311 for _user_group in q.all():
2312 for _user_group in q.all():
2312 usr = AttributeDict(_user_group.users_group.get_dict())
2313 usr = AttributeDict(_user_group.users_group.get_dict())
2313 usr.permission = _user_group.permission.permission_name
2314 usr.permission = _user_group.permission.permission_name
2314 perm_rows.append(usr)
2315 perm_rows.append(usr)
2315
2316
2316 return perm_rows
2317 return perm_rows
2317
2318
2318 def get_api_data(self):
2319 def get_api_data(self):
2319 """
2320 """
2320 Common function for generating api data
2321 Common function for generating api data
2321
2322
2322 """
2323 """
2323 group = self
2324 group = self
2324 data = {
2325 data = {
2325 'group_id': group.group_id,
2326 'group_id': group.group_id,
2326 'group_name': group.group_name,
2327 'group_name': group.group_name,
2327 'group_description': group.group_description,
2328 'group_description': group.group_description,
2328 'parent_group': group.parent_group.group_name if group.parent_group else None,
2329 'parent_group': group.parent_group.group_name if group.parent_group else None,
2329 'repositories': [x.repo_name for x in group.repositories],
2330 'repositories': [x.repo_name for x in group.repositories],
2330 'owner': group.user.username,
2331 'owner': group.user.username,
2331 }
2332 }
2332 return data
2333 return data
2333
2334
2334
2335
2335 class Permission(Base, BaseModel):
2336 class Permission(Base, BaseModel):
2336 __tablename__ = 'permissions'
2337 __tablename__ = 'permissions'
2337 __table_args__ = (
2338 __table_args__ = (
2338 Index('p_perm_name_idx', 'permission_name'),
2339 Index('p_perm_name_idx', 'permission_name'),
2339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2340 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2340 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2341 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2341 )
2342 )
2342 PERMS = [
2343 PERMS = [
2343 ('hg.admin', _('RhodeCode Super Administrator')),
2344 ('hg.admin', _('RhodeCode Super Administrator')),
2344
2345
2345 ('repository.none', _('Repository no access')),
2346 ('repository.none', _('Repository no access')),
2346 ('repository.read', _('Repository read access')),
2347 ('repository.read', _('Repository read access')),
2347 ('repository.write', _('Repository write access')),
2348 ('repository.write', _('Repository write access')),
2348 ('repository.admin', _('Repository admin access')),
2349 ('repository.admin', _('Repository admin access')),
2349
2350
2350 ('group.none', _('Repository group no access')),
2351 ('group.none', _('Repository group no access')),
2351 ('group.read', _('Repository group read access')),
2352 ('group.read', _('Repository group read access')),
2352 ('group.write', _('Repository group write access')),
2353 ('group.write', _('Repository group write access')),
2353 ('group.admin', _('Repository group admin access')),
2354 ('group.admin', _('Repository group admin access')),
2354
2355
2355 ('usergroup.none', _('User group no access')),
2356 ('usergroup.none', _('User group no access')),
2356 ('usergroup.read', _('User group read access')),
2357 ('usergroup.read', _('User group read access')),
2357 ('usergroup.write', _('User group write access')),
2358 ('usergroup.write', _('User group write access')),
2358 ('usergroup.admin', _('User group admin access')),
2359 ('usergroup.admin', _('User group admin access')),
2359
2360
2360 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2361 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2361 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2362 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2362
2363
2363 ('hg.usergroup.create.false', _('User Group creation disabled')),
2364 ('hg.usergroup.create.false', _('User Group creation disabled')),
2364 ('hg.usergroup.create.true', _('User Group creation enabled')),
2365 ('hg.usergroup.create.true', _('User Group creation enabled')),
2365
2366
2366 ('hg.create.none', _('Repository creation disabled')),
2367 ('hg.create.none', _('Repository creation disabled')),
2367 ('hg.create.repository', _('Repository creation enabled')),
2368 ('hg.create.repository', _('Repository creation enabled')),
2368 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2369 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2369 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2370 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2370
2371
2371 ('hg.fork.none', _('Repository forking disabled')),
2372 ('hg.fork.none', _('Repository forking disabled')),
2372 ('hg.fork.repository', _('Repository forking enabled')),
2373 ('hg.fork.repository', _('Repository forking enabled')),
2373
2374
2374 ('hg.register.none', _('Registration disabled')),
2375 ('hg.register.none', _('Registration disabled')),
2375 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2376 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2376 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2377 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2377
2378
2378 ('hg.password_reset.enabled', _('Password reset enabled')),
2379 ('hg.password_reset.enabled', _('Password reset enabled')),
2379 ('hg.password_reset.hidden', _('Password reset hidden')),
2380 ('hg.password_reset.hidden', _('Password reset hidden')),
2380 ('hg.password_reset.disabled', _('Password reset disabled')),
2381 ('hg.password_reset.disabled', _('Password reset disabled')),
2381
2382
2382 ('hg.extern_activate.manual', _('Manual activation of external account')),
2383 ('hg.extern_activate.manual', _('Manual activation of external account')),
2383 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2384 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2384
2385
2385 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2386 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2386 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2387 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2387 ]
2388 ]
2388
2389
2389 # definition of system default permissions for DEFAULT user
2390 # definition of system default permissions for DEFAULT user
2390 DEFAULT_USER_PERMISSIONS = [
2391 DEFAULT_USER_PERMISSIONS = [
2391 'repository.read',
2392 'repository.read',
2392 'group.read',
2393 'group.read',
2393 'usergroup.read',
2394 'usergroup.read',
2394 'hg.create.repository',
2395 'hg.create.repository',
2395 'hg.repogroup.create.false',
2396 'hg.repogroup.create.false',
2396 'hg.usergroup.create.false',
2397 'hg.usergroup.create.false',
2397 'hg.create.write_on_repogroup.true',
2398 'hg.create.write_on_repogroup.true',
2398 'hg.fork.repository',
2399 'hg.fork.repository',
2399 'hg.register.manual_activate',
2400 'hg.register.manual_activate',
2400 'hg.password_reset.enabled',
2401 'hg.password_reset.enabled',
2401 'hg.extern_activate.auto',
2402 'hg.extern_activate.auto',
2402 'hg.inherit_default_perms.true',
2403 'hg.inherit_default_perms.true',
2403 ]
2404 ]
2404
2405
2405 # defines which permissions are more important higher the more important
2406 # defines which permissions are more important higher the more important
2406 # Weight defines which permissions are more important.
2407 # Weight defines which permissions are more important.
2407 # The higher number the more important.
2408 # The higher number the more important.
2408 PERM_WEIGHTS = {
2409 PERM_WEIGHTS = {
2409 'repository.none': 0,
2410 'repository.none': 0,
2410 'repository.read': 1,
2411 'repository.read': 1,
2411 'repository.write': 3,
2412 'repository.write': 3,
2412 'repository.admin': 4,
2413 'repository.admin': 4,
2413
2414
2414 'group.none': 0,
2415 'group.none': 0,
2415 'group.read': 1,
2416 'group.read': 1,
2416 'group.write': 3,
2417 'group.write': 3,
2417 'group.admin': 4,
2418 'group.admin': 4,
2418
2419
2419 'usergroup.none': 0,
2420 'usergroup.none': 0,
2420 'usergroup.read': 1,
2421 'usergroup.read': 1,
2421 'usergroup.write': 3,
2422 'usergroup.write': 3,
2422 'usergroup.admin': 4,
2423 'usergroup.admin': 4,
2423
2424
2424 'hg.repogroup.create.false': 0,
2425 'hg.repogroup.create.false': 0,
2425 'hg.repogroup.create.true': 1,
2426 'hg.repogroup.create.true': 1,
2426
2427
2427 'hg.usergroup.create.false': 0,
2428 'hg.usergroup.create.false': 0,
2428 'hg.usergroup.create.true': 1,
2429 'hg.usergroup.create.true': 1,
2429
2430
2430 'hg.fork.none': 0,
2431 'hg.fork.none': 0,
2431 'hg.fork.repository': 1,
2432 'hg.fork.repository': 1,
2432 'hg.create.none': 0,
2433 'hg.create.none': 0,
2433 'hg.create.repository': 1
2434 'hg.create.repository': 1
2434 }
2435 }
2435
2436
2436 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2437 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2437 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2438 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2438 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2439 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2439
2440
2440 def __unicode__(self):
2441 def __unicode__(self):
2441 return u"<%s('%s:%s')>" % (
2442 return u"<%s('%s:%s')>" % (
2442 self.__class__.__name__, self.permission_id, self.permission_name
2443 self.__class__.__name__, self.permission_id, self.permission_name
2443 )
2444 )
2444
2445
2445 @classmethod
2446 @classmethod
2446 def get_by_key(cls, key):
2447 def get_by_key(cls, key):
2447 return cls.query().filter(cls.permission_name == key).scalar()
2448 return cls.query().filter(cls.permission_name == key).scalar()
2448
2449
2449 @classmethod
2450 @classmethod
2450 def get_default_repo_perms(cls, user_id, repo_id=None):
2451 def get_default_repo_perms(cls, user_id, repo_id=None):
2451 q = Session().query(UserRepoToPerm, Repository, Permission)\
2452 q = Session().query(UserRepoToPerm, Repository, Permission)\
2452 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2453 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2453 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2454 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2454 .filter(UserRepoToPerm.user_id == user_id)
2455 .filter(UserRepoToPerm.user_id == user_id)
2455 if repo_id:
2456 if repo_id:
2456 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2457 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2457 return q.all()
2458 return q.all()
2458
2459
2459 @classmethod
2460 @classmethod
2460 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2461 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2461 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2462 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2462 .join(
2463 .join(
2463 Permission,
2464 Permission,
2464 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2465 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2465 .join(
2466 .join(
2466 Repository,
2467 Repository,
2467 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2468 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2468 .join(
2469 .join(
2469 UserGroup,
2470 UserGroup,
2470 UserGroupRepoToPerm.users_group_id ==
2471 UserGroupRepoToPerm.users_group_id ==
2471 UserGroup.users_group_id)\
2472 UserGroup.users_group_id)\
2472 .join(
2473 .join(
2473 UserGroupMember,
2474 UserGroupMember,
2474 UserGroupRepoToPerm.users_group_id ==
2475 UserGroupRepoToPerm.users_group_id ==
2475 UserGroupMember.users_group_id)\
2476 UserGroupMember.users_group_id)\
2476 .filter(
2477 .filter(
2477 UserGroupMember.user_id == user_id,
2478 UserGroupMember.user_id == user_id,
2478 UserGroup.users_group_active == true())
2479 UserGroup.users_group_active == true())
2479 if repo_id:
2480 if repo_id:
2480 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2481 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2481 return q.all()
2482 return q.all()
2482
2483
2483 @classmethod
2484 @classmethod
2484 def get_default_group_perms(cls, user_id, repo_group_id=None):
2485 def get_default_group_perms(cls, user_id, repo_group_id=None):
2485 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2486 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2486 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2487 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2487 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2488 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2488 .filter(UserRepoGroupToPerm.user_id == user_id)
2489 .filter(UserRepoGroupToPerm.user_id == user_id)
2489 if repo_group_id:
2490 if repo_group_id:
2490 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2491 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2491 return q.all()
2492 return q.all()
2492
2493
2493 @classmethod
2494 @classmethod
2494 def get_default_group_perms_from_user_group(
2495 def get_default_group_perms_from_user_group(
2495 cls, user_id, repo_group_id=None):
2496 cls, user_id, repo_group_id=None):
2496 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2497 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2497 .join(
2498 .join(
2498 Permission,
2499 Permission,
2499 UserGroupRepoGroupToPerm.permission_id ==
2500 UserGroupRepoGroupToPerm.permission_id ==
2500 Permission.permission_id)\
2501 Permission.permission_id)\
2501 .join(
2502 .join(
2502 RepoGroup,
2503 RepoGroup,
2503 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2504 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2504 .join(
2505 .join(
2505 UserGroup,
2506 UserGroup,
2506 UserGroupRepoGroupToPerm.users_group_id ==
2507 UserGroupRepoGroupToPerm.users_group_id ==
2507 UserGroup.users_group_id)\
2508 UserGroup.users_group_id)\
2508 .join(
2509 .join(
2509 UserGroupMember,
2510 UserGroupMember,
2510 UserGroupRepoGroupToPerm.users_group_id ==
2511 UserGroupRepoGroupToPerm.users_group_id ==
2511 UserGroupMember.users_group_id)\
2512 UserGroupMember.users_group_id)\
2512 .filter(
2513 .filter(
2513 UserGroupMember.user_id == user_id,
2514 UserGroupMember.user_id == user_id,
2514 UserGroup.users_group_active == true())
2515 UserGroup.users_group_active == true())
2515 if repo_group_id:
2516 if repo_group_id:
2516 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2517 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2517 return q.all()
2518 return q.all()
2518
2519
2519 @classmethod
2520 @classmethod
2520 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2521 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2521 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2522 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2522 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2523 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2523 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2524 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2524 .filter(UserUserGroupToPerm.user_id == user_id)
2525 .filter(UserUserGroupToPerm.user_id == user_id)
2525 if user_group_id:
2526 if user_group_id:
2526 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2527 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2527 return q.all()
2528 return q.all()
2528
2529
2529 @classmethod
2530 @classmethod
2530 def get_default_user_group_perms_from_user_group(
2531 def get_default_user_group_perms_from_user_group(
2531 cls, user_id, user_group_id=None):
2532 cls, user_id, user_group_id=None):
2532 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2533 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2533 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2534 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2534 .join(
2535 .join(
2535 Permission,
2536 Permission,
2536 UserGroupUserGroupToPerm.permission_id ==
2537 UserGroupUserGroupToPerm.permission_id ==
2537 Permission.permission_id)\
2538 Permission.permission_id)\
2538 .join(
2539 .join(
2539 TargetUserGroup,
2540 TargetUserGroup,
2540 UserGroupUserGroupToPerm.target_user_group_id ==
2541 UserGroupUserGroupToPerm.target_user_group_id ==
2541 TargetUserGroup.users_group_id)\
2542 TargetUserGroup.users_group_id)\
2542 .join(
2543 .join(
2543 UserGroup,
2544 UserGroup,
2544 UserGroupUserGroupToPerm.user_group_id ==
2545 UserGroupUserGroupToPerm.user_group_id ==
2545 UserGroup.users_group_id)\
2546 UserGroup.users_group_id)\
2546 .join(
2547 .join(
2547 UserGroupMember,
2548 UserGroupMember,
2548 UserGroupUserGroupToPerm.user_group_id ==
2549 UserGroupUserGroupToPerm.user_group_id ==
2549 UserGroupMember.users_group_id)\
2550 UserGroupMember.users_group_id)\
2550 .filter(
2551 .filter(
2551 UserGroupMember.user_id == user_id,
2552 UserGroupMember.user_id == user_id,
2552 UserGroup.users_group_active == true())
2553 UserGroup.users_group_active == true())
2553 if user_group_id:
2554 if user_group_id:
2554 q = q.filter(
2555 q = q.filter(
2555 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2556 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2556
2557
2557 return q.all()
2558 return q.all()
2558
2559
2559
2560
2560 class UserRepoToPerm(Base, BaseModel):
2561 class UserRepoToPerm(Base, BaseModel):
2561 __tablename__ = 'repo_to_perm'
2562 __tablename__ = 'repo_to_perm'
2562 __table_args__ = (
2563 __table_args__ = (
2563 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2564 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2566 )
2567 )
2567 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2568 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2570 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2571 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2571
2572
2572 user = relationship('User')
2573 user = relationship('User')
2573 repository = relationship('Repository')
2574 repository = relationship('Repository')
2574 permission = relationship('Permission')
2575 permission = relationship('Permission')
2575
2576
2576 @classmethod
2577 @classmethod
2577 def create(cls, user, repository, permission):
2578 def create(cls, user, repository, permission):
2578 n = cls()
2579 n = cls()
2579 n.user = user
2580 n.user = user
2580 n.repository = repository
2581 n.repository = repository
2581 n.permission = permission
2582 n.permission = permission
2582 Session().add(n)
2583 Session().add(n)
2583 return n
2584 return n
2584
2585
2585 def __unicode__(self):
2586 def __unicode__(self):
2586 return u'<%s => %s >' % (self.user, self.repository)
2587 return u'<%s => %s >' % (self.user, self.repository)
2587
2588
2588
2589
2589 class UserUserGroupToPerm(Base, BaseModel):
2590 class UserUserGroupToPerm(Base, BaseModel):
2590 __tablename__ = 'user_user_group_to_perm'
2591 __tablename__ = 'user_user_group_to_perm'
2591 __table_args__ = (
2592 __table_args__ = (
2592 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2593 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 )
2596 )
2596 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2598 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2600 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2600
2601
2601 user = relationship('User')
2602 user = relationship('User')
2602 user_group = relationship('UserGroup')
2603 user_group = relationship('UserGroup')
2603 permission = relationship('Permission')
2604 permission = relationship('Permission')
2604
2605
2605 @classmethod
2606 @classmethod
2606 def create(cls, user, user_group, permission):
2607 def create(cls, user, user_group, permission):
2607 n = cls()
2608 n = cls()
2608 n.user = user
2609 n.user = user
2609 n.user_group = user_group
2610 n.user_group = user_group
2610 n.permission = permission
2611 n.permission = permission
2611 Session().add(n)
2612 Session().add(n)
2612 return n
2613 return n
2613
2614
2614 def __unicode__(self):
2615 def __unicode__(self):
2615 return u'<%s => %s >' % (self.user, self.user_group)
2616 return u'<%s => %s >' % (self.user, self.user_group)
2616
2617
2617
2618
2618 class UserToPerm(Base, BaseModel):
2619 class UserToPerm(Base, BaseModel):
2619 __tablename__ = 'user_to_perm'
2620 __tablename__ = 'user_to_perm'
2620 __table_args__ = (
2621 __table_args__ = (
2621 UniqueConstraint('user_id', 'permission_id'),
2622 UniqueConstraint('user_id', 'permission_id'),
2622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2623 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2624 )
2625 )
2625 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2626 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2626 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2627 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2627 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2628
2629
2629 user = relationship('User')
2630 user = relationship('User')
2630 permission = relationship('Permission', lazy='joined')
2631 permission = relationship('Permission', lazy='joined')
2631
2632
2632 def __unicode__(self):
2633 def __unicode__(self):
2633 return u'<%s => %s >' % (self.user, self.permission)
2634 return u'<%s => %s >' % (self.user, self.permission)
2634
2635
2635
2636
2636 class UserGroupRepoToPerm(Base, BaseModel):
2637 class UserGroupRepoToPerm(Base, BaseModel):
2637 __tablename__ = 'users_group_repo_to_perm'
2638 __tablename__ = 'users_group_repo_to_perm'
2638 __table_args__ = (
2639 __table_args__ = (
2639 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2640 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 )
2643 )
2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2645 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2646 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2646 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2647 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2647
2648
2648 users_group = relationship('UserGroup')
2649 users_group = relationship('UserGroup')
2649 permission = relationship('Permission')
2650 permission = relationship('Permission')
2650 repository = relationship('Repository')
2651 repository = relationship('Repository')
2651
2652
2652 @classmethod
2653 @classmethod
2653 def create(cls, users_group, repository, permission):
2654 def create(cls, users_group, repository, permission):
2654 n = cls()
2655 n = cls()
2655 n.users_group = users_group
2656 n.users_group = users_group
2656 n.repository = repository
2657 n.repository = repository
2657 n.permission = permission
2658 n.permission = permission
2658 Session().add(n)
2659 Session().add(n)
2659 return n
2660 return n
2660
2661
2661 def __unicode__(self):
2662 def __unicode__(self):
2662 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2663 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2663
2664
2664
2665
2665 class UserGroupUserGroupToPerm(Base, BaseModel):
2666 class UserGroupUserGroupToPerm(Base, BaseModel):
2666 __tablename__ = 'user_group_user_group_to_perm'
2667 __tablename__ = 'user_group_user_group_to_perm'
2667 __table_args__ = (
2668 __table_args__ = (
2668 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2669 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2669 CheckConstraint('target_user_group_id != user_group_id'),
2670 CheckConstraint('target_user_group_id != user_group_id'),
2670 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2671 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2671 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2672 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2672 )
2673 )
2673 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)
2674 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)
2674 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2675 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2676 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2677 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2677
2678
2678 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2679 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2679 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2680 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2680 permission = relationship('Permission')
2681 permission = relationship('Permission')
2681
2682
2682 @classmethod
2683 @classmethod
2683 def create(cls, target_user_group, user_group, permission):
2684 def create(cls, target_user_group, user_group, permission):
2684 n = cls()
2685 n = cls()
2685 n.target_user_group = target_user_group
2686 n.target_user_group = target_user_group
2686 n.user_group = user_group
2687 n.user_group = user_group
2687 n.permission = permission
2688 n.permission = permission
2688 Session().add(n)
2689 Session().add(n)
2689 return n
2690 return n
2690
2691
2691 def __unicode__(self):
2692 def __unicode__(self):
2692 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2693 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2693
2694
2694
2695
2695 class UserGroupToPerm(Base, BaseModel):
2696 class UserGroupToPerm(Base, BaseModel):
2696 __tablename__ = 'users_group_to_perm'
2697 __tablename__ = 'users_group_to_perm'
2697 __table_args__ = (
2698 __table_args__ = (
2698 UniqueConstraint('users_group_id', 'permission_id',),
2699 UniqueConstraint('users_group_id', 'permission_id',),
2699 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2700 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2700 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2701 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2701 )
2702 )
2702 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2703 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2703 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2704 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2704 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2705 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2705
2706
2706 users_group = relationship('UserGroup')
2707 users_group = relationship('UserGroup')
2707 permission = relationship('Permission')
2708 permission = relationship('Permission')
2708
2709
2709
2710
2710 class UserRepoGroupToPerm(Base, BaseModel):
2711 class UserRepoGroupToPerm(Base, BaseModel):
2711 __tablename__ = 'user_repo_group_to_perm'
2712 __tablename__ = 'user_repo_group_to_perm'
2712 __table_args__ = (
2713 __table_args__ = (
2713 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2714 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2716 )
2717 )
2717
2718
2718 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2719 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2719 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2720 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2720 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2721 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2721 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2722 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2722
2723
2723 user = relationship('User')
2724 user = relationship('User')
2724 group = relationship('RepoGroup')
2725 group = relationship('RepoGroup')
2725 permission = relationship('Permission')
2726 permission = relationship('Permission')
2726
2727
2727 @classmethod
2728 @classmethod
2728 def create(cls, user, repository_group, permission):
2729 def create(cls, user, repository_group, permission):
2729 n = cls()
2730 n = cls()
2730 n.user = user
2731 n.user = user
2731 n.group = repository_group
2732 n.group = repository_group
2732 n.permission = permission
2733 n.permission = permission
2733 Session().add(n)
2734 Session().add(n)
2734 return n
2735 return n
2735
2736
2736
2737
2737 class UserGroupRepoGroupToPerm(Base, BaseModel):
2738 class UserGroupRepoGroupToPerm(Base, BaseModel):
2738 __tablename__ = 'users_group_repo_group_to_perm'
2739 __tablename__ = 'users_group_repo_group_to_perm'
2739 __table_args__ = (
2740 __table_args__ = (
2740 UniqueConstraint('users_group_id', 'group_id'),
2741 UniqueConstraint('users_group_id', 'group_id'),
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2743 )
2744 )
2744
2745
2745 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)
2746 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)
2746 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2747 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2747 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2748 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2748 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2749 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2749
2750
2750 users_group = relationship('UserGroup')
2751 users_group = relationship('UserGroup')
2751 permission = relationship('Permission')
2752 permission = relationship('Permission')
2752 group = relationship('RepoGroup')
2753 group = relationship('RepoGroup')
2753
2754
2754 @classmethod
2755 @classmethod
2755 def create(cls, user_group, repository_group, permission):
2756 def create(cls, user_group, repository_group, permission):
2756 n = cls()
2757 n = cls()
2757 n.users_group = user_group
2758 n.users_group = user_group
2758 n.group = repository_group
2759 n.group = repository_group
2759 n.permission = permission
2760 n.permission = permission
2760 Session().add(n)
2761 Session().add(n)
2761 return n
2762 return n
2762
2763
2763 def __unicode__(self):
2764 def __unicode__(self):
2764 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2765 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2765
2766
2766
2767
2767 class Statistics(Base, BaseModel):
2768 class Statistics(Base, BaseModel):
2768 __tablename__ = 'statistics'
2769 __tablename__ = 'statistics'
2769 __table_args__ = (
2770 __table_args__ = (
2770 UniqueConstraint('repository_id'),
2771 UniqueConstraint('repository_id'),
2771 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2772 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2773 )
2774 )
2774 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2776 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2776 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2777 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2777 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2778 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2778 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2779 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2779 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2780 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2780
2781
2781 repository = relationship('Repository', single_parent=True)
2782 repository = relationship('Repository', single_parent=True)
2782
2783
2783
2784
2784 class UserFollowing(Base, BaseModel):
2785 class UserFollowing(Base, BaseModel):
2785 __tablename__ = 'user_followings'
2786 __tablename__ = 'user_followings'
2786 __table_args__ = (
2787 __table_args__ = (
2787 UniqueConstraint('user_id', 'follows_repository_id'),
2788 UniqueConstraint('user_id', 'follows_repository_id'),
2788 UniqueConstraint('user_id', 'follows_user_id'),
2789 UniqueConstraint('user_id', 'follows_user_id'),
2789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2790 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2791 )
2792 )
2792
2793
2793 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2794 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2795 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2796 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2796 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2797 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2797 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2798 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2798
2799
2799 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2800 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2800
2801
2801 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2802 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2802 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2803 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2803
2804
2804 @classmethod
2805 @classmethod
2805 def get_repo_followers(cls, repo_id):
2806 def get_repo_followers(cls, repo_id):
2806 return cls.query().filter(cls.follows_repo_id == repo_id)
2807 return cls.query().filter(cls.follows_repo_id == repo_id)
2807
2808
2808
2809
2809 class CacheKey(Base, BaseModel):
2810 class CacheKey(Base, BaseModel):
2810 __tablename__ = 'cache_invalidation'
2811 __tablename__ = 'cache_invalidation'
2811 __table_args__ = (
2812 __table_args__ = (
2812 UniqueConstraint('cache_key'),
2813 UniqueConstraint('cache_key'),
2813 Index('key_idx', 'cache_key'),
2814 Index('key_idx', 'cache_key'),
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2816 )
2817 )
2817 CACHE_TYPE_ATOM = 'ATOM'
2818 CACHE_TYPE_ATOM = 'ATOM'
2818 CACHE_TYPE_RSS = 'RSS'
2819 CACHE_TYPE_RSS = 'RSS'
2819 CACHE_TYPE_README = 'README'
2820 CACHE_TYPE_README = 'README'
2820
2821
2821 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2822 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2822 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2823 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2823 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2824 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2824 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2825 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2825
2826
2826 def __init__(self, cache_key, cache_args=''):
2827 def __init__(self, cache_key, cache_args=''):
2827 self.cache_key = cache_key
2828 self.cache_key = cache_key
2828 self.cache_args = cache_args
2829 self.cache_args = cache_args
2829 self.cache_active = False
2830 self.cache_active = False
2830
2831
2831 def __unicode__(self):
2832 def __unicode__(self):
2832 return u"<%s('%s:%s[%s]')>" % (
2833 return u"<%s('%s:%s[%s]')>" % (
2833 self.__class__.__name__,
2834 self.__class__.__name__,
2834 self.cache_id, self.cache_key, self.cache_active)
2835 self.cache_id, self.cache_key, self.cache_active)
2835
2836
2836 def _cache_key_partition(self):
2837 def _cache_key_partition(self):
2837 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2838 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2838 return prefix, repo_name, suffix
2839 return prefix, repo_name, suffix
2839
2840
2840 def get_prefix(self):
2841 def get_prefix(self):
2841 """
2842 """
2842 Try to extract prefix from existing cache key. The key could consist
2843 Try to extract prefix from existing cache key. The key could consist
2843 of prefix, repo_name, suffix
2844 of prefix, repo_name, suffix
2844 """
2845 """
2845 # this returns prefix, repo_name, suffix
2846 # this returns prefix, repo_name, suffix
2846 return self._cache_key_partition()[0]
2847 return self._cache_key_partition()[0]
2847
2848
2848 def get_suffix(self):
2849 def get_suffix(self):
2849 """
2850 """
2850 get suffix that might have been used in _get_cache_key to
2851 get suffix that might have been used in _get_cache_key to
2851 generate self.cache_key. Only used for informational purposes
2852 generate self.cache_key. Only used for informational purposes
2852 in repo_edit.mako.
2853 in repo_edit.mako.
2853 """
2854 """
2854 # prefix, repo_name, suffix
2855 # prefix, repo_name, suffix
2855 return self._cache_key_partition()[2]
2856 return self._cache_key_partition()[2]
2856
2857
2857 @classmethod
2858 @classmethod
2858 def delete_all_cache(cls):
2859 def delete_all_cache(cls):
2859 """
2860 """
2860 Delete all cache keys from database.
2861 Delete all cache keys from database.
2861 Should only be run when all instances are down and all entries
2862 Should only be run when all instances are down and all entries
2862 thus stale.
2863 thus stale.
2863 """
2864 """
2864 cls.query().delete()
2865 cls.query().delete()
2865 Session().commit()
2866 Session().commit()
2866
2867
2867 @classmethod
2868 @classmethod
2868 def get_cache_key(cls, repo_name, cache_type):
2869 def get_cache_key(cls, repo_name, cache_type):
2869 """
2870 """
2870
2871
2871 Generate a cache key for this process of RhodeCode instance.
2872 Generate a cache key for this process of RhodeCode instance.
2872 Prefix most likely will be process id or maybe explicitly set
2873 Prefix most likely will be process id or maybe explicitly set
2873 instance_id from .ini file.
2874 instance_id from .ini file.
2874 """
2875 """
2875 import rhodecode
2876 import rhodecode
2876 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2877 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2877
2878
2878 repo_as_unicode = safe_unicode(repo_name)
2879 repo_as_unicode = safe_unicode(repo_name)
2879 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2880 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2880 if cache_type else repo_as_unicode
2881 if cache_type else repo_as_unicode
2881
2882
2882 return u'{}{}'.format(prefix, key)
2883 return u'{}{}'.format(prefix, key)
2883
2884
2884 @classmethod
2885 @classmethod
2885 def set_invalidate(cls, repo_name, delete=False):
2886 def set_invalidate(cls, repo_name, delete=False):
2886 """
2887 """
2887 Mark all caches of a repo as invalid in the database.
2888 Mark all caches of a repo as invalid in the database.
2888 """
2889 """
2889
2890
2890 try:
2891 try:
2891 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2892 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2892 if delete:
2893 if delete:
2893 log.debug('cache objects deleted for repo %s',
2894 log.debug('cache objects deleted for repo %s',
2894 safe_str(repo_name))
2895 safe_str(repo_name))
2895 qry.delete()
2896 qry.delete()
2896 else:
2897 else:
2897 log.debug('cache objects marked as invalid for repo %s',
2898 log.debug('cache objects marked as invalid for repo %s',
2898 safe_str(repo_name))
2899 safe_str(repo_name))
2899 qry.update({"cache_active": False})
2900 qry.update({"cache_active": False})
2900
2901
2901 Session().commit()
2902 Session().commit()
2902 except Exception:
2903 except Exception:
2903 log.exception(
2904 log.exception(
2904 'Cache key invalidation failed for repository %s',
2905 'Cache key invalidation failed for repository %s',
2905 safe_str(repo_name))
2906 safe_str(repo_name))
2906 Session().rollback()
2907 Session().rollback()
2907
2908
2908 @classmethod
2909 @classmethod
2909 def get_active_cache(cls, cache_key):
2910 def get_active_cache(cls, cache_key):
2910 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2911 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2911 if inv_obj:
2912 if inv_obj:
2912 return inv_obj
2913 return inv_obj
2913 return None
2914 return None
2914
2915
2915 @classmethod
2916 @classmethod
2916 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2917 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2917 thread_scoped=False):
2918 thread_scoped=False):
2918 """
2919 """
2919 @cache_region('long_term')
2920 @cache_region('long_term')
2920 def _heavy_calculation(cache_key):
2921 def _heavy_calculation(cache_key):
2921 return 'result'
2922 return 'result'
2922
2923
2923 cache_context = CacheKey.repo_context_cache(
2924 cache_context = CacheKey.repo_context_cache(
2924 _heavy_calculation, repo_name, cache_type)
2925 _heavy_calculation, repo_name, cache_type)
2925
2926
2926 with cache_context as context:
2927 with cache_context as context:
2927 context.invalidate()
2928 context.invalidate()
2928 computed = context.compute()
2929 computed = context.compute()
2929
2930
2930 assert computed == 'result'
2931 assert computed == 'result'
2931 """
2932 """
2932 from rhodecode.lib import caches
2933 from rhodecode.lib import caches
2933 return caches.InvalidationContext(
2934 return caches.InvalidationContext(
2934 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2935 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2935
2936
2936
2937
2937 class ChangesetComment(Base, BaseModel):
2938 class ChangesetComment(Base, BaseModel):
2938 __tablename__ = 'changeset_comments'
2939 __tablename__ = 'changeset_comments'
2939 __table_args__ = (
2940 __table_args__ = (
2940 Index('cc_revision_idx', 'revision'),
2941 Index('cc_revision_idx', 'revision'),
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2943 )
2944 )
2944
2945
2945 COMMENT_OUTDATED = u'comment_outdated'
2946 COMMENT_OUTDATED = u'comment_outdated'
2946 COMMENT_TYPE_NOTE = u'note'
2947 COMMENT_TYPE_NOTE = u'note'
2947 COMMENT_TYPE_TODO = u'todo'
2948 COMMENT_TYPE_TODO = u'todo'
2948 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2949 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2949
2950
2950 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2951 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2951 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2952 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2952 revision = Column('revision', String(40), nullable=True)
2953 revision = Column('revision', String(40), nullable=True)
2953 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2954 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2954 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2955 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2955 line_no = Column('line_no', Unicode(10), nullable=True)
2956 line_no = Column('line_no', Unicode(10), nullable=True)
2956 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2957 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2957 f_path = Column('f_path', Unicode(1000), nullable=True)
2958 f_path = Column('f_path', Unicode(1000), nullable=True)
2958 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2959 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2959 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2960 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2960 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2961 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2961 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2962 renderer = Column('renderer', Unicode(64), nullable=True)
2963 renderer = Column('renderer', Unicode(64), nullable=True)
2963 display_state = Column('display_state', Unicode(128), nullable=True)
2964 display_state = Column('display_state', Unicode(128), nullable=True)
2964
2965
2965 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2966 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2966 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2967 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2967 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2968 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2968 author = relationship('User', lazy='joined')
2969 author = relationship('User', lazy='joined')
2969 repo = relationship('Repository')
2970 repo = relationship('Repository')
2970 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2971 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2972 pull_request = relationship('PullRequest', lazy='joined')
2972 pull_request_version = relationship('PullRequestVersion')
2973 pull_request_version = relationship('PullRequestVersion')
2973
2974
2974 @classmethod
2975 @classmethod
2975 def get_users(cls, revision=None, pull_request_id=None):
2976 def get_users(cls, revision=None, pull_request_id=None):
2976 """
2977 """
2977 Returns user associated with this ChangesetComment. ie those
2978 Returns user associated with this ChangesetComment. ie those
2978 who actually commented
2979 who actually commented
2979
2980
2980 :param cls:
2981 :param cls:
2981 :param revision:
2982 :param revision:
2982 """
2983 """
2983 q = Session().query(User)\
2984 q = Session().query(User)\
2984 .join(ChangesetComment.author)
2985 .join(ChangesetComment.author)
2985 if revision:
2986 if revision:
2986 q = q.filter(cls.revision == revision)
2987 q = q.filter(cls.revision == revision)
2987 elif pull_request_id:
2988 elif pull_request_id:
2988 q = q.filter(cls.pull_request_id == pull_request_id)
2989 q = q.filter(cls.pull_request_id == pull_request_id)
2989 return q.all()
2990 return q.all()
2990
2991
2991 @classmethod
2992 @classmethod
2992 def get_index_from_version(cls, pr_version, versions):
2993 def get_index_from_version(cls, pr_version, versions):
2993 num_versions = [x.pull_request_version_id for x in versions]
2994 num_versions = [x.pull_request_version_id for x in versions]
2994 try:
2995 try:
2995 return num_versions.index(pr_version) +1
2996 return num_versions.index(pr_version) +1
2996 except (IndexError, ValueError):
2997 except (IndexError, ValueError):
2997 return
2998 return
2998
2999
2999 @property
3000 @property
3000 def outdated(self):
3001 def outdated(self):
3001 return self.display_state == self.COMMENT_OUTDATED
3002 return self.display_state == self.COMMENT_OUTDATED
3002
3003
3003 def outdated_at_version(self, version):
3004 def outdated_at_version(self, version):
3004 """
3005 """
3005 Checks if comment is outdated for given pull request version
3006 Checks if comment is outdated for given pull request version
3006 """
3007 """
3007 return self.outdated and self.pull_request_version_id != version
3008 return self.outdated and self.pull_request_version_id != version
3008
3009
3009 def older_than_version(self, version):
3010 def older_than_version(self, version):
3010 """
3011 """
3011 Checks if comment is made from previous version than given
3012 Checks if comment is made from previous version than given
3012 """
3013 """
3013 if version is None:
3014 if version is None:
3014 return self.pull_request_version_id is not None
3015 return self.pull_request_version_id is not None
3015
3016
3016 return self.pull_request_version_id < version
3017 return self.pull_request_version_id < version
3017
3018
3018 @property
3019 @property
3019 def resolved(self):
3020 def resolved(self):
3020 return self.resolved_by[0] if self.resolved_by else None
3021 return self.resolved_by[0] if self.resolved_by else None
3021
3022
3022 @property
3023 @property
3023 def is_todo(self):
3024 def is_todo(self):
3024 return self.comment_type == self.COMMENT_TYPE_TODO
3025 return self.comment_type == self.COMMENT_TYPE_TODO
3025
3026
3026 def get_index_version(self, versions):
3027 def get_index_version(self, versions):
3027 return self.get_index_from_version(
3028 return self.get_index_from_version(
3028 self.pull_request_version_id, versions)
3029 self.pull_request_version_id, versions)
3029
3030
3030 def render(self, mentions=False):
3031 def render(self, mentions=False):
3031 from rhodecode.lib import helpers as h
3032 from rhodecode.lib import helpers as h
3032 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3033 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3033
3034
3034 def __repr__(self):
3035 def __repr__(self):
3035 if self.comment_id:
3036 if self.comment_id:
3036 return '<DB:Comment #%s>' % self.comment_id
3037 return '<DB:Comment #%s>' % self.comment_id
3037 else:
3038 else:
3038 return '<DB:Comment at %#x>' % id(self)
3039 return '<DB:Comment at %#x>' % id(self)
3039
3040
3040
3041
3041 class ChangesetStatus(Base, BaseModel):
3042 class ChangesetStatus(Base, BaseModel):
3042 __tablename__ = 'changeset_statuses'
3043 __tablename__ = 'changeset_statuses'
3043 __table_args__ = (
3044 __table_args__ = (
3044 Index('cs_revision_idx', 'revision'),
3045 Index('cs_revision_idx', 'revision'),
3045 Index('cs_version_idx', 'version'),
3046 Index('cs_version_idx', 'version'),
3046 UniqueConstraint('repo_id', 'revision', 'version'),
3047 UniqueConstraint('repo_id', 'revision', 'version'),
3047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3048 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3049 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3049 )
3050 )
3050 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3051 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3051 STATUS_APPROVED = 'approved'
3052 STATUS_APPROVED = 'approved'
3052 STATUS_REJECTED = 'rejected'
3053 STATUS_REJECTED = 'rejected'
3053 STATUS_UNDER_REVIEW = 'under_review'
3054 STATUS_UNDER_REVIEW = 'under_review'
3054
3055
3055 STATUSES = [
3056 STATUSES = [
3056 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3057 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3057 (STATUS_APPROVED, _("Approved")),
3058 (STATUS_APPROVED, _("Approved")),
3058 (STATUS_REJECTED, _("Rejected")),
3059 (STATUS_REJECTED, _("Rejected")),
3059 (STATUS_UNDER_REVIEW, _("Under Review")),
3060 (STATUS_UNDER_REVIEW, _("Under Review")),
3060 ]
3061 ]
3061
3062
3062 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3063 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3063 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3064 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3065 revision = Column('revision', String(40), nullable=False)
3066 revision = Column('revision', String(40), nullable=False)
3066 status = Column('status', String(128), nullable=False, default=DEFAULT)
3067 status = Column('status', String(128), nullable=False, default=DEFAULT)
3067 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3068 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3068 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3069 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3069 version = Column('version', Integer(), nullable=False, default=0)
3070 version = Column('version', Integer(), nullable=False, default=0)
3070 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3071 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3071
3072
3072 author = relationship('User', lazy='joined')
3073 author = relationship('User', lazy='joined')
3073 repo = relationship('Repository')
3074 repo = relationship('Repository')
3074 comment = relationship('ChangesetComment', lazy='joined')
3075 comment = relationship('ChangesetComment', lazy='joined')
3075 pull_request = relationship('PullRequest', lazy='joined')
3076 pull_request = relationship('PullRequest', lazy='joined')
3076
3077
3077 def __unicode__(self):
3078 def __unicode__(self):
3078 return u"<%s('%s[v%s]:%s')>" % (
3079 return u"<%s('%s[v%s]:%s')>" % (
3079 self.__class__.__name__,
3080 self.__class__.__name__,
3080 self.status, self.version, self.author
3081 self.status, self.version, self.author
3081 )
3082 )
3082
3083
3083 @classmethod
3084 @classmethod
3084 def get_status_lbl(cls, value):
3085 def get_status_lbl(cls, value):
3085 return dict(cls.STATUSES).get(value)
3086 return dict(cls.STATUSES).get(value)
3086
3087
3087 @property
3088 @property
3088 def status_lbl(self):
3089 def status_lbl(self):
3089 return ChangesetStatus.get_status_lbl(self.status)
3090 return ChangesetStatus.get_status_lbl(self.status)
3090
3091
3091
3092
3092 class _PullRequestBase(BaseModel):
3093 class _PullRequestBase(BaseModel):
3093 """
3094 """
3094 Common attributes of pull request and version entries.
3095 Common attributes of pull request and version entries.
3095 """
3096 """
3096
3097
3097 # .status values
3098 # .status values
3098 STATUS_NEW = u'new'
3099 STATUS_NEW = u'new'
3099 STATUS_OPEN = u'open'
3100 STATUS_OPEN = u'open'
3100 STATUS_CLOSED = u'closed'
3101 STATUS_CLOSED = u'closed'
3101
3102
3102 title = Column('title', Unicode(255), nullable=True)
3103 title = Column('title', Unicode(255), nullable=True)
3103 description = Column(
3104 description = Column(
3104 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3105 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3105 nullable=True)
3106 nullable=True)
3106 # new/open/closed status of pull request (not approve/reject/etc)
3107 # new/open/closed status of pull request (not approve/reject/etc)
3107 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3108 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3108 created_on = Column(
3109 created_on = Column(
3109 'created_on', DateTime(timezone=False), nullable=False,
3110 'created_on', DateTime(timezone=False), nullable=False,
3110 default=datetime.datetime.now)
3111 default=datetime.datetime.now)
3111 updated_on = Column(
3112 updated_on = Column(
3112 'updated_on', DateTime(timezone=False), nullable=False,
3113 'updated_on', DateTime(timezone=False), nullable=False,
3113 default=datetime.datetime.now)
3114 default=datetime.datetime.now)
3114
3115
3115 @declared_attr
3116 @declared_attr
3116 def user_id(cls):
3117 def user_id(cls):
3117 return Column(
3118 return Column(
3118 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3119 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3119 unique=None)
3120 unique=None)
3120
3121
3121 # 500 revisions max
3122 # 500 revisions max
3122 _revisions = Column(
3123 _revisions = Column(
3123 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3124 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3124
3125
3125 @declared_attr
3126 @declared_attr
3126 def source_repo_id(cls):
3127 def source_repo_id(cls):
3127 # TODO: dan: rename column to source_repo_id
3128 # TODO: dan: rename column to source_repo_id
3128 return Column(
3129 return Column(
3129 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3130 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3130 nullable=False)
3131 nullable=False)
3131
3132
3132 source_ref = Column('org_ref', Unicode(255), nullable=False)
3133 source_ref = Column('org_ref', Unicode(255), nullable=False)
3133
3134
3134 @declared_attr
3135 @declared_attr
3135 def target_repo_id(cls):
3136 def target_repo_id(cls):
3136 # TODO: dan: rename column to target_repo_id
3137 # TODO: dan: rename column to target_repo_id
3137 return Column(
3138 return Column(
3138 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3139 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3139 nullable=False)
3140 nullable=False)
3140
3141
3141 target_ref = Column('other_ref', Unicode(255), nullable=False)
3142 target_ref = Column('other_ref', Unicode(255), nullable=False)
3142 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3143 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3143
3144
3144 # TODO: dan: rename column to last_merge_source_rev
3145 # TODO: dan: rename column to last_merge_source_rev
3145 _last_merge_source_rev = Column(
3146 _last_merge_source_rev = Column(
3146 'last_merge_org_rev', String(40), nullable=True)
3147 'last_merge_org_rev', String(40), nullable=True)
3147 # TODO: dan: rename column to last_merge_target_rev
3148 # TODO: dan: rename column to last_merge_target_rev
3148 _last_merge_target_rev = Column(
3149 _last_merge_target_rev = Column(
3149 'last_merge_other_rev', String(40), nullable=True)
3150 'last_merge_other_rev', String(40), nullable=True)
3150 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3151 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3151 merge_rev = Column('merge_rev', String(40), nullable=True)
3152 merge_rev = Column('merge_rev', String(40), nullable=True)
3152
3153
3153 @hybrid_property
3154 @hybrid_property
3154 def revisions(self):
3155 def revisions(self):
3155 return self._revisions.split(':') if self._revisions else []
3156 return self._revisions.split(':') if self._revisions else []
3156
3157
3157 @revisions.setter
3158 @revisions.setter
3158 def revisions(self, val):
3159 def revisions(self, val):
3159 self._revisions = ':'.join(val)
3160 self._revisions = ':'.join(val)
3160
3161
3161 @declared_attr
3162 @declared_attr
3162 def author(cls):
3163 def author(cls):
3163 return relationship('User', lazy='joined')
3164 return relationship('User', lazy='joined')
3164
3165
3165 @declared_attr
3166 @declared_attr
3166 def source_repo(cls):
3167 def source_repo(cls):
3167 return relationship(
3168 return relationship(
3168 'Repository',
3169 'Repository',
3169 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3170 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3170
3171
3171 @property
3172 @property
3172 def source_ref_parts(self):
3173 def source_ref_parts(self):
3173 return self.unicode_to_reference(self.source_ref)
3174 return self.unicode_to_reference(self.source_ref)
3174
3175
3175 @declared_attr
3176 @declared_attr
3176 def target_repo(cls):
3177 def target_repo(cls):
3177 return relationship(
3178 return relationship(
3178 'Repository',
3179 'Repository',
3179 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3180 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3180
3181
3181 @property
3182 @property
3182 def target_ref_parts(self):
3183 def target_ref_parts(self):
3183 return self.unicode_to_reference(self.target_ref)
3184 return self.unicode_to_reference(self.target_ref)
3184
3185
3185 @property
3186 @property
3186 def shadow_merge_ref(self):
3187 def shadow_merge_ref(self):
3187 return self.unicode_to_reference(self._shadow_merge_ref)
3188 return self.unicode_to_reference(self._shadow_merge_ref)
3188
3189
3189 @shadow_merge_ref.setter
3190 @shadow_merge_ref.setter
3190 def shadow_merge_ref(self, ref):
3191 def shadow_merge_ref(self, ref):
3191 self._shadow_merge_ref = self.reference_to_unicode(ref)
3192 self._shadow_merge_ref = self.reference_to_unicode(ref)
3192
3193
3193 def unicode_to_reference(self, raw):
3194 def unicode_to_reference(self, raw):
3194 """
3195 """
3195 Convert a unicode (or string) to a reference object.
3196 Convert a unicode (or string) to a reference object.
3196 If unicode evaluates to False it returns None.
3197 If unicode evaluates to False it returns None.
3197 """
3198 """
3198 if raw:
3199 if raw:
3199 refs = raw.split(':')
3200 refs = raw.split(':')
3200 return Reference(*refs)
3201 return Reference(*refs)
3201 else:
3202 else:
3202 return None
3203 return None
3203
3204
3204 def reference_to_unicode(self, ref):
3205 def reference_to_unicode(self, ref):
3205 """
3206 """
3206 Convert a reference object to unicode.
3207 Convert a reference object to unicode.
3207 If reference is None it returns None.
3208 If reference is None it returns None.
3208 """
3209 """
3209 if ref:
3210 if ref:
3210 return u':'.join(ref)
3211 return u':'.join(ref)
3211 else:
3212 else:
3212 return None
3213 return None
3213
3214
3214 def get_api_data(self):
3215 def get_api_data(self):
3215 from rhodecode.model.pull_request import PullRequestModel
3216 from rhodecode.model.pull_request import PullRequestModel
3216 pull_request = self
3217 pull_request = self
3217 merge_status = PullRequestModel().merge_status(pull_request)
3218 merge_status = PullRequestModel().merge_status(pull_request)
3218
3219
3219 pull_request_url = url(
3220 pull_request_url = url(
3220 'pullrequest_show', repo_name=self.target_repo.repo_name,
3221 'pullrequest_show', repo_name=self.target_repo.repo_name,
3221 pull_request_id=self.pull_request_id, qualified=True)
3222 pull_request_id=self.pull_request_id, qualified=True)
3222
3223
3223 merge_data = {
3224 merge_data = {
3224 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3225 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3225 'reference': (
3226 'reference': (
3226 pull_request.shadow_merge_ref._asdict()
3227 pull_request.shadow_merge_ref._asdict()
3227 if pull_request.shadow_merge_ref else None),
3228 if pull_request.shadow_merge_ref else None),
3228 }
3229 }
3229
3230
3230 data = {
3231 data = {
3231 'pull_request_id': pull_request.pull_request_id,
3232 'pull_request_id': pull_request.pull_request_id,
3232 'url': pull_request_url,
3233 'url': pull_request_url,
3233 'title': pull_request.title,
3234 'title': pull_request.title,
3234 'description': pull_request.description,
3235 'description': pull_request.description,
3235 'status': pull_request.status,
3236 'status': pull_request.status,
3236 'created_on': pull_request.created_on,
3237 'created_on': pull_request.created_on,
3237 'updated_on': pull_request.updated_on,
3238 'updated_on': pull_request.updated_on,
3238 'commit_ids': pull_request.revisions,
3239 'commit_ids': pull_request.revisions,
3239 'review_status': pull_request.calculated_review_status(),
3240 'review_status': pull_request.calculated_review_status(),
3240 'mergeable': {
3241 'mergeable': {
3241 'status': merge_status[0],
3242 'status': merge_status[0],
3242 'message': unicode(merge_status[1]),
3243 'message': unicode(merge_status[1]),
3243 },
3244 },
3244 'source': {
3245 'source': {
3245 'clone_url': pull_request.source_repo.clone_url(),
3246 'clone_url': pull_request.source_repo.clone_url(),
3246 'repository': pull_request.source_repo.repo_name,
3247 'repository': pull_request.source_repo.repo_name,
3247 'reference': {
3248 'reference': {
3248 'name': pull_request.source_ref_parts.name,
3249 'name': pull_request.source_ref_parts.name,
3249 'type': pull_request.source_ref_parts.type,
3250 'type': pull_request.source_ref_parts.type,
3250 'commit_id': pull_request.source_ref_parts.commit_id,
3251 'commit_id': pull_request.source_ref_parts.commit_id,
3251 },
3252 },
3252 },
3253 },
3253 'target': {
3254 'target': {
3254 'clone_url': pull_request.target_repo.clone_url(),
3255 'clone_url': pull_request.target_repo.clone_url(),
3255 'repository': pull_request.target_repo.repo_name,
3256 'repository': pull_request.target_repo.repo_name,
3256 'reference': {
3257 'reference': {
3257 'name': pull_request.target_ref_parts.name,
3258 'name': pull_request.target_ref_parts.name,
3258 'type': pull_request.target_ref_parts.type,
3259 'type': pull_request.target_ref_parts.type,
3259 'commit_id': pull_request.target_ref_parts.commit_id,
3260 'commit_id': pull_request.target_ref_parts.commit_id,
3260 },
3261 },
3261 },
3262 },
3262 'merge': merge_data,
3263 'merge': merge_data,
3263 'author': pull_request.author.get_api_data(include_secrets=False,
3264 'author': pull_request.author.get_api_data(include_secrets=False,
3264 details='basic'),
3265 details='basic'),
3265 'reviewers': [
3266 'reviewers': [
3266 {
3267 {
3267 'user': reviewer.get_api_data(include_secrets=False,
3268 'user': reviewer.get_api_data(include_secrets=False,
3268 details='basic'),
3269 details='basic'),
3269 'reasons': reasons,
3270 'reasons': reasons,
3270 'review_status': st[0][1].status if st else 'not_reviewed',
3271 'review_status': st[0][1].status if st else 'not_reviewed',
3271 }
3272 }
3272 for reviewer, reasons, st in pull_request.reviewers_statuses()
3273 for reviewer, reasons, st in pull_request.reviewers_statuses()
3273 ]
3274 ]
3274 }
3275 }
3275
3276
3276 return data
3277 return data
3277
3278
3278
3279
3279 class PullRequest(Base, _PullRequestBase):
3280 class PullRequest(Base, _PullRequestBase):
3280 __tablename__ = 'pull_requests'
3281 __tablename__ = 'pull_requests'
3281 __table_args__ = (
3282 __table_args__ = (
3282 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3283 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3284 )
3285 )
3285
3286
3286 pull_request_id = Column(
3287 pull_request_id = Column(
3287 'pull_request_id', Integer(), nullable=False, primary_key=True)
3288 'pull_request_id', Integer(), nullable=False, primary_key=True)
3288
3289
3289 def __repr__(self):
3290 def __repr__(self):
3290 if self.pull_request_id:
3291 if self.pull_request_id:
3291 return '<DB:PullRequest #%s>' % self.pull_request_id
3292 return '<DB:PullRequest #%s>' % self.pull_request_id
3292 else:
3293 else:
3293 return '<DB:PullRequest at %#x>' % id(self)
3294 return '<DB:PullRequest at %#x>' % id(self)
3294
3295
3295 reviewers = relationship('PullRequestReviewers',
3296 reviewers = relationship('PullRequestReviewers',
3296 cascade="all, delete, delete-orphan")
3297 cascade="all, delete, delete-orphan")
3297 statuses = relationship('ChangesetStatus')
3298 statuses = relationship('ChangesetStatus')
3298 comments = relationship('ChangesetComment',
3299 comments = relationship('ChangesetComment',
3299 cascade="all, delete, delete-orphan")
3300 cascade="all, delete, delete-orphan")
3300 versions = relationship('PullRequestVersion',
3301 versions = relationship('PullRequestVersion',
3301 cascade="all, delete, delete-orphan",
3302 cascade="all, delete, delete-orphan",
3302 lazy='dynamic')
3303 lazy='dynamic')
3303
3304
3304 @classmethod
3305 @classmethod
3305 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3306 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3306 internal_methods=None):
3307 internal_methods=None):
3307
3308
3308 class PullRequestDisplay(object):
3309 class PullRequestDisplay(object):
3309 """
3310 """
3310 Special object wrapper for showing PullRequest data via Versions
3311 Special object wrapper for showing PullRequest data via Versions
3311 It mimics PR object as close as possible. This is read only object
3312 It mimics PR object as close as possible. This is read only object
3312 just for display
3313 just for display
3313 """
3314 """
3314
3315
3315 def __init__(self, attrs, internal=None):
3316 def __init__(self, attrs, internal=None):
3316 self.attrs = attrs
3317 self.attrs = attrs
3317 # internal have priority over the given ones via attrs
3318 # internal have priority over the given ones via attrs
3318 self.internal = internal or ['versions']
3319 self.internal = internal or ['versions']
3319
3320
3320 def __getattr__(self, item):
3321 def __getattr__(self, item):
3321 if item in self.internal:
3322 if item in self.internal:
3322 return getattr(self, item)
3323 return getattr(self, item)
3323 try:
3324 try:
3324 return self.attrs[item]
3325 return self.attrs[item]
3325 except KeyError:
3326 except KeyError:
3326 raise AttributeError(
3327 raise AttributeError(
3327 '%s object has no attribute %s' % (self, item))
3328 '%s object has no attribute %s' % (self, item))
3328
3329
3329 def __repr__(self):
3330 def __repr__(self):
3330 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3331 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3331
3332
3332 def versions(self):
3333 def versions(self):
3333 return pull_request_obj.versions.order_by(
3334 return pull_request_obj.versions.order_by(
3334 PullRequestVersion.pull_request_version_id).all()
3335 PullRequestVersion.pull_request_version_id).all()
3335
3336
3336 def is_closed(self):
3337 def is_closed(self):
3337 return pull_request_obj.is_closed()
3338 return pull_request_obj.is_closed()
3338
3339
3339 @property
3340 @property
3340 def pull_request_version_id(self):
3341 def pull_request_version_id(self):
3341 return getattr(pull_request_obj, 'pull_request_version_id', None)
3342 return getattr(pull_request_obj, 'pull_request_version_id', None)
3342
3343
3343 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3344 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3344
3345
3345 attrs.author = StrictAttributeDict(
3346 attrs.author = StrictAttributeDict(
3346 pull_request_obj.author.get_api_data())
3347 pull_request_obj.author.get_api_data())
3347 if pull_request_obj.target_repo:
3348 if pull_request_obj.target_repo:
3348 attrs.target_repo = StrictAttributeDict(
3349 attrs.target_repo = StrictAttributeDict(
3349 pull_request_obj.target_repo.get_api_data())
3350 pull_request_obj.target_repo.get_api_data())
3350 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3351 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3351
3352
3352 if pull_request_obj.source_repo:
3353 if pull_request_obj.source_repo:
3353 attrs.source_repo = StrictAttributeDict(
3354 attrs.source_repo = StrictAttributeDict(
3354 pull_request_obj.source_repo.get_api_data())
3355 pull_request_obj.source_repo.get_api_data())
3355 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3356 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3356
3357
3357 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3358 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3358 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3359 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3359 attrs.revisions = pull_request_obj.revisions
3360 attrs.revisions = pull_request_obj.revisions
3360
3361
3361 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3362 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3362
3363
3363 return PullRequestDisplay(attrs, internal=internal_methods)
3364 return PullRequestDisplay(attrs, internal=internal_methods)
3364
3365
3365 def is_closed(self):
3366 def is_closed(self):
3366 return self.status == self.STATUS_CLOSED
3367 return self.status == self.STATUS_CLOSED
3367
3368
3368 def __json__(self):
3369 def __json__(self):
3369 return {
3370 return {
3370 'revisions': self.revisions,
3371 'revisions': self.revisions,
3371 }
3372 }
3372
3373
3373 def calculated_review_status(self):
3374 def calculated_review_status(self):
3374 from rhodecode.model.changeset_status import ChangesetStatusModel
3375 from rhodecode.model.changeset_status import ChangesetStatusModel
3375 return ChangesetStatusModel().calculated_review_status(self)
3376 return ChangesetStatusModel().calculated_review_status(self)
3376
3377
3377 def reviewers_statuses(self):
3378 def reviewers_statuses(self):
3378 from rhodecode.model.changeset_status import ChangesetStatusModel
3379 from rhodecode.model.changeset_status import ChangesetStatusModel
3379 return ChangesetStatusModel().reviewers_statuses(self)
3380 return ChangesetStatusModel().reviewers_statuses(self)
3380
3381
3381 @property
3382 @property
3382 def workspace_id(self):
3383 def workspace_id(self):
3383 from rhodecode.model.pull_request import PullRequestModel
3384 from rhodecode.model.pull_request import PullRequestModel
3384 return PullRequestModel()._workspace_id(self)
3385 return PullRequestModel()._workspace_id(self)
3385
3386
3386 def get_shadow_repo(self):
3387 def get_shadow_repo(self):
3387 workspace_id = self.workspace_id
3388 workspace_id = self.workspace_id
3388 vcs_obj = self.target_repo.scm_instance()
3389 vcs_obj = self.target_repo.scm_instance()
3389 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3390 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3390 workspace_id)
3391 workspace_id)
3391 return vcs_obj._get_shadow_instance(shadow_repository_path)
3392 return vcs_obj._get_shadow_instance(shadow_repository_path)
3392
3393
3393
3394
3394 class PullRequestVersion(Base, _PullRequestBase):
3395 class PullRequestVersion(Base, _PullRequestBase):
3395 __tablename__ = 'pull_request_versions'
3396 __tablename__ = 'pull_request_versions'
3396 __table_args__ = (
3397 __table_args__ = (
3397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3398 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3399 )
3400 )
3400
3401
3401 pull_request_version_id = Column(
3402 pull_request_version_id = Column(
3402 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3403 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3403 pull_request_id = Column(
3404 pull_request_id = Column(
3404 'pull_request_id', Integer(),
3405 'pull_request_id', Integer(),
3405 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3406 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3406 pull_request = relationship('PullRequest')
3407 pull_request = relationship('PullRequest')
3407
3408
3408 def __repr__(self):
3409 def __repr__(self):
3409 if self.pull_request_version_id:
3410 if self.pull_request_version_id:
3410 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3411 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3411 else:
3412 else:
3412 return '<DB:PullRequestVersion at %#x>' % id(self)
3413 return '<DB:PullRequestVersion at %#x>' % id(self)
3413
3414
3414 @property
3415 @property
3415 def reviewers(self):
3416 def reviewers(self):
3416 return self.pull_request.reviewers
3417 return self.pull_request.reviewers
3417
3418
3418 @property
3419 @property
3419 def versions(self):
3420 def versions(self):
3420 return self.pull_request.versions
3421 return self.pull_request.versions
3421
3422
3422 def is_closed(self):
3423 def is_closed(self):
3423 # calculate from original
3424 # calculate from original
3424 return self.pull_request.status == self.STATUS_CLOSED
3425 return self.pull_request.status == self.STATUS_CLOSED
3425
3426
3426 def calculated_review_status(self):
3427 def calculated_review_status(self):
3427 return self.pull_request.calculated_review_status()
3428 return self.pull_request.calculated_review_status()
3428
3429
3429 def reviewers_statuses(self):
3430 def reviewers_statuses(self):
3430 return self.pull_request.reviewers_statuses()
3431 return self.pull_request.reviewers_statuses()
3431
3432
3432
3433
3433 class PullRequestReviewers(Base, BaseModel):
3434 class PullRequestReviewers(Base, BaseModel):
3434 __tablename__ = 'pull_request_reviewers'
3435 __tablename__ = 'pull_request_reviewers'
3435 __table_args__ = (
3436 __table_args__ = (
3436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3438 )
3439 )
3439
3440
3440 def __init__(self, user=None, pull_request=None, reasons=None):
3441 def __init__(self, user=None, pull_request=None, reasons=None):
3441 self.user = user
3442 self.user = user
3442 self.pull_request = pull_request
3443 self.pull_request = pull_request
3443 self.reasons = reasons or []
3444 self.reasons = reasons or []
3444
3445
3445 @hybrid_property
3446 @hybrid_property
3446 def reasons(self):
3447 def reasons(self):
3447 if not self._reasons:
3448 if not self._reasons:
3448 return []
3449 return []
3449 return self._reasons
3450 return self._reasons
3450
3451
3451 @reasons.setter
3452 @reasons.setter
3452 def reasons(self, val):
3453 def reasons(self, val):
3453 val = val or []
3454 val = val or []
3454 if any(not isinstance(x, basestring) for x in val):
3455 if any(not isinstance(x, basestring) for x in val):
3455 raise Exception('invalid reasons type, must be list of strings')
3456 raise Exception('invalid reasons type, must be list of strings')
3456 self._reasons = val
3457 self._reasons = val
3457
3458
3458 pull_requests_reviewers_id = Column(
3459 pull_requests_reviewers_id = Column(
3459 'pull_requests_reviewers_id', Integer(), nullable=False,
3460 'pull_requests_reviewers_id', Integer(), nullable=False,
3460 primary_key=True)
3461 primary_key=True)
3461 pull_request_id = Column(
3462 pull_request_id = Column(
3462 "pull_request_id", Integer(),
3463 "pull_request_id", Integer(),
3463 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3464 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3464 user_id = Column(
3465 user_id = Column(
3465 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3466 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3466 _reasons = Column(
3467 _reasons = Column(
3467 'reason', MutationList.as_mutable(
3468 'reason', MutationList.as_mutable(
3468 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3469 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3469
3470
3470 user = relationship('User')
3471 user = relationship('User')
3471 pull_request = relationship('PullRequest')
3472 pull_request = relationship('PullRequest')
3472
3473
3473
3474
3474 class Notification(Base, BaseModel):
3475 class Notification(Base, BaseModel):
3475 __tablename__ = 'notifications'
3476 __tablename__ = 'notifications'
3476 __table_args__ = (
3477 __table_args__ = (
3477 Index('notification_type_idx', 'type'),
3478 Index('notification_type_idx', 'type'),
3478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3479 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3480 )
3481 )
3481
3482
3482 TYPE_CHANGESET_COMMENT = u'cs_comment'
3483 TYPE_CHANGESET_COMMENT = u'cs_comment'
3483 TYPE_MESSAGE = u'message'
3484 TYPE_MESSAGE = u'message'
3484 TYPE_MENTION = u'mention'
3485 TYPE_MENTION = u'mention'
3485 TYPE_REGISTRATION = u'registration'
3486 TYPE_REGISTRATION = u'registration'
3486 TYPE_PULL_REQUEST = u'pull_request'
3487 TYPE_PULL_REQUEST = u'pull_request'
3487 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3488 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3488
3489
3489 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3490 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3490 subject = Column('subject', Unicode(512), nullable=True)
3491 subject = Column('subject', Unicode(512), nullable=True)
3491 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3492 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3492 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3493 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3493 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3494 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3494 type_ = Column('type', Unicode(255))
3495 type_ = Column('type', Unicode(255))
3495
3496
3496 created_by_user = relationship('User')
3497 created_by_user = relationship('User')
3497 notifications_to_users = relationship('UserNotification', lazy='joined',
3498 notifications_to_users = relationship('UserNotification', lazy='joined',
3498 cascade="all, delete, delete-orphan")
3499 cascade="all, delete, delete-orphan")
3499
3500
3500 @property
3501 @property
3501 def recipients(self):
3502 def recipients(self):
3502 return [x.user for x in UserNotification.query()\
3503 return [x.user for x in UserNotification.query()\
3503 .filter(UserNotification.notification == self)\
3504 .filter(UserNotification.notification == self)\
3504 .order_by(UserNotification.user_id.asc()).all()]
3505 .order_by(UserNotification.user_id.asc()).all()]
3505
3506
3506 @classmethod
3507 @classmethod
3507 def create(cls, created_by, subject, body, recipients, type_=None):
3508 def create(cls, created_by, subject, body, recipients, type_=None):
3508 if type_ is None:
3509 if type_ is None:
3509 type_ = Notification.TYPE_MESSAGE
3510 type_ = Notification.TYPE_MESSAGE
3510
3511
3511 notification = cls()
3512 notification = cls()
3512 notification.created_by_user = created_by
3513 notification.created_by_user = created_by
3513 notification.subject = subject
3514 notification.subject = subject
3514 notification.body = body
3515 notification.body = body
3515 notification.type_ = type_
3516 notification.type_ = type_
3516 notification.created_on = datetime.datetime.now()
3517 notification.created_on = datetime.datetime.now()
3517
3518
3518 for u in recipients:
3519 for u in recipients:
3519 assoc = UserNotification()
3520 assoc = UserNotification()
3520 assoc.notification = notification
3521 assoc.notification = notification
3521
3522
3522 # if created_by is inside recipients mark his notification
3523 # if created_by is inside recipients mark his notification
3523 # as read
3524 # as read
3524 if u.user_id == created_by.user_id:
3525 if u.user_id == created_by.user_id:
3525 assoc.read = True
3526 assoc.read = True
3526
3527
3527 u.notifications.append(assoc)
3528 u.notifications.append(assoc)
3528 Session().add(notification)
3529 Session().add(notification)
3529
3530
3530 return notification
3531 return notification
3531
3532
3532 @property
3533 @property
3533 def description(self):
3534 def description(self):
3534 from rhodecode.model.notification import NotificationModel
3535 from rhodecode.model.notification import NotificationModel
3535 return NotificationModel().make_description(self)
3536 return NotificationModel().make_description(self)
3536
3537
3537
3538
3538 class UserNotification(Base, BaseModel):
3539 class UserNotification(Base, BaseModel):
3539 __tablename__ = 'user_to_notification'
3540 __tablename__ = 'user_to_notification'
3540 __table_args__ = (
3541 __table_args__ = (
3541 UniqueConstraint('user_id', 'notification_id'),
3542 UniqueConstraint('user_id', 'notification_id'),
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3544 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3544 )
3545 )
3545 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3546 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3547 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3547 read = Column('read', Boolean, default=False)
3548 read = Column('read', Boolean, default=False)
3548 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3549 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3549
3550
3550 user = relationship('User', lazy="joined")
3551 user = relationship('User', lazy="joined")
3551 notification = relationship('Notification', lazy="joined",
3552 notification = relationship('Notification', lazy="joined",
3552 order_by=lambda: Notification.created_on.desc(),)
3553 order_by=lambda: Notification.created_on.desc(),)
3553
3554
3554 def mark_as_read(self):
3555 def mark_as_read(self):
3555 self.read = True
3556 self.read = True
3556 Session().add(self)
3557 Session().add(self)
3557
3558
3558
3559
3559 class Gist(Base, BaseModel):
3560 class Gist(Base, BaseModel):
3560 __tablename__ = 'gists'
3561 __tablename__ = 'gists'
3561 __table_args__ = (
3562 __table_args__ = (
3562 Index('g_gist_access_id_idx', 'gist_access_id'),
3563 Index('g_gist_access_id_idx', 'gist_access_id'),
3563 Index('g_created_on_idx', 'created_on'),
3564 Index('g_created_on_idx', 'created_on'),
3564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3566 )
3567 )
3567 GIST_PUBLIC = u'public'
3568 GIST_PUBLIC = u'public'
3568 GIST_PRIVATE = u'private'
3569 GIST_PRIVATE = u'private'
3569 DEFAULT_FILENAME = u'gistfile1.txt'
3570 DEFAULT_FILENAME = u'gistfile1.txt'
3570
3571
3571 ACL_LEVEL_PUBLIC = u'acl_public'
3572 ACL_LEVEL_PUBLIC = u'acl_public'
3572 ACL_LEVEL_PRIVATE = u'acl_private'
3573 ACL_LEVEL_PRIVATE = u'acl_private'
3573
3574
3574 gist_id = Column('gist_id', Integer(), primary_key=True)
3575 gist_id = Column('gist_id', Integer(), primary_key=True)
3575 gist_access_id = Column('gist_access_id', Unicode(250))
3576 gist_access_id = Column('gist_access_id', Unicode(250))
3576 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3577 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3577 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3578 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3578 gist_expires = Column('gist_expires', Float(53), nullable=False)
3579 gist_expires = Column('gist_expires', Float(53), nullable=False)
3579 gist_type = Column('gist_type', Unicode(128), nullable=False)
3580 gist_type = Column('gist_type', Unicode(128), nullable=False)
3580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3581 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3581 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3582 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3582 acl_level = Column('acl_level', Unicode(128), nullable=True)
3583 acl_level = Column('acl_level', Unicode(128), nullable=True)
3583
3584
3584 owner = relationship('User')
3585 owner = relationship('User')
3585
3586
3586 def __repr__(self):
3587 def __repr__(self):
3587 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3588 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3588
3589
3589 @classmethod
3590 @classmethod
3590 def get_or_404(cls, id_):
3591 def get_or_404(cls, id_):
3591 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3592 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3592 if not res:
3593 if not res:
3593 raise HTTPNotFound
3594 raise HTTPNotFound
3594 return res
3595 return res
3595
3596
3596 @classmethod
3597 @classmethod
3597 def get_by_access_id(cls, gist_access_id):
3598 def get_by_access_id(cls, gist_access_id):
3598 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3599 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3599
3600
3600 def gist_url(self):
3601 def gist_url(self):
3601 import rhodecode
3602 import rhodecode
3602 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3603 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3603 if alias_url:
3604 if alias_url:
3604 return alias_url.replace('{gistid}', self.gist_access_id)
3605 return alias_url.replace('{gistid}', self.gist_access_id)
3605
3606
3606 return url('gist', gist_id=self.gist_access_id, qualified=True)
3607 return url('gist', gist_id=self.gist_access_id, qualified=True)
3607
3608
3608 @classmethod
3609 @classmethod
3609 def base_path(cls):
3610 def base_path(cls):
3610 """
3611 """
3611 Returns base path when all gists are stored
3612 Returns base path when all gists are stored
3612
3613
3613 :param cls:
3614 :param cls:
3614 """
3615 """
3615 from rhodecode.model.gist import GIST_STORE_LOC
3616 from rhodecode.model.gist import GIST_STORE_LOC
3616 q = Session().query(RhodeCodeUi)\
3617 q = Session().query(RhodeCodeUi)\
3617 .filter(RhodeCodeUi.ui_key == URL_SEP)
3618 .filter(RhodeCodeUi.ui_key == URL_SEP)
3618 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3619 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3619 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3620 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3620
3621
3621 def get_api_data(self):
3622 def get_api_data(self):
3622 """
3623 """
3623 Common function for generating gist related data for API
3624 Common function for generating gist related data for API
3624 """
3625 """
3625 gist = self
3626 gist = self
3626 data = {
3627 data = {
3627 'gist_id': gist.gist_id,
3628 'gist_id': gist.gist_id,
3628 'type': gist.gist_type,
3629 'type': gist.gist_type,
3629 'access_id': gist.gist_access_id,
3630 'access_id': gist.gist_access_id,
3630 'description': gist.gist_description,
3631 'description': gist.gist_description,
3631 'url': gist.gist_url(),
3632 'url': gist.gist_url(),
3632 'expires': gist.gist_expires,
3633 'expires': gist.gist_expires,
3633 'created_on': gist.created_on,
3634 'created_on': gist.created_on,
3634 'modified_at': gist.modified_at,
3635 'modified_at': gist.modified_at,
3635 'content': None,
3636 'content': None,
3636 'acl_level': gist.acl_level,
3637 'acl_level': gist.acl_level,
3637 }
3638 }
3638 return data
3639 return data
3639
3640
3640 def __json__(self):
3641 def __json__(self):
3641 data = dict(
3642 data = dict(
3642 )
3643 )
3643 data.update(self.get_api_data())
3644 data.update(self.get_api_data())
3644 return data
3645 return data
3645 # SCM functions
3646 # SCM functions
3646
3647
3647 def scm_instance(self, **kwargs):
3648 def scm_instance(self, **kwargs):
3648 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3649 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3649 return get_vcs_instance(
3650 return get_vcs_instance(
3650 repo_path=safe_str(full_repo_path), create=False)
3651 repo_path=safe_str(full_repo_path), create=False)
3651
3652
3652
3653
3653 class ExternalIdentity(Base, BaseModel):
3654 class ExternalIdentity(Base, BaseModel):
3654 __tablename__ = 'external_identities'
3655 __tablename__ = 'external_identities'
3655 __table_args__ = (
3656 __table_args__ = (
3656 Index('local_user_id_idx', 'local_user_id'),
3657 Index('local_user_id_idx', 'local_user_id'),
3657 Index('external_id_idx', 'external_id'),
3658 Index('external_id_idx', 'external_id'),
3658 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3659 'mysql_charset': 'utf8'})
3660 'mysql_charset': 'utf8'})
3660
3661
3661 external_id = Column('external_id', Unicode(255), default=u'',
3662 external_id = Column('external_id', Unicode(255), default=u'',
3662 primary_key=True)
3663 primary_key=True)
3663 external_username = Column('external_username', Unicode(1024), default=u'')
3664 external_username = Column('external_username', Unicode(1024), default=u'')
3664 local_user_id = Column('local_user_id', Integer(),
3665 local_user_id = Column('local_user_id', Integer(),
3665 ForeignKey('users.user_id'), primary_key=True)
3666 ForeignKey('users.user_id'), primary_key=True)
3666 provider_name = Column('provider_name', Unicode(255), default=u'',
3667 provider_name = Column('provider_name', Unicode(255), default=u'',
3667 primary_key=True)
3668 primary_key=True)
3668 access_token = Column('access_token', String(1024), default=u'')
3669 access_token = Column('access_token', String(1024), default=u'')
3669 alt_token = Column('alt_token', String(1024), default=u'')
3670 alt_token = Column('alt_token', String(1024), default=u'')
3670 token_secret = Column('token_secret', String(1024), default=u'')
3671 token_secret = Column('token_secret', String(1024), default=u'')
3671
3672
3672 @classmethod
3673 @classmethod
3673 def by_external_id_and_provider(cls, external_id, provider_name,
3674 def by_external_id_and_provider(cls, external_id, provider_name,
3674 local_user_id=None):
3675 local_user_id=None):
3675 """
3676 """
3676 Returns ExternalIdentity instance based on search params
3677 Returns ExternalIdentity instance based on search params
3677
3678
3678 :param external_id:
3679 :param external_id:
3679 :param provider_name:
3680 :param provider_name:
3680 :return: ExternalIdentity
3681 :return: ExternalIdentity
3681 """
3682 """
3682 query = cls.query()
3683 query = cls.query()
3683 query = query.filter(cls.external_id == external_id)
3684 query = query.filter(cls.external_id == external_id)
3684 query = query.filter(cls.provider_name == provider_name)
3685 query = query.filter(cls.provider_name == provider_name)
3685 if local_user_id:
3686 if local_user_id:
3686 query = query.filter(cls.local_user_id == local_user_id)
3687 query = query.filter(cls.local_user_id == local_user_id)
3687 return query.first()
3688 return query.first()
3688
3689
3689 @classmethod
3690 @classmethod
3690 def user_by_external_id_and_provider(cls, external_id, provider_name):
3691 def user_by_external_id_and_provider(cls, external_id, provider_name):
3691 """
3692 """
3692 Returns User instance based on search params
3693 Returns User instance based on search params
3693
3694
3694 :param external_id:
3695 :param external_id:
3695 :param provider_name:
3696 :param provider_name:
3696 :return: User
3697 :return: User
3697 """
3698 """
3698 query = User.query()
3699 query = User.query()
3699 query = query.filter(cls.external_id == external_id)
3700 query = query.filter(cls.external_id == external_id)
3700 query = query.filter(cls.provider_name == provider_name)
3701 query = query.filter(cls.provider_name == provider_name)
3701 query = query.filter(User.user_id == cls.local_user_id)
3702 query = query.filter(User.user_id == cls.local_user_id)
3702 return query.first()
3703 return query.first()
3703
3704
3704 @classmethod
3705 @classmethod
3705 def by_local_user_id(cls, local_user_id):
3706 def by_local_user_id(cls, local_user_id):
3706 """
3707 """
3707 Returns all tokens for user
3708 Returns all tokens for user
3708
3709
3709 :param local_user_id:
3710 :param local_user_id:
3710 :return: ExternalIdentity
3711 :return: ExternalIdentity
3711 """
3712 """
3712 query = cls.query()
3713 query = cls.query()
3713 query = query.filter(cls.local_user_id == local_user_id)
3714 query = query.filter(cls.local_user_id == local_user_id)
3714 return query
3715 return query
3715
3716
3716
3717
3717 class Integration(Base, BaseModel):
3718 class Integration(Base, BaseModel):
3718 __tablename__ = 'integrations'
3719 __tablename__ = 'integrations'
3719 __table_args__ = (
3720 __table_args__ = (
3720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3721 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3722 )
3723 )
3723
3724
3724 integration_id = Column('integration_id', Integer(), primary_key=True)
3725 integration_id = Column('integration_id', Integer(), primary_key=True)
3725 integration_type = Column('integration_type', String(255))
3726 integration_type = Column('integration_type', String(255))
3726 enabled = Column('enabled', Boolean(), nullable=False)
3727 enabled = Column('enabled', Boolean(), nullable=False)
3727 name = Column('name', String(255), nullable=False)
3728 name = Column('name', String(255), nullable=False)
3728 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3729 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3729 default=False)
3730 default=False)
3730
3731
3731 settings = Column(
3732 settings = Column(
3732 'settings_json', MutationObj.as_mutable(
3733 'settings_json', MutationObj.as_mutable(
3733 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3734 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3734 repo_id = Column(
3735 repo_id = Column(
3735 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3736 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3736 nullable=True, unique=None, default=None)
3737 nullable=True, unique=None, default=None)
3737 repo = relationship('Repository', lazy='joined')
3738 repo = relationship('Repository', lazy='joined')
3738
3739
3739 repo_group_id = Column(
3740 repo_group_id = Column(
3740 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3741 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3741 nullable=True, unique=None, default=None)
3742 nullable=True, unique=None, default=None)
3742 repo_group = relationship('RepoGroup', lazy='joined')
3743 repo_group = relationship('RepoGroup', lazy='joined')
3743
3744
3744 @property
3745 @property
3745 def scope(self):
3746 def scope(self):
3746 if self.repo:
3747 if self.repo:
3747 return repr(self.repo)
3748 return repr(self.repo)
3748 if self.repo_group:
3749 if self.repo_group:
3749 if self.child_repos_only:
3750 if self.child_repos_only:
3750 return repr(self.repo_group) + ' (child repos only)'
3751 return repr(self.repo_group) + ' (child repos only)'
3751 else:
3752 else:
3752 return repr(self.repo_group) + ' (recursive)'
3753 return repr(self.repo_group) + ' (recursive)'
3753 if self.child_repos_only:
3754 if self.child_repos_only:
3754 return 'root_repos'
3755 return 'root_repos'
3755 return 'global'
3756 return 'global'
3756
3757
3757 def __repr__(self):
3758 def __repr__(self):
3758 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3759 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3759
3760
3760
3761
3761 class RepoReviewRuleUser(Base, BaseModel):
3762 class RepoReviewRuleUser(Base, BaseModel):
3762 __tablename__ = 'repo_review_rules_users'
3763 __tablename__ = 'repo_review_rules_users'
3763 __table_args__ = (
3764 __table_args__ = (
3764 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3765 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3765 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3766 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3766 )
3767 )
3767 repo_review_rule_user_id = Column(
3768 repo_review_rule_user_id = Column(
3768 'repo_review_rule_user_id', Integer(), primary_key=True)
3769 'repo_review_rule_user_id', Integer(), primary_key=True)
3769 repo_review_rule_id = Column("repo_review_rule_id",
3770 repo_review_rule_id = Column("repo_review_rule_id",
3770 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3771 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3771 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3772 nullable=False)
3773 nullable=False)
3773 user = relationship('User')
3774 user = relationship('User')
3774
3775
3775
3776
3776 class RepoReviewRuleUserGroup(Base, BaseModel):
3777 class RepoReviewRuleUserGroup(Base, BaseModel):
3777 __tablename__ = 'repo_review_rules_users_groups'
3778 __tablename__ = 'repo_review_rules_users_groups'
3778 __table_args__ = (
3779 __table_args__ = (
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3781 )
3782 )
3782 repo_review_rule_users_group_id = Column(
3783 repo_review_rule_users_group_id = Column(
3783 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3784 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3784 repo_review_rule_id = Column("repo_review_rule_id",
3785 repo_review_rule_id = Column("repo_review_rule_id",
3785 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3786 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3786 users_group_id = Column("users_group_id", Integer(),
3787 users_group_id = Column("users_group_id", Integer(),
3787 ForeignKey('users_groups.users_group_id'), nullable=False)
3788 ForeignKey('users_groups.users_group_id'), nullable=False)
3788 users_group = relationship('UserGroup')
3789 users_group = relationship('UserGroup')
3789
3790
3790
3791
3791 class RepoReviewRule(Base, BaseModel):
3792 class RepoReviewRule(Base, BaseModel):
3792 __tablename__ = 'repo_review_rules'
3793 __tablename__ = 'repo_review_rules'
3793 __table_args__ = (
3794 __table_args__ = (
3794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3795 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3796 )
3797 )
3797
3798
3798 repo_review_rule_id = Column(
3799 repo_review_rule_id = Column(
3799 'repo_review_rule_id', Integer(), primary_key=True)
3800 'repo_review_rule_id', Integer(), primary_key=True)
3800 repo_id = Column(
3801 repo_id = Column(
3801 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3802 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3802 repo = relationship('Repository', backref='review_rules')
3803 repo = relationship('Repository', backref='review_rules')
3803
3804
3804 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3805 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3805 default=u'*') # glob
3806 default=u'*') # glob
3806 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3807 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3807 default=u'*') # glob
3808 default=u'*') # glob
3808
3809
3809 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3810 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3810 nullable=False, default=False)
3811 nullable=False, default=False)
3811 rule_users = relationship('RepoReviewRuleUser')
3812 rule_users = relationship('RepoReviewRuleUser')
3812 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3813 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3813
3814
3814 @hybrid_property
3815 @hybrid_property
3815 def branch_pattern(self):
3816 def branch_pattern(self):
3816 return self._branch_pattern or '*'
3817 return self._branch_pattern or '*'
3817
3818
3818 def _validate_glob(self, value):
3819 def _validate_glob(self, value):
3819 re.compile('^' + glob2re(value) + '$')
3820 re.compile('^' + glob2re(value) + '$')
3820
3821
3821 @branch_pattern.setter
3822 @branch_pattern.setter
3822 def branch_pattern(self, value):
3823 def branch_pattern(self, value):
3823 self._validate_glob(value)
3824 self._validate_glob(value)
3824 self._branch_pattern = value or '*'
3825 self._branch_pattern = value or '*'
3825
3826
3826 @hybrid_property
3827 @hybrid_property
3827 def file_pattern(self):
3828 def file_pattern(self):
3828 return self._file_pattern or '*'
3829 return self._file_pattern or '*'
3829
3830
3830 @file_pattern.setter
3831 @file_pattern.setter
3831 def file_pattern(self, value):
3832 def file_pattern(self, value):
3832 self._validate_glob(value)
3833 self._validate_glob(value)
3833 self._file_pattern = value or '*'
3834 self._file_pattern = value or '*'
3834
3835
3835 def matches(self, branch, files_changed):
3836 def matches(self, branch, files_changed):
3836 """
3837 """
3837 Check if this review rule matches a branch/files in a pull request
3838 Check if this review rule matches a branch/files in a pull request
3838
3839
3839 :param branch: branch name for the commit
3840 :param branch: branch name for the commit
3840 :param files_changed: list of file paths changed in the pull request
3841 :param files_changed: list of file paths changed in the pull request
3841 """
3842 """
3842
3843
3843 branch = branch or ''
3844 branch = branch or ''
3844 files_changed = files_changed or []
3845 files_changed = files_changed or []
3845
3846
3846 branch_matches = True
3847 branch_matches = True
3847 if branch:
3848 if branch:
3848 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3849 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3849 branch_matches = bool(branch_regex.search(branch))
3850 branch_matches = bool(branch_regex.search(branch))
3850
3851
3851 files_matches = True
3852 files_matches = True
3852 if self.file_pattern != '*':
3853 if self.file_pattern != '*':
3853 files_matches = False
3854 files_matches = False
3854 file_regex = re.compile(glob2re(self.file_pattern))
3855 file_regex = re.compile(glob2re(self.file_pattern))
3855 for filename in files_changed:
3856 for filename in files_changed:
3856 if file_regex.search(filename):
3857 if file_regex.search(filename):
3857 files_matches = True
3858 files_matches = True
3858 break
3859 break
3859
3860
3860 return branch_matches and files_matches
3861 return branch_matches and files_matches
3861
3862
3862 @property
3863 @property
3863 def review_users(self):
3864 def review_users(self):
3864 """ Returns the users which this rule applies to """
3865 """ Returns the users which this rule applies to """
3865
3866
3866 users = set()
3867 users = set()
3867 users |= set([
3868 users |= set([
3868 rule_user.user for rule_user in self.rule_users
3869 rule_user.user for rule_user in self.rule_users
3869 if rule_user.user.active])
3870 if rule_user.user.active])
3870 users |= set(
3871 users |= set(
3871 member.user
3872 member.user
3872 for rule_user_group in self.rule_user_groups
3873 for rule_user_group in self.rule_user_groups
3873 for member in rule_user_group.users_group.members
3874 for member in rule_user_group.users_group.members
3874 if member.user.active
3875 if member.user.active
3875 )
3876 )
3876 return users
3877 return users
3877
3878
3878 def __repr__(self):
3879 def __repr__(self):
3879 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3880 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3880 self.repo_review_rule_id, self.repo)
3881 self.repo_review_rule_id, self.repo)
3881
3882
3882
3883
3883 class DbMigrateVersion(Base, BaseModel):
3884 class DbMigrateVersion(Base, BaseModel):
3884 __tablename__ = 'db_migrate_version'
3885 __tablename__ = 'db_migrate_version'
3885 __table_args__ = (
3886 __table_args__ = (
3886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3888 )
3889 )
3889 repository_id = Column('repository_id', String(250), primary_key=True)
3890 repository_id = Column('repository_id', String(250), primary_key=True)
3890 repository_path = Column('repository_path', Text)
3891 repository_path = Column('repository_path', Text)
3891 version = Column('version', Integer)
3892 version = Column('version', Integer)
3892
3893
3893
3894
3894 class DbSession(Base, BaseModel):
3895 class DbSession(Base, BaseModel):
3895 __tablename__ = 'db_session'
3896 __tablename__ = 'db_session'
3896 __table_args__ = (
3897 __table_args__ = (
3897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3898 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3899 )
3900 )
3900
3901
3901 def __repr__(self):
3902 def __repr__(self):
3902 return '<DB:DbSession({})>'.format(self.id)
3903 return '<DB:DbSession({})>'.format(self.id)
3903
3904
3904 id = Column('id', Integer())
3905 id = Column('id', Integer())
3905 namespace = Column('namespace', String(255), primary_key=True)
3906 namespace = Column('namespace', String(255), primary_key=True)
3906 accessed = Column('accessed', DateTime, nullable=False)
3907 accessed = Column('accessed', DateTime, nullable=False)
3907 created = Column('created', DateTime, nullable=False)
3908 created = Column('created', DateTime, nullable=False)
3908 data = Column('data', PickleType, nullable=False)
3909 data = Column('data', PickleType, nullable=False)
@@ -1,731 +1,731 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import hashlib
21 import hashlib
22 import logging
22 import logging
23 from collections import namedtuple
23 from collections import namedtuple
24 from functools import wraps
24 from functools import wraps
25
25
26 from rhodecode.lib import caches
26 from rhodecode.lib import caches
27 from rhodecode.lib.utils2 import (
27 from rhodecode.lib.utils2 import (
28 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
28 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 from rhodecode.lib.vcs.backends import base
29 from rhodecode.lib.vcs.backends import base
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 UiSetting = namedtuple(
39 UiSetting = namedtuple(
40 'UiSetting', ['section', 'key', 'value', 'active'])
40 'UiSetting', ['section', 'key', 'value', 'active'])
41
41
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43
43
44
44
45 class SettingNotFound(Exception):
45 class SettingNotFound(Exception):
46 def __init__(self):
46 def __init__(self):
47 super(SettingNotFound, self).__init__('Setting is not found')
47 super(SettingNotFound, self).__init__('Setting is not found')
48
48
49
49
50 class SettingsModel(BaseModel):
50 class SettingsModel(BaseModel):
51 BUILTIN_HOOKS = (
51 BUILTIN_HOOKS = (
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 RhodeCodeUi.HOOK_PRE_PULL)
54 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
55 HOOKS_SECTION = 'hooks'
55 HOOKS_SECTION = 'hooks'
56
56
57 def __init__(self, sa=None, repo=None):
57 def __init__(self, sa=None, repo=None):
58 self.repo = repo
58 self.repo = repo
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 self.SettingsDbModel = (
60 self.SettingsDbModel = (
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 super(SettingsModel, self).__init__(sa)
62 super(SettingsModel, self).__init__(sa)
63
63
64 def get_ui_by_key(self, key):
64 def get_ui_by_key(self, key):
65 q = self.UiDbModel.query()
65 q = self.UiDbModel.query()
66 q = q.filter(self.UiDbModel.ui_key == key)
66 q = q.filter(self.UiDbModel.ui_key == key)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 return q.scalar()
68 return q.scalar()
69
69
70 def get_ui_by_section(self, section):
70 def get_ui_by_section(self, section):
71 q = self.UiDbModel.query()
71 q = self.UiDbModel.query()
72 q = q.filter(self.UiDbModel.ui_section == section)
72 q = q.filter(self.UiDbModel.ui_section == section)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 return q.all()
74 return q.all()
75
75
76 def get_ui_by_section_and_key(self, section, key):
76 def get_ui_by_section_and_key(self, section, key):
77 q = self.UiDbModel.query()
77 q = self.UiDbModel.query()
78 q = q.filter(self.UiDbModel.ui_section == section)
78 q = q.filter(self.UiDbModel.ui_section == section)
79 q = q.filter(self.UiDbModel.ui_key == key)
79 q = q.filter(self.UiDbModel.ui_key == key)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 return q.scalar()
81 return q.scalar()
82
82
83 def get_ui(self, section=None, key=None):
83 def get_ui(self, section=None, key=None):
84 q = self.UiDbModel.query()
84 q = self.UiDbModel.query()
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86
86
87 if section:
87 if section:
88 q = q.filter(self.UiDbModel.ui_section == section)
88 q = q.filter(self.UiDbModel.ui_section == section)
89 if key:
89 if key:
90 q = q.filter(self.UiDbModel.ui_key == key)
90 q = q.filter(self.UiDbModel.ui_key == key)
91
91
92 # TODO: mikhail: add caching
92 # TODO: mikhail: add caching
93 result = [
93 result = [
94 UiSetting(
94 UiSetting(
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 value=safe_str(r.ui_value), active=r.ui_active
96 value=safe_str(r.ui_value), active=r.ui_active
97 )
97 )
98 for r in q.all()
98 for r in q.all()
99 ]
99 ]
100 return result
100 return result
101
101
102 def get_builtin_hooks(self):
102 def get_builtin_hooks(self):
103 q = self.UiDbModel.query()
103 q = self.UiDbModel.query()
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 return self._get_hooks(q)
105 return self._get_hooks(q)
106
106
107 def get_custom_hooks(self):
107 def get_custom_hooks(self):
108 q = self.UiDbModel.query()
108 q = self.UiDbModel.query()
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 return self._get_hooks(q)
110 return self._get_hooks(q)
111
111
112 def create_ui_section_value(self, section, val, key=None, active=True):
112 def create_ui_section_value(self, section, val, key=None, active=True):
113 new_ui = self.UiDbModel()
113 new_ui = self.UiDbModel()
114 new_ui.ui_section = section
114 new_ui.ui_section = section
115 new_ui.ui_value = val
115 new_ui.ui_value = val
116 new_ui.ui_active = active
116 new_ui.ui_active = active
117
117
118 if self.repo:
118 if self.repo:
119 repo = self._get_repo(self.repo)
119 repo = self._get_repo(self.repo)
120 repository_id = repo.repo_id
120 repository_id = repo.repo_id
121 new_ui.repository_id = repository_id
121 new_ui.repository_id = repository_id
122
122
123 if not key:
123 if not key:
124 # keys are unique so they need appended info
124 # keys are unique so they need appended info
125 if self.repo:
125 if self.repo:
126 key = hashlib.sha1(
126 key = hashlib.sha1(
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 else:
128 else:
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130
130
131 new_ui.ui_key = key
131 new_ui.ui_key = key
132
132
133 Session().add(new_ui)
133 Session().add(new_ui)
134 return new_ui
134 return new_ui
135
135
136 def create_or_update_hook(self, key, value):
136 def create_or_update_hook(self, key, value):
137 ui = (
137 ui = (
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 self.UiDbModel())
139 self.UiDbModel())
140 ui.ui_section = self.HOOKS_SECTION
140 ui.ui_section = self.HOOKS_SECTION
141 ui.ui_active = True
141 ui.ui_active = True
142 ui.ui_key = key
142 ui.ui_key = key
143 ui.ui_value = value
143 ui.ui_value = value
144
144
145 if self.repo:
145 if self.repo:
146 repo = self._get_repo(self.repo)
146 repo = self._get_repo(self.repo)
147 repository_id = repo.repo_id
147 repository_id = repo.repo_id
148 ui.repository_id = repository_id
148 ui.repository_id = repository_id
149
149
150 Session().add(ui)
150 Session().add(ui)
151 return ui
151 return ui
152
152
153 def delete_ui(self, id_):
153 def delete_ui(self, id_):
154 ui = self.UiDbModel.get(id_)
154 ui = self.UiDbModel.get(id_)
155 if not ui:
155 if not ui:
156 raise SettingNotFound()
156 raise SettingNotFound()
157 Session().delete(ui)
157 Session().delete(ui)
158
158
159 def get_setting_by_name(self, name):
159 def get_setting_by_name(self, name):
160 q = self._get_settings_query()
160 q = self._get_settings_query()
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 return q.scalar()
162 return q.scalar()
163
163
164 def create_or_update_setting(
164 def create_or_update_setting(
165 self, name, val=Optional(''), type_=Optional('unicode')):
165 self, name, val=Optional(''), type_=Optional('unicode')):
166 """
166 """
167 Creates or updates RhodeCode setting. If updates is triggered it will
167 Creates or updates RhodeCode setting. If updates is triggered it will
168 only update parameters that are explicityl set Optional instance will
168 only update parameters that are explicityl set Optional instance will
169 be skipped
169 be skipped
170
170
171 :param name:
171 :param name:
172 :param val:
172 :param val:
173 :param type_:
173 :param type_:
174 :return:
174 :return:
175 """
175 """
176
176
177 res = self.get_setting_by_name(name)
177 res = self.get_setting_by_name(name)
178 repo = self._get_repo(self.repo) if self.repo else None
178 repo = self._get_repo(self.repo) if self.repo else None
179
179
180 if not res:
180 if not res:
181 val = Optional.extract(val)
181 val = Optional.extract(val)
182 type_ = Optional.extract(type_)
182 type_ = Optional.extract(type_)
183
183
184 args = (
184 args = (
185 (repo.repo_id, name, val, type_)
185 (repo.repo_id, name, val, type_)
186 if repo else (name, val, type_))
186 if repo else (name, val, type_))
187 res = self.SettingsDbModel(*args)
187 res = self.SettingsDbModel(*args)
188
188
189 else:
189 else:
190 if self.repo:
190 if self.repo:
191 res.repository_id = repo.repo_id
191 res.repository_id = repo.repo_id
192
192
193 res.app_settings_name = name
193 res.app_settings_name = name
194 if not isinstance(type_, Optional):
194 if not isinstance(type_, Optional):
195 # update if set
195 # update if set
196 res.app_settings_type = type_
196 res.app_settings_type = type_
197 if not isinstance(val, Optional):
197 if not isinstance(val, Optional):
198 # update if set
198 # update if set
199 res.app_settings_value = val
199 res.app_settings_value = val
200
200
201 Session().add(res)
201 Session().add(res)
202 return res
202 return res
203
203
204 def invalidate_settings_cache(self):
204 def invalidate_settings_cache(self):
205 namespace = 'rhodecode_settings'
205 namespace = 'rhodecode_settings'
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 caches.clear_cache_manager(cache_manager)
207 caches.clear_cache_manager(cache_manager)
208
208
209 def get_all_settings(self, cache=False):
209 def get_all_settings(self, cache=False):
210 def _compute():
210 def _compute():
211 q = self._get_settings_query()
211 q = self._get_settings_query()
212 if not q:
212 if not q:
213 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
214
214
215 settings = {
215 settings = {
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 for result in q
217 for result in q
218 }
218 }
219 return settings
219 return settings
220
220
221 if cache:
221 if cache:
222 log.debug('Fetching app settings using cache')
222 log.debug('Fetching app settings using cache')
223 repo = self._get_repo(self.repo) if self.repo else None
223 repo = self._get_repo(self.repo) if self.repo else None
224 namespace = 'rhodecode_settings'
224 namespace = 'rhodecode_settings'
225 cache_manager = caches.get_cache_manager(
225 cache_manager = caches.get_cache_manager(
226 'sql_cache_short', namespace)
226 'sql_cache_short', namespace)
227 _cache_key = (
227 _cache_key = (
228 "get_repo_{}_settings".format(repo.repo_id)
228 "get_repo_{}_settings".format(repo.repo_id)
229 if repo else "get_app_settings")
229 if repo else "get_app_settings")
230
230
231 return cache_manager.get(_cache_key, createfunc=_compute)
231 return cache_manager.get(_cache_key, createfunc=_compute)
232
232
233 else:
233 else:
234 return _compute()
234 return _compute()
235
235
236 def get_auth_settings(self):
236 def get_auth_settings(self):
237 q = self._get_settings_query()
237 q = self._get_settings_query()
238 q = q.filter(
238 q = q.filter(
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
240 rows = q.all()
240 rows = q.all()
241 auth_settings = {
241 auth_settings = {
242 row.app_settings_name: row.app_settings_value for row in rows}
242 row.app_settings_name: row.app_settings_value for row in rows}
243 return auth_settings
243 return auth_settings
244
244
245 def get_auth_plugins(self):
245 def get_auth_plugins(self):
246 auth_plugins = self.get_setting_by_name("auth_plugins")
246 auth_plugins = self.get_setting_by_name("auth_plugins")
247 return auth_plugins.app_settings_value
247 return auth_plugins.app_settings_value
248
248
249 def get_default_repo_settings(self, strip_prefix=False):
249 def get_default_repo_settings(self, strip_prefix=False):
250 q = self._get_settings_query()
250 q = self._get_settings_query()
251 q = q.filter(
251 q = q.filter(
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
253 rows = q.all()
253 rows = q.all()
254
254
255 result = {}
255 result = {}
256 for row in rows:
256 for row in rows:
257 key = row.app_settings_name
257 key = row.app_settings_name
258 if strip_prefix:
258 if strip_prefix:
259 key = remove_prefix(key, prefix='default_')
259 key = remove_prefix(key, prefix='default_')
260 result.update({key: row.app_settings_value})
260 result.update({key: row.app_settings_value})
261 return result
261 return result
262
262
263 def get_repo(self):
263 def get_repo(self):
264 repo = self._get_repo(self.repo)
264 repo = self._get_repo(self.repo)
265 if not repo:
265 if not repo:
266 raise Exception(
266 raise Exception(
267 'Repository `{}` cannot be found inside the database'.format(
267 'Repository `{}` cannot be found inside the database'.format(
268 self.repo))
268 self.repo))
269 return repo
269 return repo
270
270
271 def _filter_by_repo(self, model, query):
271 def _filter_by_repo(self, model, query):
272 if self.repo:
272 if self.repo:
273 repo = self.get_repo()
273 repo = self.get_repo()
274 query = query.filter(model.repository_id == repo.repo_id)
274 query = query.filter(model.repository_id == repo.repo_id)
275 return query
275 return query
276
276
277 def _get_hooks(self, query):
277 def _get_hooks(self, query):
278 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
278 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
279 query = self._filter_by_repo(RepoRhodeCodeUi, query)
279 query = self._filter_by_repo(RepoRhodeCodeUi, query)
280 return query.all()
280 return query.all()
281
281
282 def _get_settings_query(self):
282 def _get_settings_query(self):
283 q = self.SettingsDbModel.query()
283 q = self.SettingsDbModel.query()
284 return self._filter_by_repo(RepoRhodeCodeSetting, q)
284 return self._filter_by_repo(RepoRhodeCodeSetting, q)
285
285
286 def list_enabled_social_plugins(self, settings):
286 def list_enabled_social_plugins(self, settings):
287 enabled = []
287 enabled = []
288 for plug in SOCIAL_PLUGINS_LIST:
288 for plug in SOCIAL_PLUGINS_LIST:
289 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
289 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
290 )):
290 )):
291 enabled.append(plug)
291 enabled.append(plug)
292 return enabled
292 return enabled
293
293
294
294
295 def assert_repo_settings(func):
295 def assert_repo_settings(func):
296 @wraps(func)
296 @wraps(func)
297 def _wrapper(self, *args, **kwargs):
297 def _wrapper(self, *args, **kwargs):
298 if not self.repo_settings:
298 if not self.repo_settings:
299 raise Exception('Repository is not specified')
299 raise Exception('Repository is not specified')
300 return func(self, *args, **kwargs)
300 return func(self, *args, **kwargs)
301 return _wrapper
301 return _wrapper
302
302
303
303
304 class IssueTrackerSettingsModel(object):
304 class IssueTrackerSettingsModel(object):
305 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
305 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
306 SETTINGS_PREFIX = 'issuetracker_'
306 SETTINGS_PREFIX = 'issuetracker_'
307
307
308 def __init__(self, sa=None, repo=None):
308 def __init__(self, sa=None, repo=None):
309 self.global_settings = SettingsModel(sa=sa)
309 self.global_settings = SettingsModel(sa=sa)
310 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
310 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
311
311
312 @property
312 @property
313 def inherit_global_settings(self):
313 def inherit_global_settings(self):
314 if not self.repo_settings:
314 if not self.repo_settings:
315 return True
315 return True
316 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
316 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
317 return setting.app_settings_value if setting else True
317 return setting.app_settings_value if setting else True
318
318
319 @inherit_global_settings.setter
319 @inherit_global_settings.setter
320 def inherit_global_settings(self, value):
320 def inherit_global_settings(self, value):
321 if self.repo_settings:
321 if self.repo_settings:
322 settings = self.repo_settings.create_or_update_setting(
322 settings = self.repo_settings.create_or_update_setting(
323 self.INHERIT_SETTINGS, value, type_='bool')
323 self.INHERIT_SETTINGS, value, type_='bool')
324 Session().add(settings)
324 Session().add(settings)
325
325
326 def _get_keyname(self, key, uid, prefix=''):
326 def _get_keyname(self, key, uid, prefix=''):
327 return '{0}{1}{2}_{3}'.format(
327 return '{0}{1}{2}_{3}'.format(
328 prefix, self.SETTINGS_PREFIX, key, uid)
328 prefix, self.SETTINGS_PREFIX, key, uid)
329
329
330 def _make_dict_for_settings(self, qs):
330 def _make_dict_for_settings(self, qs):
331 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
331 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
332
332
333 issuetracker_entries = {}
333 issuetracker_entries = {}
334 # create keys
334 # create keys
335 for k, v in qs.items():
335 for k, v in qs.items():
336 if k.startswith(prefix_match):
336 if k.startswith(prefix_match):
337 uid = k[len(prefix_match):]
337 uid = k[len(prefix_match):]
338 issuetracker_entries[uid] = None
338 issuetracker_entries[uid] = None
339
339
340 # populate
340 # populate
341 for uid in issuetracker_entries:
341 for uid in issuetracker_entries:
342 issuetracker_entries[uid] = AttributeDict({
342 issuetracker_entries[uid] = AttributeDict({
343 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
343 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
344 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
344 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
345 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
345 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
346 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
346 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
347 })
347 })
348 return issuetracker_entries
348 return issuetracker_entries
349
349
350 def get_global_settings(self, cache=False):
350 def get_global_settings(self, cache=False):
351 """
351 """
352 Returns list of global issue tracker settings
352 Returns list of global issue tracker settings
353 """
353 """
354 defaults = self.global_settings.get_all_settings(cache=cache)
354 defaults = self.global_settings.get_all_settings(cache=cache)
355 settings = self._make_dict_for_settings(defaults)
355 settings = self._make_dict_for_settings(defaults)
356 return settings
356 return settings
357
357
358 def get_repo_settings(self, cache=False):
358 def get_repo_settings(self, cache=False):
359 """
359 """
360 Returns list of issue tracker settings per repository
360 Returns list of issue tracker settings per repository
361 """
361 """
362 if not self.repo_settings:
362 if not self.repo_settings:
363 raise Exception('Repository is not specified')
363 raise Exception('Repository is not specified')
364 all_settings = self.repo_settings.get_all_settings(cache=cache)
364 all_settings = self.repo_settings.get_all_settings(cache=cache)
365 settings = self._make_dict_for_settings(all_settings)
365 settings = self._make_dict_for_settings(all_settings)
366 return settings
366 return settings
367
367
368 def get_settings(self, cache=False):
368 def get_settings(self, cache=False):
369 if self.inherit_global_settings:
369 if self.inherit_global_settings:
370 return self.get_global_settings(cache=cache)
370 return self.get_global_settings(cache=cache)
371 else:
371 else:
372 return self.get_repo_settings(cache=cache)
372 return self.get_repo_settings(cache=cache)
373
373
374 def delete_entries(self, uid):
374 def delete_entries(self, uid):
375 if self.repo_settings:
375 if self.repo_settings:
376 all_patterns = self.get_repo_settings()
376 all_patterns = self.get_repo_settings()
377 settings_model = self.repo_settings
377 settings_model = self.repo_settings
378 else:
378 else:
379 all_patterns = self.get_global_settings()
379 all_patterns = self.get_global_settings()
380 settings_model = self.global_settings
380 settings_model = self.global_settings
381 entries = all_patterns.get(uid)
381 entries = all_patterns.get(uid)
382
382
383 for del_key in entries:
383 for del_key in entries:
384 setting_name = self._get_keyname(del_key, uid)
384 setting_name = self._get_keyname(del_key, uid)
385 entry = settings_model.get_setting_by_name(setting_name)
385 entry = settings_model.get_setting_by_name(setting_name)
386 if entry:
386 if entry:
387 Session().delete(entry)
387 Session().delete(entry)
388
388
389 Session().commit()
389 Session().commit()
390
390
391 def create_or_update_setting(
391 def create_or_update_setting(
392 self, name, val=Optional(''), type_=Optional('unicode')):
392 self, name, val=Optional(''), type_=Optional('unicode')):
393 if self.repo_settings:
393 if self.repo_settings:
394 setting = self.repo_settings.create_or_update_setting(
394 setting = self.repo_settings.create_or_update_setting(
395 name, val, type_)
395 name, val, type_)
396 else:
396 else:
397 setting = self.global_settings.create_or_update_setting(
397 setting = self.global_settings.create_or_update_setting(
398 name, val, type_)
398 name, val, type_)
399 return setting
399 return setting
400
400
401
401
402 class VcsSettingsModel(object):
402 class VcsSettingsModel(object):
403
403
404 INHERIT_SETTINGS = 'inherit_vcs_settings'
404 INHERIT_SETTINGS = 'inherit_vcs_settings'
405 GENERAL_SETTINGS = (
405 GENERAL_SETTINGS = (
406 'use_outdated_comments',
406 'use_outdated_comments',
407 'pr_merge_enabled',
407 'pr_merge_enabled',
408 'hg_use_rebase_for_merging')
408 'hg_use_rebase_for_merging')
409
409
410 HOOKS_SETTINGS = (
410 HOOKS_SETTINGS = (
411 ('hooks', 'changegroup.repo_size'),
411 ('hooks', 'changegroup.repo_size'),
412 ('hooks', 'changegroup.push_logger'),
412 ('hooks', 'changegroup.push_logger'),
413 ('hooks', 'outgoing.pull_logger'))
413 ('hooks', 'outgoing.pull_logger'))
414 HG_SETTINGS = (
414 HG_SETTINGS = (
415 ('extensions', 'largefiles'),
415 ('extensions', 'largefiles'),
416 ('phases', 'publish'))
416 ('phases', 'publish'))
417 GLOBAL_HG_SETTINGS = (
417 GLOBAL_HG_SETTINGS = (
418 ('extensions', 'largefiles'),
418 ('extensions', 'largefiles'),
419 ('phases', 'publish'),
419 ('phases', 'publish'),
420 ('extensions', 'hgsubversion'))
420 ('extensions', 'hgsubversion'))
421 GLOBAL_SVN_SETTINGS = (
421 GLOBAL_SVN_SETTINGS = (
422 ('vcs_svn_proxy', 'http_requests_enabled'),
422 ('vcs_svn_proxy', 'http_requests_enabled'),
423 ('vcs_svn_proxy', 'http_server_url'))
423 ('vcs_svn_proxy', 'http_server_url'))
424
424
425 SVN_BRANCH_SECTION = 'vcs_svn_branch'
425 SVN_BRANCH_SECTION = 'vcs_svn_branch'
426 SVN_TAG_SECTION = 'vcs_svn_tag'
426 SVN_TAG_SECTION = 'vcs_svn_tag'
427 SSL_SETTING = ('web', 'push_ssl')
427 SSL_SETTING = ('web', 'push_ssl')
428 PATH_SETTING = ('paths', '/')
428 PATH_SETTING = ('paths', '/')
429
429
430 def __init__(self, sa=None, repo=None):
430 def __init__(self, sa=None, repo=None):
431 self.global_settings = SettingsModel(sa=sa)
431 self.global_settings = SettingsModel(sa=sa)
432 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
432 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
433 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
433 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
434 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
434 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
435
435
436 @property
436 @property
437 @assert_repo_settings
437 @assert_repo_settings
438 def inherit_global_settings(self):
438 def inherit_global_settings(self):
439 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
439 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
440 return setting.app_settings_value if setting else True
440 return setting.app_settings_value if setting else True
441
441
442 @inherit_global_settings.setter
442 @inherit_global_settings.setter
443 @assert_repo_settings
443 @assert_repo_settings
444 def inherit_global_settings(self, value):
444 def inherit_global_settings(self, value):
445 self.repo_settings.create_or_update_setting(
445 self.repo_settings.create_or_update_setting(
446 self.INHERIT_SETTINGS, value, type_='bool')
446 self.INHERIT_SETTINGS, value, type_='bool')
447
447
448 def get_global_svn_branch_patterns(self):
448 def get_global_svn_branch_patterns(self):
449 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
449 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
450
450
451 @assert_repo_settings
451 @assert_repo_settings
452 def get_repo_svn_branch_patterns(self):
452 def get_repo_svn_branch_patterns(self):
453 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
453 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
454
454
455 def get_global_svn_tag_patterns(self):
455 def get_global_svn_tag_patterns(self):
456 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
456 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
457
457
458 @assert_repo_settings
458 @assert_repo_settings
459 def get_repo_svn_tag_patterns(self):
459 def get_repo_svn_tag_patterns(self):
460 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
460 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
461
461
462 def get_global_settings(self):
462 def get_global_settings(self):
463 return self._collect_all_settings(global_=True)
463 return self._collect_all_settings(global_=True)
464
464
465 @assert_repo_settings
465 @assert_repo_settings
466 def get_repo_settings(self):
466 def get_repo_settings(self):
467 return self._collect_all_settings(global_=False)
467 return self._collect_all_settings(global_=False)
468
468
469 @assert_repo_settings
469 @assert_repo_settings
470 def create_or_update_repo_settings(
470 def create_or_update_repo_settings(
471 self, data, inherit_global_settings=False):
471 self, data, inherit_global_settings=False):
472 from rhodecode.model.scm import ScmModel
472 from rhodecode.model.scm import ScmModel
473
473
474 self.inherit_global_settings = inherit_global_settings
474 self.inherit_global_settings = inherit_global_settings
475
475
476 repo = self.repo_settings.get_repo()
476 repo = self.repo_settings.get_repo()
477 if not inherit_global_settings:
477 if not inherit_global_settings:
478 if repo.repo_type == 'svn':
478 if repo.repo_type == 'svn':
479 self.create_repo_svn_settings(data)
479 self.create_repo_svn_settings(data)
480 else:
480 else:
481 self.create_or_update_repo_hook_settings(data)
481 self.create_or_update_repo_hook_settings(data)
482 self.create_or_update_repo_pr_settings(data)
482 self.create_or_update_repo_pr_settings(data)
483
483
484 if repo.repo_type == 'hg':
484 if repo.repo_type == 'hg':
485 self.create_or_update_repo_hg_settings(data)
485 self.create_or_update_repo_hg_settings(data)
486
486
487 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
487 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
488
488
489 @assert_repo_settings
489 @assert_repo_settings
490 def create_or_update_repo_hook_settings(self, data):
490 def create_or_update_repo_hook_settings(self, data):
491 for section, key in self.HOOKS_SETTINGS:
491 for section, key in self.HOOKS_SETTINGS:
492 data_key = self._get_form_ui_key(section, key)
492 data_key = self._get_form_ui_key(section, key)
493 if data_key not in data:
493 if data_key not in data:
494 raise ValueError(
494 raise ValueError(
495 'The given data does not contain {} key'.format(data_key))
495 'The given data does not contain {} key'.format(data_key))
496
496
497 active = data.get(data_key)
497 active = data.get(data_key)
498 repo_setting = self.repo_settings.get_ui_by_section_and_key(
498 repo_setting = self.repo_settings.get_ui_by_section_and_key(
499 section, key)
499 section, key)
500 if not repo_setting:
500 if not repo_setting:
501 global_setting = self.global_settings.\
501 global_setting = self.global_settings.\
502 get_ui_by_section_and_key(section, key)
502 get_ui_by_section_and_key(section, key)
503 self.repo_settings.create_ui_section_value(
503 self.repo_settings.create_ui_section_value(
504 section, global_setting.ui_value, key=key, active=active)
504 section, global_setting.ui_value, key=key, active=active)
505 else:
505 else:
506 repo_setting.ui_active = active
506 repo_setting.ui_active = active
507 Session().add(repo_setting)
507 Session().add(repo_setting)
508
508
509 def update_global_hook_settings(self, data):
509 def update_global_hook_settings(self, data):
510 for section, key in self.HOOKS_SETTINGS:
510 for section, key in self.HOOKS_SETTINGS:
511 data_key = self._get_form_ui_key(section, key)
511 data_key = self._get_form_ui_key(section, key)
512 if data_key not in data:
512 if data_key not in data:
513 raise ValueError(
513 raise ValueError(
514 'The given data does not contain {} key'.format(data_key))
514 'The given data does not contain {} key'.format(data_key))
515 active = data.get(data_key)
515 active = data.get(data_key)
516 repo_setting = self.global_settings.get_ui_by_section_and_key(
516 repo_setting = self.global_settings.get_ui_by_section_and_key(
517 section, key)
517 section, key)
518 repo_setting.ui_active = active
518 repo_setting.ui_active = active
519 Session().add(repo_setting)
519 Session().add(repo_setting)
520
520
521 @assert_repo_settings
521 @assert_repo_settings
522 def create_or_update_repo_pr_settings(self, data):
522 def create_or_update_repo_pr_settings(self, data):
523 return self._create_or_update_general_settings(
523 return self._create_or_update_general_settings(
524 self.repo_settings, data)
524 self.repo_settings, data)
525
525
526 def create_or_update_global_pr_settings(self, data):
526 def create_or_update_global_pr_settings(self, data):
527 return self._create_or_update_general_settings(
527 return self._create_or_update_general_settings(
528 self.global_settings, data)
528 self.global_settings, data)
529
529
530 @assert_repo_settings
530 @assert_repo_settings
531 def create_repo_svn_settings(self, data):
531 def create_repo_svn_settings(self, data):
532 return self._create_svn_settings(self.repo_settings, data)
532 return self._create_svn_settings(self.repo_settings, data)
533
533
534 @assert_repo_settings
534 @assert_repo_settings
535 def create_or_update_repo_hg_settings(self, data):
535 def create_or_update_repo_hg_settings(self, data):
536 largefiles, phases = self.HG_SETTINGS
536 largefiles, phases = self.HG_SETTINGS
537 largefiles_key, phases_key = self._get_settings_keys(
537 largefiles_key, phases_key = self._get_settings_keys(
538 self.HG_SETTINGS, data)
538 self.HG_SETTINGS, data)
539 self._create_or_update_ui(
539 self._create_or_update_ui(
540 self.repo_settings, *largefiles, value='',
540 self.repo_settings, *largefiles, value='',
541 active=data[largefiles_key])
541 active=data[largefiles_key])
542 self._create_or_update_ui(
542 self._create_or_update_ui(
543 self.repo_settings, *phases, value=safe_str(data[phases_key]))
543 self.repo_settings, *phases, value=safe_str(data[phases_key]))
544
544
545 def create_or_update_global_hg_settings(self, data):
545 def create_or_update_global_hg_settings(self, data):
546 largefiles, phases, hgsubversion = self.GLOBAL_HG_SETTINGS
546 largefiles, phases, hgsubversion = self.GLOBAL_HG_SETTINGS
547 largefiles_key, phases_key, subversion_key = self._get_settings_keys(
547 largefiles_key, phases_key, subversion_key = self._get_settings_keys(
548 self.GLOBAL_HG_SETTINGS, data)
548 self.GLOBAL_HG_SETTINGS, data)
549 self._create_or_update_ui(
549 self._create_or_update_ui(
550 self.global_settings, *largefiles, value='',
550 self.global_settings, *largefiles, value='',
551 active=data[largefiles_key])
551 active=data[largefiles_key])
552 self._create_or_update_ui(
552 self._create_or_update_ui(
553 self.global_settings, *phases, value=safe_str(data[phases_key]))
553 self.global_settings, *phases, value=safe_str(data[phases_key]))
554 self._create_or_update_ui(
554 self._create_or_update_ui(
555 self.global_settings, *hgsubversion, active=data[subversion_key])
555 self.global_settings, *hgsubversion, active=data[subversion_key])
556
556
557 def create_or_update_global_svn_settings(self, data):
557 def create_or_update_global_svn_settings(self, data):
558 # branch/tags patterns
558 # branch/tags patterns
559 self._create_svn_settings(self.global_settings, data)
559 self._create_svn_settings(self.global_settings, data)
560
560
561 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
561 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
562 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
562 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
563 self.GLOBAL_SVN_SETTINGS, data)
563 self.GLOBAL_SVN_SETTINGS, data)
564
564
565 self._create_or_update_ui(
565 self._create_or_update_ui(
566 self.global_settings, *http_requests_enabled,
566 self.global_settings, *http_requests_enabled,
567 value=safe_str(data[http_requests_enabled_key]))
567 value=safe_str(data[http_requests_enabled_key]))
568 self._create_or_update_ui(
568 self._create_or_update_ui(
569 self.global_settings, *http_server_url,
569 self.global_settings, *http_server_url,
570 value=data[http_server_url_key])
570 value=data[http_server_url_key])
571
571
572 def update_global_ssl_setting(self, value):
572 def update_global_ssl_setting(self, value):
573 self._create_or_update_ui(
573 self._create_or_update_ui(
574 self.global_settings, *self.SSL_SETTING, value=value)
574 self.global_settings, *self.SSL_SETTING, value=value)
575
575
576 def update_global_path_setting(self, value):
576 def update_global_path_setting(self, value):
577 self._create_or_update_ui(
577 self._create_or_update_ui(
578 self.global_settings, *self.PATH_SETTING, value=value)
578 self.global_settings, *self.PATH_SETTING, value=value)
579
579
580 @assert_repo_settings
580 @assert_repo_settings
581 def delete_repo_svn_pattern(self, id_):
581 def delete_repo_svn_pattern(self, id_):
582 self.repo_settings.delete_ui(id_)
582 self.repo_settings.delete_ui(id_)
583
583
584 def delete_global_svn_pattern(self, id_):
584 def delete_global_svn_pattern(self, id_):
585 self.global_settings.delete_ui(id_)
585 self.global_settings.delete_ui(id_)
586
586
587 @assert_repo_settings
587 @assert_repo_settings
588 def get_repo_ui_settings(self, section=None, key=None):
588 def get_repo_ui_settings(self, section=None, key=None):
589 global_uis = self.global_settings.get_ui(section, key)
589 global_uis = self.global_settings.get_ui(section, key)
590 repo_uis = self.repo_settings.get_ui(section, key)
590 repo_uis = self.repo_settings.get_ui(section, key)
591 filtered_repo_uis = self._filter_ui_settings(repo_uis)
591 filtered_repo_uis = self._filter_ui_settings(repo_uis)
592 filtered_repo_uis_keys = [
592 filtered_repo_uis_keys = [
593 (s.section, s.key) for s in filtered_repo_uis]
593 (s.section, s.key) for s in filtered_repo_uis]
594
594
595 def _is_global_ui_filtered(ui):
595 def _is_global_ui_filtered(ui):
596 return (
596 return (
597 (ui.section, ui.key) in filtered_repo_uis_keys
597 (ui.section, ui.key) in filtered_repo_uis_keys
598 or ui.section in self._svn_sections)
598 or ui.section in self._svn_sections)
599
599
600 filtered_global_uis = [
600 filtered_global_uis = [
601 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
601 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
602
602
603 return filtered_global_uis + filtered_repo_uis
603 return filtered_global_uis + filtered_repo_uis
604
604
605 def get_global_ui_settings(self, section=None, key=None):
605 def get_global_ui_settings(self, section=None, key=None):
606 return self.global_settings.get_ui(section, key)
606 return self.global_settings.get_ui(section, key)
607
607
608 def get_ui_settings_as_config_obj(self, section=None, key=None):
608 def get_ui_settings_as_config_obj(self, section=None, key=None):
609 config = base.Config()
609 config = base.Config()
610
610
611 ui_settings = self.get_ui_settings(section=section, key=key)
611 ui_settings = self.get_ui_settings(section=section, key=key)
612
612
613 for entry in ui_settings:
613 for entry in ui_settings:
614 config.set(entry.section, entry.key, entry.value)
614 config.set(entry.section, entry.key, entry.value)
615
615
616 return config
616 return config
617
617
618 def get_ui_settings(self, section=None, key=None):
618 def get_ui_settings(self, section=None, key=None):
619 if not self.repo_settings or self.inherit_global_settings:
619 if not self.repo_settings or self.inherit_global_settings:
620 return self.get_global_ui_settings(section, key)
620 return self.get_global_ui_settings(section, key)
621 else:
621 else:
622 return self.get_repo_ui_settings(section, key)
622 return self.get_repo_ui_settings(section, key)
623
623
624 def get_svn_patterns(self, section=None):
624 def get_svn_patterns(self, section=None):
625 if not self.repo_settings:
625 if not self.repo_settings:
626 return self.get_global_ui_settings(section)
626 return self.get_global_ui_settings(section)
627 else:
627 else:
628 return self.get_repo_ui_settings(section)
628 return self.get_repo_ui_settings(section)
629
629
630 @assert_repo_settings
630 @assert_repo_settings
631 def get_repo_general_settings(self):
631 def get_repo_general_settings(self):
632 global_settings = self.global_settings.get_all_settings()
632 global_settings = self.global_settings.get_all_settings()
633 repo_settings = self.repo_settings.get_all_settings()
633 repo_settings = self.repo_settings.get_all_settings()
634 filtered_repo_settings = self._filter_general_settings(repo_settings)
634 filtered_repo_settings = self._filter_general_settings(repo_settings)
635 global_settings.update(filtered_repo_settings)
635 global_settings.update(filtered_repo_settings)
636 return global_settings
636 return global_settings
637
637
638 def get_global_general_settings(self):
638 def get_global_general_settings(self):
639 return self.global_settings.get_all_settings()
639 return self.global_settings.get_all_settings()
640
640
641 def get_general_settings(self):
641 def get_general_settings(self):
642 if not self.repo_settings or self.inherit_global_settings:
642 if not self.repo_settings or self.inherit_global_settings:
643 return self.get_global_general_settings()
643 return self.get_global_general_settings()
644 else:
644 else:
645 return self.get_repo_general_settings()
645 return self.get_repo_general_settings()
646
646
647 def get_repos_location(self):
647 def get_repos_location(self):
648 return self.global_settings.get_ui_by_key('/').ui_value
648 return self.global_settings.get_ui_by_key('/').ui_value
649
649
650 def _filter_ui_settings(self, settings):
650 def _filter_ui_settings(self, settings):
651 filtered_settings = [
651 filtered_settings = [
652 s for s in settings if self._should_keep_setting(s)]
652 s for s in settings if self._should_keep_setting(s)]
653 return filtered_settings
653 return filtered_settings
654
654
655 def _should_keep_setting(self, setting):
655 def _should_keep_setting(self, setting):
656 keep = (
656 keep = (
657 (setting.section, setting.key) in self._ui_settings or
657 (setting.section, setting.key) in self._ui_settings or
658 setting.section in self._svn_sections)
658 setting.section in self._svn_sections)
659 return keep
659 return keep
660
660
661 def _filter_general_settings(self, settings):
661 def _filter_general_settings(self, settings):
662 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
662 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
663 return {
663 return {
664 k: settings[k]
664 k: settings[k]
665 for k in settings if k in keys}
665 for k in settings if k in keys}
666
666
667 def _collect_all_settings(self, global_=False):
667 def _collect_all_settings(self, global_=False):
668 settings = self.global_settings if global_ else self.repo_settings
668 settings = self.global_settings if global_ else self.repo_settings
669 result = {}
669 result = {}
670
670
671 for section, key in self._ui_settings:
671 for section, key in self._ui_settings:
672 ui = settings.get_ui_by_section_and_key(section, key)
672 ui = settings.get_ui_by_section_and_key(section, key)
673 result_key = self._get_form_ui_key(section, key)
673 result_key = self._get_form_ui_key(section, key)
674 if ui:
674 if ui:
675 if section in ('hooks', 'extensions'):
675 if section in ('hooks', 'extensions'):
676 result[result_key] = ui.ui_active
676 result[result_key] = ui.ui_active
677 else:
677 else:
678 result[result_key] = ui.ui_value
678 result[result_key] = ui.ui_value
679
679
680 for name in self.GENERAL_SETTINGS:
680 for name in self.GENERAL_SETTINGS:
681 setting = settings.get_setting_by_name(name)
681 setting = settings.get_setting_by_name(name)
682 if setting:
682 if setting:
683 result_key = 'rhodecode_{}'.format(name)
683 result_key = 'rhodecode_{}'.format(name)
684 result[result_key] = setting.app_settings_value
684 result[result_key] = setting.app_settings_value
685
685
686 return result
686 return result
687
687
688 def _get_form_ui_key(self, section, key):
688 def _get_form_ui_key(self, section, key):
689 return '{section}_{key}'.format(
689 return '{section}_{key}'.format(
690 section=section, key=key.replace('.', '_'))
690 section=section, key=key.replace('.', '_'))
691
691
692 def _create_or_update_ui(
692 def _create_or_update_ui(
693 self, settings, section, key, value=None, active=None):
693 self, settings, section, key, value=None, active=None):
694 ui = settings.get_ui_by_section_and_key(section, key)
694 ui = settings.get_ui_by_section_and_key(section, key)
695 if not ui:
695 if not ui:
696 active = True if active is None else active
696 active = True if active is None else active
697 settings.create_ui_section_value(
697 settings.create_ui_section_value(
698 section, value, key=key, active=active)
698 section, value, key=key, active=active)
699 else:
699 else:
700 if active is not None:
700 if active is not None:
701 ui.ui_active = active
701 ui.ui_active = active
702 if value is not None:
702 if value is not None:
703 ui.ui_value = value
703 ui.ui_value = value
704 Session().add(ui)
704 Session().add(ui)
705
705
706 def _create_svn_settings(self, settings, data):
706 def _create_svn_settings(self, settings, data):
707 svn_settings = {
707 svn_settings = {
708 'new_svn_branch': self.SVN_BRANCH_SECTION,
708 'new_svn_branch': self.SVN_BRANCH_SECTION,
709 'new_svn_tag': self.SVN_TAG_SECTION
709 'new_svn_tag': self.SVN_TAG_SECTION
710 }
710 }
711 for key in svn_settings:
711 for key in svn_settings:
712 if data.get(key):
712 if data.get(key):
713 settings.create_ui_section_value(svn_settings[key], data[key])
713 settings.create_ui_section_value(svn_settings[key], data[key])
714
714
715 def _create_or_update_general_settings(self, settings, data):
715 def _create_or_update_general_settings(self, settings, data):
716 for name in self.GENERAL_SETTINGS:
716 for name in self.GENERAL_SETTINGS:
717 data_key = 'rhodecode_{}'.format(name)
717 data_key = 'rhodecode_{}'.format(name)
718 if data_key not in data:
718 if data_key not in data:
719 raise ValueError(
719 raise ValueError(
720 'The given data does not contain {} key'.format(data_key))
720 'The given data does not contain {} key'.format(data_key))
721 setting = settings.create_or_update_setting(
721 setting = settings.create_or_update_setting(
722 name, data[data_key], 'bool')
722 name, data[data_key], 'bool')
723 Session().add(setting)
723 Session().add(setting)
724
724
725 def _get_settings_keys(self, settings, data):
725 def _get_settings_keys(self, settings, data):
726 data_keys = [self._get_form_ui_key(*s) for s in settings]
726 data_keys = [self._get_form_ui_key(*s) for s in settings]
727 for data_key in data_keys:
727 for data_key in data_keys:
728 if data_key not in data:
728 if data_key not in data:
729 raise ValueError(
729 raise ValueError(
730 'The given data does not contain {} key'.format(data_key))
730 'The given data does not contain {} key'.format(data_key))
731 return data_keys
731 return data_keys
@@ -1,467 +1,471 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22 import multiprocessing
22 import multiprocessing
23 import os
23 import os
24
24
25 import mock
25 import mock
26 import py
26 import py
27 import pytest
27 import pytest
28
28
29 from rhodecode.lib import caching_query
29 from rhodecode.lib import caching_query
30 from rhodecode.lib import utils
30 from rhodecode.lib import utils
31 from rhodecode.lib.utils2 import md5
31 from rhodecode.lib.utils2 import md5
32 from rhodecode.model import settings
32 from rhodecode.model import settings
33 from rhodecode.model import db
33 from rhodecode.model import db
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
39 from rhodecode.tests.fixture import Fixture
39 from rhodecode.tests.fixture import Fixture
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
41
41
42
42
43 fixture = Fixture()
43 fixture = Fixture()
44
44
45
45
46 def extract_hooks(config):
46 def extract_hooks(config):
47 """Return a dictionary with the hook entries of the given config."""
47 """Return a dictionary with the hook entries of the given config."""
48 hooks = {}
48 hooks = {}
49 config_items = config.serialize()
49 config_items = config.serialize()
50 for section, name, value in config_items:
50 for section, name, value in config_items:
51 if section != 'hooks':
51 if section != 'hooks':
52 continue
52 continue
53 hooks[name] = value
53 hooks[name] = value
54
54
55 return hooks
55 return hooks
56
56
57
57
58 def disable_hooks(request, hooks):
58 def disable_hooks(request, hooks):
59 """Disables the given hooks from the UI settings."""
59 """Disables the given hooks from the UI settings."""
60 session = meta.Session()
60 session = meta.Session()
61
61
62 model = SettingsModel()
62 model = SettingsModel()
63 for hook_key in hooks:
63 for hook_key in hooks:
64 sett = model.get_ui_by_key(hook_key)
64 sett = model.get_ui_by_key(hook_key)
65 sett.ui_active = False
65 sett.ui_active = False
66 session.add(sett)
66 session.add(sett)
67
67
68 # Invalidate cache
68 # Invalidate cache
69 ui_settings = session.query(db.RhodeCodeUi).options(
69 ui_settings = session.query(db.RhodeCodeUi).options(
70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
71 ui_settings.invalidate()
71 ui_settings.invalidate()
72
72
73 ui_settings = session.query(db.RhodeCodeUi).options(
73 ui_settings = session.query(db.RhodeCodeUi).options(
74 caching_query.FromCache(
74 caching_query.FromCache(
75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
76 ui_settings.invalidate()
76 ui_settings.invalidate()
77
77
78 @request.addfinalizer
78 @request.addfinalizer
79 def rollback():
79 def rollback():
80 session.rollback()
80 session.rollback()
81
81
82
82
83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
84 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
84 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
85 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
85 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
86 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
87 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
88
89
89 HG_HOOKS = frozenset(
90 HG_HOOKS = frozenset(
90 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PUSH, HOOK_REPO_SIZE))
91 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
92 HOOK_REPO_SIZE))
91
93
92
94
93 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
95 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
94 ([], HG_HOOKS),
96 ([], HG_HOOKS),
95 ([HOOK_PRE_PUSH, HOOK_REPO_SIZE], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
96 (HG_HOOKS, []),
97 (HG_HOOKS, []),
98
99 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
100
97 # When a pull/push hook is disabled, its pre-pull/push counterpart should
101 # When a pull/push hook is disabled, its pre-pull/push counterpart should
98 # be disabled too.
102 # be disabled too.
99 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
103 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
100 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PUSH, HOOK_REPO_SIZE]),
104 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE]),
101 ])
105 ])
102 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
106 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
103 expected_hooks):
107 expected_hooks):
104 disable_hooks(request, disabled_hooks)
108 disable_hooks(request, disabled_hooks)
105
109
106 config = utils.make_db_config()
110 config = utils.make_db_config()
107 hooks = extract_hooks(config)
111 hooks = extract_hooks(config)
108
112
109 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
113 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
110
114
111
115
112 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
116 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
113 ([], ['pull', 'push']),
117 ([], ['pull', 'push']),
114 ([HOOK_PUSH], ['pull']),
118 ([HOOK_PUSH], ['pull']),
115 ([HOOK_PULL], ['push']),
119 ([HOOK_PULL], ['push']),
116 ([HOOK_PULL, HOOK_PUSH], []),
120 ([HOOK_PULL, HOOK_PUSH], []),
117 ])
121 ])
118 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
122 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
119 hook_keys = (HOOK_PUSH, HOOK_PULL)
123 hook_keys = (HOOK_PUSH, HOOK_PULL)
120 ui_settings = [
124 ui_settings = [
121 ('hooks', key, 'some value', key not in disabled_hooks)
125 ('hooks', key, 'some value', key not in disabled_hooks)
122 for key in hook_keys]
126 for key in hook_keys]
123
127
124 result = utils.get_enabled_hook_classes(ui_settings)
128 result = utils.get_enabled_hook_classes(ui_settings)
125 assert sorted(result) == expected_hooks
129 assert sorted(result) == expected_hooks
126
130
127
131
128 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
132 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
129 _stub_git_repo(tmpdir.ensure('repo', dir=True))
133 _stub_git_repo(tmpdir.ensure('repo', dir=True))
130 repos = list(utils.get_filesystem_repos(str(tmpdir)))
134 repos = list(utils.get_filesystem_repos(str(tmpdir)))
131 assert repos == [('repo', ('git', tmpdir.join('repo')))]
135 assert repos == [('repo', ('git', tmpdir.join('repo')))]
132
136
133
137
134 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
138 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
135 tmpdir.ensure('not-a-repo', dir=True)
139 tmpdir.ensure('not-a-repo', dir=True)
136 repos = list(utils.get_filesystem_repos(str(tmpdir)))
140 repos = list(utils.get_filesystem_repos(str(tmpdir)))
137 assert repos == []
141 assert repos == []
138
142
139
143
140 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
144 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
141 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
145 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
142 repos = list(utils.get_filesystem_repos(str(tmpdir)))
146 repos = list(utils.get_filesystem_repos(str(tmpdir)))
143 assert repos == []
147 assert repos == []
144
148
145
149
146 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
150 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
147 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
151 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
148 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
152 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
149 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
153 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
150
154
151
155
152 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
156 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
153 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
157 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
154 repos = list(utils.get_filesystem_repos(str(tmpdir)))
158 repos = list(utils.get_filesystem_repos(str(tmpdir)))
155 assert repos == []
159 assert repos == []
156
160
157
161
158 def test_get_filesystem_repos_skips_files(tmpdir):
162 def test_get_filesystem_repos_skips_files(tmpdir):
159 tmpdir.ensure('test-file')
163 tmpdir.ensure('test-file')
160 repos = list(utils.get_filesystem_repos(str(tmpdir)))
164 repos = list(utils.get_filesystem_repos(str(tmpdir)))
161 assert repos == []
165 assert repos == []
162
166
163
167
164 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
168 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
165 removed_repo_name = 'rm__00000000_000000_000000__.stub'
169 removed_repo_name = 'rm__00000000_000000_000000__.stub'
166 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
170 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
167 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
171 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
168 repos = list(utils.get_filesystem_repos(str(tmpdir)))
172 repos = list(utils.get_filesystem_repos(str(tmpdir)))
169 assert repos == []
173 assert repos == []
170
174
171
175
172 def _stub_git_repo(repo_path):
176 def _stub_git_repo(repo_path):
173 """
177 """
174 Make `repo_path` look like a Git repository.
178 Make `repo_path` look like a Git repository.
175 """
179 """
176 repo_path.ensure('.git', dir=True)
180 repo_path.ensure('.git', dir=True)
177
181
178
182
179 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
183 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
180 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
184 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
181 tmpdir.ensure('test-file')
185 tmpdir.ensure('test-file')
182 dirpaths = utils._get_dirpaths(str_class(tmpdir))
186 dirpaths = utils._get_dirpaths(str_class(tmpdir))
183 assert dirpaths == ['test-file']
187 assert dirpaths == ['test-file']
184
188
185
189
186 def test_get_dirpaths_returns_all_paths_bytes(
190 def test_get_dirpaths_returns_all_paths_bytes(
187 tmpdir, platform_encodes_filenames):
191 tmpdir, platform_encodes_filenames):
188 if platform_encodes_filenames:
192 if platform_encodes_filenames:
189 pytest.skip("This platform seems to encode filenames.")
193 pytest.skip("This platform seems to encode filenames.")
190 tmpdir.ensure('repo-a-umlaut-\xe4')
194 tmpdir.ensure('repo-a-umlaut-\xe4')
191 dirpaths = utils._get_dirpaths(str(tmpdir))
195 dirpaths = utils._get_dirpaths(str(tmpdir))
192 assert dirpaths == ['repo-a-umlaut-\xe4']
196 assert dirpaths == ['repo-a-umlaut-\xe4']
193
197
194
198
195 def test_get_dirpaths_skips_paths_it_cannot_decode(
199 def test_get_dirpaths_skips_paths_it_cannot_decode(
196 tmpdir, platform_encodes_filenames):
200 tmpdir, platform_encodes_filenames):
197 if platform_encodes_filenames:
201 if platform_encodes_filenames:
198 pytest.skip("This platform seems to encode filenames.")
202 pytest.skip("This platform seems to encode filenames.")
199 path_with_latin1 = 'repo-a-umlaut-\xe4'
203 path_with_latin1 = 'repo-a-umlaut-\xe4'
200 tmpdir.ensure(path_with_latin1)
204 tmpdir.ensure(path_with_latin1)
201 dirpaths = utils._get_dirpaths(unicode(tmpdir))
205 dirpaths = utils._get_dirpaths(unicode(tmpdir))
202 assert dirpaths == []
206 assert dirpaths == []
203
207
204
208
205 @pytest.fixture(scope='session')
209 @pytest.fixture(scope='session')
206 def platform_encodes_filenames():
210 def platform_encodes_filenames():
207 """
211 """
208 Boolean indicator if the current platform changes filename encodings.
212 Boolean indicator if the current platform changes filename encodings.
209 """
213 """
210 path_with_latin1 = 'repo-a-umlaut-\xe4'
214 path_with_latin1 = 'repo-a-umlaut-\xe4'
211 tmpdir = py.path.local.mkdtemp()
215 tmpdir = py.path.local.mkdtemp()
212 tmpdir.ensure(path_with_latin1)
216 tmpdir.ensure(path_with_latin1)
213 read_path = tmpdir.listdir()[0].basename
217 read_path = tmpdir.listdir()[0].basename
214 tmpdir.remove()
218 tmpdir.remove()
215 return path_with_latin1 != read_path
219 return path_with_latin1 != read_path
216
220
217
221
218 def test_action_logger_action_size(pylonsapp, test_repo):
222 def test_action_logger_action_size(pylonsapp, test_repo):
219 action = 'x' * 1200001
223 action = 'x' * 1200001
220 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
224 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
221
225
222
226
223 @pytest.fixture
227 @pytest.fixture
224 def repo_groups(request):
228 def repo_groups(request):
225 session = meta.Session()
229 session = meta.Session()
226 zombie_group = fixture.create_repo_group('zombie')
230 zombie_group = fixture.create_repo_group('zombie')
227 parent_group = fixture.create_repo_group('parent')
231 parent_group = fixture.create_repo_group('parent')
228 child_group = fixture.create_repo_group('parent/child')
232 child_group = fixture.create_repo_group('parent/child')
229 groups_in_db = session.query(db.RepoGroup).all()
233 groups_in_db = session.query(db.RepoGroup).all()
230 assert len(groups_in_db) == 3
234 assert len(groups_in_db) == 3
231 assert child_group.group_parent_id == parent_group.group_id
235 assert child_group.group_parent_id == parent_group.group_id
232
236
233 @request.addfinalizer
237 @request.addfinalizer
234 def cleanup():
238 def cleanup():
235 fixture.destroy_repo_group(zombie_group)
239 fixture.destroy_repo_group(zombie_group)
236 fixture.destroy_repo_group(child_group)
240 fixture.destroy_repo_group(child_group)
237 fixture.destroy_repo_group(parent_group)
241 fixture.destroy_repo_group(parent_group)
238
242
239 return (zombie_group, parent_group, child_group)
243 return (zombie_group, parent_group, child_group)
240
244
241
245
242 def test_repo2db_mapper_groups(repo_groups):
246 def test_repo2db_mapper_groups(repo_groups):
243 session = meta.Session()
247 session = meta.Session()
244 zombie_group, parent_group, child_group = repo_groups
248 zombie_group, parent_group, child_group = repo_groups
245 zombie_path = os.path.join(
249 zombie_path = os.path.join(
246 RepoGroupModel().repos_path, zombie_group.full_path)
250 RepoGroupModel().repos_path, zombie_group.full_path)
247 os.rmdir(zombie_path)
251 os.rmdir(zombie_path)
248
252
249 # Avoid removing test repos when calling repo2db_mapper
253 # Avoid removing test repos when calling repo2db_mapper
250 repo_list = {
254 repo_list = {
251 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
255 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
252 }
256 }
253 utils.repo2db_mapper(repo_list, remove_obsolete=True)
257 utils.repo2db_mapper(repo_list, remove_obsolete=True)
254
258
255 groups_in_db = session.query(db.RepoGroup).all()
259 groups_in_db = session.query(db.RepoGroup).all()
256 assert child_group in groups_in_db
260 assert child_group in groups_in_db
257 assert parent_group in groups_in_db
261 assert parent_group in groups_in_db
258 assert zombie_path not in groups_in_db
262 assert zombie_path not in groups_in_db
259
263
260
264
261 def test_repo2db_mapper_enables_largefiles(backend):
265 def test_repo2db_mapper_enables_largefiles(backend):
262 repo = backend.create_repo()
266 repo = backend.create_repo()
263 repo_list = {repo.repo_name: 'test'}
267 repo_list = {repo.repo_name: 'test'}
264 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
268 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
265 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
269 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
266 install_git_hook=mock.DEFAULT,
270 install_git_hook=mock.DEFAULT,
267 install_svn_hooks=mock.DEFAULT):
271 install_svn_hooks=mock.DEFAULT):
268 utils.repo2db_mapper(repo_list, remove_obsolete=False)
272 utils.repo2db_mapper(repo_list, remove_obsolete=False)
269 _, kwargs = scm_mock.call_args
273 _, kwargs = scm_mock.call_args
270 assert kwargs['config'].get('extensions', 'largefiles') == ''
274 assert kwargs['config'].get('extensions', 'largefiles') == ''
271
275
272
276
273 @pytest.mark.backends("git", "svn")
277 @pytest.mark.backends("git", "svn")
274 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
278 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
275 repo = backend.create_repo()
279 repo = backend.create_repo()
276 repo_list = {repo.repo_name: 'test'}
280 repo_list = {repo.repo_name: 'test'}
277 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
281 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
278 utils.repo2db_mapper(repo_list, remove_obsolete=False)
282 utils.repo2db_mapper(repo_list, remove_obsolete=False)
279 install_hooks_mock.assert_called_once_with(
283 install_hooks_mock.assert_called_once_with(
280 repo.scm_instance(), repo_type=backend.alias)
284 repo.scm_instance(), repo_type=backend.alias)
281
285
282
286
283 @pytest.mark.backends("git", "svn")
287 @pytest.mark.backends("git", "svn")
284 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
288 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
285 repo = backend.create_repo()
289 repo = backend.create_repo()
286 RepoModel().delete(repo, fs_remove=False)
290 RepoModel().delete(repo, fs_remove=False)
287 meta.Session().commit()
291 meta.Session().commit()
288 repo_list = {repo.repo_name: repo.scm_instance()}
292 repo_list = {repo.repo_name: repo.scm_instance()}
289 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
293 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
290 utils.repo2db_mapper(repo_list, remove_obsolete=False)
294 utils.repo2db_mapper(repo_list, remove_obsolete=False)
291 assert install_hooks_mock.call_count == 1
295 assert install_hooks_mock.call_count == 1
292 install_hooks_args, _ = install_hooks_mock.call_args
296 install_hooks_args, _ = install_hooks_mock.call_args
293 assert install_hooks_args[0].name == repo.repo_name
297 assert install_hooks_args[0].name == repo.repo_name
294
298
295
299
296 class TestPasswordChanged(object):
300 class TestPasswordChanged(object):
297 def setup(self):
301 def setup(self):
298 self.session = {
302 self.session = {
299 'rhodecode_user': {
303 'rhodecode_user': {
300 'password': '0cc175b9c0f1b6a831c399e269772661'
304 'password': '0cc175b9c0f1b6a831c399e269772661'
301 }
305 }
302 }
306 }
303 self.auth_user = mock.Mock()
307 self.auth_user = mock.Mock()
304 self.auth_user.userame = 'test'
308 self.auth_user.userame = 'test'
305 self.auth_user.password = 'abc123'
309 self.auth_user.password = 'abc123'
306
310
307 def test_returns_false_for_default_user(self):
311 def test_returns_false_for_default_user(self):
308 self.auth_user.username = db.User.DEFAULT_USER
312 self.auth_user.username = db.User.DEFAULT_USER
309 result = utils.password_changed(self.auth_user, self.session)
313 result = utils.password_changed(self.auth_user, self.session)
310 assert result is False
314 assert result is False
311
315
312 def test_returns_false_if_password_was_not_changed(self):
316 def test_returns_false_if_password_was_not_changed(self):
313 self.session['rhodecode_user']['password'] = md5(
317 self.session['rhodecode_user']['password'] = md5(
314 self.auth_user.password)
318 self.auth_user.password)
315 result = utils.password_changed(self.auth_user, self.session)
319 result = utils.password_changed(self.auth_user, self.session)
316 assert result is False
320 assert result is False
317
321
318 def test_returns_true_if_password_was_changed(self):
322 def test_returns_true_if_password_was_changed(self):
319 result = utils.password_changed(self.auth_user, self.session)
323 result = utils.password_changed(self.auth_user, self.session)
320 assert result is True
324 assert result is True
321
325
322 def test_returns_true_if_auth_user_password_is_empty(self):
326 def test_returns_true_if_auth_user_password_is_empty(self):
323 self.auth_user.password = None
327 self.auth_user.password = None
324 result = utils.password_changed(self.auth_user, self.session)
328 result = utils.password_changed(self.auth_user, self.session)
325 assert result is True
329 assert result is True
326
330
327 def test_returns_true_if_session_password_is_empty(self):
331 def test_returns_true_if_session_password_is_empty(self):
328 self.session['rhodecode_user'].pop('password')
332 self.session['rhodecode_user'].pop('password')
329 result = utils.password_changed(self.auth_user, self.session)
333 result = utils.password_changed(self.auth_user, self.session)
330 assert result is True
334 assert result is True
331
335
332
336
333 class TestReadOpensourceLicenses(object):
337 class TestReadOpensourceLicenses(object):
334 def test_success(self):
338 def test_success(self):
335 utils._license_cache = None
339 utils._license_cache = None
336 json_data = '''
340 json_data = '''
337 {
341 {
338 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
342 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
339 "python2.7-Markdown-2.6.2": {
343 "python2.7-Markdown-2.6.2": {
340 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
344 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
341 }
345 }
342 }
346 }
343 '''
347 '''
344 resource_string_patch = mock.patch.object(
348 resource_string_patch = mock.patch.object(
345 utils.pkg_resources, 'resource_string', return_value=json_data)
349 utils.pkg_resources, 'resource_string', return_value=json_data)
346 with resource_string_patch:
350 with resource_string_patch:
347 result = utils.read_opensource_licenses()
351 result = utils.read_opensource_licenses()
348 assert result == json.loads(json_data)
352 assert result == json.loads(json_data)
349
353
350 def test_caching(self):
354 def test_caching(self):
351 utils._license_cache = {
355 utils._license_cache = {
352 "python2.7-pytest-2.7.1": {
356 "python2.7-pytest-2.7.1": {
353 "UNKNOWN": None
357 "UNKNOWN": None
354 },
358 },
355 "python2.7-Markdown-2.6.2": {
359 "python2.7-Markdown-2.6.2": {
356 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
360 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
357 }
361 }
358 }
362 }
359 resource_patch = mock.patch.object(
363 resource_patch = mock.patch.object(
360 utils.pkg_resources, 'resource_string', side_effect=Exception)
364 utils.pkg_resources, 'resource_string', side_effect=Exception)
361 json_patch = mock.patch.object(
365 json_patch = mock.patch.object(
362 utils.json, 'loads', side_effect=Exception)
366 utils.json, 'loads', side_effect=Exception)
363
367
364 with resource_patch as resource_mock, json_patch as json_mock:
368 with resource_patch as resource_mock, json_patch as json_mock:
365 result = utils.read_opensource_licenses()
369 result = utils.read_opensource_licenses()
366
370
367 assert resource_mock.call_count == 0
371 assert resource_mock.call_count == 0
368 assert json_mock.call_count == 0
372 assert json_mock.call_count == 0
369 assert result == utils._license_cache
373 assert result == utils._license_cache
370
374
371 def test_licenses_file_contains_no_unknown_licenses(self):
375 def test_licenses_file_contains_no_unknown_licenses(self):
372 utils._license_cache = None
376 utils._license_cache = None
373 result = utils.read_opensource_licenses()
377 result = utils.read_opensource_licenses()
374 license_names = []
378 license_names = []
375 for licenses in result.values():
379 for licenses in result.values():
376 license_names.extend(licenses.keys())
380 license_names.extend(licenses.keys())
377 assert 'UNKNOWN' not in license_names
381 assert 'UNKNOWN' not in license_names
378
382
379
383
380 class TestMakeDbConfig(object):
384 class TestMakeDbConfig(object):
381 def test_data_from_config_data_from_db_returned(self):
385 def test_data_from_config_data_from_db_returned(self):
382 test_data = [
386 test_data = [
383 ('section1', 'option1', 'value1'),
387 ('section1', 'option1', 'value1'),
384 ('section2', 'option2', 'value2'),
388 ('section2', 'option2', 'value2'),
385 ('section3', 'option3', 'value3'),
389 ('section3', 'option3', 'value3'),
386 ]
390 ]
387 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
391 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
388 config_mock.return_value = test_data
392 config_mock.return_value = test_data
389 kwargs = {'clear_session': False, 'repo': 'test_repo'}
393 kwargs = {'clear_session': False, 'repo': 'test_repo'}
390 result = utils.make_db_config(**kwargs)
394 result = utils.make_db_config(**kwargs)
391 config_mock.assert_called_once_with(**kwargs)
395 config_mock.assert_called_once_with(**kwargs)
392 for section, option, expected_value in test_data:
396 for section, option, expected_value in test_data:
393 value = result.get(section, option)
397 value = result.get(section, option)
394 assert value == expected_value
398 assert value == expected_value
395
399
396
400
397 class TestConfigDataFromDb(object):
401 class TestConfigDataFromDb(object):
398 def test_config_data_from_db_returns_active_settings(self):
402 def test_config_data_from_db_returns_active_settings(self):
399 test_data = [
403 test_data = [
400 UiSetting('section1', 'option1', 'value1', True),
404 UiSetting('section1', 'option1', 'value1', True),
401 UiSetting('section2', 'option2', 'value2', True),
405 UiSetting('section2', 'option2', 'value2', True),
402 UiSetting('section3', 'option3', 'value3', False),
406 UiSetting('section3', 'option3', 'value3', False),
403 ]
407 ]
404 repo_name = 'test_repo'
408 repo_name = 'test_repo'
405
409
406 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
410 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
407 hooks_patch = mock.patch.object(
411 hooks_patch = mock.patch.object(
408 utils, 'get_enabled_hook_classes',
412 utils, 'get_enabled_hook_classes',
409 return_value=['pull', 'push', 'repo_size'])
413 return_value=['pull', 'push', 'repo_size'])
410 with model_patch as model_mock, hooks_patch:
414 with model_patch as model_mock, hooks_patch:
411 instance_mock = mock.Mock()
415 instance_mock = mock.Mock()
412 model_mock.return_value = instance_mock
416 model_mock.return_value = instance_mock
413 instance_mock.get_ui_settings.return_value = test_data
417 instance_mock.get_ui_settings.return_value = test_data
414 result = utils.config_data_from_db(
418 result = utils.config_data_from_db(
415 clear_session=False, repo=repo_name)
419 clear_session=False, repo=repo_name)
416
420
417 self._assert_repo_name_passed(model_mock, repo_name)
421 self._assert_repo_name_passed(model_mock, repo_name)
418
422
419 expected_result = [
423 expected_result = [
420 ('section1', 'option1', 'value1'),
424 ('section1', 'option1', 'value1'),
421 ('section2', 'option2', 'value2'),
425 ('section2', 'option2', 'value2'),
422 ]
426 ]
423 assert result == expected_result
427 assert result == expected_result
424
428
425 def _assert_repo_name_passed(self, model_mock, repo_name):
429 def _assert_repo_name_passed(self, model_mock, repo_name):
426 assert model_mock.call_count == 1
430 assert model_mock.call_count == 1
427 call_args, call_kwargs = model_mock.call_args
431 call_args, call_kwargs = model_mock.call_args
428 assert call_kwargs['repo'] == repo_name
432 assert call_kwargs['repo'] == repo_name
429
433
430
434
431 class TestIsDirWritable(object):
435 class TestIsDirWritable(object):
432 def test_returns_false_when_not_writable(self):
436 def test_returns_false_when_not_writable(self):
433 with mock.patch('__builtin__.open', side_effect=OSError):
437 with mock.patch('__builtin__.open', side_effect=OSError):
434 assert not utils._is_dir_writable('/stub-path')
438 assert not utils._is_dir_writable('/stub-path')
435
439
436 def test_returns_true_when_writable(self, tmpdir):
440 def test_returns_true_when_writable(self, tmpdir):
437 assert utils._is_dir_writable(str(tmpdir))
441 assert utils._is_dir_writable(str(tmpdir))
438
442
439 def test_is_safe_against_race_conditions(self, tmpdir):
443 def test_is_safe_against_race_conditions(self, tmpdir):
440 workers = multiprocessing.Pool()
444 workers = multiprocessing.Pool()
441 directories = [str(tmpdir)] * 10
445 directories = [str(tmpdir)] * 10
442 workers.map(utils._is_dir_writable, directories)
446 workers.map(utils._is_dir_writable, directories)
443
447
444
448
445 class TestGetEnabledHooks(object):
449 class TestGetEnabledHooks(object):
446 def test_only_active_hooks_are_enabled(self):
450 def test_only_active_hooks_are_enabled(self):
447 ui_settings = [
451 ui_settings = [
448 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
452 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
449 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
453 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
450 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
454 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
451 ]
455 ]
452 result = utils.get_enabled_hook_classes(ui_settings)
456 result = utils.get_enabled_hook_classes(ui_settings)
453 assert result == ['push', 'repo_size']
457 assert result == ['push', 'repo_size']
454
458
455 def test_all_hooks_are_enabled(self):
459 def test_all_hooks_are_enabled(self):
456 ui_settings = [
460 ui_settings = [
457 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
461 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
458 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
462 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
459 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
463 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
460 ]
464 ]
461 result = utils.get_enabled_hook_classes(ui_settings)
465 result = utils.get_enabled_hook_classes(ui_settings)
462 assert result == ['push', 'repo_size', 'pull']
466 assert result == ['push', 'repo_size', 'pull']
463
467
464 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
468 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
465 ui_settings = []
469 ui_settings = []
466 result = utils.get_enabled_hook_classes(ui_settings)
470 result = utils.get_enabled_hook_classes(ui_settings)
467 assert result == []
471 assert result == []
General Comments 0
You need to be logged in to leave comments. Login now