##// END OF EJS Templates
core: added user-notice logic to push notice messages....
ergo -
r4300:8f93504d default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (5547 lines changed) Show them Hide them
@@ -0,0 +1,5547 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2019 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 string
29 import hashlib
30 import logging
31 import datetime
32 import uuid
33 import warnings
34 import ipaddress
35 import functools
36 import traceback
37 import collections
38
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
56
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
73
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
76
77 # =============================================================================
78 # BASE CLASSES
79 # =============================================================================
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
90 'write': '###',
91 'read': '##',
92 'none': '#',
93 }
94
95
96 def display_user_sort(obj):
97 """
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
101 """
102
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
107
108
109 def display_user_group_sort(obj):
110 """
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
114 """
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
118
119
120 def _hash_key(k):
121 return sha1_safe(k)
122
123
124 def in_filter_generator(qry, items, limit=500):
125 """
126 Splits IN() into multiple with OR
127 e.g.::
128 cnt = Repository.query().filter(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
132 """
133 if not items:
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
136 items = [-1]
137
138 parts = []
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
142 )
143
144 return parts
145
146
147 base_table_args = {
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
152 }
153
154
155 class EncryptedTextValue(TypeDecorator):
156 """
157 Special column for encrypted long text data, use like::
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
163 """
164 impl = Text
165
166 def process_bind_param(self, value, dialect):
167 """
168 Setter for storing value
169 """
170 import rhodecode
171 if not value:
172 return value
173
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
189 def process_result_value(self, value, dialect):
190 """
191 Getter for retrieving value
192 """
193
194 import rhodecode
195 if not value:
196 return value
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
207
208
209 class BaseModel(object):
210 """
211 Base Model for all classes
212 """
213
214 @classmethod
215 def _get_keys(cls):
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
218
219 def get_dict(self):
220 """
221 return dict with keys and values corresponding
222 to this model data """
223
224 d = {}
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
227
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
231 # update with attributes from __json__
232 if callable(_json_attr):
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
235 d[k] = val
236 return d
237
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
240 to this model data """
241
242 lst = []
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
245 return lst
246
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
249
250 for k in self._get_keys():
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
253
254 @classmethod
255 def query(cls):
256 return Session().query(cls)
257
258 @classmethod
259 def get(cls, id_):
260 if id_:
261 return cls.query().get(id_)
262
263 @classmethod
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
266
267 try:
268 id_ = int(id_)
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
271
272 res = cls.query().get(id_)
273 if not res:
274 raise HTTPNotFound()
275 return res
276
277 @classmethod
278 def getAll(cls):
279 # deprecated and left for backward compatibility
280 return cls.get_all()
281
282 @classmethod
283 def get_all(cls):
284 return cls.query().all()
285
286 @classmethod
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
289 Session().delete(obj)
290
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
297 if exist_in_session:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
300 log.exception(
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
304
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
308 try:
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
313
314
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
319 base_table_args
320 )
321
322 SETTINGS_TYPES = {
323 'str': safe_str,
324 'int': safe_int,
325 'unicode': safe_unicode,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
339 self.app_settings_type = type
340 self.app_settings_value = val
341
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
345 return val
346
347 @hybrid_property
348 def app_settings_value(self):
349 v = self._app_settings_value
350 _type = self.app_settings_type
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
361
362 @app_settings_value.setter
363 def app_settings_value(self, val):
364 """
365 Setter that will always make sure we use unicode in app_settings_value
366
367 :param val:
368 """
369 val = safe_unicode(val)
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
375
376 @hybrid_property
377 def app_settings_type(self):
378 return self._app_settings_type
379
380 @app_settings_type.setter
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
386
387 @classmethod
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
392
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
398 )
399
400
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
405 base_table_args
406 )
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
424 ]
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
428
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
448
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
457 )
458
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
474
475 repository = relationship('Repository')
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
479 self.app_settings_name = key
480 self.app_settings_type = type
481 self.app_settings_value = val
482
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
486 return val
487
488 @hybrid_property
489 def app_settings_value(self):
490 v = self._app_settings_value
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
495
496 @app_settings_value.setter
497 def app_settings_value(self, val):
498 """
499 Setter that will always make sure we use unicode in app_settings_value
500
501 :param val:
502 """
503 self._app_settings_value = safe_unicode(val)
504
505 @hybrid_property
506 def app_settings_type(self):
507 return self._app_settings_type
508
509 @app_settings_type.setter
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
516
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
522 )
523
524
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
532 )
533
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
549 repository = relationship('Repository')
550
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
555
556
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
563 base_table_args
564 )
565
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
603 group_member = relationship('UserGroupMember', cascade='all')
604
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
616 # gists
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
620 # external identities
621 external_identities = relationship(
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
637
638 @hybrid_property
639 def email(self):
640 return self._email
641
642 @email.setter
643 def email(self, val):
644 self._email = val.lower() if val else None
645
646 @hybrid_property
647 def first_name(self):
648 from rhodecode.lib import helpers as h
649 if self.name:
650 return h.escape(self.name)
651 return self.name
652
653 @hybrid_property
654 def last_name(self):
655 from rhodecode.lib import helpers as h
656 if self.lastname:
657 return h.escape(self.lastname)
658 return self.lastname
659
660 @hybrid_property
661 def api_key(self):
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
672
673 return user_auth_token
674
675 @api_key.setter
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
679
680 @property
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
686
687 @property
688 def firstname(self):
689 # alias for future
690 return self.name
691
692 @property
693 def emails(self):
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
698 return [self.email] + [x.email for x in other]
699
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
704
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
708
709 return [self.email] + [x.email for x in emails]
710
711 @property
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
715
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
721
722 @LazyProperty
723 def feed_token(self):
724 return self.get_feed_token()
725
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
739 @LazyProperty
740 def artifact_token(self):
741 return self.get_artifact_token()
742
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
756 @classmethod
757 def get(cls, user_id, cache=False):
758 if not user_id:
759 return
760
761 user = cls.query()
762 if cache:
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
766
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
782
783 if not auth_token:
784 return False
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
800 else:
801 plain_token_map[token.api_key] = token
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
805
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
808
809 # check encrypted tokens now
810 if not match:
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
815 break
816
817 if match:
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
822 return True
823 else:
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
828 return False
829 else:
830 return True
831
832 return False
833
834 @property
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
838
839 @property
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
843 @property
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
847
848 @property
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
851
852 @property
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
856
857 @property
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
861 @property
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
864
865 @property
866 def is_admin(self):
867 return self.admin
868
869 @property
870 def language(self):
871 return self.user_data.get('language')
872
873 def AuthUser(self, **kwargs):
874 """
875 Returns instance of AuthUser for this user
876 """
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
880 @hybrid_property
881 def user_data(self):
882 if not self._user_data:
883 return {}
884
885 try:
886 return json.loads(self._user_data)
887 except TypeError:
888 return {}
889
890 @user_data.setter
891 def user_data(self, val):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
895 self._user_data = json.dumps(val)
896 except Exception:
897 log.error(traceback.format_exc())
898
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
902 session = Session()
903
904 if case_insensitive:
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
907 else:
908 q = cls.query().filter(cls.username == username)
909
910 if cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
913 if val:
914 return val
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
919
920 return q.scalar()
921
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
928 if cache:
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
932 match = q.first()
933 if match:
934 return match.user
935
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
942 else:
943 q = cls.query().filter(cls.email == email)
944
945 email_key = _hash_key(email)
946 if cache:
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
950 ret = q.scalar()
951 if ret is None:
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
963
964 return ret
965
966 @classmethod
967 def get_from_cs_author(cls, author):
968 """
969 Tries to get User objects out of commit author string
970
971 :param author:
972 """
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
979 return user
980 # Maybe we can match by username?
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
984 return user
985
986 def update_userdata(self, **kwargs):
987 usr = self
988 old = usr.user_data
989 old.update(**kwargs)
990 usr.user_data = old
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
993
994 def update_lastlogin(self):
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
999
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1002
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1005
1006 @classmethod
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1012
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1016
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1020 Returns all admin accounts sorted by username
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1026
1027 @classmethod
1028 def get_all_user_ids(cls, only_active=True):
1029 """
1030 Returns all users IDs
1031 """
1032 qry = Session().query(User.user_id)
1033
1034 if only_active:
1035 qry = qry.filter(User.active == true())
1036 return [x.user_id for x in qry]
1037
1038 @classmethod
1039 def get_default_user(cls, cache=False, refresh=False):
1040 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1041 if user is None:
1042 raise Exception('FATAL: Missing default account!')
1043 if refresh:
1044 # The default user might be based on outdated state which
1045 # has been loaded from the cache.
1046 # A call to refresh() ensures that the
1047 # latest state from the database is used.
1048 Session().refresh(user)
1049 return user
1050
1051 def _get_default_perms(self, user, suffix=''):
1052 from rhodecode.model.permission import PermissionModel
1053 return PermissionModel().get_default_perms(user.user_perms, suffix)
1054
1055 def get_default_perms(self, suffix=''):
1056 return self._get_default_perms(self, suffix)
1057
1058 def get_api_data(self, include_secrets=False, details='full'):
1059 """
1060 Common function for generating user related data for API
1061
1062 :param include_secrets: By default secrets in the API data will be replaced
1063 by a placeholder value to prevent exposing this data by accident. In case
1064 this data shall be exposed, set this flag to ``True``.
1065
1066 :param details: details can be 'basic|full' basic gives only a subset of
1067 the available user information that includes user_id, name and emails.
1068 """
1069 user = self
1070 user_data = self.user_data
1071 data = {
1072 'user_id': user.user_id,
1073 'username': user.username,
1074 'firstname': user.name,
1075 'lastname': user.lastname,
1076 'description': user.description,
1077 'email': user.email,
1078 'emails': user.emails,
1079 }
1080 if details == 'basic':
1081 return data
1082
1083 auth_token_length = 40
1084 auth_token_replacement = '*' * auth_token_length
1085
1086 extras = {
1087 'auth_tokens': [auth_token_replacement],
1088 'active': user.active,
1089 'admin': user.admin,
1090 'extern_type': user.extern_type,
1091 'extern_name': user.extern_name,
1092 'last_login': user.last_login,
1093 'last_activity': user.last_activity,
1094 'ip_addresses': user.ip_addresses,
1095 'language': user_data.get('language')
1096 }
1097 data.update(extras)
1098
1099 if include_secrets:
1100 data['auth_tokens'] = user.auth_tokens
1101 return data
1102
1103 def __json__(self):
1104 data = {
1105 'full_name': self.full_name,
1106 'full_name_or_username': self.full_name_or_username,
1107 'short_contact': self.short_contact,
1108 'full_contact': self.full_contact,
1109 }
1110 data.update(self.get_api_data())
1111 return data
1112
1113
1114 class UserApiKeys(Base, BaseModel):
1115 __tablename__ = 'user_api_keys'
1116 __table_args__ = (
1117 Index('uak_api_key_idx', 'api_key'),
1118 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1119 base_table_args
1120 )
1121 __mapper_args__ = {}
1122
1123 # ApiKey role
1124 ROLE_ALL = 'token_role_all'
1125 ROLE_HTTP = 'token_role_http'
1126 ROLE_VCS = 'token_role_vcs'
1127 ROLE_API = 'token_role_api'
1128 ROLE_FEED = 'token_role_feed'
1129 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1130 ROLE_PASSWORD_RESET = 'token_password_reset'
1131
1132 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1133
1134 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1136 api_key = Column("api_key", String(255), nullable=False, unique=True)
1137 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1138 expires = Column('expires', Float(53), nullable=False)
1139 role = Column('role', String(255), nullable=True)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1141
1142 # scope columns
1143 repo_id = Column(
1144 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1145 nullable=True, unique=None, default=None)
1146 repo = relationship('Repository', lazy='joined')
1147
1148 repo_group_id = Column(
1149 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1150 nullable=True, unique=None, default=None)
1151 repo_group = relationship('RepoGroup', lazy='joined')
1152
1153 user = relationship('User', lazy='joined')
1154
1155 def __unicode__(self):
1156 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1157
1158 def __json__(self):
1159 data = {
1160 'auth_token': self.api_key,
1161 'role': self.role,
1162 'scope': self.scope_humanized,
1163 'expired': self.expired
1164 }
1165 return data
1166
1167 def get_api_data(self, include_secrets=False):
1168 data = self.__json__()
1169 if include_secrets:
1170 return data
1171 else:
1172 data['auth_token'] = self.token_obfuscated
1173 return data
1174
1175 @hybrid_property
1176 def description_safe(self):
1177 from rhodecode.lib import helpers as h
1178 return h.escape(self.description)
1179
1180 @property
1181 def expired(self):
1182 if self.expires == -1:
1183 return False
1184 return time.time() > self.expires
1185
1186 @classmethod
1187 def _get_role_name(cls, role):
1188 return {
1189 cls.ROLE_ALL: _('all'),
1190 cls.ROLE_HTTP: _('http/web interface'),
1191 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1192 cls.ROLE_API: _('api calls'),
1193 cls.ROLE_FEED: _('feed access'),
1194 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1195 }.get(role, role)
1196
1197 @property
1198 def role_humanized(self):
1199 return self._get_role_name(self.role)
1200
1201 def _get_scope(self):
1202 if self.repo:
1203 return 'Repository: {}'.format(self.repo.repo_name)
1204 if self.repo_group:
1205 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1206 return 'Global'
1207
1208 @property
1209 def scope_humanized(self):
1210 return self._get_scope()
1211
1212 @property
1213 def token_obfuscated(self):
1214 if self.api_key:
1215 return self.api_key[:4] + "****"
1216
1217
1218 class UserEmailMap(Base, BaseModel):
1219 __tablename__ = 'user_email_map'
1220 __table_args__ = (
1221 Index('uem_email_idx', 'email'),
1222 UniqueConstraint('email'),
1223 base_table_args
1224 )
1225 __mapper_args__ = {}
1226
1227 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1229 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1230 user = relationship('User', lazy='joined')
1231
1232 @validates('_email')
1233 def validate_email(self, key, email):
1234 # check if this email is not main one
1235 main_email = Session().query(User).filter(User.email == email).scalar()
1236 if main_email is not None:
1237 raise AttributeError('email %s is present is user table' % email)
1238 return email
1239
1240 @hybrid_property
1241 def email(self):
1242 return self._email
1243
1244 @email.setter
1245 def email(self, val):
1246 self._email = val.lower() if val else None
1247
1248
1249 class UserIpMap(Base, BaseModel):
1250 __tablename__ = 'user_ip_map'
1251 __table_args__ = (
1252 UniqueConstraint('user_id', 'ip_addr'),
1253 base_table_args
1254 )
1255 __mapper_args__ = {}
1256
1257 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1259 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1260 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1261 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1262 user = relationship('User', lazy='joined')
1263
1264 @hybrid_property
1265 def description_safe(self):
1266 from rhodecode.lib import helpers as h
1267 return h.escape(self.description)
1268
1269 @classmethod
1270 def _get_ip_range(cls, ip_addr):
1271 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1272 return [str(net.network_address), str(net.broadcast_address)]
1273
1274 def __json__(self):
1275 return {
1276 'ip_addr': self.ip_addr,
1277 'ip_range': self._get_ip_range(self.ip_addr),
1278 }
1279
1280 def __unicode__(self):
1281 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1282 self.user_id, self.ip_addr)
1283
1284
1285 class UserSshKeys(Base, BaseModel):
1286 __tablename__ = 'user_ssh_keys'
1287 __table_args__ = (
1288 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1289
1290 UniqueConstraint('ssh_key_fingerprint'),
1291
1292 base_table_args
1293 )
1294 __mapper_args__ = {}
1295
1296 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1297 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1298 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1299
1300 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1301
1302 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1303 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1305
1306 user = relationship('User', lazy='joined')
1307
1308 def __json__(self):
1309 data = {
1310 'ssh_fingerprint': self.ssh_key_fingerprint,
1311 'description': self.description,
1312 'created_on': self.created_on
1313 }
1314 return data
1315
1316 def get_api_data(self):
1317 data = self.__json__()
1318 return data
1319
1320
1321 class UserLog(Base, BaseModel):
1322 __tablename__ = 'user_logs'
1323 __table_args__ = (
1324 base_table_args,
1325 )
1326
1327 VERSION_1 = 'v1'
1328 VERSION_2 = 'v2'
1329 VERSIONS = [VERSION_1, VERSION_2]
1330
1331 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1333 username = Column("username", String(255), nullable=True, unique=None, default=None)
1334 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1335 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1336 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1337 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1338 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1339
1340 version = Column("version", String(255), nullable=True, default=VERSION_1)
1341 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1342 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1343
1344 def __unicode__(self):
1345 return u"<%s('id:%s:%s')>" % (
1346 self.__class__.__name__, self.repository_name, self.action)
1347
1348 def __json__(self):
1349 return {
1350 'user_id': self.user_id,
1351 'username': self.username,
1352 'repository_id': self.repository_id,
1353 'repository_name': self.repository_name,
1354 'user_ip': self.user_ip,
1355 'action_date': self.action_date,
1356 'action': self.action,
1357 }
1358
1359 @hybrid_property
1360 def entry_id(self):
1361 return self.user_log_id
1362
1363 @property
1364 def action_as_day(self):
1365 return datetime.date(*self.action_date.timetuple()[:3])
1366
1367 user = relationship('User')
1368 repository = relationship('Repository', cascade='')
1369
1370
1371 class UserGroup(Base, BaseModel):
1372 __tablename__ = 'users_groups'
1373 __table_args__ = (
1374 base_table_args,
1375 )
1376
1377 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1378 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1379 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1380 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1381 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1382 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1383 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1384 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1385
1386 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1387 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1388 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1389 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1390 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1391 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1392
1393 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1394 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1395
1396 @classmethod
1397 def _load_group_data(cls, column):
1398 if not column:
1399 return {}
1400
1401 try:
1402 return json.loads(column) or {}
1403 except TypeError:
1404 return {}
1405
1406 @hybrid_property
1407 def description_safe(self):
1408 from rhodecode.lib import helpers as h
1409 return h.escape(self.user_group_description)
1410
1411 @hybrid_property
1412 def group_data(self):
1413 return self._load_group_data(self._group_data)
1414
1415 @group_data.expression
1416 def group_data(self, **kwargs):
1417 return self._group_data
1418
1419 @group_data.setter
1420 def group_data(self, val):
1421 try:
1422 self._group_data = json.dumps(val)
1423 except Exception:
1424 log.error(traceback.format_exc())
1425
1426 @classmethod
1427 def _load_sync(cls, group_data):
1428 if group_data:
1429 return group_data.get('extern_type')
1430
1431 @property
1432 def sync(self):
1433 return self._load_sync(self.group_data)
1434
1435 def __unicode__(self):
1436 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1437 self.users_group_id,
1438 self.users_group_name)
1439
1440 @classmethod
1441 def get_by_group_name(cls, group_name, cache=False,
1442 case_insensitive=False):
1443 if case_insensitive:
1444 q = cls.query().filter(func.lower(cls.users_group_name) ==
1445 func.lower(group_name))
1446
1447 else:
1448 q = cls.query().filter(cls.users_group_name == group_name)
1449 if cache:
1450 q = q.options(
1451 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1452 return q.scalar()
1453
1454 @classmethod
1455 def get(cls, user_group_id, cache=False):
1456 if not user_group_id:
1457 return
1458
1459 user_group = cls.query()
1460 if cache:
1461 user_group = user_group.options(
1462 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1463 return user_group.get(user_group_id)
1464
1465 def permissions(self, with_admins=True, with_owner=True,
1466 expand_from_user_groups=False):
1467 """
1468 Permissions for user groups
1469 """
1470 _admin_perm = 'usergroup.admin'
1471
1472 owner_row = []
1473 if with_owner:
1474 usr = AttributeDict(self.user.get_dict())
1475 usr.owner_row = True
1476 usr.permission = _admin_perm
1477 owner_row.append(usr)
1478
1479 super_admin_ids = []
1480 super_admin_rows = []
1481 if with_admins:
1482 for usr in User.get_all_super_admins():
1483 super_admin_ids.append(usr.user_id)
1484 # if this admin is also owner, don't double the record
1485 if usr.user_id == owner_row[0].user_id:
1486 owner_row[0].admin_row = True
1487 else:
1488 usr = AttributeDict(usr.get_dict())
1489 usr.admin_row = True
1490 usr.permission = _admin_perm
1491 super_admin_rows.append(usr)
1492
1493 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1494 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1495 joinedload(UserUserGroupToPerm.user),
1496 joinedload(UserUserGroupToPerm.permission),)
1497
1498 # get owners and admins and permissions. We do a trick of re-writing
1499 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1500 # has a global reference and changing one object propagates to all
1501 # others. This means if admin is also an owner admin_row that change
1502 # would propagate to both objects
1503 perm_rows = []
1504 for _usr in q.all():
1505 usr = AttributeDict(_usr.user.get_dict())
1506 # if this user is also owner/admin, mark as duplicate record
1507 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1508 usr.duplicate_perm = True
1509 usr.permission = _usr.permission.permission_name
1510 perm_rows.append(usr)
1511
1512 # filter the perm rows by 'default' first and then sort them by
1513 # admin,write,read,none permissions sorted again alphabetically in
1514 # each group
1515 perm_rows = sorted(perm_rows, key=display_user_sort)
1516
1517 user_groups_rows = []
1518 if expand_from_user_groups:
1519 for ug in self.permission_user_groups(with_members=True):
1520 for user_data in ug.members:
1521 user_groups_rows.append(user_data)
1522
1523 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1524
1525 def permission_user_groups(self, with_members=False):
1526 q = UserGroupUserGroupToPerm.query()\
1527 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1528 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1529 joinedload(UserGroupUserGroupToPerm.target_user_group),
1530 joinedload(UserGroupUserGroupToPerm.permission),)
1531
1532 perm_rows = []
1533 for _user_group in q.all():
1534 entry = AttributeDict(_user_group.user_group.get_dict())
1535 entry.permission = _user_group.permission.permission_name
1536 if with_members:
1537 entry.members = [x.user.get_dict()
1538 for x in _user_group.user_group.members]
1539 perm_rows.append(entry)
1540
1541 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1542 return perm_rows
1543
1544 def _get_default_perms(self, user_group, suffix=''):
1545 from rhodecode.model.permission import PermissionModel
1546 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1547
1548 def get_default_perms(self, suffix=''):
1549 return self._get_default_perms(self, suffix)
1550
1551 def get_api_data(self, with_group_members=True, include_secrets=False):
1552 """
1553 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1554 basically forwarded.
1555
1556 """
1557 user_group = self
1558 data = {
1559 'users_group_id': user_group.users_group_id,
1560 'group_name': user_group.users_group_name,
1561 'group_description': user_group.user_group_description,
1562 'active': user_group.users_group_active,
1563 'owner': user_group.user.username,
1564 'sync': user_group.sync,
1565 'owner_email': user_group.user.email,
1566 }
1567
1568 if with_group_members:
1569 users = []
1570 for user in user_group.members:
1571 user = user.user
1572 users.append(user.get_api_data(include_secrets=include_secrets))
1573 data['users'] = users
1574
1575 return data
1576
1577
1578 class UserGroupMember(Base, BaseModel):
1579 __tablename__ = 'users_groups_members'
1580 __table_args__ = (
1581 base_table_args,
1582 )
1583
1584 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1587
1588 user = relationship('User', lazy='joined')
1589 users_group = relationship('UserGroup')
1590
1591 def __init__(self, gr_id='', u_id=''):
1592 self.users_group_id = gr_id
1593 self.user_id = u_id
1594
1595
1596 class RepositoryField(Base, BaseModel):
1597 __tablename__ = 'repositories_fields'
1598 __table_args__ = (
1599 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1600 base_table_args,
1601 )
1602
1603 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1604
1605 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1607 field_key = Column("field_key", String(250))
1608 field_label = Column("field_label", String(1024), nullable=False)
1609 field_value = Column("field_value", String(10000), nullable=False)
1610 field_desc = Column("field_desc", String(1024), nullable=False)
1611 field_type = Column("field_type", String(255), nullable=False, unique=None)
1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1613
1614 repository = relationship('Repository')
1615
1616 @property
1617 def field_key_prefixed(self):
1618 return 'ex_%s' % self.field_key
1619
1620 @classmethod
1621 def un_prefix_key(cls, key):
1622 if key.startswith(cls.PREFIX):
1623 return key[len(cls.PREFIX):]
1624 return key
1625
1626 @classmethod
1627 def get_by_key_name(cls, key, repo):
1628 row = cls.query()\
1629 .filter(cls.repository == repo)\
1630 .filter(cls.field_key == key).scalar()
1631 return row
1632
1633
1634 class Repository(Base, BaseModel):
1635 __tablename__ = 'repositories'
1636 __table_args__ = (
1637 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1638 base_table_args,
1639 )
1640 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1641 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1642 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1643
1644 STATE_CREATED = 'repo_state_created'
1645 STATE_PENDING = 'repo_state_pending'
1646 STATE_ERROR = 'repo_state_error'
1647
1648 LOCK_AUTOMATIC = 'lock_auto'
1649 LOCK_API = 'lock_api'
1650 LOCK_WEB = 'lock_web'
1651 LOCK_PULL = 'lock_pull'
1652
1653 NAME_SEP = URL_SEP
1654
1655 repo_id = Column(
1656 "repo_id", Integer(), nullable=False, unique=True, default=None,
1657 primary_key=True)
1658 _repo_name = Column(
1659 "repo_name", Text(), nullable=False, default=None)
1660 repo_name_hash = Column(
1661 "repo_name_hash", String(255), nullable=False, unique=True)
1662 repo_state = Column("repo_state", String(255), nullable=True)
1663
1664 clone_uri = Column(
1665 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1666 default=None)
1667 push_uri = Column(
1668 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1669 default=None)
1670 repo_type = Column(
1671 "repo_type", String(255), nullable=False, unique=False, default=None)
1672 user_id = Column(
1673 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1674 unique=False, default=None)
1675 private = Column(
1676 "private", Boolean(), nullable=True, unique=None, default=None)
1677 archived = Column(
1678 "archived", Boolean(), nullable=True, unique=None, default=None)
1679 enable_statistics = Column(
1680 "statistics", Boolean(), nullable=True, unique=None, default=True)
1681 enable_downloads = Column(
1682 "downloads", Boolean(), nullable=True, unique=None, default=True)
1683 description = Column(
1684 "description", String(10000), nullable=True, unique=None, default=None)
1685 created_on = Column(
1686 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1687 default=datetime.datetime.now)
1688 updated_on = Column(
1689 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1690 default=datetime.datetime.now)
1691 _landing_revision = Column(
1692 "landing_revision", String(255), nullable=False, unique=False,
1693 default=None)
1694 enable_locking = Column(
1695 "enable_locking", Boolean(), nullable=False, unique=None,
1696 default=False)
1697 _locked = Column(
1698 "locked", String(255), nullable=True, unique=False, default=None)
1699 _changeset_cache = Column(
1700 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1701
1702 fork_id = Column(
1703 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1704 nullable=True, unique=False, default=None)
1705 group_id = Column(
1706 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1707 unique=False, default=None)
1708
1709 user = relationship('User', lazy='joined')
1710 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1711 group = relationship('RepoGroup', lazy='joined')
1712 repo_to_perm = relationship(
1713 'UserRepoToPerm', cascade='all',
1714 order_by='UserRepoToPerm.repo_to_perm_id')
1715 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1716 stats = relationship('Statistics', cascade='all', uselist=False)
1717
1718 followers = relationship(
1719 'UserFollowing',
1720 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1721 cascade='all')
1722 extra_fields = relationship(
1723 'RepositoryField', cascade="all, delete-orphan")
1724 logs = relationship('UserLog')
1725 comments = relationship(
1726 'ChangesetComment', cascade="all, delete-orphan")
1727 pull_requests_source = relationship(
1728 'PullRequest',
1729 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1730 cascade="all, delete-orphan")
1731 pull_requests_target = relationship(
1732 'PullRequest',
1733 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1734 cascade="all, delete-orphan")
1735 ui = relationship('RepoRhodeCodeUi', cascade="all")
1736 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1737 integrations = relationship('Integration', cascade="all, delete-orphan")
1738
1739 scoped_tokens = relationship('UserApiKeys', cascade="all")
1740
1741 # no cascade, set NULL
1742 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1743
1744 def __unicode__(self):
1745 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1746 safe_unicode(self.repo_name))
1747
1748 @hybrid_property
1749 def description_safe(self):
1750 from rhodecode.lib import helpers as h
1751 return h.escape(self.description)
1752
1753 @hybrid_property
1754 def landing_rev(self):
1755 # always should return [rev_type, rev]
1756 if self._landing_revision:
1757 _rev_info = self._landing_revision.split(':')
1758 if len(_rev_info) < 2:
1759 _rev_info.insert(0, 'rev')
1760 return [_rev_info[0], _rev_info[1]]
1761 return [None, None]
1762
1763 @landing_rev.setter
1764 def landing_rev(self, val):
1765 if ':' not in val:
1766 raise ValueError('value must be delimited with `:` and consist '
1767 'of <rev_type>:<rev>, got %s instead' % val)
1768 self._landing_revision = val
1769
1770 @hybrid_property
1771 def locked(self):
1772 if self._locked:
1773 user_id, timelocked, reason = self._locked.split(':')
1774 lock_values = int(user_id), timelocked, reason
1775 else:
1776 lock_values = [None, None, None]
1777 return lock_values
1778
1779 @locked.setter
1780 def locked(self, val):
1781 if val and isinstance(val, (list, tuple)):
1782 self._locked = ':'.join(map(str, val))
1783 else:
1784 self._locked = None
1785
1786 @classmethod
1787 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1788 from rhodecode.lib.vcs.backends.base import EmptyCommit
1789 dummy = EmptyCommit().__json__()
1790 if not changeset_cache_raw:
1791 dummy['source_repo_id'] = repo_id
1792 return json.loads(json.dumps(dummy))
1793
1794 try:
1795 return json.loads(changeset_cache_raw)
1796 except TypeError:
1797 return dummy
1798 except Exception:
1799 log.error(traceback.format_exc())
1800 return dummy
1801
1802 @hybrid_property
1803 def changeset_cache(self):
1804 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1805
1806 @changeset_cache.setter
1807 def changeset_cache(self, val):
1808 try:
1809 self._changeset_cache = json.dumps(val)
1810 except Exception:
1811 log.error(traceback.format_exc())
1812
1813 @hybrid_property
1814 def repo_name(self):
1815 return self._repo_name
1816
1817 @repo_name.setter
1818 def repo_name(self, value):
1819 self._repo_name = value
1820 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1821
1822 @classmethod
1823 def normalize_repo_name(cls, repo_name):
1824 """
1825 Normalizes os specific repo_name to the format internally stored inside
1826 database using URL_SEP
1827
1828 :param cls:
1829 :param repo_name:
1830 """
1831 return cls.NAME_SEP.join(repo_name.split(os.sep))
1832
1833 @classmethod
1834 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1835 session = Session()
1836 q = session.query(cls).filter(cls.repo_name == repo_name)
1837
1838 if cache:
1839 if identity_cache:
1840 val = cls.identity_cache(session, 'repo_name', repo_name)
1841 if val:
1842 return val
1843 else:
1844 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1845 q = q.options(
1846 FromCache("sql_cache_short", cache_key))
1847
1848 return q.scalar()
1849
1850 @classmethod
1851 def get_by_id_or_repo_name(cls, repoid):
1852 if isinstance(repoid, (int, long)):
1853 try:
1854 repo = cls.get(repoid)
1855 except ValueError:
1856 repo = None
1857 else:
1858 repo = cls.get_by_repo_name(repoid)
1859 return repo
1860
1861 @classmethod
1862 def get_by_full_path(cls, repo_full_path):
1863 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1864 repo_name = cls.normalize_repo_name(repo_name)
1865 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1866
1867 @classmethod
1868 def get_repo_forks(cls, repo_id):
1869 return cls.query().filter(Repository.fork_id == repo_id)
1870
1871 @classmethod
1872 def base_path(cls):
1873 """
1874 Returns base path when all repos are stored
1875
1876 :param cls:
1877 """
1878 q = Session().query(RhodeCodeUi)\
1879 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1880 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1881 return q.one().ui_value
1882
1883 @classmethod
1884 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1885 case_insensitive=True, archived=False):
1886 q = Repository.query()
1887
1888 if not archived:
1889 q = q.filter(Repository.archived.isnot(true()))
1890
1891 if not isinstance(user_id, Optional):
1892 q = q.filter(Repository.user_id == user_id)
1893
1894 if not isinstance(group_id, Optional):
1895 q = q.filter(Repository.group_id == group_id)
1896
1897 if case_insensitive:
1898 q = q.order_by(func.lower(Repository.repo_name))
1899 else:
1900 q = q.order_by(Repository.repo_name)
1901
1902 return q.all()
1903
1904 @property
1905 def repo_uid(self):
1906 return '_{}'.format(self.repo_id)
1907
1908 @property
1909 def forks(self):
1910 """
1911 Return forks of this repo
1912 """
1913 return Repository.get_repo_forks(self.repo_id)
1914
1915 @property
1916 def parent(self):
1917 """
1918 Returns fork parent
1919 """
1920 return self.fork
1921
1922 @property
1923 def just_name(self):
1924 return self.repo_name.split(self.NAME_SEP)[-1]
1925
1926 @property
1927 def groups_with_parents(self):
1928 groups = []
1929 if self.group is None:
1930 return groups
1931
1932 cur_gr = self.group
1933 groups.insert(0, cur_gr)
1934 while 1:
1935 gr = getattr(cur_gr, 'parent_group', None)
1936 cur_gr = cur_gr.parent_group
1937 if gr is None:
1938 break
1939 groups.insert(0, gr)
1940
1941 return groups
1942
1943 @property
1944 def groups_and_repo(self):
1945 return self.groups_with_parents, self
1946
1947 @LazyProperty
1948 def repo_path(self):
1949 """
1950 Returns base full path for that repository means where it actually
1951 exists on a filesystem
1952 """
1953 q = Session().query(RhodeCodeUi).filter(
1954 RhodeCodeUi.ui_key == self.NAME_SEP)
1955 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1956 return q.one().ui_value
1957
1958 @property
1959 def repo_full_path(self):
1960 p = [self.repo_path]
1961 # we need to split the name by / since this is how we store the
1962 # names in the database, but that eventually needs to be converted
1963 # into a valid system path
1964 p += self.repo_name.split(self.NAME_SEP)
1965 return os.path.join(*map(safe_unicode, p))
1966
1967 @property
1968 def cache_keys(self):
1969 """
1970 Returns associated cache keys for that repo
1971 """
1972 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1973 repo_id=self.repo_id)
1974 return CacheKey.query()\
1975 .filter(CacheKey.cache_args == invalidation_namespace)\
1976 .order_by(CacheKey.cache_key)\
1977 .all()
1978
1979 @property
1980 def cached_diffs_relative_dir(self):
1981 """
1982 Return a relative to the repository store path of cached diffs
1983 used for safe display for users, who shouldn't know the absolute store
1984 path
1985 """
1986 return os.path.join(
1987 os.path.dirname(self.repo_name),
1988 self.cached_diffs_dir.split(os.path.sep)[-1])
1989
1990 @property
1991 def cached_diffs_dir(self):
1992 path = self.repo_full_path
1993 return os.path.join(
1994 os.path.dirname(path),
1995 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1996
1997 def cached_diffs(self):
1998 diff_cache_dir = self.cached_diffs_dir
1999 if os.path.isdir(diff_cache_dir):
2000 return os.listdir(diff_cache_dir)
2001 return []
2002
2003 def shadow_repos(self):
2004 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2005 return [
2006 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2007 if x.startswith(shadow_repos_pattern)]
2008
2009 def get_new_name(self, repo_name):
2010 """
2011 returns new full repository name based on assigned group and new new
2012
2013 :param group_name:
2014 """
2015 path_prefix = self.group.full_path_splitted if self.group else []
2016 return self.NAME_SEP.join(path_prefix + [repo_name])
2017
2018 @property
2019 def _config(self):
2020 """
2021 Returns db based config object.
2022 """
2023 from rhodecode.lib.utils import make_db_config
2024 return make_db_config(clear_session=False, repo=self)
2025
2026 def permissions(self, with_admins=True, with_owner=True,
2027 expand_from_user_groups=False):
2028 """
2029 Permissions for repositories
2030 """
2031 _admin_perm = 'repository.admin'
2032
2033 owner_row = []
2034 if with_owner:
2035 usr = AttributeDict(self.user.get_dict())
2036 usr.owner_row = True
2037 usr.permission = _admin_perm
2038 usr.permission_id = None
2039 owner_row.append(usr)
2040
2041 super_admin_ids = []
2042 super_admin_rows = []
2043 if with_admins:
2044 for usr in User.get_all_super_admins():
2045 super_admin_ids.append(usr.user_id)
2046 # if this admin is also owner, don't double the record
2047 if usr.user_id == owner_row[0].user_id:
2048 owner_row[0].admin_row = True
2049 else:
2050 usr = AttributeDict(usr.get_dict())
2051 usr.admin_row = True
2052 usr.permission = _admin_perm
2053 usr.permission_id = None
2054 super_admin_rows.append(usr)
2055
2056 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2057 q = q.options(joinedload(UserRepoToPerm.repository),
2058 joinedload(UserRepoToPerm.user),
2059 joinedload(UserRepoToPerm.permission),)
2060
2061 # get owners and admins and permissions. We do a trick of re-writing
2062 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2063 # has a global reference and changing one object propagates to all
2064 # others. This means if admin is also an owner admin_row that change
2065 # would propagate to both objects
2066 perm_rows = []
2067 for _usr in q.all():
2068 usr = AttributeDict(_usr.user.get_dict())
2069 # if this user is also owner/admin, mark as duplicate record
2070 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2071 usr.duplicate_perm = True
2072 # also check if this permission is maybe used by branch_permissions
2073 if _usr.branch_perm_entry:
2074 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2075
2076 usr.permission = _usr.permission.permission_name
2077 usr.permission_id = _usr.repo_to_perm_id
2078 perm_rows.append(usr)
2079
2080 # filter the perm rows by 'default' first and then sort them by
2081 # admin,write,read,none permissions sorted again alphabetically in
2082 # each group
2083 perm_rows = sorted(perm_rows, key=display_user_sort)
2084
2085 user_groups_rows = []
2086 if expand_from_user_groups:
2087 for ug in self.permission_user_groups(with_members=True):
2088 for user_data in ug.members:
2089 user_groups_rows.append(user_data)
2090
2091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2092
2093 def permission_user_groups(self, with_members=True):
2094 q = UserGroupRepoToPerm.query()\
2095 .filter(UserGroupRepoToPerm.repository == self)
2096 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2097 joinedload(UserGroupRepoToPerm.users_group),
2098 joinedload(UserGroupRepoToPerm.permission),)
2099
2100 perm_rows = []
2101 for _user_group in q.all():
2102 entry = AttributeDict(_user_group.users_group.get_dict())
2103 entry.permission = _user_group.permission.permission_name
2104 if with_members:
2105 entry.members = [x.user.get_dict()
2106 for x in _user_group.users_group.members]
2107 perm_rows.append(entry)
2108
2109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2110 return perm_rows
2111
2112 def get_api_data(self, include_secrets=False):
2113 """
2114 Common function for generating repo api data
2115
2116 :param include_secrets: See :meth:`User.get_api_data`.
2117
2118 """
2119 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2120 # move this methods on models level.
2121 from rhodecode.model.settings import SettingsModel
2122 from rhodecode.model.repo import RepoModel
2123
2124 repo = self
2125 _user_id, _time, _reason = self.locked
2126
2127 data = {
2128 'repo_id': repo.repo_id,
2129 'repo_name': repo.repo_name,
2130 'repo_type': repo.repo_type,
2131 'clone_uri': repo.clone_uri or '',
2132 'push_uri': repo.push_uri or '',
2133 'url': RepoModel().get_url(self),
2134 'private': repo.private,
2135 'created_on': repo.created_on,
2136 'description': repo.description_safe,
2137 'landing_rev': repo.landing_rev,
2138 'owner': repo.user.username,
2139 'fork_of': repo.fork.repo_name if repo.fork else None,
2140 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2141 'enable_statistics': repo.enable_statistics,
2142 'enable_locking': repo.enable_locking,
2143 'enable_downloads': repo.enable_downloads,
2144 'last_changeset': repo.changeset_cache,
2145 'locked_by': User.get(_user_id).get_api_data(
2146 include_secrets=include_secrets) if _user_id else None,
2147 'locked_date': time_to_datetime(_time) if _time else None,
2148 'lock_reason': _reason if _reason else None,
2149 }
2150
2151 # TODO: mikhail: should be per-repo settings here
2152 rc_config = SettingsModel().get_all_settings()
2153 repository_fields = str2bool(
2154 rc_config.get('rhodecode_repository_fields'))
2155 if repository_fields:
2156 for f in self.extra_fields:
2157 data[f.field_key_prefixed] = f.field_value
2158
2159 return data
2160
2161 @classmethod
2162 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2163 if not lock_time:
2164 lock_time = time.time()
2165 if not lock_reason:
2166 lock_reason = cls.LOCK_AUTOMATIC
2167 repo.locked = [user_id, lock_time, lock_reason]
2168 Session().add(repo)
2169 Session().commit()
2170
2171 @classmethod
2172 def unlock(cls, repo):
2173 repo.locked = None
2174 Session().add(repo)
2175 Session().commit()
2176
2177 @classmethod
2178 def getlock(cls, repo):
2179 return repo.locked
2180
2181 def is_user_lock(self, user_id):
2182 if self.lock[0]:
2183 lock_user_id = safe_int(self.lock[0])
2184 user_id = safe_int(user_id)
2185 # both are ints, and they are equal
2186 return all([lock_user_id, user_id]) and lock_user_id == user_id
2187
2188 return False
2189
2190 def get_locking_state(self, action, user_id, only_when_enabled=True):
2191 """
2192 Checks locking on this repository, if locking is enabled and lock is
2193 present returns a tuple of make_lock, locked, locked_by.
2194 make_lock can have 3 states None (do nothing) True, make lock
2195 False release lock, This value is later propagated to hooks, which
2196 do the locking. Think about this as signals passed to hooks what to do.
2197
2198 """
2199 # TODO: johbo: This is part of the business logic and should be moved
2200 # into the RepositoryModel.
2201
2202 if action not in ('push', 'pull'):
2203 raise ValueError("Invalid action value: %s" % repr(action))
2204
2205 # defines if locked error should be thrown to user
2206 currently_locked = False
2207 # defines if new lock should be made, tri-state
2208 make_lock = None
2209 repo = self
2210 user = User.get(user_id)
2211
2212 lock_info = repo.locked
2213
2214 if repo and (repo.enable_locking or not only_when_enabled):
2215 if action == 'push':
2216 # check if it's already locked !, if it is compare users
2217 locked_by_user_id = lock_info[0]
2218 if user.user_id == locked_by_user_id:
2219 log.debug(
2220 'Got `push` action from user %s, now unlocking', user)
2221 # unlock if we have push from user who locked
2222 make_lock = False
2223 else:
2224 # we're not the same user who locked, ban with
2225 # code defined in settings (default is 423 HTTP Locked) !
2226 log.debug('Repo %s is currently locked by %s', repo, user)
2227 currently_locked = True
2228 elif action == 'pull':
2229 # [0] user [1] date
2230 if lock_info[0] and lock_info[1]:
2231 log.debug('Repo %s is currently locked by %s', repo, user)
2232 currently_locked = True
2233 else:
2234 log.debug('Setting lock on repo %s by %s', repo, user)
2235 make_lock = True
2236
2237 else:
2238 log.debug('Repository %s do not have locking enabled', repo)
2239
2240 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2241 make_lock, currently_locked, lock_info)
2242
2243 from rhodecode.lib.auth import HasRepoPermissionAny
2244 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2245 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2246 # if we don't have at least write permission we cannot make a lock
2247 log.debug('lock state reset back to FALSE due to lack '
2248 'of at least read permission')
2249 make_lock = False
2250
2251 return make_lock, currently_locked, lock_info
2252
2253 @property
2254 def last_commit_cache_update_diff(self):
2255 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2256
2257 @classmethod
2258 def _load_commit_change(cls, last_commit_cache):
2259 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2260 empty_date = datetime.datetime.fromtimestamp(0)
2261 date_latest = last_commit_cache.get('date', empty_date)
2262 try:
2263 return parse_datetime(date_latest)
2264 except Exception:
2265 return empty_date
2266
2267 @property
2268 def last_commit_change(self):
2269 return self._load_commit_change(self.changeset_cache)
2270
2271 @property
2272 def last_db_change(self):
2273 return self.updated_on
2274
2275 @property
2276 def clone_uri_hidden(self):
2277 clone_uri = self.clone_uri
2278 if clone_uri:
2279 import urlobject
2280 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2281 if url_obj.password:
2282 clone_uri = url_obj.with_password('*****')
2283 return clone_uri
2284
2285 @property
2286 def push_uri_hidden(self):
2287 push_uri = self.push_uri
2288 if push_uri:
2289 import urlobject
2290 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2291 if url_obj.password:
2292 push_uri = url_obj.with_password('*****')
2293 return push_uri
2294
2295 def clone_url(self, **override):
2296 from rhodecode.model.settings import SettingsModel
2297
2298 uri_tmpl = None
2299 if 'with_id' in override:
2300 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2301 del override['with_id']
2302
2303 if 'uri_tmpl' in override:
2304 uri_tmpl = override['uri_tmpl']
2305 del override['uri_tmpl']
2306
2307 ssh = False
2308 if 'ssh' in override:
2309 ssh = True
2310 del override['ssh']
2311
2312 # we didn't override our tmpl from **overrides
2313 request = get_current_request()
2314 if not uri_tmpl:
2315 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2316 rc_config = request.call_context.rc_config
2317 else:
2318 rc_config = SettingsModel().get_all_settings(cache=True)
2319
2320 if ssh:
2321 uri_tmpl = rc_config.get(
2322 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2323
2324 else:
2325 uri_tmpl = rc_config.get(
2326 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2327
2328 return get_clone_url(request=request,
2329 uri_tmpl=uri_tmpl,
2330 repo_name=self.repo_name,
2331 repo_id=self.repo_id,
2332 repo_type=self.repo_type,
2333 **override)
2334
2335 def set_state(self, state):
2336 self.repo_state = state
2337 Session().add(self)
2338 #==========================================================================
2339 # SCM PROPERTIES
2340 #==========================================================================
2341
2342 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2343 return get_commit_safe(
2344 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2345 maybe_unreachable=maybe_unreachable)
2346
2347 def get_changeset(self, rev=None, pre_load=None):
2348 warnings.warn("Use get_commit", DeprecationWarning)
2349 commit_id = None
2350 commit_idx = None
2351 if isinstance(rev, compat.string_types):
2352 commit_id = rev
2353 else:
2354 commit_idx = rev
2355 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2356 pre_load=pre_load)
2357
2358 def get_landing_commit(self):
2359 """
2360 Returns landing commit, or if that doesn't exist returns the tip
2361 """
2362 _rev_type, _rev = self.landing_rev
2363 commit = self.get_commit(_rev)
2364 if isinstance(commit, EmptyCommit):
2365 return self.get_commit()
2366 return commit
2367
2368 def flush_commit_cache(self):
2369 self.update_commit_cache(cs_cache={'raw_id':'0'})
2370 self.update_commit_cache()
2371
2372 def update_commit_cache(self, cs_cache=None, config=None):
2373 """
2374 Update cache of last commit for repository
2375 cache_keys should be::
2376
2377 source_repo_id
2378 short_id
2379 raw_id
2380 revision
2381 parents
2382 message
2383 date
2384 author
2385 updated_on
2386
2387 """
2388 from rhodecode.lib.vcs.backends.base import BaseChangeset
2389 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2390 empty_date = datetime.datetime.fromtimestamp(0)
2391
2392 if cs_cache is None:
2393 # use no-cache version here
2394 try:
2395 scm_repo = self.scm_instance(cache=False, config=config)
2396 except VCSError:
2397 scm_repo = None
2398 empty = scm_repo is None or scm_repo.is_empty()
2399
2400 if not empty:
2401 cs_cache = scm_repo.get_commit(
2402 pre_load=["author", "date", "message", "parents", "branch"])
2403 else:
2404 cs_cache = EmptyCommit()
2405
2406 if isinstance(cs_cache, BaseChangeset):
2407 cs_cache = cs_cache.__json__()
2408
2409 def is_outdated(new_cs_cache):
2410 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2411 new_cs_cache['revision'] != self.changeset_cache['revision']):
2412 return True
2413 return False
2414
2415 # check if we have maybe already latest cached revision
2416 if is_outdated(cs_cache) or not self.changeset_cache:
2417 _current_datetime = datetime.datetime.utcnow()
2418 last_change = cs_cache.get('date') or _current_datetime
2419 # we check if last update is newer than the new value
2420 # if yes, we use the current timestamp instead. Imagine you get
2421 # old commit pushed 1y ago, we'd set last update 1y to ago.
2422 last_change_timestamp = datetime_to_time(last_change)
2423 current_timestamp = datetime_to_time(last_change)
2424 if last_change_timestamp > current_timestamp and not empty:
2425 cs_cache['date'] = _current_datetime
2426
2427 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2428 cs_cache['updated_on'] = time.time()
2429 self.changeset_cache = cs_cache
2430 self.updated_on = last_change
2431 Session().add(self)
2432 Session().commit()
2433
2434 else:
2435 if empty:
2436 cs_cache = EmptyCommit().__json__()
2437 else:
2438 cs_cache = self.changeset_cache
2439
2440 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2441
2442 cs_cache['updated_on'] = time.time()
2443 self.changeset_cache = cs_cache
2444 self.updated_on = _date_latest
2445 Session().add(self)
2446 Session().commit()
2447
2448 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2449 self.repo_name, cs_cache, _date_latest)
2450
2451 @property
2452 def tip(self):
2453 return self.get_commit('tip')
2454
2455 @property
2456 def author(self):
2457 return self.tip.author
2458
2459 @property
2460 def last_change(self):
2461 return self.scm_instance().last_change
2462
2463 def get_comments(self, revisions=None):
2464 """
2465 Returns comments for this repository grouped by revisions
2466
2467 :param revisions: filter query by revisions only
2468 """
2469 cmts = ChangesetComment.query()\
2470 .filter(ChangesetComment.repo == self)
2471 if revisions:
2472 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2473 grouped = collections.defaultdict(list)
2474 for cmt in cmts.all():
2475 grouped[cmt.revision].append(cmt)
2476 return grouped
2477
2478 def statuses(self, revisions=None):
2479 """
2480 Returns statuses for this repository
2481
2482 :param revisions: list of revisions to get statuses for
2483 """
2484 statuses = ChangesetStatus.query()\
2485 .filter(ChangesetStatus.repo == self)\
2486 .filter(ChangesetStatus.version == 0)
2487
2488 if revisions:
2489 # Try doing the filtering in chunks to avoid hitting limits
2490 size = 500
2491 status_results = []
2492 for chunk in xrange(0, len(revisions), size):
2493 status_results += statuses.filter(
2494 ChangesetStatus.revision.in_(
2495 revisions[chunk: chunk+size])
2496 ).all()
2497 else:
2498 status_results = statuses.all()
2499
2500 grouped = {}
2501
2502 # maybe we have open new pullrequest without a status?
2503 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2504 status_lbl = ChangesetStatus.get_status_lbl(stat)
2505 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2506 for rev in pr.revisions:
2507 pr_id = pr.pull_request_id
2508 pr_repo = pr.target_repo.repo_name
2509 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2510
2511 for stat in status_results:
2512 pr_id = pr_repo = None
2513 if stat.pull_request:
2514 pr_id = stat.pull_request.pull_request_id
2515 pr_repo = stat.pull_request.target_repo.repo_name
2516 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2517 pr_id, pr_repo]
2518 return grouped
2519
2520 # ==========================================================================
2521 # SCM CACHE INSTANCE
2522 # ==========================================================================
2523
2524 def scm_instance(self, **kwargs):
2525 import rhodecode
2526
2527 # Passing a config will not hit the cache currently only used
2528 # for repo2dbmapper
2529 config = kwargs.pop('config', None)
2530 cache = kwargs.pop('cache', None)
2531 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2532 if vcs_full_cache is not None:
2533 # allows override global config
2534 full_cache = vcs_full_cache
2535 else:
2536 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2537 # if cache is NOT defined use default global, else we have a full
2538 # control over cache behaviour
2539 if cache is None and full_cache and not config:
2540 log.debug('Initializing pure cached instance for %s', self.repo_path)
2541 return self._get_instance_cached()
2542
2543 # cache here is sent to the "vcs server"
2544 return self._get_instance(cache=bool(cache), config=config)
2545
2546 def _get_instance_cached(self):
2547 from rhodecode.lib import rc_cache
2548
2549 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2550 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2551 repo_id=self.repo_id)
2552 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2553
2554 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2555 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2556 return self._get_instance(repo_state_uid=_cache_state_uid)
2557
2558 # we must use thread scoped cache here,
2559 # because each thread of gevent needs it's own not shared connection and cache
2560 # we also alter `args` so the cache key is individual for every green thread.
2561 inv_context_manager = rc_cache.InvalidationContext(
2562 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2563 thread_scoped=True)
2564 with inv_context_manager as invalidation_context:
2565 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2566 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2567
2568 # re-compute and store cache if we get invalidate signal
2569 if invalidation_context.should_invalidate():
2570 instance = get_instance_cached.refresh(*args)
2571 else:
2572 instance = get_instance_cached(*args)
2573
2574 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2575 return instance
2576
2577 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2578 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2579 self.repo_type, self.repo_path, cache)
2580 config = config or self._config
2581 custom_wire = {
2582 'cache': cache, # controls the vcs.remote cache
2583 'repo_state_uid': repo_state_uid
2584 }
2585 repo = get_vcs_instance(
2586 repo_path=safe_str(self.repo_full_path),
2587 config=config,
2588 with_wire=custom_wire,
2589 create=False,
2590 _vcs_alias=self.repo_type)
2591 if repo is not None:
2592 repo.count() # cache rebuild
2593 return repo
2594
2595 def get_shadow_repository_path(self, workspace_id):
2596 from rhodecode.lib.vcs.backends.base import BaseRepository
2597 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2598 self.repo_full_path, self.repo_id, workspace_id)
2599 return shadow_repo_path
2600
2601 def __json__(self):
2602 return {'landing_rev': self.landing_rev}
2603
2604 def get_dict(self):
2605
2606 # Since we transformed `repo_name` to a hybrid property, we need to
2607 # keep compatibility with the code which uses `repo_name` field.
2608
2609 result = super(Repository, self).get_dict()
2610 result['repo_name'] = result.pop('_repo_name', None)
2611 return result
2612
2613
2614 class RepoGroup(Base, BaseModel):
2615 __tablename__ = 'groups'
2616 __table_args__ = (
2617 UniqueConstraint('group_name', 'group_parent_id'),
2618 base_table_args,
2619 )
2620 __mapper_args__ = {'order_by': 'group_name'}
2621
2622 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2623
2624 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2625 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2626 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2627 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2628 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2629 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2633 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2634 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2635
2636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2638 parent_group = relationship('RepoGroup', remote_side=group_id)
2639 user = relationship('User')
2640 integrations = relationship('Integration', cascade="all, delete-orphan")
2641
2642 # no cascade, set NULL
2643 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2644
2645 def __init__(self, group_name='', parent_group=None):
2646 self.group_name = group_name
2647 self.parent_group = parent_group
2648
2649 def __unicode__(self):
2650 return u"<%s('id:%s:%s')>" % (
2651 self.__class__.__name__, self.group_id, self.group_name)
2652
2653 @hybrid_property
2654 def group_name(self):
2655 return self._group_name
2656
2657 @group_name.setter
2658 def group_name(self, value):
2659 self._group_name = value
2660 self.group_name_hash = self.hash_repo_group_name(value)
2661
2662 @classmethod
2663 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2664 from rhodecode.lib.vcs.backends.base import EmptyCommit
2665 dummy = EmptyCommit().__json__()
2666 if not changeset_cache_raw:
2667 dummy['source_repo_id'] = repo_id
2668 return json.loads(json.dumps(dummy))
2669
2670 try:
2671 return json.loads(changeset_cache_raw)
2672 except TypeError:
2673 return dummy
2674 except Exception:
2675 log.error(traceback.format_exc())
2676 return dummy
2677
2678 @hybrid_property
2679 def changeset_cache(self):
2680 return self._load_changeset_cache('', self._changeset_cache)
2681
2682 @changeset_cache.setter
2683 def changeset_cache(self, val):
2684 try:
2685 self._changeset_cache = json.dumps(val)
2686 except Exception:
2687 log.error(traceback.format_exc())
2688
2689 @validates('group_parent_id')
2690 def validate_group_parent_id(self, key, val):
2691 """
2692 Check cycle references for a parent group to self
2693 """
2694 if self.group_id and val:
2695 assert val != self.group_id
2696
2697 return val
2698
2699 @hybrid_property
2700 def description_safe(self):
2701 from rhodecode.lib import helpers as h
2702 return h.escape(self.group_description)
2703
2704 @classmethod
2705 def hash_repo_group_name(cls, repo_group_name):
2706 val = remove_formatting(repo_group_name)
2707 val = safe_str(val).lower()
2708 chars = []
2709 for c in val:
2710 if c not in string.ascii_letters:
2711 c = str(ord(c))
2712 chars.append(c)
2713
2714 return ''.join(chars)
2715
2716 @classmethod
2717 def _generate_choice(cls, repo_group):
2718 from webhelpers2.html import literal as _literal
2719 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2720 return repo_group.group_id, _name(repo_group.full_path_splitted)
2721
2722 @classmethod
2723 def groups_choices(cls, groups=None, show_empty_group=True):
2724 if not groups:
2725 groups = cls.query().all()
2726
2727 repo_groups = []
2728 if show_empty_group:
2729 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2730
2731 repo_groups.extend([cls._generate_choice(x) for x in groups])
2732
2733 repo_groups = sorted(
2734 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2735 return repo_groups
2736
2737 @classmethod
2738 def url_sep(cls):
2739 return URL_SEP
2740
2741 @classmethod
2742 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2743 if case_insensitive:
2744 gr = cls.query().filter(func.lower(cls.group_name)
2745 == func.lower(group_name))
2746 else:
2747 gr = cls.query().filter(cls.group_name == group_name)
2748 if cache:
2749 name_key = _hash_key(group_name)
2750 gr = gr.options(
2751 FromCache("sql_cache_short", "get_group_%s" % name_key))
2752 return gr.scalar()
2753
2754 @classmethod
2755 def get_user_personal_repo_group(cls, user_id):
2756 user = User.get(user_id)
2757 if user.username == User.DEFAULT_USER:
2758 return None
2759
2760 return cls.query()\
2761 .filter(cls.personal == true()) \
2762 .filter(cls.user == user) \
2763 .order_by(cls.group_id.asc()) \
2764 .first()
2765
2766 @classmethod
2767 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2768 case_insensitive=True):
2769 q = RepoGroup.query()
2770
2771 if not isinstance(user_id, Optional):
2772 q = q.filter(RepoGroup.user_id == user_id)
2773
2774 if not isinstance(group_id, Optional):
2775 q = q.filter(RepoGroup.group_parent_id == group_id)
2776
2777 if case_insensitive:
2778 q = q.order_by(func.lower(RepoGroup.group_name))
2779 else:
2780 q = q.order_by(RepoGroup.group_name)
2781 return q.all()
2782
2783 @property
2784 def parents(self, parents_recursion_limit=10):
2785 groups = []
2786 if self.parent_group is None:
2787 return groups
2788 cur_gr = self.parent_group
2789 groups.insert(0, cur_gr)
2790 cnt = 0
2791 while 1:
2792 cnt += 1
2793 gr = getattr(cur_gr, 'parent_group', None)
2794 cur_gr = cur_gr.parent_group
2795 if gr is None:
2796 break
2797 if cnt == parents_recursion_limit:
2798 # this will prevent accidental infinit loops
2799 log.error('more than %s parents found for group %s, stopping '
2800 'recursive parent fetching', parents_recursion_limit, self)
2801 break
2802
2803 groups.insert(0, gr)
2804 return groups
2805
2806 @property
2807 def last_commit_cache_update_diff(self):
2808 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2809
2810 @classmethod
2811 def _load_commit_change(cls, last_commit_cache):
2812 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2813 empty_date = datetime.datetime.fromtimestamp(0)
2814 date_latest = last_commit_cache.get('date', empty_date)
2815 try:
2816 return parse_datetime(date_latest)
2817 except Exception:
2818 return empty_date
2819
2820 @property
2821 def last_commit_change(self):
2822 return self._load_commit_change(self.changeset_cache)
2823
2824 @property
2825 def last_db_change(self):
2826 return self.updated_on
2827
2828 @property
2829 def children(self):
2830 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2831
2832 @property
2833 def name(self):
2834 return self.group_name.split(RepoGroup.url_sep())[-1]
2835
2836 @property
2837 def full_path(self):
2838 return self.group_name
2839
2840 @property
2841 def full_path_splitted(self):
2842 return self.group_name.split(RepoGroup.url_sep())
2843
2844 @property
2845 def repositories(self):
2846 return Repository.query()\
2847 .filter(Repository.group == self)\
2848 .order_by(Repository.repo_name)
2849
2850 @property
2851 def repositories_recursive_count(self):
2852 cnt = self.repositories.count()
2853
2854 def children_count(group):
2855 cnt = 0
2856 for child in group.children:
2857 cnt += child.repositories.count()
2858 cnt += children_count(child)
2859 return cnt
2860
2861 return cnt + children_count(self)
2862
2863 def _recursive_objects(self, include_repos=True, include_groups=True):
2864 all_ = []
2865
2866 def _get_members(root_gr):
2867 if include_repos:
2868 for r in root_gr.repositories:
2869 all_.append(r)
2870 childs = root_gr.children.all()
2871 if childs:
2872 for gr in childs:
2873 if include_groups:
2874 all_.append(gr)
2875 _get_members(gr)
2876
2877 root_group = []
2878 if include_groups:
2879 root_group = [self]
2880
2881 _get_members(self)
2882 return root_group + all_
2883
2884 def recursive_groups_and_repos(self):
2885 """
2886 Recursive return all groups, with repositories in those groups
2887 """
2888 return self._recursive_objects()
2889
2890 def recursive_groups(self):
2891 """
2892 Returns all children groups for this group including children of children
2893 """
2894 return self._recursive_objects(include_repos=False)
2895
2896 def recursive_repos(self):
2897 """
2898 Returns all children repositories for this group
2899 """
2900 return self._recursive_objects(include_groups=False)
2901
2902 def get_new_name(self, group_name):
2903 """
2904 returns new full group name based on parent and new name
2905
2906 :param group_name:
2907 """
2908 path_prefix = (self.parent_group.full_path_splitted if
2909 self.parent_group else [])
2910 return RepoGroup.url_sep().join(path_prefix + [group_name])
2911
2912 def update_commit_cache(self, config=None):
2913 """
2914 Update cache of last commit for newest repository inside this repository group.
2915 cache_keys should be::
2916
2917 source_repo_id
2918 short_id
2919 raw_id
2920 revision
2921 parents
2922 message
2923 date
2924 author
2925
2926 """
2927 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2928 empty_date = datetime.datetime.fromtimestamp(0)
2929
2930 def repo_groups_and_repos(root_gr):
2931 for _repo in root_gr.repositories:
2932 yield _repo
2933 for child_group in root_gr.children.all():
2934 yield child_group
2935
2936 latest_repo_cs_cache = {}
2937 for obj in repo_groups_and_repos(self):
2938 repo_cs_cache = obj.changeset_cache
2939 date_latest = latest_repo_cs_cache.get('date', empty_date)
2940 date_current = repo_cs_cache.get('date', empty_date)
2941 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2942 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2943 latest_repo_cs_cache = repo_cs_cache
2944 if hasattr(obj, 'repo_id'):
2945 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2946 else:
2947 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2948
2949 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2950
2951 latest_repo_cs_cache['updated_on'] = time.time()
2952 self.changeset_cache = latest_repo_cs_cache
2953 self.updated_on = _date_latest
2954 Session().add(self)
2955 Session().commit()
2956
2957 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2958 self.group_name, latest_repo_cs_cache, _date_latest)
2959
2960 def permissions(self, with_admins=True, with_owner=True,
2961 expand_from_user_groups=False):
2962 """
2963 Permissions for repository groups
2964 """
2965 _admin_perm = 'group.admin'
2966
2967 owner_row = []
2968 if with_owner:
2969 usr = AttributeDict(self.user.get_dict())
2970 usr.owner_row = True
2971 usr.permission = _admin_perm
2972 owner_row.append(usr)
2973
2974 super_admin_ids = []
2975 super_admin_rows = []
2976 if with_admins:
2977 for usr in User.get_all_super_admins():
2978 super_admin_ids.append(usr.user_id)
2979 # if this admin is also owner, don't double the record
2980 if usr.user_id == owner_row[0].user_id:
2981 owner_row[0].admin_row = True
2982 else:
2983 usr = AttributeDict(usr.get_dict())
2984 usr.admin_row = True
2985 usr.permission = _admin_perm
2986 super_admin_rows.append(usr)
2987
2988 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2989 q = q.options(joinedload(UserRepoGroupToPerm.group),
2990 joinedload(UserRepoGroupToPerm.user),
2991 joinedload(UserRepoGroupToPerm.permission),)
2992
2993 # get owners and admins and permissions. We do a trick of re-writing
2994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2995 # has a global reference and changing one object propagates to all
2996 # others. This means if admin is also an owner admin_row that change
2997 # would propagate to both objects
2998 perm_rows = []
2999 for _usr in q.all():
3000 usr = AttributeDict(_usr.user.get_dict())
3001 # if this user is also owner/admin, mark as duplicate record
3002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3003 usr.duplicate_perm = True
3004 usr.permission = _usr.permission.permission_name
3005 perm_rows.append(usr)
3006
3007 # filter the perm rows by 'default' first and then sort them by
3008 # admin,write,read,none permissions sorted again alphabetically in
3009 # each group
3010 perm_rows = sorted(perm_rows, key=display_user_sort)
3011
3012 user_groups_rows = []
3013 if expand_from_user_groups:
3014 for ug in self.permission_user_groups(with_members=True):
3015 for user_data in ug.members:
3016 user_groups_rows.append(user_data)
3017
3018 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3019
3020 def permission_user_groups(self, with_members=False):
3021 q = UserGroupRepoGroupToPerm.query()\
3022 .filter(UserGroupRepoGroupToPerm.group == self)
3023 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3024 joinedload(UserGroupRepoGroupToPerm.users_group),
3025 joinedload(UserGroupRepoGroupToPerm.permission),)
3026
3027 perm_rows = []
3028 for _user_group in q.all():
3029 entry = AttributeDict(_user_group.users_group.get_dict())
3030 entry.permission = _user_group.permission.permission_name
3031 if with_members:
3032 entry.members = [x.user.get_dict()
3033 for x in _user_group.users_group.members]
3034 perm_rows.append(entry)
3035
3036 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3037 return perm_rows
3038
3039 def get_api_data(self):
3040 """
3041 Common function for generating api data
3042
3043 """
3044 group = self
3045 data = {
3046 'group_id': group.group_id,
3047 'group_name': group.group_name,
3048 'group_description': group.description_safe,
3049 'parent_group': group.parent_group.group_name if group.parent_group else None,
3050 'repositories': [x.repo_name for x in group.repositories],
3051 'owner': group.user.username,
3052 }
3053 return data
3054
3055 def get_dict(self):
3056 # Since we transformed `group_name` to a hybrid property, we need to
3057 # keep compatibility with the code which uses `group_name` field.
3058 result = super(RepoGroup, self).get_dict()
3059 result['group_name'] = result.pop('_group_name', None)
3060 return result
3061
3062
3063 class Permission(Base, BaseModel):
3064 __tablename__ = 'permissions'
3065 __table_args__ = (
3066 Index('p_perm_name_idx', 'permission_name'),
3067 base_table_args,
3068 )
3069
3070 PERMS = [
3071 ('hg.admin', _('RhodeCode Super Administrator')),
3072
3073 ('repository.none', _('Repository no access')),
3074 ('repository.read', _('Repository read access')),
3075 ('repository.write', _('Repository write access')),
3076 ('repository.admin', _('Repository admin access')),
3077
3078 ('group.none', _('Repository group no access')),
3079 ('group.read', _('Repository group read access')),
3080 ('group.write', _('Repository group write access')),
3081 ('group.admin', _('Repository group admin access')),
3082
3083 ('usergroup.none', _('User group no access')),
3084 ('usergroup.read', _('User group read access')),
3085 ('usergroup.write', _('User group write access')),
3086 ('usergroup.admin', _('User group admin access')),
3087
3088 ('branch.none', _('Branch no permissions')),
3089 ('branch.merge', _('Branch access by web merge')),
3090 ('branch.push', _('Branch access by push')),
3091 ('branch.push_force', _('Branch access by push with force')),
3092
3093 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3094 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3095
3096 ('hg.usergroup.create.false', _('User Group creation disabled')),
3097 ('hg.usergroup.create.true', _('User Group creation enabled')),
3098
3099 ('hg.create.none', _('Repository creation disabled')),
3100 ('hg.create.repository', _('Repository creation enabled')),
3101 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3102 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3103
3104 ('hg.fork.none', _('Repository forking disabled')),
3105 ('hg.fork.repository', _('Repository forking enabled')),
3106
3107 ('hg.register.none', _('Registration disabled')),
3108 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3109 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3110
3111 ('hg.password_reset.enabled', _('Password reset enabled')),
3112 ('hg.password_reset.hidden', _('Password reset hidden')),
3113 ('hg.password_reset.disabled', _('Password reset disabled')),
3114
3115 ('hg.extern_activate.manual', _('Manual activation of external account')),
3116 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3117
3118 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3119 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3120 ]
3121
3122 # definition of system default permissions for DEFAULT user, created on
3123 # system setup
3124 DEFAULT_USER_PERMISSIONS = [
3125 # object perms
3126 'repository.read',
3127 'group.read',
3128 'usergroup.read',
3129 # branch, for backward compat we need same value as before so forced pushed
3130 'branch.push_force',
3131 # global
3132 'hg.create.repository',
3133 'hg.repogroup.create.false',
3134 'hg.usergroup.create.false',
3135 'hg.create.write_on_repogroup.true',
3136 'hg.fork.repository',
3137 'hg.register.manual_activate',
3138 'hg.password_reset.enabled',
3139 'hg.extern_activate.auto',
3140 'hg.inherit_default_perms.true',
3141 ]
3142
3143 # defines which permissions are more important higher the more important
3144 # Weight defines which permissions are more important.
3145 # The higher number the more important.
3146 PERM_WEIGHTS = {
3147 'repository.none': 0,
3148 'repository.read': 1,
3149 'repository.write': 3,
3150 'repository.admin': 4,
3151
3152 'group.none': 0,
3153 'group.read': 1,
3154 'group.write': 3,
3155 'group.admin': 4,
3156
3157 'usergroup.none': 0,
3158 'usergroup.read': 1,
3159 'usergroup.write': 3,
3160 'usergroup.admin': 4,
3161
3162 'branch.none': 0,
3163 'branch.merge': 1,
3164 'branch.push': 3,
3165 'branch.push_force': 4,
3166
3167 'hg.repogroup.create.false': 0,
3168 'hg.repogroup.create.true': 1,
3169
3170 'hg.usergroup.create.false': 0,
3171 'hg.usergroup.create.true': 1,
3172
3173 'hg.fork.none': 0,
3174 'hg.fork.repository': 1,
3175 'hg.create.none': 0,
3176 'hg.create.repository': 1
3177 }
3178
3179 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3180 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3181 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3182
3183 def __unicode__(self):
3184 return u"<%s('%s:%s')>" % (
3185 self.__class__.__name__, self.permission_id, self.permission_name
3186 )
3187
3188 @classmethod
3189 def get_by_key(cls, key):
3190 return cls.query().filter(cls.permission_name == key).scalar()
3191
3192 @classmethod
3193 def get_default_repo_perms(cls, user_id, repo_id=None):
3194 q = Session().query(UserRepoToPerm, Repository, Permission)\
3195 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3196 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3197 .filter(UserRepoToPerm.user_id == user_id)
3198 if repo_id:
3199 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3200 return q.all()
3201
3202 @classmethod
3203 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3204 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3205 .join(
3206 Permission,
3207 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3208 .join(
3209 UserRepoToPerm,
3210 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3211 .filter(UserRepoToPerm.user_id == user_id)
3212
3213 if repo_id:
3214 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3215 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3216
3217 @classmethod
3218 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3219 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3220 .join(
3221 Permission,
3222 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3223 .join(
3224 Repository,
3225 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3226 .join(
3227 UserGroup,
3228 UserGroupRepoToPerm.users_group_id ==
3229 UserGroup.users_group_id)\
3230 .join(
3231 UserGroupMember,
3232 UserGroupRepoToPerm.users_group_id ==
3233 UserGroupMember.users_group_id)\
3234 .filter(
3235 UserGroupMember.user_id == user_id,
3236 UserGroup.users_group_active == true())
3237 if repo_id:
3238 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3239 return q.all()
3240
3241 @classmethod
3242 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3243 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3244 .join(
3245 Permission,
3246 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3247 .join(
3248 UserGroupRepoToPerm,
3249 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3250 .join(
3251 UserGroup,
3252 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3253 .join(
3254 UserGroupMember,
3255 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3256 .filter(
3257 UserGroupMember.user_id == user_id,
3258 UserGroup.users_group_active == true())
3259
3260 if repo_id:
3261 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3262 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3263
3264 @classmethod
3265 def get_default_group_perms(cls, user_id, repo_group_id=None):
3266 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3267 .join(
3268 Permission,
3269 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3270 .join(
3271 RepoGroup,
3272 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3273 .filter(UserRepoGroupToPerm.user_id == user_id)
3274 if repo_group_id:
3275 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3276 return q.all()
3277
3278 @classmethod
3279 def get_default_group_perms_from_user_group(
3280 cls, user_id, repo_group_id=None):
3281 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3282 .join(
3283 Permission,
3284 UserGroupRepoGroupToPerm.permission_id ==
3285 Permission.permission_id)\
3286 .join(
3287 RepoGroup,
3288 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3289 .join(
3290 UserGroup,
3291 UserGroupRepoGroupToPerm.users_group_id ==
3292 UserGroup.users_group_id)\
3293 .join(
3294 UserGroupMember,
3295 UserGroupRepoGroupToPerm.users_group_id ==
3296 UserGroupMember.users_group_id)\
3297 .filter(
3298 UserGroupMember.user_id == user_id,
3299 UserGroup.users_group_active == true())
3300 if repo_group_id:
3301 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3302 return q.all()
3303
3304 @classmethod
3305 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3306 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3307 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3308 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3309 .filter(UserUserGroupToPerm.user_id == user_id)
3310 if user_group_id:
3311 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3312 return q.all()
3313
3314 @classmethod
3315 def get_default_user_group_perms_from_user_group(
3316 cls, user_id, user_group_id=None):
3317 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3318 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3319 .join(
3320 Permission,
3321 UserGroupUserGroupToPerm.permission_id ==
3322 Permission.permission_id)\
3323 .join(
3324 TargetUserGroup,
3325 UserGroupUserGroupToPerm.target_user_group_id ==
3326 TargetUserGroup.users_group_id)\
3327 .join(
3328 UserGroup,
3329 UserGroupUserGroupToPerm.user_group_id ==
3330 UserGroup.users_group_id)\
3331 .join(
3332 UserGroupMember,
3333 UserGroupUserGroupToPerm.user_group_id ==
3334 UserGroupMember.users_group_id)\
3335 .filter(
3336 UserGroupMember.user_id == user_id,
3337 UserGroup.users_group_active == true())
3338 if user_group_id:
3339 q = q.filter(
3340 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3341
3342 return q.all()
3343
3344
3345 class UserRepoToPerm(Base, BaseModel):
3346 __tablename__ = 'repo_to_perm'
3347 __table_args__ = (
3348 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3349 base_table_args
3350 )
3351
3352 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3354 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3355 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3356
3357 user = relationship('User')
3358 repository = relationship('Repository')
3359 permission = relationship('Permission')
3360
3361 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3362
3363 @classmethod
3364 def create(cls, user, repository, permission):
3365 n = cls()
3366 n.user = user
3367 n.repository = repository
3368 n.permission = permission
3369 Session().add(n)
3370 return n
3371
3372 def __unicode__(self):
3373 return u'<%s => %s >' % (self.user, self.repository)
3374
3375
3376 class UserUserGroupToPerm(Base, BaseModel):
3377 __tablename__ = 'user_user_group_to_perm'
3378 __table_args__ = (
3379 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3380 base_table_args
3381 )
3382
3383 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3387
3388 user = relationship('User')
3389 user_group = relationship('UserGroup')
3390 permission = relationship('Permission')
3391
3392 @classmethod
3393 def create(cls, user, user_group, permission):
3394 n = cls()
3395 n.user = user
3396 n.user_group = user_group
3397 n.permission = permission
3398 Session().add(n)
3399 return n
3400
3401 def __unicode__(self):
3402 return u'<%s => %s >' % (self.user, self.user_group)
3403
3404
3405 class UserToPerm(Base, BaseModel):
3406 __tablename__ = 'user_to_perm'
3407 __table_args__ = (
3408 UniqueConstraint('user_id', 'permission_id'),
3409 base_table_args
3410 )
3411
3412 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3415
3416 user = relationship('User')
3417 permission = relationship('Permission', lazy='joined')
3418
3419 def __unicode__(self):
3420 return u'<%s => %s >' % (self.user, self.permission)
3421
3422
3423 class UserGroupRepoToPerm(Base, BaseModel):
3424 __tablename__ = 'users_group_repo_to_perm'
3425 __table_args__ = (
3426 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3427 base_table_args
3428 )
3429
3430 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3431 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3432 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3433 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3434
3435 users_group = relationship('UserGroup')
3436 permission = relationship('Permission')
3437 repository = relationship('Repository')
3438 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3439
3440 @classmethod
3441 def create(cls, users_group, repository, permission):
3442 n = cls()
3443 n.users_group = users_group
3444 n.repository = repository
3445 n.permission = permission
3446 Session().add(n)
3447 return n
3448
3449 def __unicode__(self):
3450 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3451
3452
3453 class UserGroupUserGroupToPerm(Base, BaseModel):
3454 __tablename__ = 'user_group_user_group_to_perm'
3455 __table_args__ = (
3456 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3457 CheckConstraint('target_user_group_id != user_group_id'),
3458 base_table_args
3459 )
3460
3461 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)
3462 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3464 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3465
3466 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3467 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3468 permission = relationship('Permission')
3469
3470 @classmethod
3471 def create(cls, target_user_group, user_group, permission):
3472 n = cls()
3473 n.target_user_group = target_user_group
3474 n.user_group = user_group
3475 n.permission = permission
3476 Session().add(n)
3477 return n
3478
3479 def __unicode__(self):
3480 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3481
3482
3483 class UserGroupToPerm(Base, BaseModel):
3484 __tablename__ = 'users_group_to_perm'
3485 __table_args__ = (
3486 UniqueConstraint('users_group_id', 'permission_id',),
3487 base_table_args
3488 )
3489
3490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3493
3494 users_group = relationship('UserGroup')
3495 permission = relationship('Permission')
3496
3497
3498 class UserRepoGroupToPerm(Base, BaseModel):
3499 __tablename__ = 'user_repo_group_to_perm'
3500 __table_args__ = (
3501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3502 base_table_args
3503 )
3504
3505 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3509
3510 user = relationship('User')
3511 group = relationship('RepoGroup')
3512 permission = relationship('Permission')
3513
3514 @classmethod
3515 def create(cls, user, repository_group, permission):
3516 n = cls()
3517 n.user = user
3518 n.group = repository_group
3519 n.permission = permission
3520 Session().add(n)
3521 return n
3522
3523
3524 class UserGroupRepoGroupToPerm(Base, BaseModel):
3525 __tablename__ = 'users_group_repo_group_to_perm'
3526 __table_args__ = (
3527 UniqueConstraint('users_group_id', 'group_id'),
3528 base_table_args
3529 )
3530
3531 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)
3532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3533 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3534 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3535
3536 users_group = relationship('UserGroup')
3537 permission = relationship('Permission')
3538 group = relationship('RepoGroup')
3539
3540 @classmethod
3541 def create(cls, user_group, repository_group, permission):
3542 n = cls()
3543 n.users_group = user_group
3544 n.group = repository_group
3545 n.permission = permission
3546 Session().add(n)
3547 return n
3548
3549 def __unicode__(self):
3550 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3551
3552
3553 class Statistics(Base, BaseModel):
3554 __tablename__ = 'statistics'
3555 __table_args__ = (
3556 base_table_args
3557 )
3558
3559 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3560 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3561 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3562 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3563 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3564 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3565
3566 repository = relationship('Repository', single_parent=True)
3567
3568
3569 class UserFollowing(Base, BaseModel):
3570 __tablename__ = 'user_followings'
3571 __table_args__ = (
3572 UniqueConstraint('user_id', 'follows_repository_id'),
3573 UniqueConstraint('user_id', 'follows_user_id'),
3574 base_table_args
3575 )
3576
3577 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3579 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3580 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3581 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3582
3583 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3584
3585 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3586 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3587
3588 @classmethod
3589 def get_repo_followers(cls, repo_id):
3590 return cls.query().filter(cls.follows_repo_id == repo_id)
3591
3592
3593 class CacheKey(Base, BaseModel):
3594 __tablename__ = 'cache_invalidation'
3595 __table_args__ = (
3596 UniqueConstraint('cache_key'),
3597 Index('key_idx', 'cache_key'),
3598 base_table_args,
3599 )
3600
3601 CACHE_TYPE_FEED = 'FEED'
3602
3603 # namespaces used to register process/thread aware caches
3604 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3605 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3606
3607 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3608 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3609 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3610 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3611 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3612
3613 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3614 self.cache_key = cache_key
3615 self.cache_args = cache_args
3616 self.cache_active = False
3617 # first key should be same for all entries, since all workers should share it
3618 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3619
3620 def __unicode__(self):
3621 return u"<%s('%s:%s[%s]')>" % (
3622 self.__class__.__name__,
3623 self.cache_id, self.cache_key, self.cache_active)
3624
3625 def _cache_key_partition(self):
3626 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3627 return prefix, repo_name, suffix
3628
3629 def get_prefix(self):
3630 """
3631 Try to extract prefix from existing cache key. The key could consist
3632 of prefix, repo_name, suffix
3633 """
3634 # this returns prefix, repo_name, suffix
3635 return self._cache_key_partition()[0]
3636
3637 def get_suffix(self):
3638 """
3639 get suffix that might have been used in _get_cache_key to
3640 generate self.cache_key. Only used for informational purposes
3641 in repo_edit.mako.
3642 """
3643 # prefix, repo_name, suffix
3644 return self._cache_key_partition()[2]
3645
3646 @classmethod
3647 def generate_new_state_uid(cls, based_on=None):
3648 if based_on:
3649 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3650 else:
3651 return str(uuid.uuid4())
3652
3653 @classmethod
3654 def delete_all_cache(cls):
3655 """
3656 Delete all cache keys from database.
3657 Should only be run when all instances are down and all entries
3658 thus stale.
3659 """
3660 cls.query().delete()
3661 Session().commit()
3662
3663 @classmethod
3664 def set_invalidate(cls, cache_uid, delete=False):
3665 """
3666 Mark all caches of a repo as invalid in the database.
3667 """
3668
3669 try:
3670 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3671 if delete:
3672 qry.delete()
3673 log.debug('cache objects deleted for cache args %s',
3674 safe_str(cache_uid))
3675 else:
3676 qry.update({"cache_active": False,
3677 "cache_state_uid": cls.generate_new_state_uid()})
3678 log.debug('cache objects marked as invalid for cache args %s',
3679 safe_str(cache_uid))
3680
3681 Session().commit()
3682 except Exception:
3683 log.exception(
3684 'Cache key invalidation failed for cache args %s',
3685 safe_str(cache_uid))
3686 Session().rollback()
3687
3688 @classmethod
3689 def get_active_cache(cls, cache_key):
3690 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3691 if inv_obj:
3692 return inv_obj
3693 return None
3694
3695 @classmethod
3696 def get_namespace_map(cls, namespace):
3697 return {
3698 x.cache_key: x
3699 for x in cls.query().filter(cls.cache_args == namespace)}
3700
3701
3702 class ChangesetComment(Base, BaseModel):
3703 __tablename__ = 'changeset_comments'
3704 __table_args__ = (
3705 Index('cc_revision_idx', 'revision'),
3706 base_table_args,
3707 )
3708
3709 COMMENT_OUTDATED = u'comment_outdated'
3710 COMMENT_TYPE_NOTE = u'note'
3711 COMMENT_TYPE_TODO = u'todo'
3712 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3713
3714 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3715 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3716 revision = Column('revision', String(40), nullable=True)
3717 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3718 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3719 line_no = Column('line_no', Unicode(10), nullable=True)
3720 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3721 f_path = Column('f_path', Unicode(1000), nullable=True)
3722 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3723 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3724 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3725 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3726 renderer = Column('renderer', Unicode(64), nullable=True)
3727 display_state = Column('display_state', Unicode(128), nullable=True)
3728
3729 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3730 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3731
3732 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3733 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3734
3735 author = relationship('User', lazy='joined')
3736 repo = relationship('Repository')
3737 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3738 pull_request = relationship('PullRequest', lazy='joined')
3739 pull_request_version = relationship('PullRequestVersion')
3740
3741 @classmethod
3742 def get_users(cls, revision=None, pull_request_id=None):
3743 """
3744 Returns user associated with this ChangesetComment. ie those
3745 who actually commented
3746
3747 :param cls:
3748 :param revision:
3749 """
3750 q = Session().query(User)\
3751 .join(ChangesetComment.author)
3752 if revision:
3753 q = q.filter(cls.revision == revision)
3754 elif pull_request_id:
3755 q = q.filter(cls.pull_request_id == pull_request_id)
3756 return q.all()
3757
3758 @classmethod
3759 def get_index_from_version(cls, pr_version, versions):
3760 num_versions = [x.pull_request_version_id for x in versions]
3761 try:
3762 return num_versions.index(pr_version) +1
3763 except (IndexError, ValueError):
3764 return
3765
3766 @property
3767 def outdated(self):
3768 return self.display_state == self.COMMENT_OUTDATED
3769
3770 def outdated_at_version(self, version):
3771 """
3772 Checks if comment is outdated for given pull request version
3773 """
3774 return self.outdated and self.pull_request_version_id != version
3775
3776 def older_than_version(self, version):
3777 """
3778 Checks if comment is made from previous version than given
3779 """
3780 if version is None:
3781 return self.pull_request_version_id is not None
3782
3783 return self.pull_request_version_id < version
3784
3785 @property
3786 def resolved(self):
3787 return self.resolved_by[0] if self.resolved_by else None
3788
3789 @property
3790 def is_todo(self):
3791 return self.comment_type == self.COMMENT_TYPE_TODO
3792
3793 @property
3794 def is_inline(self):
3795 return self.line_no and self.f_path
3796
3797 def get_index_version(self, versions):
3798 return self.get_index_from_version(
3799 self.pull_request_version_id, versions)
3800
3801 def __repr__(self):
3802 if self.comment_id:
3803 return '<DB:Comment #%s>' % self.comment_id
3804 else:
3805 return '<DB:Comment at %#x>' % id(self)
3806
3807 def get_api_data(self):
3808 comment = self
3809 data = {
3810 'comment_id': comment.comment_id,
3811 'comment_type': comment.comment_type,
3812 'comment_text': comment.text,
3813 'comment_status': comment.status_change,
3814 'comment_f_path': comment.f_path,
3815 'comment_lineno': comment.line_no,
3816 'comment_author': comment.author,
3817 'comment_created_on': comment.created_on,
3818 'comment_resolved_by': self.resolved
3819 }
3820 return data
3821
3822 def __json__(self):
3823 data = dict()
3824 data.update(self.get_api_data())
3825 return data
3826
3827
3828 class ChangesetStatus(Base, BaseModel):
3829 __tablename__ = 'changeset_statuses'
3830 __table_args__ = (
3831 Index('cs_revision_idx', 'revision'),
3832 Index('cs_version_idx', 'version'),
3833 UniqueConstraint('repo_id', 'revision', 'version'),
3834 base_table_args
3835 )
3836
3837 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3838 STATUS_APPROVED = 'approved'
3839 STATUS_REJECTED = 'rejected'
3840 STATUS_UNDER_REVIEW = 'under_review'
3841
3842 STATUSES = [
3843 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3844 (STATUS_APPROVED, _("Approved")),
3845 (STATUS_REJECTED, _("Rejected")),
3846 (STATUS_UNDER_REVIEW, _("Under Review")),
3847 ]
3848
3849 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3850 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3852 revision = Column('revision', String(40), nullable=False)
3853 status = Column('status', String(128), nullable=False, default=DEFAULT)
3854 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3855 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3856 version = Column('version', Integer(), nullable=False, default=0)
3857 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3858
3859 author = relationship('User', lazy='joined')
3860 repo = relationship('Repository')
3861 comment = relationship('ChangesetComment', lazy='joined')
3862 pull_request = relationship('PullRequest', lazy='joined')
3863
3864 def __unicode__(self):
3865 return u"<%s('%s[v%s]:%s')>" % (
3866 self.__class__.__name__,
3867 self.status, self.version, self.author
3868 )
3869
3870 @classmethod
3871 def get_status_lbl(cls, value):
3872 return dict(cls.STATUSES).get(value)
3873
3874 @property
3875 def status_lbl(self):
3876 return ChangesetStatus.get_status_lbl(self.status)
3877
3878 def get_api_data(self):
3879 status = self
3880 data = {
3881 'status_id': status.changeset_status_id,
3882 'status': status.status,
3883 }
3884 return data
3885
3886 def __json__(self):
3887 data = dict()
3888 data.update(self.get_api_data())
3889 return data
3890
3891
3892 class _SetState(object):
3893 """
3894 Context processor allowing changing state for sensitive operation such as
3895 pull request update or merge
3896 """
3897
3898 def __init__(self, pull_request, pr_state, back_state=None):
3899 self._pr = pull_request
3900 self._org_state = back_state or pull_request.pull_request_state
3901 self._pr_state = pr_state
3902 self._current_state = None
3903
3904 def __enter__(self):
3905 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3906 self._pr, self._pr_state)
3907 self.set_pr_state(self._pr_state)
3908 return self
3909
3910 def __exit__(self, exc_type, exc_val, exc_tb):
3911 if exc_val is not None:
3912 log.error(traceback.format_exc(exc_tb))
3913 return None
3914
3915 self.set_pr_state(self._org_state)
3916 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3917 self._pr, self._org_state)
3918
3919 @property
3920 def state(self):
3921 return self._current_state
3922
3923 def set_pr_state(self, pr_state):
3924 try:
3925 self._pr.pull_request_state = pr_state
3926 Session().add(self._pr)
3927 Session().commit()
3928 self._current_state = pr_state
3929 except Exception:
3930 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3931 raise
3932
3933
3934 class _PullRequestBase(BaseModel):
3935 """
3936 Common attributes of pull request and version entries.
3937 """
3938
3939 # .status values
3940 STATUS_NEW = u'new'
3941 STATUS_OPEN = u'open'
3942 STATUS_CLOSED = u'closed'
3943
3944 # available states
3945 STATE_CREATING = u'creating'
3946 STATE_UPDATING = u'updating'
3947 STATE_MERGING = u'merging'
3948 STATE_CREATED = u'created'
3949
3950 title = Column('title', Unicode(255), nullable=True)
3951 description = Column(
3952 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3953 nullable=True)
3954 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3955
3956 # new/open/closed status of pull request (not approve/reject/etc)
3957 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3958 created_on = Column(
3959 'created_on', DateTime(timezone=False), nullable=False,
3960 default=datetime.datetime.now)
3961 updated_on = Column(
3962 'updated_on', DateTime(timezone=False), nullable=False,
3963 default=datetime.datetime.now)
3964
3965 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3966
3967 @declared_attr
3968 def user_id(cls):
3969 return Column(
3970 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3971 unique=None)
3972
3973 # 500 revisions max
3974 _revisions = Column(
3975 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3976
3977 @declared_attr
3978 def source_repo_id(cls):
3979 # TODO: dan: rename column to source_repo_id
3980 return Column(
3981 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3982 nullable=False)
3983
3984 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3985
3986 @hybrid_property
3987 def source_ref(self):
3988 return self._source_ref
3989
3990 @source_ref.setter
3991 def source_ref(self, val):
3992 parts = (val or '').split(':')
3993 if len(parts) != 3:
3994 raise ValueError(
3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3996 self._source_ref = safe_unicode(val)
3997
3998 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3999
4000 @hybrid_property
4001 def target_ref(self):
4002 return self._target_ref
4003
4004 @target_ref.setter
4005 def target_ref(self, val):
4006 parts = (val or '').split(':')
4007 if len(parts) != 3:
4008 raise ValueError(
4009 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4010 self._target_ref = safe_unicode(val)
4011
4012 @declared_attr
4013 def target_repo_id(cls):
4014 # TODO: dan: rename column to target_repo_id
4015 return Column(
4016 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4017 nullable=False)
4018
4019 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4020
4021 # TODO: dan: rename column to last_merge_source_rev
4022 _last_merge_source_rev = Column(
4023 'last_merge_org_rev', String(40), nullable=True)
4024 # TODO: dan: rename column to last_merge_target_rev
4025 _last_merge_target_rev = Column(
4026 'last_merge_other_rev', String(40), nullable=True)
4027 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4028 last_merge_metadata = Column(
4029 'last_merge_metadata', MutationObj.as_mutable(
4030 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4031
4032 merge_rev = Column('merge_rev', String(40), nullable=True)
4033
4034 reviewer_data = Column(
4035 'reviewer_data_json', MutationObj.as_mutable(
4036 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4037
4038 @property
4039 def reviewer_data_json(self):
4040 return json.dumps(self.reviewer_data)
4041
4042 @property
4043 def work_in_progress(self):
4044 """checks if pull request is work in progress by checking the title"""
4045 title = self.title.upper()
4046 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4047 return True
4048 return False
4049
4050 @hybrid_property
4051 def description_safe(self):
4052 from rhodecode.lib import helpers as h
4053 return h.escape(self.description)
4054
4055 @hybrid_property
4056 def revisions(self):
4057 return self._revisions.split(':') if self._revisions else []
4058
4059 @revisions.setter
4060 def revisions(self, val):
4061 self._revisions = u':'.join(val)
4062
4063 @hybrid_property
4064 def last_merge_status(self):
4065 return safe_int(self._last_merge_status)
4066
4067 @last_merge_status.setter
4068 def last_merge_status(self, val):
4069 self._last_merge_status = val
4070
4071 @declared_attr
4072 def author(cls):
4073 return relationship('User', lazy='joined')
4074
4075 @declared_attr
4076 def source_repo(cls):
4077 return relationship(
4078 'Repository',
4079 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4080
4081 @property
4082 def source_ref_parts(self):
4083 return self.unicode_to_reference(self.source_ref)
4084
4085 @declared_attr
4086 def target_repo(cls):
4087 return relationship(
4088 'Repository',
4089 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4090
4091 @property
4092 def target_ref_parts(self):
4093 return self.unicode_to_reference(self.target_ref)
4094
4095 @property
4096 def shadow_merge_ref(self):
4097 return self.unicode_to_reference(self._shadow_merge_ref)
4098
4099 @shadow_merge_ref.setter
4100 def shadow_merge_ref(self, ref):
4101 self._shadow_merge_ref = self.reference_to_unicode(ref)
4102
4103 @staticmethod
4104 def unicode_to_reference(raw):
4105 """
4106 Convert a unicode (or string) to a reference object.
4107 If unicode evaluates to False it returns None.
4108 """
4109 if raw:
4110 refs = raw.split(':')
4111 return Reference(*refs)
4112 else:
4113 return None
4114
4115 @staticmethod
4116 def reference_to_unicode(ref):
4117 """
4118 Convert a reference object to unicode.
4119 If reference is None it returns None.
4120 """
4121 if ref:
4122 return u':'.join(ref)
4123 else:
4124 return None
4125
4126 def get_api_data(self, with_merge_state=True):
4127 from rhodecode.model.pull_request import PullRequestModel
4128
4129 pull_request = self
4130 if with_merge_state:
4131 merge_response, merge_status, msg = \
4132 PullRequestModel().merge_status(pull_request)
4133 merge_state = {
4134 'status': merge_status,
4135 'message': safe_unicode(msg),
4136 }
4137 else:
4138 merge_state = {'status': 'not_available',
4139 'message': 'not_available'}
4140
4141 merge_data = {
4142 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4143 'reference': (
4144 pull_request.shadow_merge_ref._asdict()
4145 if pull_request.shadow_merge_ref else None),
4146 }
4147
4148 data = {
4149 'pull_request_id': pull_request.pull_request_id,
4150 'url': PullRequestModel().get_url(pull_request),
4151 'title': pull_request.title,
4152 'description': pull_request.description,
4153 'status': pull_request.status,
4154 'state': pull_request.pull_request_state,
4155 'created_on': pull_request.created_on,
4156 'updated_on': pull_request.updated_on,
4157 'commit_ids': pull_request.revisions,
4158 'review_status': pull_request.calculated_review_status(),
4159 'mergeable': merge_state,
4160 'source': {
4161 'clone_url': pull_request.source_repo.clone_url(),
4162 'repository': pull_request.source_repo.repo_name,
4163 'reference': {
4164 'name': pull_request.source_ref_parts.name,
4165 'type': pull_request.source_ref_parts.type,
4166 'commit_id': pull_request.source_ref_parts.commit_id,
4167 },
4168 },
4169 'target': {
4170 'clone_url': pull_request.target_repo.clone_url(),
4171 'repository': pull_request.target_repo.repo_name,
4172 'reference': {
4173 'name': pull_request.target_ref_parts.name,
4174 'type': pull_request.target_ref_parts.type,
4175 'commit_id': pull_request.target_ref_parts.commit_id,
4176 },
4177 },
4178 'merge': merge_data,
4179 'author': pull_request.author.get_api_data(include_secrets=False,
4180 details='basic'),
4181 'reviewers': [
4182 {
4183 'user': reviewer.get_api_data(include_secrets=False,
4184 details='basic'),
4185 'reasons': reasons,
4186 'review_status': st[0][1].status if st else 'not_reviewed',
4187 }
4188 for obj, reviewer, reasons, mandatory, st in
4189 pull_request.reviewers_statuses()
4190 ]
4191 }
4192
4193 return data
4194
4195 def set_state(self, pull_request_state, final_state=None):
4196 """
4197 # goes from initial state to updating to initial state.
4198 # initial state can be changed by specifying back_state=
4199 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4200 pull_request.merge()
4201
4202 :param pull_request_state:
4203 :param final_state:
4204
4205 """
4206
4207 return _SetState(self, pull_request_state, back_state=final_state)
4208
4209
4210 class PullRequest(Base, _PullRequestBase):
4211 __tablename__ = 'pull_requests'
4212 __table_args__ = (
4213 base_table_args,
4214 )
4215
4216 pull_request_id = Column(
4217 'pull_request_id', Integer(), nullable=False, primary_key=True)
4218
4219 def __repr__(self):
4220 if self.pull_request_id:
4221 return '<DB:PullRequest #%s>' % self.pull_request_id
4222 else:
4223 return '<DB:PullRequest at %#x>' % id(self)
4224
4225 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4226 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4227 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4228 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4229 lazy='dynamic')
4230
4231 @classmethod
4232 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4233 internal_methods=None):
4234
4235 class PullRequestDisplay(object):
4236 """
4237 Special object wrapper for showing PullRequest data via Versions
4238 It mimics PR object as close as possible. This is read only object
4239 just for display
4240 """
4241
4242 def __init__(self, attrs, internal=None):
4243 self.attrs = attrs
4244 # internal have priority over the given ones via attrs
4245 self.internal = internal or ['versions']
4246
4247 def __getattr__(self, item):
4248 if item in self.internal:
4249 return getattr(self, item)
4250 try:
4251 return self.attrs[item]
4252 except KeyError:
4253 raise AttributeError(
4254 '%s object has no attribute %s' % (self, item))
4255
4256 def __repr__(self):
4257 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4258
4259 def versions(self):
4260 return pull_request_obj.versions.order_by(
4261 PullRequestVersion.pull_request_version_id).all()
4262
4263 def is_closed(self):
4264 return pull_request_obj.is_closed()
4265
4266 def is_state_changing(self):
4267 return pull_request_obj.is_state_changing()
4268
4269 @property
4270 def pull_request_version_id(self):
4271 return getattr(pull_request_obj, 'pull_request_version_id', None)
4272
4273 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4274
4275 attrs.author = StrictAttributeDict(
4276 pull_request_obj.author.get_api_data())
4277 if pull_request_obj.target_repo:
4278 attrs.target_repo = StrictAttributeDict(
4279 pull_request_obj.target_repo.get_api_data())
4280 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4281
4282 if pull_request_obj.source_repo:
4283 attrs.source_repo = StrictAttributeDict(
4284 pull_request_obj.source_repo.get_api_data())
4285 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4286
4287 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4288 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4289 attrs.revisions = pull_request_obj.revisions
4290
4291 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4292 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4293 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4294
4295 return PullRequestDisplay(attrs, internal=internal_methods)
4296
4297 def is_closed(self):
4298 return self.status == self.STATUS_CLOSED
4299
4300 def is_state_changing(self):
4301 return self.pull_request_state != PullRequest.STATE_CREATED
4302
4303 def __json__(self):
4304 return {
4305 'revisions': self.revisions,
4306 'versions': self.versions_count
4307 }
4308
4309 def calculated_review_status(self):
4310 from rhodecode.model.changeset_status import ChangesetStatusModel
4311 return ChangesetStatusModel().calculated_review_status(self)
4312
4313 def reviewers_statuses(self):
4314 from rhodecode.model.changeset_status import ChangesetStatusModel
4315 return ChangesetStatusModel().reviewers_statuses(self)
4316
4317 @property
4318 def workspace_id(self):
4319 from rhodecode.model.pull_request import PullRequestModel
4320 return PullRequestModel()._workspace_id(self)
4321
4322 def get_shadow_repo(self):
4323 workspace_id = self.workspace_id
4324 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4325 if os.path.isdir(shadow_repository_path):
4326 vcs_obj = self.target_repo.scm_instance()
4327 return vcs_obj.get_shadow_instance(shadow_repository_path)
4328
4329 @property
4330 def versions_count(self):
4331 """
4332 return number of versions this PR have, e.g a PR that once been
4333 updated will have 2 versions
4334 """
4335 return self.versions.count() + 1
4336
4337
4338 class PullRequestVersion(Base, _PullRequestBase):
4339 __tablename__ = 'pull_request_versions'
4340 __table_args__ = (
4341 base_table_args,
4342 )
4343
4344 pull_request_version_id = Column(
4345 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4346 pull_request_id = Column(
4347 'pull_request_id', Integer(),
4348 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4349 pull_request = relationship('PullRequest')
4350
4351 def __repr__(self):
4352 if self.pull_request_version_id:
4353 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4354 else:
4355 return '<DB:PullRequestVersion at %#x>' % id(self)
4356
4357 @property
4358 def reviewers(self):
4359 return self.pull_request.reviewers
4360
4361 @property
4362 def versions(self):
4363 return self.pull_request.versions
4364
4365 def is_closed(self):
4366 # calculate from original
4367 return self.pull_request.status == self.STATUS_CLOSED
4368
4369 def is_state_changing(self):
4370 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4371
4372 def calculated_review_status(self):
4373 return self.pull_request.calculated_review_status()
4374
4375 def reviewers_statuses(self):
4376 return self.pull_request.reviewers_statuses()
4377
4378
4379 class PullRequestReviewers(Base, BaseModel):
4380 __tablename__ = 'pull_request_reviewers'
4381 __table_args__ = (
4382 base_table_args,
4383 )
4384
4385 @hybrid_property
4386 def reasons(self):
4387 if not self._reasons:
4388 return []
4389 return self._reasons
4390
4391 @reasons.setter
4392 def reasons(self, val):
4393 val = val or []
4394 if any(not isinstance(x, compat.string_types) for x in val):
4395 raise Exception('invalid reasons type, must be list of strings')
4396 self._reasons = val
4397
4398 pull_requests_reviewers_id = Column(
4399 'pull_requests_reviewers_id', Integer(), nullable=False,
4400 primary_key=True)
4401 pull_request_id = Column(
4402 "pull_request_id", Integer(),
4403 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4404 user_id = Column(
4405 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4406 _reasons = Column(
4407 'reason', MutationList.as_mutable(
4408 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4409
4410 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4411 user = relationship('User')
4412 pull_request = relationship('PullRequest')
4413
4414 rule_data = Column(
4415 'rule_data_json',
4416 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4417
4418 def rule_user_group_data(self):
4419 """
4420 Returns the voting user group rule data for this reviewer
4421 """
4422
4423 if self.rule_data and 'vote_rule' in self.rule_data:
4424 user_group_data = {}
4425 if 'rule_user_group_entry_id' in self.rule_data:
4426 # means a group with voting rules !
4427 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4428 user_group_data['name'] = self.rule_data['rule_name']
4429 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4430
4431 return user_group_data
4432
4433 def __unicode__(self):
4434 return u"<%s('id:%s')>" % (self.__class__.__name__,
4435 self.pull_requests_reviewers_id)
4436
4437
4438 class Notification(Base, BaseModel):
4439 __tablename__ = 'notifications'
4440 __table_args__ = (
4441 Index('notification_type_idx', 'type'),
4442 base_table_args,
4443 )
4444
4445 TYPE_CHANGESET_COMMENT = u'cs_comment'
4446 TYPE_MESSAGE = u'message'
4447 TYPE_MENTION = u'mention'
4448 TYPE_REGISTRATION = u'registration'
4449 TYPE_PULL_REQUEST = u'pull_request'
4450 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4451 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4452
4453 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4454 subject = Column('subject', Unicode(512), nullable=True)
4455 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4456 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4457 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4458 type_ = Column('type', Unicode(255))
4459
4460 created_by_user = relationship('User')
4461 notifications_to_users = relationship('UserNotification', lazy='joined',
4462 cascade="all, delete-orphan")
4463
4464 @property
4465 def recipients(self):
4466 return [x.user for x in UserNotification.query()\
4467 .filter(UserNotification.notification == self)\
4468 .order_by(UserNotification.user_id.asc()).all()]
4469
4470 @classmethod
4471 def create(cls, created_by, subject, body, recipients, type_=None):
4472 if type_ is None:
4473 type_ = Notification.TYPE_MESSAGE
4474
4475 notification = cls()
4476 notification.created_by_user = created_by
4477 notification.subject = subject
4478 notification.body = body
4479 notification.type_ = type_
4480 notification.created_on = datetime.datetime.now()
4481
4482 # For each recipient link the created notification to his account
4483 for u in recipients:
4484 assoc = UserNotification()
4485 assoc.user_id = u.user_id
4486 assoc.notification = notification
4487
4488 # if created_by is inside recipients mark his notification
4489 # as read
4490 if u.user_id == created_by.user_id:
4491 assoc.read = True
4492 Session().add(assoc)
4493
4494 Session().add(notification)
4495
4496 return notification
4497
4498
4499 class UserNotification(Base, BaseModel):
4500 __tablename__ = 'user_to_notification'
4501 __table_args__ = (
4502 UniqueConstraint('user_id', 'notification_id'),
4503 base_table_args
4504 )
4505
4506 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4507 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4508 read = Column('read', Boolean, default=False)
4509 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4510
4511 user = relationship('User', lazy="joined")
4512 notification = relationship('Notification', lazy="joined",
4513 order_by=lambda: Notification.created_on.desc(),)
4514
4515 def mark_as_read(self):
4516 self.read = True
4517 Session().add(self)
4518
4519
4520 class UserNotice(Base, BaseModel):
4521 __tablename__ = 'user_notices'
4522 __table_args__ = (
4523 base_table_args
4524 )
4525
4526 NOTIFICATION_TYPE_MESSAGE = 'message'
4527 NOTIFICATION_TYPE_NOTICE = 'notice'
4528
4529 NOTIFICATION_LEVEL_INFO = 'info'
4530 NOTIFICATION_LEVEL_WARNING = 'warning'
4531 NOTIFICATION_LEVEL_ERROR = 'error'
4532
4533 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4534
4535 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4536 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4537
4538 notice_read = Column('notice_read', Boolean, default=False)
4539
4540 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4541 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4542
4543 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4544 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4545
4546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4547 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4548
4549
4550 class Gist(Base, BaseModel):
4551 __tablename__ = 'gists'
4552 __table_args__ = (
4553 Index('g_gist_access_id_idx', 'gist_access_id'),
4554 Index('g_created_on_idx', 'created_on'),
4555 base_table_args
4556 )
4557
4558 GIST_PUBLIC = u'public'
4559 GIST_PRIVATE = u'private'
4560 DEFAULT_FILENAME = u'gistfile1.txt'
4561
4562 ACL_LEVEL_PUBLIC = u'acl_public'
4563 ACL_LEVEL_PRIVATE = u'acl_private'
4564
4565 gist_id = Column('gist_id', Integer(), primary_key=True)
4566 gist_access_id = Column('gist_access_id', Unicode(250))
4567 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4568 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4569 gist_expires = Column('gist_expires', Float(53), nullable=False)
4570 gist_type = Column('gist_type', Unicode(128), nullable=False)
4571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4572 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4573 acl_level = Column('acl_level', Unicode(128), nullable=True)
4574
4575 owner = relationship('User')
4576
4577 def __repr__(self):
4578 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4579
4580 @hybrid_property
4581 def description_safe(self):
4582 from rhodecode.lib import helpers as h
4583 return h.escape(self.gist_description)
4584
4585 @classmethod
4586 def get_or_404(cls, id_):
4587 from pyramid.httpexceptions import HTTPNotFound
4588
4589 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4590 if not res:
4591 raise HTTPNotFound()
4592 return res
4593
4594 @classmethod
4595 def get_by_access_id(cls, gist_access_id):
4596 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4597
4598 def gist_url(self):
4599 from rhodecode.model.gist import GistModel
4600 return GistModel().get_url(self)
4601
4602 @classmethod
4603 def base_path(cls):
4604 """
4605 Returns base path when all gists are stored
4606
4607 :param cls:
4608 """
4609 from rhodecode.model.gist import GIST_STORE_LOC
4610 q = Session().query(RhodeCodeUi)\
4611 .filter(RhodeCodeUi.ui_key == URL_SEP)
4612 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4613 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4614
4615 def get_api_data(self):
4616 """
4617 Common function for generating gist related data for API
4618 """
4619 gist = self
4620 data = {
4621 'gist_id': gist.gist_id,
4622 'type': gist.gist_type,
4623 'access_id': gist.gist_access_id,
4624 'description': gist.gist_description,
4625 'url': gist.gist_url(),
4626 'expires': gist.gist_expires,
4627 'created_on': gist.created_on,
4628 'modified_at': gist.modified_at,
4629 'content': None,
4630 'acl_level': gist.acl_level,
4631 }
4632 return data
4633
4634 def __json__(self):
4635 data = dict(
4636 )
4637 data.update(self.get_api_data())
4638 return data
4639 # SCM functions
4640
4641 def scm_instance(self, **kwargs):
4642 """
4643 Get an instance of VCS Repository
4644
4645 :param kwargs:
4646 """
4647 from rhodecode.model.gist import GistModel
4648 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4649 return get_vcs_instance(
4650 repo_path=safe_str(full_repo_path), create=False,
4651 _vcs_alias=GistModel.vcs_backend)
4652
4653
4654 class ExternalIdentity(Base, BaseModel):
4655 __tablename__ = 'external_identities'
4656 __table_args__ = (
4657 Index('local_user_id_idx', 'local_user_id'),
4658 Index('external_id_idx', 'external_id'),
4659 base_table_args
4660 )
4661
4662 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4663 external_username = Column('external_username', Unicode(1024), default=u'')
4664 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4665 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4666 access_token = Column('access_token', String(1024), default=u'')
4667 alt_token = Column('alt_token', String(1024), default=u'')
4668 token_secret = Column('token_secret', String(1024), default=u'')
4669
4670 @classmethod
4671 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4672 """
4673 Returns ExternalIdentity instance based on search params
4674
4675 :param external_id:
4676 :param provider_name:
4677 :return: ExternalIdentity
4678 """
4679 query = cls.query()
4680 query = query.filter(cls.external_id == external_id)
4681 query = query.filter(cls.provider_name == provider_name)
4682 if local_user_id:
4683 query = query.filter(cls.local_user_id == local_user_id)
4684 return query.first()
4685
4686 @classmethod
4687 def user_by_external_id_and_provider(cls, external_id, provider_name):
4688 """
4689 Returns User instance based on search params
4690
4691 :param external_id:
4692 :param provider_name:
4693 :return: User
4694 """
4695 query = User.query()
4696 query = query.filter(cls.external_id == external_id)
4697 query = query.filter(cls.provider_name == provider_name)
4698 query = query.filter(User.user_id == cls.local_user_id)
4699 return query.first()
4700
4701 @classmethod
4702 def by_local_user_id(cls, local_user_id):
4703 """
4704 Returns all tokens for user
4705
4706 :param local_user_id:
4707 :return: ExternalIdentity
4708 """
4709 query = cls.query()
4710 query = query.filter(cls.local_user_id == local_user_id)
4711 return query
4712
4713 @classmethod
4714 def load_provider_plugin(cls, plugin_id):
4715 from rhodecode.authentication.base import loadplugin
4716 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4717 auth_plugin = loadplugin(_plugin_id)
4718 return auth_plugin
4719
4720
4721 class Integration(Base, BaseModel):
4722 __tablename__ = 'integrations'
4723 __table_args__ = (
4724 base_table_args
4725 )
4726
4727 integration_id = Column('integration_id', Integer(), primary_key=True)
4728 integration_type = Column('integration_type', String(255))
4729 enabled = Column('enabled', Boolean(), nullable=False)
4730 name = Column('name', String(255), nullable=False)
4731 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4732 default=False)
4733
4734 settings = Column(
4735 'settings_json', MutationObj.as_mutable(
4736 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4737 repo_id = Column(
4738 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4739 nullable=True, unique=None, default=None)
4740 repo = relationship('Repository', lazy='joined')
4741
4742 repo_group_id = Column(
4743 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4744 nullable=True, unique=None, default=None)
4745 repo_group = relationship('RepoGroup', lazy='joined')
4746
4747 @property
4748 def scope(self):
4749 if self.repo:
4750 return repr(self.repo)
4751 if self.repo_group:
4752 if self.child_repos_only:
4753 return repr(self.repo_group) + ' (child repos only)'
4754 else:
4755 return repr(self.repo_group) + ' (recursive)'
4756 if self.child_repos_only:
4757 return 'root_repos'
4758 return 'global'
4759
4760 def __repr__(self):
4761 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4762
4763
4764 class RepoReviewRuleUser(Base, BaseModel):
4765 __tablename__ = 'repo_review_rules_users'
4766 __table_args__ = (
4767 base_table_args
4768 )
4769
4770 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4771 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4773 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4774 user = relationship('User')
4775
4776 def rule_data(self):
4777 return {
4778 'mandatory': self.mandatory
4779 }
4780
4781
4782 class RepoReviewRuleUserGroup(Base, BaseModel):
4783 __tablename__ = 'repo_review_rules_users_groups'
4784 __table_args__ = (
4785 base_table_args
4786 )
4787
4788 VOTE_RULE_ALL = -1
4789
4790 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4791 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4792 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4793 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4794 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4795 users_group = relationship('UserGroup')
4796
4797 def rule_data(self):
4798 return {
4799 'mandatory': self.mandatory,
4800 'vote_rule': self.vote_rule
4801 }
4802
4803 @property
4804 def vote_rule_label(self):
4805 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4806 return 'all must vote'
4807 else:
4808 return 'min. vote {}'.format(self.vote_rule)
4809
4810
4811 class RepoReviewRule(Base, BaseModel):
4812 __tablename__ = 'repo_review_rules'
4813 __table_args__ = (
4814 base_table_args
4815 )
4816
4817 repo_review_rule_id = Column(
4818 'repo_review_rule_id', Integer(), primary_key=True)
4819 repo_id = Column(
4820 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4821 repo = relationship('Repository', backref='review_rules')
4822
4823 review_rule_name = Column('review_rule_name', String(255))
4824 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4825 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4826 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4827
4828 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4829 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4830 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4831 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4832
4833 rule_users = relationship('RepoReviewRuleUser')
4834 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4835
4836 def _validate_pattern(self, value):
4837 re.compile('^' + glob2re(value) + '$')
4838
4839 @hybrid_property
4840 def source_branch_pattern(self):
4841 return self._branch_pattern or '*'
4842
4843 @source_branch_pattern.setter
4844 def source_branch_pattern(self, value):
4845 self._validate_pattern(value)
4846 self._branch_pattern = value or '*'
4847
4848 @hybrid_property
4849 def target_branch_pattern(self):
4850 return self._target_branch_pattern or '*'
4851
4852 @target_branch_pattern.setter
4853 def target_branch_pattern(self, value):
4854 self._validate_pattern(value)
4855 self._target_branch_pattern = value or '*'
4856
4857 @hybrid_property
4858 def file_pattern(self):
4859 return self._file_pattern or '*'
4860
4861 @file_pattern.setter
4862 def file_pattern(self, value):
4863 self._validate_pattern(value)
4864 self._file_pattern = value or '*'
4865
4866 def matches(self, source_branch, target_branch, files_changed):
4867 """
4868 Check if this review rule matches a branch/files in a pull request
4869
4870 :param source_branch: source branch name for the commit
4871 :param target_branch: target branch name for the commit
4872 :param files_changed: list of file paths changed in the pull request
4873 """
4874
4875 source_branch = source_branch or ''
4876 target_branch = target_branch or ''
4877 files_changed = files_changed or []
4878
4879 branch_matches = True
4880 if source_branch or target_branch:
4881 if self.source_branch_pattern == '*':
4882 source_branch_match = True
4883 else:
4884 if self.source_branch_pattern.startswith('re:'):
4885 source_pattern = self.source_branch_pattern[3:]
4886 else:
4887 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4888 source_branch_regex = re.compile(source_pattern)
4889 source_branch_match = bool(source_branch_regex.search(source_branch))
4890 if self.target_branch_pattern == '*':
4891 target_branch_match = True
4892 else:
4893 if self.target_branch_pattern.startswith('re:'):
4894 target_pattern = self.target_branch_pattern[3:]
4895 else:
4896 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4897 target_branch_regex = re.compile(target_pattern)
4898 target_branch_match = bool(target_branch_regex.search(target_branch))
4899
4900 branch_matches = source_branch_match and target_branch_match
4901
4902 files_matches = True
4903 if self.file_pattern != '*':
4904 files_matches = False
4905 if self.file_pattern.startswith('re:'):
4906 file_pattern = self.file_pattern[3:]
4907 else:
4908 file_pattern = glob2re(self.file_pattern)
4909 file_regex = re.compile(file_pattern)
4910 for filename in files_changed:
4911 if file_regex.search(filename):
4912 files_matches = True
4913 break
4914
4915 return branch_matches and files_matches
4916
4917 @property
4918 def review_users(self):
4919 """ Returns the users which this rule applies to """
4920
4921 users = collections.OrderedDict()
4922
4923 for rule_user in self.rule_users:
4924 if rule_user.user.active:
4925 if rule_user.user not in users:
4926 users[rule_user.user.username] = {
4927 'user': rule_user.user,
4928 'source': 'user',
4929 'source_data': {},
4930 'data': rule_user.rule_data()
4931 }
4932
4933 for rule_user_group in self.rule_user_groups:
4934 source_data = {
4935 'user_group_id': rule_user_group.users_group.users_group_id,
4936 'name': rule_user_group.users_group.users_group_name,
4937 'members': len(rule_user_group.users_group.members)
4938 }
4939 for member in rule_user_group.users_group.members:
4940 if member.user.active:
4941 key = member.user.username
4942 if key in users:
4943 # skip this member as we have him already
4944 # this prevents from override the "first" matched
4945 # users with duplicates in multiple groups
4946 continue
4947
4948 users[key] = {
4949 'user': member.user,
4950 'source': 'user_group',
4951 'source_data': source_data,
4952 'data': rule_user_group.rule_data()
4953 }
4954
4955 return users
4956
4957 def user_group_vote_rule(self, user_id):
4958
4959 rules = []
4960 if not self.rule_user_groups:
4961 return rules
4962
4963 for user_group in self.rule_user_groups:
4964 user_group_members = [x.user_id for x in user_group.users_group.members]
4965 if user_id in user_group_members:
4966 rules.append(user_group)
4967 return rules
4968
4969 def __repr__(self):
4970 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4971 self.repo_review_rule_id, self.repo)
4972
4973
4974 class ScheduleEntry(Base, BaseModel):
4975 __tablename__ = 'schedule_entries'
4976 __table_args__ = (
4977 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4978 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4979 base_table_args,
4980 )
4981
4982 schedule_types = ['crontab', 'timedelta', 'integer']
4983 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4984
4985 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4986 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4987 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4988
4989 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4990 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4991
4992 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4993 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4994
4995 # task
4996 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4997 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4998 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4999 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5000
5001 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5002 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5003
5004 @hybrid_property
5005 def schedule_type(self):
5006 return self._schedule_type
5007
5008 @schedule_type.setter
5009 def schedule_type(self, val):
5010 if val not in self.schedule_types:
5011 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5012 val, self.schedule_type))
5013
5014 self._schedule_type = val
5015
5016 @classmethod
5017 def get_uid(cls, obj):
5018 args = obj.task_args
5019 kwargs = obj.task_kwargs
5020 if isinstance(args, JsonRaw):
5021 try:
5022 args = json.loads(args)
5023 except ValueError:
5024 args = tuple()
5025
5026 if isinstance(kwargs, JsonRaw):
5027 try:
5028 kwargs = json.loads(kwargs)
5029 except ValueError:
5030 kwargs = dict()
5031
5032 dot_notation = obj.task_dot_notation
5033 val = '.'.join(map(safe_str, [
5034 sorted(dot_notation), args, sorted(kwargs.items())]))
5035 return hashlib.sha1(val).hexdigest()
5036
5037 @classmethod
5038 def get_by_schedule_name(cls, schedule_name):
5039 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5040
5041 @classmethod
5042 def get_by_schedule_id(cls, schedule_id):
5043 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5044
5045 @property
5046 def task(self):
5047 return self.task_dot_notation
5048
5049 @property
5050 def schedule(self):
5051 from rhodecode.lib.celerylib.utils import raw_2_schedule
5052 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5053 return schedule
5054
5055 @property
5056 def args(self):
5057 try:
5058 return list(self.task_args or [])
5059 except ValueError:
5060 return list()
5061
5062 @property
5063 def kwargs(self):
5064 try:
5065 return dict(self.task_kwargs or {})
5066 except ValueError:
5067 return dict()
5068
5069 def _as_raw(self, val):
5070 if hasattr(val, 'de_coerce'):
5071 val = val.de_coerce()
5072 if val:
5073 val = json.dumps(val)
5074
5075 return val
5076
5077 @property
5078 def schedule_definition_raw(self):
5079 return self._as_raw(self.schedule_definition)
5080
5081 @property
5082 def args_raw(self):
5083 return self._as_raw(self.task_args)
5084
5085 @property
5086 def kwargs_raw(self):
5087 return self._as_raw(self.task_kwargs)
5088
5089 def __repr__(self):
5090 return '<DB:ScheduleEntry({}:{})>'.format(
5091 self.schedule_entry_id, self.schedule_name)
5092
5093
5094 @event.listens_for(ScheduleEntry, 'before_update')
5095 def update_task_uid(mapper, connection, target):
5096 target.task_uid = ScheduleEntry.get_uid(target)
5097
5098
5099 @event.listens_for(ScheduleEntry, 'before_insert')
5100 def set_task_uid(mapper, connection, target):
5101 target.task_uid = ScheduleEntry.get_uid(target)
5102
5103
5104 class _BaseBranchPerms(BaseModel):
5105 @classmethod
5106 def compute_hash(cls, value):
5107 return sha1_safe(value)
5108
5109 @hybrid_property
5110 def branch_pattern(self):
5111 return self._branch_pattern or '*'
5112
5113 @hybrid_property
5114 def branch_hash(self):
5115 return self._branch_hash
5116
5117 def _validate_glob(self, value):
5118 re.compile('^' + glob2re(value) + '$')
5119
5120 @branch_pattern.setter
5121 def branch_pattern(self, value):
5122 self._validate_glob(value)
5123 self._branch_pattern = value or '*'
5124 # set the Hash when setting the branch pattern
5125 self._branch_hash = self.compute_hash(self._branch_pattern)
5126
5127 def matches(self, branch):
5128 """
5129 Check if this the branch matches entry
5130
5131 :param branch: branch name for the commit
5132 """
5133
5134 branch = branch or ''
5135
5136 branch_matches = True
5137 if branch:
5138 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5139 branch_matches = bool(branch_regex.search(branch))
5140
5141 return branch_matches
5142
5143
5144 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5145 __tablename__ = 'user_to_repo_branch_permissions'
5146 __table_args__ = (
5147 base_table_args
5148 )
5149
5150 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5151
5152 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5153 repo = relationship('Repository', backref='user_branch_perms')
5154
5155 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5156 permission = relationship('Permission')
5157
5158 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5159 user_repo_to_perm = relationship('UserRepoToPerm')
5160
5161 rule_order = Column('rule_order', Integer(), nullable=False)
5162 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5163 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5164
5165 def __unicode__(self):
5166 return u'<UserBranchPermission(%s => %r)>' % (
5167 self.user_repo_to_perm, self.branch_pattern)
5168
5169
5170 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5171 __tablename__ = 'user_group_to_repo_branch_permissions'
5172 __table_args__ = (
5173 base_table_args
5174 )
5175
5176 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5177
5178 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5179 repo = relationship('Repository', backref='user_group_branch_perms')
5180
5181 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5182 permission = relationship('Permission')
5183
5184 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5185 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5186
5187 rule_order = Column('rule_order', Integer(), nullable=False)
5188 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5189 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5190
5191 def __unicode__(self):
5192 return u'<UserBranchPermission(%s => %r)>' % (
5193 self.user_group_repo_to_perm, self.branch_pattern)
5194
5195
5196 class UserBookmark(Base, BaseModel):
5197 __tablename__ = 'user_bookmarks'
5198 __table_args__ = (
5199 UniqueConstraint('user_id', 'bookmark_repo_id'),
5200 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5201 UniqueConstraint('user_id', 'bookmark_position'),
5202 base_table_args
5203 )
5204
5205 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5206 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5207 position = Column("bookmark_position", Integer(), nullable=False)
5208 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5209 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5210 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5211
5212 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5213 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5214
5215 user = relationship("User")
5216
5217 repository = relationship("Repository")
5218 repository_group = relationship("RepoGroup")
5219
5220 @classmethod
5221 def get_by_position_for_user(cls, position, user_id):
5222 return cls.query() \
5223 .filter(UserBookmark.user_id == user_id) \
5224 .filter(UserBookmark.position == position).scalar()
5225
5226 @classmethod
5227 def get_bookmarks_for_user(cls, user_id, cache=True):
5228 bookmarks = cls.query() \
5229 .filter(UserBookmark.user_id == user_id) \
5230 .options(joinedload(UserBookmark.repository)) \
5231 .options(joinedload(UserBookmark.repository_group)) \
5232 .order_by(UserBookmark.position.asc())
5233
5234 if cache:
5235 bookmarks = bookmarks.options(
5236 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5237 )
5238
5239 return bookmarks.all()
5240
5241 def __unicode__(self):
5242 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5243
5244
5245 class FileStore(Base, BaseModel):
5246 __tablename__ = 'file_store'
5247 __table_args__ = (
5248 base_table_args
5249 )
5250
5251 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5252 file_uid = Column('file_uid', String(1024), nullable=False)
5253 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5254 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5255 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5256
5257 # sha256 hash
5258 file_hash = Column('file_hash', String(512), nullable=False)
5259 file_size = Column('file_size', BigInteger(), nullable=False)
5260
5261 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5262 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5263 accessed_count = Column('accessed_count', Integer(), default=0)
5264
5265 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5266
5267 # if repo/repo_group reference is set, check for permissions
5268 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5269
5270 # hidden defines an attachment that should be hidden from showing in artifact listing
5271 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5272
5273 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5274 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5275
5276 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5277
5278 # scope limited to user, which requester have access to
5279 scope_user_id = Column(
5280 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5281 nullable=True, unique=None, default=None)
5282 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5283
5284 # scope limited to user group, which requester have access to
5285 scope_user_group_id = Column(
5286 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5287 nullable=True, unique=None, default=None)
5288 user_group = relationship('UserGroup', lazy='joined')
5289
5290 # scope limited to repo, which requester have access to
5291 scope_repo_id = Column(
5292 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5293 nullable=True, unique=None, default=None)
5294 repo = relationship('Repository', lazy='joined')
5295
5296 # scope limited to repo group, which requester have access to
5297 scope_repo_group_id = Column(
5298 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5299 nullable=True, unique=None, default=None)
5300 repo_group = relationship('RepoGroup', lazy='joined')
5301
5302 @classmethod
5303 def get_by_store_uid(cls, file_store_uid):
5304 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5305
5306 @classmethod
5307 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5308 file_description='', enabled=True, hidden=False, check_acl=True,
5309 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5310
5311 store_entry = FileStore()
5312 store_entry.file_uid = file_uid
5313 store_entry.file_display_name = file_display_name
5314 store_entry.file_org_name = filename
5315 store_entry.file_size = file_size
5316 store_entry.file_hash = file_hash
5317 store_entry.file_description = file_description
5318
5319 store_entry.check_acl = check_acl
5320 store_entry.enabled = enabled
5321 store_entry.hidden = hidden
5322
5323 store_entry.user_id = user_id
5324 store_entry.scope_user_id = scope_user_id
5325 store_entry.scope_repo_id = scope_repo_id
5326 store_entry.scope_repo_group_id = scope_repo_group_id
5327
5328 return store_entry
5329
5330 @classmethod
5331 def store_metadata(cls, file_store_id, args, commit=True):
5332 file_store = FileStore.get(file_store_id)
5333 if file_store is None:
5334 return
5335
5336 for section, key, value, value_type in args:
5337 has_key = FileStoreMetadata().query() \
5338 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5339 .filter(FileStoreMetadata.file_store_meta_section == section) \
5340 .filter(FileStoreMetadata.file_store_meta_key == key) \
5341 .scalar()
5342 if has_key:
5343 msg = 'key `{}` already defined under section `{}` for this file.'\
5344 .format(key, section)
5345 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5346
5347 # NOTE(marcink): raises ArtifactMetadataBadValueType
5348 FileStoreMetadata.valid_value_type(value_type)
5349
5350 meta_entry = FileStoreMetadata()
5351 meta_entry.file_store = file_store
5352 meta_entry.file_store_meta_section = section
5353 meta_entry.file_store_meta_key = key
5354 meta_entry.file_store_meta_value_type = value_type
5355 meta_entry.file_store_meta_value = value
5356
5357 Session().add(meta_entry)
5358
5359 try:
5360 if commit:
5361 Session().commit()
5362 except IntegrityError:
5363 Session().rollback()
5364 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5365
5366 @classmethod
5367 def bump_access_counter(cls, file_uid, commit=True):
5368 FileStore().query()\
5369 .filter(FileStore.file_uid == file_uid)\
5370 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5371 FileStore.accessed_on: datetime.datetime.now()})
5372 if commit:
5373 Session().commit()
5374
5375 def __json__(self):
5376 data = {
5377 'filename': self.file_display_name,
5378 'filename_org': self.file_org_name,
5379 'file_uid': self.file_uid,
5380 'description': self.file_description,
5381 'hidden': self.hidden,
5382 'size': self.file_size,
5383 'created_on': self.created_on,
5384 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5385 'downloaded_times': self.accessed_count,
5386 'sha256': self.file_hash,
5387 'metadata': self.file_metadata,
5388 }
5389
5390 return data
5391
5392 def __repr__(self):
5393 return '<FileStore({})>'.format(self.file_store_id)
5394
5395
5396 class FileStoreMetadata(Base, BaseModel):
5397 __tablename__ = 'file_store_metadata'
5398 __table_args__ = (
5399 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5400 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5401 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5402 base_table_args
5403 )
5404 SETTINGS_TYPES = {
5405 'str': safe_str,
5406 'int': safe_int,
5407 'unicode': safe_unicode,
5408 'bool': str2bool,
5409 'list': functools.partial(aslist, sep=',')
5410 }
5411
5412 file_store_meta_id = Column(
5413 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5414 primary_key=True)
5415 _file_store_meta_section = Column(
5416 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5417 nullable=True, unique=None, default=None)
5418 _file_store_meta_section_hash = Column(
5419 "file_store_meta_section_hash", String(255),
5420 nullable=True, unique=None, default=None)
5421 _file_store_meta_key = Column(
5422 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5423 nullable=True, unique=None, default=None)
5424 _file_store_meta_key_hash = Column(
5425 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5426 _file_store_meta_value = Column(
5427 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5428 nullable=True, unique=None, default=None)
5429 _file_store_meta_value_type = Column(
5430 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5431 default='unicode')
5432
5433 file_store_id = Column(
5434 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5435 nullable=True, unique=None, default=None)
5436
5437 file_store = relationship('FileStore', lazy='joined')
5438
5439 @classmethod
5440 def valid_value_type(cls, value):
5441 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5442 raise ArtifactMetadataBadValueType(
5443 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5444
5445 @hybrid_property
5446 def file_store_meta_section(self):
5447 return self._file_store_meta_section
5448
5449 @file_store_meta_section.setter
5450 def file_store_meta_section(self, value):
5451 self._file_store_meta_section = value
5452 self._file_store_meta_section_hash = _hash_key(value)
5453
5454 @hybrid_property
5455 def file_store_meta_key(self):
5456 return self._file_store_meta_key
5457
5458 @file_store_meta_key.setter
5459 def file_store_meta_key(self, value):
5460 self._file_store_meta_key = value
5461 self._file_store_meta_key_hash = _hash_key(value)
5462
5463 @hybrid_property
5464 def file_store_meta_value(self):
5465 val = self._file_store_meta_value
5466
5467 if self._file_store_meta_value_type:
5468 # e.g unicode.encrypted == unicode
5469 _type = self._file_store_meta_value_type.split('.')[0]
5470 # decode the encrypted value if it's encrypted field type
5471 if '.encrypted' in self._file_store_meta_value_type:
5472 cipher = EncryptedTextValue()
5473 val = safe_unicode(cipher.process_result_value(val, None))
5474 # do final type conversion
5475 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5476 val = converter(val)
5477
5478 return val
5479
5480 @file_store_meta_value.setter
5481 def file_store_meta_value(self, val):
5482 val = safe_unicode(val)
5483 # encode the encrypted value
5484 if '.encrypted' in self.file_store_meta_value_type:
5485 cipher = EncryptedTextValue()
5486 val = safe_unicode(cipher.process_bind_param(val, None))
5487 self._file_store_meta_value = val
5488
5489 @hybrid_property
5490 def file_store_meta_value_type(self):
5491 return self._file_store_meta_value_type
5492
5493 @file_store_meta_value_type.setter
5494 def file_store_meta_value_type(self, val):
5495 # e.g unicode.encrypted
5496 self.valid_value_type(val)
5497 self._file_store_meta_value_type = val
5498
5499 def __json__(self):
5500 data = {
5501 'artifact': self.file_store.file_uid,
5502 'section': self.file_store_meta_section,
5503 'key': self.file_store_meta_key,
5504 'value': self.file_store_meta_value,
5505 }
5506
5507 return data
5508
5509 def __repr__(self):
5510 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5511 self.file_store_meta_key, self.file_store_meta_value)
5512
5513
5514 class DbMigrateVersion(Base, BaseModel):
5515 __tablename__ = 'db_migrate_version'
5516 __table_args__ = (
5517 base_table_args,
5518 )
5519
5520 repository_id = Column('repository_id', String(250), primary_key=True)
5521 repository_path = Column('repository_path', Text)
5522 version = Column('version', Integer)
5523
5524 @classmethod
5525 def set_version(cls, version):
5526 """
5527 Helper for forcing a different version, usually for debugging purposes via ishell.
5528 """
5529 ver = DbMigrateVersion.query().first()
5530 ver.version = version
5531 Session().commit()
5532
5533
5534 class DbSession(Base, BaseModel):
5535 __tablename__ = 'db_session'
5536 __table_args__ = (
5537 base_table_args,
5538 )
5539
5540 def __repr__(self):
5541 return '<DB:DbSession({})>'.format(self.id)
5542
5543 id = Column('id', Integer())
5544 namespace = Column('namespace', String(255), primary_key=True)
5545 accessed = Column('accessed', DateTime, nullable=False)
5546 created = Column('created', DateTime, nullable=False)
5547 data = Column('data', PickleType, nullable=False)
@@ -0,0 +1,35 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from sqlalchemy import *
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8 from sqlalchemy import BigInteger
9
10 from rhodecode.lib.dbmigrate.versions import _reset_base
11 from rhodecode.model import init_model_encryption
12
13
14 log = logging.getLogger(__name__)
15
16
17 def upgrade(migrate_engine):
18 """
19 Upgrade operations go here.
20 Don't create your own engine; bind migrate_engine to your metadata
21 """
22 _reset_base(migrate_engine)
23 from rhodecode.lib.dbmigrate.schema import db_4_19_0_0 as db
24
25 init_model_encryption(db)
26 db.UserNotice().__table__.create()
27
28
29 def downgrade(migrate_engine):
30 meta = MetaData()
31 meta.bind = migrate_engine
32
33
34 def fixups(models, _SESSION):
35 pass
@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 os
21 import os
22 import sys
22 import sys
23 import platform
23 import platform
24
24
25 VERSION = tuple(open(os.path.join(
25 VERSION = tuple(open(os.path.join(
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
27
27
28 BACKENDS = {
28 BACKENDS = {
29 'hg': 'Mercurial repository',
29 'hg': 'Mercurial repository',
30 'git': 'Git repository',
30 'git': 'Git repository',
31 'svn': 'Subversion repository',
31 'svn': 'Subversion repository',
32 }
32 }
33
33
34 CELERY_ENABLED = False
34 CELERY_ENABLED = False
35 CELERY_EAGER = False
35 CELERY_EAGER = False
36
36
37 # link to config for pyramid
37 # link to config for pyramid
38 CONFIG = {}
38 CONFIG = {}
39
39
40 # Populated with the settings dictionary from application init in
40 # Populated with the settings dictionary from application init in
41 # rhodecode.conf.environment.load_pyramid_environment
41 # rhodecode.conf.environment.load_pyramid_environment
42 PYRAMID_SETTINGS = {}
42 PYRAMID_SETTINGS = {}
43
43
44 # Linked module for extensions
44 # Linked module for extensions
45 EXTENSIONS = {}
45 EXTENSIONS = {}
46
46
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 __dbversion__ = 104 # defines current db version for migrations
48 __dbversion__ = 105 # defines current db version for migrations
49 __platform__ = platform.system()
49 __platform__ = platform.system()
50 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
51 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
52 __url__ = 'https://code.rhodecode.com'
52 __url__ = 'https://code.rhodecode.com'
53
53
54 is_windows = __platform__ in ['Windows']
54 is_windows = __platform__ in ['Windows']
55 is_unix = not is_windows
55 is_unix = not is_windows
56 is_test = False
56 is_test = False
57 disable_error_handler = False
57 disable_error_handler = False
@@ -1,457 +1,462 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def admin_routes(config):
25 def admin_routes(config):
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29
30 config.add_route(
29 config.add_route(
31 name='admin_audit_logs',
30 name='admin_audit_logs',
32 pattern='/audit_logs')
31 pattern='/audit_logs')
33
32
34 config.add_route(
33 config.add_route(
35 name='admin_audit_log_entry',
34 name='admin_audit_log_entry',
36 pattern='/audit_logs/{audit_log_id}')
35 pattern='/audit_logs/{audit_log_id}')
37
36
38 config.add_route(
37 config.add_route(
39 name='pull_requests_global_0', # backward compat
38 name='pull_requests_global_0', # backward compat
40 pattern='/pull_requests/{pull_request_id:\d+}')
39 pattern='/pull_requests/{pull_request_id:\d+}')
41 config.add_route(
40 config.add_route(
42 name='pull_requests_global_1', # backward compat
41 name='pull_requests_global_1', # backward compat
43 pattern='/pull-requests/{pull_request_id:\d+}')
42 pattern='/pull-requests/{pull_request_id:\d+}')
44 config.add_route(
43 config.add_route(
45 name='pull_requests_global',
44 name='pull_requests_global',
46 pattern='/pull-request/{pull_request_id:\d+}')
45 pattern='/pull-request/{pull_request_id:\d+}')
47
46
48 config.add_route(
47 config.add_route(
49 name='admin_settings_open_source',
48 name='admin_settings_open_source',
50 pattern='/settings/open_source')
49 pattern='/settings/open_source')
51 config.add_route(
50 config.add_route(
52 name='admin_settings_vcs_svn_generate_cfg',
51 name='admin_settings_vcs_svn_generate_cfg',
53 pattern='/settings/vcs/svn_generate_cfg')
52 pattern='/settings/vcs/svn_generate_cfg')
54
53
55 config.add_route(
54 config.add_route(
56 name='admin_settings_system',
55 name='admin_settings_system',
57 pattern='/settings/system')
56 pattern='/settings/system')
58 config.add_route(
57 config.add_route(
59 name='admin_settings_system_update',
58 name='admin_settings_system_update',
60 pattern='/settings/system/updates')
59 pattern='/settings/system/updates')
61
60
62 config.add_route(
61 config.add_route(
63 name='admin_settings_exception_tracker',
62 name='admin_settings_exception_tracker',
64 pattern='/settings/exceptions')
63 pattern='/settings/exceptions')
65 config.add_route(
64 config.add_route(
66 name='admin_settings_exception_tracker_delete_all',
65 name='admin_settings_exception_tracker_delete_all',
67 pattern='/settings/exceptions/delete')
66 pattern='/settings/exceptions/delete')
68 config.add_route(
67 config.add_route(
69 name='admin_settings_exception_tracker_show',
68 name='admin_settings_exception_tracker_show',
70 pattern='/settings/exceptions/{exception_id}')
69 pattern='/settings/exceptions/{exception_id}')
71 config.add_route(
70 config.add_route(
72 name='admin_settings_exception_tracker_delete',
71 name='admin_settings_exception_tracker_delete',
73 pattern='/settings/exceptions/{exception_id}/delete')
72 pattern='/settings/exceptions/{exception_id}/delete')
74
73
75 config.add_route(
74 config.add_route(
76 name='admin_settings_sessions',
75 name='admin_settings_sessions',
77 pattern='/settings/sessions')
76 pattern='/settings/sessions')
78 config.add_route(
77 config.add_route(
79 name='admin_settings_sessions_cleanup',
78 name='admin_settings_sessions_cleanup',
80 pattern='/settings/sessions/cleanup')
79 pattern='/settings/sessions/cleanup')
81
80
82 config.add_route(
81 config.add_route(
83 name='admin_settings_process_management',
82 name='admin_settings_process_management',
84 pattern='/settings/process_management')
83 pattern='/settings/process_management')
85 config.add_route(
84 config.add_route(
86 name='admin_settings_process_management_data',
85 name='admin_settings_process_management_data',
87 pattern='/settings/process_management/data')
86 pattern='/settings/process_management/data')
88 config.add_route(
87 config.add_route(
89 name='admin_settings_process_management_signal',
88 name='admin_settings_process_management_signal',
90 pattern='/settings/process_management/signal')
89 pattern='/settings/process_management/signal')
91 config.add_route(
90 config.add_route(
92 name='admin_settings_process_management_master_signal',
91 name='admin_settings_process_management_master_signal',
93 pattern='/settings/process_management/master_signal')
92 pattern='/settings/process_management/master_signal')
94
93
95 # default settings
94 # default settings
96 config.add_route(
95 config.add_route(
97 name='admin_defaults_repositories',
96 name='admin_defaults_repositories',
98 pattern='/defaults/repositories')
97 pattern='/defaults/repositories')
99 config.add_route(
98 config.add_route(
100 name='admin_defaults_repositories_update',
99 name='admin_defaults_repositories_update',
101 pattern='/defaults/repositories/update')
100 pattern='/defaults/repositories/update')
102
101
103 # admin settings
102 # admin settings
104
103
105 config.add_route(
104 config.add_route(
106 name='admin_settings',
105 name='admin_settings',
107 pattern='/settings')
106 pattern='/settings')
108 config.add_route(
107 config.add_route(
109 name='admin_settings_update',
108 name='admin_settings_update',
110 pattern='/settings/update')
109 pattern='/settings/update')
111
110
112 config.add_route(
111 config.add_route(
113 name='admin_settings_global',
112 name='admin_settings_global',
114 pattern='/settings/global')
113 pattern='/settings/global')
115 config.add_route(
114 config.add_route(
116 name='admin_settings_global_update',
115 name='admin_settings_global_update',
117 pattern='/settings/global/update')
116 pattern='/settings/global/update')
118
117
119 config.add_route(
118 config.add_route(
120 name='admin_settings_vcs',
119 name='admin_settings_vcs',
121 pattern='/settings/vcs')
120 pattern='/settings/vcs')
122 config.add_route(
121 config.add_route(
123 name='admin_settings_vcs_update',
122 name='admin_settings_vcs_update',
124 pattern='/settings/vcs/update')
123 pattern='/settings/vcs/update')
125 config.add_route(
124 config.add_route(
126 name='admin_settings_vcs_svn_pattern_delete',
125 name='admin_settings_vcs_svn_pattern_delete',
127 pattern='/settings/vcs/svn_pattern_delete')
126 pattern='/settings/vcs/svn_pattern_delete')
128
127
129 config.add_route(
128 config.add_route(
130 name='admin_settings_mapping',
129 name='admin_settings_mapping',
131 pattern='/settings/mapping')
130 pattern='/settings/mapping')
132 config.add_route(
131 config.add_route(
133 name='admin_settings_mapping_update',
132 name='admin_settings_mapping_update',
134 pattern='/settings/mapping/update')
133 pattern='/settings/mapping/update')
135
134
136 config.add_route(
135 config.add_route(
137 name='admin_settings_visual',
136 name='admin_settings_visual',
138 pattern='/settings/visual')
137 pattern='/settings/visual')
139 config.add_route(
138 config.add_route(
140 name='admin_settings_visual_update',
139 name='admin_settings_visual_update',
141 pattern='/settings/visual/update')
140 pattern='/settings/visual/update')
142
141
143 config.add_route(
142 config.add_route(
144 name='admin_settings_issuetracker',
143 name='admin_settings_issuetracker',
145 pattern='/settings/issue-tracker')
144 pattern='/settings/issue-tracker')
146 config.add_route(
145 config.add_route(
147 name='admin_settings_issuetracker_update',
146 name='admin_settings_issuetracker_update',
148 pattern='/settings/issue-tracker/update')
147 pattern='/settings/issue-tracker/update')
149 config.add_route(
148 config.add_route(
150 name='admin_settings_issuetracker_test',
149 name='admin_settings_issuetracker_test',
151 pattern='/settings/issue-tracker/test')
150 pattern='/settings/issue-tracker/test')
152 config.add_route(
151 config.add_route(
153 name='admin_settings_issuetracker_delete',
152 name='admin_settings_issuetracker_delete',
154 pattern='/settings/issue-tracker/delete')
153 pattern='/settings/issue-tracker/delete')
155
154
156 config.add_route(
155 config.add_route(
157 name='admin_settings_email',
156 name='admin_settings_email',
158 pattern='/settings/email')
157 pattern='/settings/email')
159 config.add_route(
158 config.add_route(
160 name='admin_settings_email_update',
159 name='admin_settings_email_update',
161 pattern='/settings/email/update')
160 pattern='/settings/email/update')
162
161
163 config.add_route(
162 config.add_route(
164 name='admin_settings_hooks',
163 name='admin_settings_hooks',
165 pattern='/settings/hooks')
164 pattern='/settings/hooks')
166 config.add_route(
165 config.add_route(
167 name='admin_settings_hooks_update',
166 name='admin_settings_hooks_update',
168 pattern='/settings/hooks/update')
167 pattern='/settings/hooks/update')
169 config.add_route(
168 config.add_route(
170 name='admin_settings_hooks_delete',
169 name='admin_settings_hooks_delete',
171 pattern='/settings/hooks/delete')
170 pattern='/settings/hooks/delete')
172
171
173 config.add_route(
172 config.add_route(
174 name='admin_settings_search',
173 name='admin_settings_search',
175 pattern='/settings/search')
174 pattern='/settings/search')
176
175
177 config.add_route(
176 config.add_route(
178 name='admin_settings_labs',
177 name='admin_settings_labs',
179 pattern='/settings/labs')
178 pattern='/settings/labs')
180 config.add_route(
179 config.add_route(
181 name='admin_settings_labs_update',
180 name='admin_settings_labs_update',
182 pattern='/settings/labs/update')
181 pattern='/settings/labs/update')
183
182
184 # Automation EE feature
183 # Automation EE feature
185 config.add_route(
184 config.add_route(
186 'admin_settings_automation',
185 'admin_settings_automation',
187 pattern=ADMIN_PREFIX + '/settings/automation')
186 pattern=ADMIN_PREFIX + '/settings/automation')
188
187
189 # global permissions
188 # global permissions
190
189
191 config.add_route(
190 config.add_route(
192 name='admin_permissions_application',
191 name='admin_permissions_application',
193 pattern='/permissions/application')
192 pattern='/permissions/application')
194 config.add_route(
193 config.add_route(
195 name='admin_permissions_application_update',
194 name='admin_permissions_application_update',
196 pattern='/permissions/application/update')
195 pattern='/permissions/application/update')
197
196
198 config.add_route(
197 config.add_route(
199 name='admin_permissions_global',
198 name='admin_permissions_global',
200 pattern='/permissions/global')
199 pattern='/permissions/global')
201 config.add_route(
200 config.add_route(
202 name='admin_permissions_global_update',
201 name='admin_permissions_global_update',
203 pattern='/permissions/global/update')
202 pattern='/permissions/global/update')
204
203
205 config.add_route(
204 config.add_route(
206 name='admin_permissions_object',
205 name='admin_permissions_object',
207 pattern='/permissions/object')
206 pattern='/permissions/object')
208 config.add_route(
207 config.add_route(
209 name='admin_permissions_object_update',
208 name='admin_permissions_object_update',
210 pattern='/permissions/object/update')
209 pattern='/permissions/object/update')
211
210
212 # Branch perms EE feature
211 # Branch perms EE feature
213 config.add_route(
212 config.add_route(
214 name='admin_permissions_branch',
213 name='admin_permissions_branch',
215 pattern='/permissions/branch')
214 pattern='/permissions/branch')
216
215
217 config.add_route(
216 config.add_route(
218 name='admin_permissions_ips',
217 name='admin_permissions_ips',
219 pattern='/permissions/ips')
218 pattern='/permissions/ips')
220
219
221 config.add_route(
220 config.add_route(
222 name='admin_permissions_overview',
221 name='admin_permissions_overview',
223 pattern='/permissions/overview')
222 pattern='/permissions/overview')
224
223
225 config.add_route(
224 config.add_route(
226 name='admin_permissions_auth_token_access',
225 name='admin_permissions_auth_token_access',
227 pattern='/permissions/auth_token_access')
226 pattern='/permissions/auth_token_access')
228
227
229 config.add_route(
228 config.add_route(
230 name='admin_permissions_ssh_keys',
229 name='admin_permissions_ssh_keys',
231 pattern='/permissions/ssh_keys')
230 pattern='/permissions/ssh_keys')
232 config.add_route(
231 config.add_route(
233 name='admin_permissions_ssh_keys_data',
232 name='admin_permissions_ssh_keys_data',
234 pattern='/permissions/ssh_keys/data')
233 pattern='/permissions/ssh_keys/data')
235 config.add_route(
234 config.add_route(
236 name='admin_permissions_ssh_keys_update',
235 name='admin_permissions_ssh_keys_update',
237 pattern='/permissions/ssh_keys/update')
236 pattern='/permissions/ssh_keys/update')
238
237
239 # users admin
238 # users admin
240 config.add_route(
239 config.add_route(
241 name='users',
240 name='users',
242 pattern='/users')
241 pattern='/users')
243
242
244 config.add_route(
243 config.add_route(
245 name='users_data',
244 name='users_data',
246 pattern='/users_data')
245 pattern='/users_data')
247
246
248 config.add_route(
247 config.add_route(
249 name='users_create',
248 name='users_create',
250 pattern='/users/create')
249 pattern='/users/create')
251
250
252 config.add_route(
251 config.add_route(
253 name='users_new',
252 name='users_new',
254 pattern='/users/new')
253 pattern='/users/new')
255
254
256 # user management
255 # user management
257 config.add_route(
256 config.add_route(
258 name='user_edit',
257 name='user_edit',
259 pattern='/users/{user_id:\d+}/edit',
258 pattern='/users/{user_id:\d+}/edit',
260 user_route=True)
259 user_route=True)
261 config.add_route(
260 config.add_route(
262 name='user_edit_advanced',
261 name='user_edit_advanced',
263 pattern='/users/{user_id:\d+}/edit/advanced',
262 pattern='/users/{user_id:\d+}/edit/advanced',
264 user_route=True)
263 user_route=True)
265 config.add_route(
264 config.add_route(
266 name='user_edit_global_perms',
265 name='user_edit_global_perms',
267 pattern='/users/{user_id:\d+}/edit/global_permissions',
266 pattern='/users/{user_id:\d+}/edit/global_permissions',
268 user_route=True)
267 user_route=True)
269 config.add_route(
268 config.add_route(
270 name='user_edit_global_perms_update',
269 name='user_edit_global_perms_update',
271 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
270 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
272 user_route=True)
271 user_route=True)
273 config.add_route(
272 config.add_route(
274 name='user_update',
273 name='user_update',
275 pattern='/users/{user_id:\d+}/update',
274 pattern='/users/{user_id:\d+}/update',
276 user_route=True)
275 user_route=True)
277 config.add_route(
276 config.add_route(
278 name='user_delete',
277 name='user_delete',
279 pattern='/users/{user_id:\d+}/delete',
278 pattern='/users/{user_id:\d+}/delete',
280 user_route=True)
279 user_route=True)
281 config.add_route(
280 config.add_route(
282 name='user_enable_force_password_reset',
281 name='user_enable_force_password_reset',
283 pattern='/users/{user_id:\d+}/password_reset_enable',
282 pattern='/users/{user_id:\d+}/password_reset_enable',
284 user_route=True)
283 user_route=True)
285 config.add_route(
284 config.add_route(
286 name='user_disable_force_password_reset',
285 name='user_disable_force_password_reset',
287 pattern='/users/{user_id:\d+}/password_reset_disable',
286 pattern='/users/{user_id:\d+}/password_reset_disable',
288 user_route=True)
287 user_route=True)
289 config.add_route(
288 config.add_route(
290 name='user_create_personal_repo_group',
289 name='user_create_personal_repo_group',
291 pattern='/users/{user_id:\d+}/create_repo_group',
290 pattern='/users/{user_id:\d+}/create_repo_group',
292 user_route=True)
291 user_route=True)
293
292
293 # user notice
294 config.add_route(
295 name='user_notice_dismiss',
296 pattern='/users/{user_id:\d+}/notice_dismiss',
297 user_route=True)
298
294 # user auth tokens
299 # user auth tokens
295 config.add_route(
300 config.add_route(
296 name='edit_user_auth_tokens',
301 name='edit_user_auth_tokens',
297 pattern='/users/{user_id:\d+}/edit/auth_tokens',
302 pattern='/users/{user_id:\d+}/edit/auth_tokens',
298 user_route=True)
303 user_route=True)
299 config.add_route(
304 config.add_route(
300 name='edit_user_auth_tokens_add',
305 name='edit_user_auth_tokens_add',
301 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
302 user_route=True)
307 user_route=True)
303 config.add_route(
308 config.add_route(
304 name='edit_user_auth_tokens_delete',
309 name='edit_user_auth_tokens_delete',
305 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
310 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
306 user_route=True)
311 user_route=True)
307
312
308 # user ssh keys
313 # user ssh keys
309 config.add_route(
314 config.add_route(
310 name='edit_user_ssh_keys',
315 name='edit_user_ssh_keys',
311 pattern='/users/{user_id:\d+}/edit/ssh_keys',
316 pattern='/users/{user_id:\d+}/edit/ssh_keys',
312 user_route=True)
317 user_route=True)
313 config.add_route(
318 config.add_route(
314 name='edit_user_ssh_keys_generate_keypair',
319 name='edit_user_ssh_keys_generate_keypair',
315 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
320 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
316 user_route=True)
321 user_route=True)
317 config.add_route(
322 config.add_route(
318 name='edit_user_ssh_keys_add',
323 name='edit_user_ssh_keys_add',
319 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
324 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
320 user_route=True)
325 user_route=True)
321 config.add_route(
326 config.add_route(
322 name='edit_user_ssh_keys_delete',
327 name='edit_user_ssh_keys_delete',
323 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
328 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
324 user_route=True)
329 user_route=True)
325
330
326 # user emails
331 # user emails
327 config.add_route(
332 config.add_route(
328 name='edit_user_emails',
333 name='edit_user_emails',
329 pattern='/users/{user_id:\d+}/edit/emails',
334 pattern='/users/{user_id:\d+}/edit/emails',
330 user_route=True)
335 user_route=True)
331 config.add_route(
336 config.add_route(
332 name='edit_user_emails_add',
337 name='edit_user_emails_add',
333 pattern='/users/{user_id:\d+}/edit/emails/new',
338 pattern='/users/{user_id:\d+}/edit/emails/new',
334 user_route=True)
339 user_route=True)
335 config.add_route(
340 config.add_route(
336 name='edit_user_emails_delete',
341 name='edit_user_emails_delete',
337 pattern='/users/{user_id:\d+}/edit/emails/delete',
342 pattern='/users/{user_id:\d+}/edit/emails/delete',
338 user_route=True)
343 user_route=True)
339
344
340 # user IPs
345 # user IPs
341 config.add_route(
346 config.add_route(
342 name='edit_user_ips',
347 name='edit_user_ips',
343 pattern='/users/{user_id:\d+}/edit/ips',
348 pattern='/users/{user_id:\d+}/edit/ips',
344 user_route=True)
349 user_route=True)
345 config.add_route(
350 config.add_route(
346 name='edit_user_ips_add',
351 name='edit_user_ips_add',
347 pattern='/users/{user_id:\d+}/edit/ips/new',
352 pattern='/users/{user_id:\d+}/edit/ips/new',
348 user_route_with_default=True) # enabled for default user too
353 user_route_with_default=True) # enabled for default user too
349 config.add_route(
354 config.add_route(
350 name='edit_user_ips_delete',
355 name='edit_user_ips_delete',
351 pattern='/users/{user_id:\d+}/edit/ips/delete',
356 pattern='/users/{user_id:\d+}/edit/ips/delete',
352 user_route_with_default=True) # enabled for default user too
357 user_route_with_default=True) # enabled for default user too
353
358
354 # user perms
359 # user perms
355 config.add_route(
360 config.add_route(
356 name='edit_user_perms_summary',
361 name='edit_user_perms_summary',
357 pattern='/users/{user_id:\d+}/edit/permissions_summary',
362 pattern='/users/{user_id:\d+}/edit/permissions_summary',
358 user_route=True)
363 user_route=True)
359 config.add_route(
364 config.add_route(
360 name='edit_user_perms_summary_json',
365 name='edit_user_perms_summary_json',
361 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
366 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
362 user_route=True)
367 user_route=True)
363
368
364 # user user groups management
369 # user user groups management
365 config.add_route(
370 config.add_route(
366 name='edit_user_groups_management',
371 name='edit_user_groups_management',
367 pattern='/users/{user_id:\d+}/edit/groups_management',
372 pattern='/users/{user_id:\d+}/edit/groups_management',
368 user_route=True)
373 user_route=True)
369
374
370 config.add_route(
375 config.add_route(
371 name='edit_user_groups_management_updates',
376 name='edit_user_groups_management_updates',
372 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
377 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
373 user_route=True)
378 user_route=True)
374
379
375 # user audit logs
380 # user audit logs
376 config.add_route(
381 config.add_route(
377 name='edit_user_audit_logs',
382 name='edit_user_audit_logs',
378 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
383 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
379
384
380 config.add_route(
385 config.add_route(
381 name='edit_user_audit_logs_download',
386 name='edit_user_audit_logs_download',
382 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
387 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
383
388
384 # user caches
389 # user caches
385 config.add_route(
390 config.add_route(
386 name='edit_user_caches',
391 name='edit_user_caches',
387 pattern='/users/{user_id:\d+}/edit/caches',
392 pattern='/users/{user_id:\d+}/edit/caches',
388 user_route=True)
393 user_route=True)
389 config.add_route(
394 config.add_route(
390 name='edit_user_caches_update',
395 name='edit_user_caches_update',
391 pattern='/users/{user_id:\d+}/edit/caches/update',
396 pattern='/users/{user_id:\d+}/edit/caches/update',
392 user_route=True)
397 user_route=True)
393
398
394 # user-groups admin
399 # user-groups admin
395 config.add_route(
400 config.add_route(
396 name='user_groups',
401 name='user_groups',
397 pattern='/user_groups')
402 pattern='/user_groups')
398
403
399 config.add_route(
404 config.add_route(
400 name='user_groups_data',
405 name='user_groups_data',
401 pattern='/user_groups_data')
406 pattern='/user_groups_data')
402
407
403 config.add_route(
408 config.add_route(
404 name='user_groups_new',
409 name='user_groups_new',
405 pattern='/user_groups/new')
410 pattern='/user_groups/new')
406
411
407 config.add_route(
412 config.add_route(
408 name='user_groups_create',
413 name='user_groups_create',
409 pattern='/user_groups/create')
414 pattern='/user_groups/create')
410
415
411 # repos admin
416 # repos admin
412 config.add_route(
417 config.add_route(
413 name='repos',
418 name='repos',
414 pattern='/repos')
419 pattern='/repos')
415
420
416 config.add_route(
421 config.add_route(
417 name='repos_data',
422 name='repos_data',
418 pattern='/repos_data')
423 pattern='/repos_data')
419
424
420 config.add_route(
425 config.add_route(
421 name='repo_new',
426 name='repo_new',
422 pattern='/repos/new')
427 pattern='/repos/new')
423
428
424 config.add_route(
429 config.add_route(
425 name='repo_create',
430 name='repo_create',
426 pattern='/repos/create')
431 pattern='/repos/create')
427
432
428 # repo groups admin
433 # repo groups admin
429 config.add_route(
434 config.add_route(
430 name='repo_groups',
435 name='repo_groups',
431 pattern='/repo_groups')
436 pattern='/repo_groups')
432
437
433 config.add_route(
438 config.add_route(
434 name='repo_groups_data',
439 name='repo_groups_data',
435 pattern='/repo_groups_data')
440 pattern='/repo_groups_data')
436
441
437 config.add_route(
442 config.add_route(
438 name='repo_group_new',
443 name='repo_group_new',
439 pattern='/repo_group/new')
444 pattern='/repo_group/new')
440
445
441 config.add_route(
446 config.add_route(
442 name='repo_group_create',
447 name='repo_group_create',
443 pattern='/repo_group/create')
448 pattern='/repo_group/create')
444
449
445
450
446 def includeme(config):
451 def includeme(config):
447 from rhodecode.apps._base.navigation import includeme as nav_includeme
452 from rhodecode.apps._base.navigation import includeme as nav_includeme
448
453
449 # Create admin navigation registry and add it to the pyramid registry.
454 # Create admin navigation registry and add it to the pyramid registry.
450 nav_includeme(config)
455 nav_includeme(config)
451
456
452 # main admin routes
457 # main admin routes
453 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
458 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
454 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
459 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
455
460
456 # Scan module for configuration decorators.
461 # Scan module for configuration decorators.
457 config.scan('.views', ignore='.tests')
462 config.scan('.views', ignore='.tests')
@@ -1,1336 +1,1362 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true, UserNotice
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, DefaultUserException)
42 UserOwnsUserGroupsException, DefaultUserException)
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.helpers import SqlPage
47 from rhodecode.lib.helpers import SqlPage
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
52 UserExtraEmailForm, UserExtraIpForm)
52 UserExtraEmailForm, UserExtraIpForm)
53 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.permission import PermissionModel
54 from rhodecode.model.repo_group import RepoGroupModel
54 from rhodecode.model.repo_group import RepoGroupModel
55 from rhodecode.model.ssh_key import SshKeyModel
55 from rhodecode.model.ssh_key import SshKeyModel
56 from rhodecode.model.user import UserModel
56 from rhodecode.model.user import UserModel
57 from rhodecode.model.user_group import UserGroupModel
57 from rhodecode.model.user_group import UserGroupModel
58 from rhodecode.model.db import (
58 from rhodecode.model.db import (
59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
60 UserApiKeys, UserSshKeys, RepoGroup)
60 UserApiKeys, UserSshKeys, RepoGroup)
61 from rhodecode.model.meta import Session
61 from rhodecode.model.meta import Session
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 class AdminUsersView(BaseAppView, DataGridAppView):
66 class AdminUsersView(BaseAppView, DataGridAppView):
67
67
68 def load_default_context(self):
68 def load_default_context(self):
69 c = self._get_local_tmpl_context()
69 c = self._get_local_tmpl_context()
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @HasPermissionAllDecorator('hg.admin')
73 @HasPermissionAllDecorator('hg.admin')
74 @view_config(
74 @view_config(
75 route_name='users', request_method='GET',
75 route_name='users', request_method='GET',
76 renderer='rhodecode:templates/admin/users/users.mako')
76 renderer='rhodecode:templates/admin/users/users.mako')
77 def users_list(self):
77 def users_list(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 return self._get_template_context(c)
79 return self._get_template_context(c)
80
80
81 @LoginRequired()
81 @LoginRequired()
82 @HasPermissionAllDecorator('hg.admin')
82 @HasPermissionAllDecorator('hg.admin')
83 @view_config(
83 @view_config(
84 # renderer defined below
84 # renderer defined below
85 route_name='users_data', request_method='GET',
85 route_name='users_data', request_method='GET',
86 renderer='json_ext', xhr=True)
86 renderer='json_ext', xhr=True)
87 def users_list_data(self):
87 def users_list_data(self):
88 self.load_default_context()
88 self.load_default_context()
89 column_map = {
89 column_map = {
90 'first_name': 'name',
90 'first_name': 'name',
91 'last_name': 'lastname',
91 'last_name': 'lastname',
92 }
92 }
93 draw, start, limit = self._extract_chunk(self.request)
93 draw, start, limit = self._extract_chunk(self.request)
94 search_q, order_by, order_dir = self._extract_ordering(
94 search_q, order_by, order_dir = self._extract_ordering(
95 self.request, column_map=column_map)
95 self.request, column_map=column_map)
96 _render = self.request.get_partial_renderer(
96 _render = self.request.get_partial_renderer(
97 'rhodecode:templates/data_table/_dt_elements.mako')
97 'rhodecode:templates/data_table/_dt_elements.mako')
98
98
99 def user_actions(user_id, username):
99 def user_actions(user_id, username):
100 return _render("user_actions", user_id, username)
100 return _render("user_actions", user_id, username)
101
101
102 users_data_total_count = User.query()\
102 users_data_total_count = User.query()\
103 .filter(User.username != User.DEFAULT_USER) \
103 .filter(User.username != User.DEFAULT_USER) \
104 .count()
104 .count()
105
105
106 users_data_total_inactive_count = User.query()\
106 users_data_total_inactive_count = User.query()\
107 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.username != User.DEFAULT_USER) \
108 .filter(User.active != true())\
108 .filter(User.active != true())\
109 .count()
109 .count()
110
110
111 # json generate
111 # json generate
112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
113 base_inactive_q = base_q.filter(User.active != true())
113 base_inactive_q = base_q.filter(User.active != true())
114
114
115 if search_q:
115 if search_q:
116 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 base_q = base_q.filter(or_(
117 base_q = base_q.filter(or_(
118 User.username.ilike(like_expression),
118 User.username.ilike(like_expression),
119 User._email.ilike(like_expression),
119 User._email.ilike(like_expression),
120 User.name.ilike(like_expression),
120 User.name.ilike(like_expression),
121 User.lastname.ilike(like_expression),
121 User.lastname.ilike(like_expression),
122 ))
122 ))
123 base_inactive_q = base_q.filter(User.active != true())
123 base_inactive_q = base_q.filter(User.active != true())
124
124
125 users_data_total_filtered_count = base_q.count()
125 users_data_total_filtered_count = base_q.count()
126 users_data_total_filtered_inactive_count = base_inactive_q.count()
126 users_data_total_filtered_inactive_count = base_inactive_q.count()
127
127
128 sort_col = getattr(User, order_by, None)
128 sort_col = getattr(User, order_by, None)
129 if sort_col:
129 if sort_col:
130 if order_dir == 'asc':
130 if order_dir == 'asc':
131 # handle null values properly to order by NULL last
131 # handle null values properly to order by NULL last
132 if order_by in ['last_activity']:
132 if order_by in ['last_activity']:
133 sort_col = coalesce(sort_col, datetime.date.max)
133 sort_col = coalesce(sort_col, datetime.date.max)
134 sort_col = sort_col.asc()
134 sort_col = sort_col.asc()
135 else:
135 else:
136 # handle null values properly to order by NULL last
136 # handle null values properly to order by NULL last
137 if order_by in ['last_activity']:
137 if order_by in ['last_activity']:
138 sort_col = coalesce(sort_col, datetime.date.min)
138 sort_col = coalesce(sort_col, datetime.date.min)
139 sort_col = sort_col.desc()
139 sort_col = sort_col.desc()
140
140
141 base_q = base_q.order_by(sort_col)
141 base_q = base_q.order_by(sort_col)
142 base_q = base_q.offset(start).limit(limit)
142 base_q = base_q.offset(start).limit(limit)
143
143
144 users_list = base_q.all()
144 users_list = base_q.all()
145
145
146 users_data = []
146 users_data = []
147 for user in users_list:
147 for user in users_list:
148 users_data.append({
148 users_data.append({
149 "username": h.gravatar_with_user(self.request, user.username),
149 "username": h.gravatar_with_user(self.request, user.username),
150 "email": user.email,
150 "email": user.email,
151 "first_name": user.first_name,
151 "first_name": user.first_name,
152 "last_name": user.last_name,
152 "last_name": user.last_name,
153 "last_login": h.format_date(user.last_login),
153 "last_login": h.format_date(user.last_login),
154 "last_activity": h.format_date(user.last_activity),
154 "last_activity": h.format_date(user.last_activity),
155 "active": h.bool2icon(user.active),
155 "active": h.bool2icon(user.active),
156 "active_raw": user.active,
156 "active_raw": user.active,
157 "admin": h.bool2icon(user.admin),
157 "admin": h.bool2icon(user.admin),
158 "extern_type": user.extern_type,
158 "extern_type": user.extern_type,
159 "extern_name": user.extern_name,
159 "extern_name": user.extern_name,
160 "action": user_actions(user.user_id, user.username),
160 "action": user_actions(user.user_id, user.username),
161 })
161 })
162 data = ({
162 data = ({
163 'draw': draw,
163 'draw': draw,
164 'data': users_data,
164 'data': users_data,
165 'recordsTotal': users_data_total_count,
165 'recordsTotal': users_data_total_count,
166 'recordsFiltered': users_data_total_filtered_count,
166 'recordsFiltered': users_data_total_filtered_count,
167 'recordsTotalInactive': users_data_total_inactive_count,
167 'recordsTotalInactive': users_data_total_inactive_count,
168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
169 })
169 })
170
170
171 return data
171 return data
172
172
173 def _set_personal_repo_group_template_vars(self, c_obj):
173 def _set_personal_repo_group_template_vars(self, c_obj):
174 DummyUser = AttributeDict({
174 DummyUser = AttributeDict({
175 'username': '${username}',
175 'username': '${username}',
176 'user_id': '${user_id}',
176 'user_id': '${user_id}',
177 })
177 })
178 c_obj.default_create_repo_group = RepoGroupModel() \
178 c_obj.default_create_repo_group = RepoGroupModel() \
179 .get_default_create_personal_repo_group()
179 .get_default_create_personal_repo_group()
180 c_obj.personal_repo_group_name = RepoGroupModel() \
180 c_obj.personal_repo_group_name = RepoGroupModel() \
181 .get_personal_group_name(DummyUser)
181 .get_personal_group_name(DummyUser)
182
182
183 @LoginRequired()
183 @LoginRequired()
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 @view_config(
185 @view_config(
186 route_name='users_new', request_method='GET',
186 route_name='users_new', request_method='GET',
187 renderer='rhodecode:templates/admin/users/user_add.mako')
187 renderer='rhodecode:templates/admin/users/user_add.mako')
188 def users_new(self):
188 def users_new(self):
189 _ = self.request.translate
189 _ = self.request.translate
190 c = self.load_default_context()
190 c = self.load_default_context()
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
192 self._set_personal_repo_group_template_vars(c)
192 self._set_personal_repo_group_template_vars(c)
193 return self._get_template_context(c)
193 return self._get_template_context(c)
194
194
195 @LoginRequired()
195 @LoginRequired()
196 @HasPermissionAllDecorator('hg.admin')
196 @HasPermissionAllDecorator('hg.admin')
197 @CSRFRequired()
197 @CSRFRequired()
198 @view_config(
198 @view_config(
199 route_name='users_create', request_method='POST',
199 route_name='users_create', request_method='POST',
200 renderer='rhodecode:templates/admin/users/user_add.mako')
200 renderer='rhodecode:templates/admin/users/user_add.mako')
201 def users_create(self):
201 def users_create(self):
202 _ = self.request.translate
202 _ = self.request.translate
203 c = self.load_default_context()
203 c = self.load_default_context()
204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
205 user_model = UserModel()
205 user_model = UserModel()
206 user_form = UserForm(self.request.translate)()
206 user_form = UserForm(self.request.translate)()
207 try:
207 try:
208 form_result = user_form.to_python(dict(self.request.POST))
208 form_result = user_form.to_python(dict(self.request.POST))
209 user = user_model.create(form_result)
209 user = user_model.create(form_result)
210 Session().flush()
210 Session().flush()
211 creation_data = user.get_api_data()
211 creation_data = user.get_api_data()
212 username = form_result['username']
212 username = form_result['username']
213
213
214 audit_logger.store_web(
214 audit_logger.store_web(
215 'user.create', action_data={'data': creation_data},
215 'user.create', action_data={'data': creation_data},
216 user=c.rhodecode_user)
216 user=c.rhodecode_user)
217
217
218 user_link = h.link_to(
218 user_link = h.link_to(
219 h.escape(username),
219 h.escape(username),
220 h.route_path('user_edit', user_id=user.user_id))
220 h.route_path('user_edit', user_id=user.user_id))
221 h.flash(h.literal(_('Created user %(user_link)s')
221 h.flash(h.literal(_('Created user %(user_link)s')
222 % {'user_link': user_link}), category='success')
222 % {'user_link': user_link}), category='success')
223 Session().commit()
223 Session().commit()
224 except formencode.Invalid as errors:
224 except formencode.Invalid as errors:
225 self._set_personal_repo_group_template_vars(c)
225 self._set_personal_repo_group_template_vars(c)
226 data = render(
226 data = render(
227 'rhodecode:templates/admin/users/user_add.mako',
227 'rhodecode:templates/admin/users/user_add.mako',
228 self._get_template_context(c), self.request)
228 self._get_template_context(c), self.request)
229 html = formencode.htmlfill.render(
229 html = formencode.htmlfill.render(
230 data,
230 data,
231 defaults=errors.value,
231 defaults=errors.value,
232 errors=errors.error_dict or {},
232 errors=errors.error_dict or {},
233 prefix_error=False,
233 prefix_error=False,
234 encoding="UTF-8",
234 encoding="UTF-8",
235 force_defaults=False
235 force_defaults=False
236 )
236 )
237 return Response(html)
237 return Response(html)
238 except UserCreationError as e:
238 except UserCreationError as e:
239 h.flash(e, 'error')
239 h.flash(e, 'error')
240 except Exception:
240 except Exception:
241 log.exception("Exception creation of user")
241 log.exception("Exception creation of user")
242 h.flash(_('Error occurred during creation of user %s')
242 h.flash(_('Error occurred during creation of user %s')
243 % self.request.POST.get('username'), category='error')
243 % self.request.POST.get('username'), category='error')
244 raise HTTPFound(h.route_path('users'))
244 raise HTTPFound(h.route_path('users'))
245
245
246
246
247 class UsersView(UserAppView):
247 class UsersView(UserAppView):
248 ALLOW_SCOPED_TOKENS = False
248 ALLOW_SCOPED_TOKENS = False
249 """
249 """
250 This view has alternative version inside EE, if modified please take a look
250 This view has alternative version inside EE, if modified please take a look
251 in there as well.
251 in there as well.
252 """
252 """
253
253
254 def get_auth_plugins(self):
254 def get_auth_plugins(self):
255 valid_plugins = []
255 valid_plugins = []
256 authn_registry = get_authn_registry(self.request.registry)
256 authn_registry = get_authn_registry(self.request.registry)
257 for plugin in authn_registry.get_plugins_for_authentication():
257 for plugin in authn_registry.get_plugins_for_authentication():
258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
259 valid_plugins.append(plugin)
259 valid_plugins.append(plugin)
260 elif plugin.name == 'rhodecode':
260 elif plugin.name == 'rhodecode':
261 valid_plugins.append(plugin)
261 valid_plugins.append(plugin)
262
262
263 # extend our choices if user has set a bound plugin which isn't enabled at the
263 # extend our choices if user has set a bound plugin which isn't enabled at the
264 # moment
264 # moment
265 extern_type = self.db_user.extern_type
265 extern_type = self.db_user.extern_type
266 if extern_type not in [x.uid for x in valid_plugins]:
266 if extern_type not in [x.uid for x in valid_plugins]:
267 try:
267 try:
268 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 plugin = authn_registry.get_plugin_by_uid(extern_type)
269 if plugin:
269 if plugin:
270 valid_plugins.append(plugin)
270 valid_plugins.append(plugin)
271
271
272 except Exception:
272 except Exception:
273 log.exception(
273 log.exception(
274 'Could not extend user plugins with `{}`'.format(extern_type))
274 'Could not extend user plugins with `{}`'.format(extern_type))
275 return valid_plugins
275 return valid_plugins
276
276
277 def load_default_context(self):
277 def load_default_context(self):
278 req = self.request
278 req = self.request
279
279
280 c = self._get_local_tmpl_context()
280 c = self._get_local_tmpl_context()
281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
282 c.allowed_languages = [
282 c.allowed_languages = [
283 ('en', 'English (en)'),
283 ('en', 'English (en)'),
284 ('de', 'German (de)'),
284 ('de', 'German (de)'),
285 ('fr', 'French (fr)'),
285 ('fr', 'French (fr)'),
286 ('it', 'Italian (it)'),
286 ('it', 'Italian (it)'),
287 ('ja', 'Japanese (ja)'),
287 ('ja', 'Japanese (ja)'),
288 ('pl', 'Polish (pl)'),
288 ('pl', 'Polish (pl)'),
289 ('pt', 'Portuguese (pt)'),
289 ('pt', 'Portuguese (pt)'),
290 ('ru', 'Russian (ru)'),
290 ('ru', 'Russian (ru)'),
291 ('zh', 'Chinese (zh)'),
291 ('zh', 'Chinese (zh)'),
292 ]
292 ]
293
293
294 c.allowed_extern_types = [
294 c.allowed_extern_types = [
295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
296 ]
296 ]
297
297
298 c.available_permissions = req.registry.settings['available_permissions']
298 c.available_permissions = req.registry.settings['available_permissions']
299 PermissionModel().set_global_permission_choices(
299 PermissionModel().set_global_permission_choices(
300 c, gettext_translator=req.translate)
300 c, gettext_translator=req.translate)
301
301
302 return c
302 return c
303
303
304 @LoginRequired()
304 @LoginRequired()
305 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
306 @CSRFRequired()
306 @CSRFRequired()
307 @view_config(
307 @view_config(
308 route_name='user_update', request_method='POST',
308 route_name='user_update', request_method='POST',
309 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 renderer='rhodecode:templates/admin/users/user_edit.mako')
310 def user_update(self):
310 def user_update(self):
311 _ = self.request.translate
311 _ = self.request.translate
312 c = self.load_default_context()
312 c = self.load_default_context()
313
313
314 user_id = self.db_user_id
314 user_id = self.db_user_id
315 c.user = self.db_user
315 c.user = self.db_user
316
316
317 c.active = 'profile'
317 c.active = 'profile'
318 c.extern_type = c.user.extern_type
318 c.extern_type = c.user.extern_type
319 c.extern_name = c.user.extern_name
319 c.extern_name = c.user.extern_name
320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
321 available_languages = [x[0] for x in c.allowed_languages]
321 available_languages = [x[0] for x in c.allowed_languages]
322 _form = UserForm(self.request.translate, edit=True,
322 _form = UserForm(self.request.translate, edit=True,
323 available_languages=available_languages,
323 available_languages=available_languages,
324 old_data={'user_id': user_id,
324 old_data={'user_id': user_id,
325 'email': c.user.email})()
325 'email': c.user.email})()
326 form_result = {}
326 form_result = {}
327 old_values = c.user.get_api_data()
327 old_values = c.user.get_api_data()
328 try:
328 try:
329 form_result = _form.to_python(dict(self.request.POST))
329 form_result = _form.to_python(dict(self.request.POST))
330 skip_attrs = ['extern_name']
330 skip_attrs = ['extern_name']
331 # TODO: plugin should define if username can be updated
331 # TODO: plugin should define if username can be updated
332 if c.extern_type != "rhodecode":
332 if c.extern_type != "rhodecode":
333 # forbid updating username for external accounts
333 # forbid updating username for external accounts
334 skip_attrs.append('username')
334 skip_attrs.append('username')
335
335
336 UserModel().update_user(
336 UserModel().update_user(
337 user_id, skip_attrs=skip_attrs, **form_result)
337 user_id, skip_attrs=skip_attrs, **form_result)
338
338
339 audit_logger.store_web(
339 audit_logger.store_web(
340 'user.edit', action_data={'old_data': old_values},
340 'user.edit', action_data={'old_data': old_values},
341 user=c.rhodecode_user)
341 user=c.rhodecode_user)
342
342
343 Session().commit()
343 Session().commit()
344 h.flash(_('User updated successfully'), category='success')
344 h.flash(_('User updated successfully'), category='success')
345 except formencode.Invalid as errors:
345 except formencode.Invalid as errors:
346 data = render(
346 data = render(
347 'rhodecode:templates/admin/users/user_edit.mako',
347 'rhodecode:templates/admin/users/user_edit.mako',
348 self._get_template_context(c), self.request)
348 self._get_template_context(c), self.request)
349 html = formencode.htmlfill.render(
349 html = formencode.htmlfill.render(
350 data,
350 data,
351 defaults=errors.value,
351 defaults=errors.value,
352 errors=errors.error_dict or {},
352 errors=errors.error_dict or {},
353 prefix_error=False,
353 prefix_error=False,
354 encoding="UTF-8",
354 encoding="UTF-8",
355 force_defaults=False
355 force_defaults=False
356 )
356 )
357 return Response(html)
357 return Response(html)
358 except UserCreationError as e:
358 except UserCreationError as e:
359 h.flash(e, 'error')
359 h.flash(e, 'error')
360 except Exception:
360 except Exception:
361 log.exception("Exception updating user")
361 log.exception("Exception updating user")
362 h.flash(_('Error occurred during update of user %s')
362 h.flash(_('Error occurred during update of user %s')
363 % form_result.get('username'), category='error')
363 % form_result.get('username'), category='error')
364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
365
365
366 @LoginRequired()
366 @LoginRequired()
367 @HasPermissionAllDecorator('hg.admin')
367 @HasPermissionAllDecorator('hg.admin')
368 @CSRFRequired()
368 @CSRFRequired()
369 @view_config(
369 @view_config(
370 route_name='user_delete', request_method='POST',
370 route_name='user_delete', request_method='POST',
371 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 renderer='rhodecode:templates/admin/users/user_edit.mako')
372 def user_delete(self):
372 def user_delete(self):
373 _ = self.request.translate
373 _ = self.request.translate
374 c = self.load_default_context()
374 c = self.load_default_context()
375 c.user = self.db_user
375 c.user = self.db_user
376
376
377 _repos = c.user.repositories
377 _repos = c.user.repositories
378 _repo_groups = c.user.repository_groups
378 _repo_groups = c.user.repository_groups
379 _user_groups = c.user.user_groups
379 _user_groups = c.user.user_groups
380 _artifacts = c.user.artifacts
380 _artifacts = c.user.artifacts
381
381
382 handle_repos = None
382 handle_repos = None
383 handle_repo_groups = None
383 handle_repo_groups = None
384 handle_user_groups = None
384 handle_user_groups = None
385 handle_artifacts = None
385 handle_artifacts = None
386
386
387 # calls for flash of handle based on handle case detach or delete
387 # calls for flash of handle based on handle case detach or delete
388 def set_handle_flash_repos():
388 def set_handle_flash_repos():
389 handle = handle_repos
389 handle = handle_repos
390 if handle == 'detach':
390 if handle == 'detach':
391 h.flash(_('Detached %s repositories') % len(_repos),
391 h.flash(_('Detached %s repositories') % len(_repos),
392 category='success')
392 category='success')
393 elif handle == 'delete':
393 elif handle == 'delete':
394 h.flash(_('Deleted %s repositories') % len(_repos),
394 h.flash(_('Deleted %s repositories') % len(_repos),
395 category='success')
395 category='success')
396
396
397 def set_handle_flash_repo_groups():
397 def set_handle_flash_repo_groups():
398 handle = handle_repo_groups
398 handle = handle_repo_groups
399 if handle == 'detach':
399 if handle == 'detach':
400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
401 category='success')
401 category='success')
402 elif handle == 'delete':
402 elif handle == 'delete':
403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
404 category='success')
404 category='success')
405
405
406 def set_handle_flash_user_groups():
406 def set_handle_flash_user_groups():
407 handle = handle_user_groups
407 handle = handle_user_groups
408 if handle == 'detach':
408 if handle == 'detach':
409 h.flash(_('Detached %s user groups') % len(_user_groups),
409 h.flash(_('Detached %s user groups') % len(_user_groups),
410 category='success')
410 category='success')
411 elif handle == 'delete':
411 elif handle == 'delete':
412 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 h.flash(_('Deleted %s user groups') % len(_user_groups),
413 category='success')
413 category='success')
414
414
415 def set_handle_flash_artifacts():
415 def set_handle_flash_artifacts():
416 handle = handle_artifacts
416 handle = handle_artifacts
417 if handle == 'detach':
417 if handle == 'detach':
418 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 h.flash(_('Detached %s artifacts') % len(_artifacts),
419 category='success')
419 category='success')
420 elif handle == 'delete':
420 elif handle == 'delete':
421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
422 category='success')
422 category='success')
423
423
424 if _repos and self.request.POST.get('user_repos'):
424 if _repos and self.request.POST.get('user_repos'):
425 handle_repos = self.request.POST['user_repos']
425 handle_repos = self.request.POST['user_repos']
426
426
427 if _repo_groups and self.request.POST.get('user_repo_groups'):
427 if _repo_groups and self.request.POST.get('user_repo_groups'):
428 handle_repo_groups = self.request.POST['user_repo_groups']
428 handle_repo_groups = self.request.POST['user_repo_groups']
429
429
430 if _user_groups and self.request.POST.get('user_user_groups'):
430 if _user_groups and self.request.POST.get('user_user_groups'):
431 handle_user_groups = self.request.POST['user_user_groups']
431 handle_user_groups = self.request.POST['user_user_groups']
432
432
433 if _artifacts and self.request.POST.get('user_artifacts'):
433 if _artifacts and self.request.POST.get('user_artifacts'):
434 handle_artifacts = self.request.POST['user_artifacts']
434 handle_artifacts = self.request.POST['user_artifacts']
435
435
436 old_values = c.user.get_api_data()
436 old_values = c.user.get_api_data()
437
437
438 try:
438 try:
439 UserModel().delete(c.user, handle_repos=handle_repos,
439 UserModel().delete(c.user, handle_repos=handle_repos,
440 handle_repo_groups=handle_repo_groups,
440 handle_repo_groups=handle_repo_groups,
441 handle_user_groups=handle_user_groups,
441 handle_user_groups=handle_user_groups,
442 handle_artifacts=handle_artifacts)
442 handle_artifacts=handle_artifacts)
443
443
444 audit_logger.store_web(
444 audit_logger.store_web(
445 'user.delete', action_data={'old_data': old_values},
445 'user.delete', action_data={'old_data': old_values},
446 user=c.rhodecode_user)
446 user=c.rhodecode_user)
447
447
448 Session().commit()
448 Session().commit()
449 set_handle_flash_repos()
449 set_handle_flash_repos()
450 set_handle_flash_repo_groups()
450 set_handle_flash_repo_groups()
451 set_handle_flash_user_groups()
451 set_handle_flash_user_groups()
452 set_handle_flash_artifacts()
452 set_handle_flash_artifacts()
453 username = h.escape(old_values['username'])
453 username = h.escape(old_values['username'])
454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
456 UserOwnsUserGroupsException, DefaultUserException) as e:
456 UserOwnsUserGroupsException, DefaultUserException) as e:
457 h.flash(e, category='warning')
457 h.flash(e, category='warning')
458 except Exception:
458 except Exception:
459 log.exception("Exception during deletion of user")
459 log.exception("Exception during deletion of user")
460 h.flash(_('An error occurred during deletion of user'),
460 h.flash(_('An error occurred during deletion of user'),
461 category='error')
461 category='error')
462 raise HTTPFound(h.route_path('users'))
462 raise HTTPFound(h.route_path('users'))
463
463
464 @LoginRequired()
464 @LoginRequired()
465 @HasPermissionAllDecorator('hg.admin')
465 @HasPermissionAllDecorator('hg.admin')
466 @view_config(
466 @view_config(
467 route_name='user_edit', request_method='GET',
467 route_name='user_edit', request_method='GET',
468 renderer='rhodecode:templates/admin/users/user_edit.mako')
468 renderer='rhodecode:templates/admin/users/user_edit.mako')
469 def user_edit(self):
469 def user_edit(self):
470 _ = self.request.translate
470 _ = self.request.translate
471 c = self.load_default_context()
471 c = self.load_default_context()
472 c.user = self.db_user
472 c.user = self.db_user
473
473
474 c.active = 'profile'
474 c.active = 'profile'
475 c.extern_type = c.user.extern_type
475 c.extern_type = c.user.extern_type
476 c.extern_name = c.user.extern_name
476 c.extern_name = c.user.extern_name
477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
478
478
479 defaults = c.user.get_dict()
479 defaults = c.user.get_dict()
480 defaults.update({'language': c.user.user_data.get('language')})
480 defaults.update({'language': c.user.user_data.get('language')})
481
481
482 data = render(
482 data = render(
483 'rhodecode:templates/admin/users/user_edit.mako',
483 'rhodecode:templates/admin/users/user_edit.mako',
484 self._get_template_context(c), self.request)
484 self._get_template_context(c), self.request)
485 html = formencode.htmlfill.render(
485 html = formencode.htmlfill.render(
486 data,
486 data,
487 defaults=defaults,
487 defaults=defaults,
488 encoding="UTF-8",
488 encoding="UTF-8",
489 force_defaults=False
489 force_defaults=False
490 )
490 )
491 return Response(html)
491 return Response(html)
492
492
493 @LoginRequired()
493 @LoginRequired()
494 @HasPermissionAllDecorator('hg.admin')
494 @HasPermissionAllDecorator('hg.admin')
495 @view_config(
495 @view_config(
496 route_name='user_edit_advanced', request_method='GET',
496 route_name='user_edit_advanced', request_method='GET',
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
498 def user_edit_advanced(self):
498 def user_edit_advanced(self):
499 _ = self.request.translate
499 _ = self.request.translate
500 c = self.load_default_context()
500 c = self.load_default_context()
501
501
502 user_id = self.db_user_id
502 user_id = self.db_user_id
503 c.user = self.db_user
503 c.user = self.db_user
504
504
505 c.active = 'advanced'
505 c.active = 'advanced'
506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
507 c.personal_repo_group_name = RepoGroupModel()\
507 c.personal_repo_group_name = RepoGroupModel()\
508 .get_personal_group_name(c.user)
508 .get_personal_group_name(c.user)
509
509
510 c.user_to_review_rules = sorted(
510 c.user_to_review_rules = sorted(
511 (x.user for x in c.user.user_review_rules),
511 (x.user for x in c.user.user_review_rules),
512 key=lambda u: u.username.lower())
512 key=lambda u: u.username.lower())
513
513
514 c.first_admin = User.get_first_super_admin()
514 c.first_admin = User.get_first_super_admin()
515 defaults = c.user.get_dict()
515 defaults = c.user.get_dict()
516
516
517 # Interim workaround if the user participated on any pull requests as a
517 # Interim workaround if the user participated on any pull requests as a
518 # reviewer.
518 # reviewer.
519 has_review = len(c.user.reviewer_pull_requests)
519 has_review = len(c.user.reviewer_pull_requests)
520 c.can_delete_user = not has_review
520 c.can_delete_user = not has_review
521 c.can_delete_user_message = ''
521 c.can_delete_user_message = ''
522 inactive_link = h.link_to(
522 inactive_link = h.link_to(
523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
524 if has_review == 1:
524 if has_review == 1:
525 c.can_delete_user_message = h.literal(_(
525 c.can_delete_user_message = h.literal(_(
526 'The user participates as reviewer in {} pull request and '
526 'The user participates as reviewer in {} pull request and '
527 'cannot be deleted. \nYou can set the user to '
527 'cannot be deleted. \nYou can set the user to '
528 '"{}" instead of deleting it.').format(
528 '"{}" instead of deleting it.').format(
529 has_review, inactive_link))
529 has_review, inactive_link))
530 elif has_review:
530 elif has_review:
531 c.can_delete_user_message = h.literal(_(
531 c.can_delete_user_message = h.literal(_(
532 'The user participates as reviewer in {} pull requests and '
532 'The user participates as reviewer in {} pull requests and '
533 'cannot be deleted. \nYou can set the user to '
533 'cannot be deleted. \nYou can set the user to '
534 '"{}" instead of deleting it.').format(
534 '"{}" instead of deleting it.').format(
535 has_review, inactive_link))
535 has_review, inactive_link))
536
536
537 data = render(
537 data = render(
538 'rhodecode:templates/admin/users/user_edit.mako',
538 'rhodecode:templates/admin/users/user_edit.mako',
539 self._get_template_context(c), self.request)
539 self._get_template_context(c), self.request)
540 html = formencode.htmlfill.render(
540 html = formencode.htmlfill.render(
541 data,
541 data,
542 defaults=defaults,
542 defaults=defaults,
543 encoding="UTF-8",
543 encoding="UTF-8",
544 force_defaults=False
544 force_defaults=False
545 )
545 )
546 return Response(html)
546 return Response(html)
547
547
548 @LoginRequired()
548 @LoginRequired()
549 @HasPermissionAllDecorator('hg.admin')
549 @HasPermissionAllDecorator('hg.admin')
550 @view_config(
550 @view_config(
551 route_name='user_edit_global_perms', request_method='GET',
551 route_name='user_edit_global_perms', request_method='GET',
552 renderer='rhodecode:templates/admin/users/user_edit.mako')
552 renderer='rhodecode:templates/admin/users/user_edit.mako')
553 def user_edit_global_perms(self):
553 def user_edit_global_perms(self):
554 _ = self.request.translate
554 _ = self.request.translate
555 c = self.load_default_context()
555 c = self.load_default_context()
556 c.user = self.db_user
556 c.user = self.db_user
557
557
558 c.active = 'global_perms'
558 c.active = 'global_perms'
559
559
560 c.default_user = User.get_default_user()
560 c.default_user = User.get_default_user()
561 defaults = c.user.get_dict()
561 defaults = c.user.get_dict()
562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
563 defaults.update(c.default_user.get_default_perms())
563 defaults.update(c.default_user.get_default_perms())
564 defaults.update(c.user.get_default_perms())
564 defaults.update(c.user.get_default_perms())
565
565
566 data = render(
566 data = render(
567 'rhodecode:templates/admin/users/user_edit.mako',
567 'rhodecode:templates/admin/users/user_edit.mako',
568 self._get_template_context(c), self.request)
568 self._get_template_context(c), self.request)
569 html = formencode.htmlfill.render(
569 html = formencode.htmlfill.render(
570 data,
570 data,
571 defaults=defaults,
571 defaults=defaults,
572 encoding="UTF-8",
572 encoding="UTF-8",
573 force_defaults=False
573 force_defaults=False
574 )
574 )
575 return Response(html)
575 return Response(html)
576
576
577 @LoginRequired()
577 @LoginRequired()
578 @HasPermissionAllDecorator('hg.admin')
578 @HasPermissionAllDecorator('hg.admin')
579 @CSRFRequired()
579 @CSRFRequired()
580 @view_config(
580 @view_config(
581 route_name='user_edit_global_perms_update', request_method='POST',
581 route_name='user_edit_global_perms_update', request_method='POST',
582 renderer='rhodecode:templates/admin/users/user_edit.mako')
582 renderer='rhodecode:templates/admin/users/user_edit.mako')
583 def user_edit_global_perms_update(self):
583 def user_edit_global_perms_update(self):
584 _ = self.request.translate
584 _ = self.request.translate
585 c = self.load_default_context()
585 c = self.load_default_context()
586
586
587 user_id = self.db_user_id
587 user_id = self.db_user_id
588 c.user = self.db_user
588 c.user = self.db_user
589
589
590 c.active = 'global_perms'
590 c.active = 'global_perms'
591 try:
591 try:
592 # first stage that verifies the checkbox
592 # first stage that verifies the checkbox
593 _form = UserIndividualPermissionsForm(self.request.translate)
593 _form = UserIndividualPermissionsForm(self.request.translate)
594 form_result = _form.to_python(dict(self.request.POST))
594 form_result = _form.to_python(dict(self.request.POST))
595 inherit_perms = form_result['inherit_default_permissions']
595 inherit_perms = form_result['inherit_default_permissions']
596 c.user.inherit_default_permissions = inherit_perms
596 c.user.inherit_default_permissions = inherit_perms
597 Session().add(c.user)
597 Session().add(c.user)
598
598
599 if not inherit_perms:
599 if not inherit_perms:
600 # only update the individual ones if we un check the flag
600 # only update the individual ones if we un check the flag
601 _form = UserPermissionsForm(
601 _form = UserPermissionsForm(
602 self.request.translate,
602 self.request.translate,
603 [x[0] for x in c.repo_create_choices],
603 [x[0] for x in c.repo_create_choices],
604 [x[0] for x in c.repo_create_on_write_choices],
604 [x[0] for x in c.repo_create_on_write_choices],
605 [x[0] for x in c.repo_group_create_choices],
605 [x[0] for x in c.repo_group_create_choices],
606 [x[0] for x in c.user_group_create_choices],
606 [x[0] for x in c.user_group_create_choices],
607 [x[0] for x in c.fork_choices],
607 [x[0] for x in c.fork_choices],
608 [x[0] for x in c.inherit_default_permission_choices])()
608 [x[0] for x in c.inherit_default_permission_choices])()
609
609
610 form_result = _form.to_python(dict(self.request.POST))
610 form_result = _form.to_python(dict(self.request.POST))
611 form_result.update({'perm_user_id': c.user.user_id})
611 form_result.update({'perm_user_id': c.user.user_id})
612
612
613 PermissionModel().update_user_permissions(form_result)
613 PermissionModel().update_user_permissions(form_result)
614
614
615 # TODO(marcink): implement global permissions
615 # TODO(marcink): implement global permissions
616 # audit_log.store_web('user.edit.permissions')
616 # audit_log.store_web('user.edit.permissions')
617
617
618 Session().commit()
618 Session().commit()
619
619
620 h.flash(_('User global permissions updated successfully'),
620 h.flash(_('User global permissions updated successfully'),
621 category='success')
621 category='success')
622
622
623 except formencode.Invalid as errors:
623 except formencode.Invalid as errors:
624 data = render(
624 data = render(
625 'rhodecode:templates/admin/users/user_edit.mako',
625 'rhodecode:templates/admin/users/user_edit.mako',
626 self._get_template_context(c), self.request)
626 self._get_template_context(c), self.request)
627 html = formencode.htmlfill.render(
627 html = formencode.htmlfill.render(
628 data,
628 data,
629 defaults=errors.value,
629 defaults=errors.value,
630 errors=errors.error_dict or {},
630 errors=errors.error_dict or {},
631 prefix_error=False,
631 prefix_error=False,
632 encoding="UTF-8",
632 encoding="UTF-8",
633 force_defaults=False
633 force_defaults=False
634 )
634 )
635 return Response(html)
635 return Response(html)
636 except Exception:
636 except Exception:
637 log.exception("Exception during permissions saving")
637 log.exception("Exception during permissions saving")
638 h.flash(_('An error occurred during permissions saving'),
638 h.flash(_('An error occurred during permissions saving'),
639 category='error')
639 category='error')
640
640
641 affected_user_ids = [user_id]
641 affected_user_ids = [user_id]
642 PermissionModel().trigger_permission_flush(affected_user_ids)
642 PermissionModel().trigger_permission_flush(affected_user_ids)
643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
644
644
645 @LoginRequired()
645 @LoginRequired()
646 @HasPermissionAllDecorator('hg.admin')
646 @HasPermissionAllDecorator('hg.admin')
647 @CSRFRequired()
647 @CSRFRequired()
648 @view_config(
648 @view_config(
649 route_name='user_enable_force_password_reset', request_method='POST',
649 route_name='user_enable_force_password_reset', request_method='POST',
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
651 def user_enable_force_password_reset(self):
651 def user_enable_force_password_reset(self):
652 _ = self.request.translate
652 _ = self.request.translate
653 c = self.load_default_context()
653 c = self.load_default_context()
654
654
655 user_id = self.db_user_id
655 user_id = self.db_user_id
656 c.user = self.db_user
656 c.user = self.db_user
657
657
658 try:
658 try:
659 c.user.update_userdata(force_password_change=True)
659 c.user.update_userdata(force_password_change=True)
660
660
661 msg = _('Force password change enabled for user')
661 msg = _('Force password change enabled for user')
662 audit_logger.store_web('user.edit.password_reset.enabled',
662 audit_logger.store_web('user.edit.password_reset.enabled',
663 user=c.rhodecode_user)
663 user=c.rhodecode_user)
664
664
665 Session().commit()
665 Session().commit()
666 h.flash(msg, category='success')
666 h.flash(msg, category='success')
667 except Exception:
667 except Exception:
668 log.exception("Exception during password reset for user")
668 log.exception("Exception during password reset for user")
669 h.flash(_('An error occurred during password reset for user'),
669 h.flash(_('An error occurred during password reset for user'),
670 category='error')
670 category='error')
671
671
672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
673
673
674 @LoginRequired()
674 @LoginRequired()
675 @HasPermissionAllDecorator('hg.admin')
675 @HasPermissionAllDecorator('hg.admin')
676 @CSRFRequired()
676 @CSRFRequired()
677 @view_config(
677 @view_config(
678 route_name='user_disable_force_password_reset', request_method='POST',
678 route_name='user_disable_force_password_reset', request_method='POST',
679 renderer='rhodecode:templates/admin/users/user_edit.mako')
679 renderer='rhodecode:templates/admin/users/user_edit.mako')
680 def user_disable_force_password_reset(self):
680 def user_disable_force_password_reset(self):
681 _ = self.request.translate
681 _ = self.request.translate
682 c = self.load_default_context()
682 c = self.load_default_context()
683
683
684 user_id = self.db_user_id
684 user_id = self.db_user_id
685 c.user = self.db_user
685 c.user = self.db_user
686
686
687 try:
687 try:
688 c.user.update_userdata(force_password_change=False)
688 c.user.update_userdata(force_password_change=False)
689
689
690 msg = _('Force password change disabled for user')
690 msg = _('Force password change disabled for user')
691 audit_logger.store_web(
691 audit_logger.store_web(
692 'user.edit.password_reset.disabled',
692 'user.edit.password_reset.disabled',
693 user=c.rhodecode_user)
693 user=c.rhodecode_user)
694
694
695 Session().commit()
695 Session().commit()
696 h.flash(msg, category='success')
696 h.flash(msg, category='success')
697 except Exception:
697 except Exception:
698 log.exception("Exception during password reset for user")
698 log.exception("Exception during password reset for user")
699 h.flash(_('An error occurred during password reset for user'),
699 h.flash(_('An error occurred during password reset for user'),
700 category='error')
700 category='error')
701
701
702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
703
703
704 @LoginRequired()
704 @LoginRequired()
705 @HasPermissionAllDecorator('hg.admin')
705 @HasPermissionAllDecorator('hg.admin')
706 @CSRFRequired()
706 @CSRFRequired()
707 @view_config(
707 @view_config(
708 route_name='user_notice_dismiss', request_method='POST',
709 renderer='json_ext', xhr=True)
710 def user_notice_dismiss(self):
711 _ = self.request.translate
712 c = self.load_default_context()
713
714 user_id = self.db_user_id
715 c.user = self.db_user
716 user_notice_id = safe_int(self.request.POST.get('notice_id'))
717 notice = UserNotice().query()\
718 .filter(UserNotice.user_id == user_id)\
719 .filter(UserNotice.user_notice_id == user_notice_id)\
720 .scalar()
721 read = False
722 if notice:
723 notice.notice_read = True
724 Session().add(notice)
725 Session().commit()
726 read = True
727
728 return {'notice': user_notice_id, 'read': read}
729
730 @LoginRequired()
731 @HasPermissionAllDecorator('hg.admin')
732 @CSRFRequired()
733 @view_config(
708 route_name='user_create_personal_repo_group', request_method='POST',
734 route_name='user_create_personal_repo_group', request_method='POST',
709 renderer='rhodecode:templates/admin/users/user_edit.mako')
735 renderer='rhodecode:templates/admin/users/user_edit.mako')
710 def user_create_personal_repo_group(self):
736 def user_create_personal_repo_group(self):
711 """
737 """
712 Create personal repository group for this user
738 Create personal repository group for this user
713 """
739 """
714 from rhodecode.model.repo_group import RepoGroupModel
740 from rhodecode.model.repo_group import RepoGroupModel
715
741
716 _ = self.request.translate
742 _ = self.request.translate
717 c = self.load_default_context()
743 c = self.load_default_context()
718
744
719 user_id = self.db_user_id
745 user_id = self.db_user_id
720 c.user = self.db_user
746 c.user = self.db_user
721
747
722 personal_repo_group = RepoGroup.get_user_personal_repo_group(
748 personal_repo_group = RepoGroup.get_user_personal_repo_group(
723 c.user.user_id)
749 c.user.user_id)
724 if personal_repo_group:
750 if personal_repo_group:
725 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
751 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
726
752
727 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
753 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
728 named_personal_group = RepoGroup.get_by_group_name(
754 named_personal_group = RepoGroup.get_by_group_name(
729 personal_repo_group_name)
755 personal_repo_group_name)
730 try:
756 try:
731
757
732 if named_personal_group and named_personal_group.user_id == c.user.user_id:
758 if named_personal_group and named_personal_group.user_id == c.user.user_id:
733 # migrate the same named group, and mark it as personal
759 # migrate the same named group, and mark it as personal
734 named_personal_group.personal = True
760 named_personal_group.personal = True
735 Session().add(named_personal_group)
761 Session().add(named_personal_group)
736 Session().commit()
762 Session().commit()
737 msg = _('Linked repository group `%s` as personal' % (
763 msg = _('Linked repository group `%s` as personal' % (
738 personal_repo_group_name,))
764 personal_repo_group_name,))
739 h.flash(msg, category='success')
765 h.flash(msg, category='success')
740 elif not named_personal_group:
766 elif not named_personal_group:
741 RepoGroupModel().create_personal_repo_group(c.user)
767 RepoGroupModel().create_personal_repo_group(c.user)
742
768
743 msg = _('Created repository group `%s`' % (
769 msg = _('Created repository group `%s`' % (
744 personal_repo_group_name,))
770 personal_repo_group_name,))
745 h.flash(msg, category='success')
771 h.flash(msg, category='success')
746 else:
772 else:
747 msg = _('Repository group `%s` is already taken' % (
773 msg = _('Repository group `%s` is already taken' % (
748 personal_repo_group_name,))
774 personal_repo_group_name,))
749 h.flash(msg, category='warning')
775 h.flash(msg, category='warning')
750 except Exception:
776 except Exception:
751 log.exception("Exception during repository group creation")
777 log.exception("Exception during repository group creation")
752 msg = _(
778 msg = _(
753 'An error occurred during repository group creation for user')
779 'An error occurred during repository group creation for user')
754 h.flash(msg, category='error')
780 h.flash(msg, category='error')
755 Session().rollback()
781 Session().rollback()
756
782
757 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
783 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
758
784
759 @LoginRequired()
785 @LoginRequired()
760 @HasPermissionAllDecorator('hg.admin')
786 @HasPermissionAllDecorator('hg.admin')
761 @view_config(
787 @view_config(
762 route_name='edit_user_auth_tokens', request_method='GET',
788 route_name='edit_user_auth_tokens', request_method='GET',
763 renderer='rhodecode:templates/admin/users/user_edit.mako')
789 renderer='rhodecode:templates/admin/users/user_edit.mako')
764 def auth_tokens(self):
790 def auth_tokens(self):
765 _ = self.request.translate
791 _ = self.request.translate
766 c = self.load_default_context()
792 c = self.load_default_context()
767 c.user = self.db_user
793 c.user = self.db_user
768
794
769 c.active = 'auth_tokens'
795 c.active = 'auth_tokens'
770
796
771 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
797 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
772 c.role_values = [
798 c.role_values = [
773 (x, AuthTokenModel.cls._get_role_name(x))
799 (x, AuthTokenModel.cls._get_role_name(x))
774 for x in AuthTokenModel.cls.ROLES]
800 for x in AuthTokenModel.cls.ROLES]
775 c.role_options = [(c.role_values, _("Role"))]
801 c.role_options = [(c.role_values, _("Role"))]
776 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
802 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
777 c.user.user_id, show_expired=True)
803 c.user.user_id, show_expired=True)
778 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
804 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
779 return self._get_template_context(c)
805 return self._get_template_context(c)
780
806
781 def maybe_attach_token_scope(self, token):
807 def maybe_attach_token_scope(self, token):
782 # implemented in EE edition
808 # implemented in EE edition
783 pass
809 pass
784
810
785 @LoginRequired()
811 @LoginRequired()
786 @HasPermissionAllDecorator('hg.admin')
812 @HasPermissionAllDecorator('hg.admin')
787 @CSRFRequired()
813 @CSRFRequired()
788 @view_config(
814 @view_config(
789 route_name='edit_user_auth_tokens_add', request_method='POST')
815 route_name='edit_user_auth_tokens_add', request_method='POST')
790 def auth_tokens_add(self):
816 def auth_tokens_add(self):
791 _ = self.request.translate
817 _ = self.request.translate
792 c = self.load_default_context()
818 c = self.load_default_context()
793
819
794 user_id = self.db_user_id
820 user_id = self.db_user_id
795 c.user = self.db_user
821 c.user = self.db_user
796
822
797 user_data = c.user.get_api_data()
823 user_data = c.user.get_api_data()
798 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
824 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
799 description = self.request.POST.get('description')
825 description = self.request.POST.get('description')
800 role = self.request.POST.get('role')
826 role = self.request.POST.get('role')
801
827
802 token = UserModel().add_auth_token(
828 token = UserModel().add_auth_token(
803 user=c.user.user_id,
829 user=c.user.user_id,
804 lifetime_minutes=lifetime, role=role, description=description,
830 lifetime_minutes=lifetime, role=role, description=description,
805 scope_callback=self.maybe_attach_token_scope)
831 scope_callback=self.maybe_attach_token_scope)
806 token_data = token.get_api_data()
832 token_data = token.get_api_data()
807
833
808 audit_logger.store_web(
834 audit_logger.store_web(
809 'user.edit.token.add', action_data={
835 'user.edit.token.add', action_data={
810 'data': {'token': token_data, 'user': user_data}},
836 'data': {'token': token_data, 'user': user_data}},
811 user=self._rhodecode_user, )
837 user=self._rhodecode_user, )
812 Session().commit()
838 Session().commit()
813
839
814 h.flash(_("Auth token successfully created"), category='success')
840 h.flash(_("Auth token successfully created"), category='success')
815 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
841 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
816
842
817 @LoginRequired()
843 @LoginRequired()
818 @HasPermissionAllDecorator('hg.admin')
844 @HasPermissionAllDecorator('hg.admin')
819 @CSRFRequired()
845 @CSRFRequired()
820 @view_config(
846 @view_config(
821 route_name='edit_user_auth_tokens_delete', request_method='POST')
847 route_name='edit_user_auth_tokens_delete', request_method='POST')
822 def auth_tokens_delete(self):
848 def auth_tokens_delete(self):
823 _ = self.request.translate
849 _ = self.request.translate
824 c = self.load_default_context()
850 c = self.load_default_context()
825
851
826 user_id = self.db_user_id
852 user_id = self.db_user_id
827 c.user = self.db_user
853 c.user = self.db_user
828
854
829 user_data = c.user.get_api_data()
855 user_data = c.user.get_api_data()
830
856
831 del_auth_token = self.request.POST.get('del_auth_token')
857 del_auth_token = self.request.POST.get('del_auth_token')
832
858
833 if del_auth_token:
859 if del_auth_token:
834 token = UserApiKeys.get_or_404(del_auth_token)
860 token = UserApiKeys.get_or_404(del_auth_token)
835 token_data = token.get_api_data()
861 token_data = token.get_api_data()
836
862
837 AuthTokenModel().delete(del_auth_token, c.user.user_id)
863 AuthTokenModel().delete(del_auth_token, c.user.user_id)
838 audit_logger.store_web(
864 audit_logger.store_web(
839 'user.edit.token.delete', action_data={
865 'user.edit.token.delete', action_data={
840 'data': {'token': token_data, 'user': user_data}},
866 'data': {'token': token_data, 'user': user_data}},
841 user=self._rhodecode_user,)
867 user=self._rhodecode_user,)
842 Session().commit()
868 Session().commit()
843 h.flash(_("Auth token successfully deleted"), category='success')
869 h.flash(_("Auth token successfully deleted"), category='success')
844
870
845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
871 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
846
872
847 @LoginRequired()
873 @LoginRequired()
848 @HasPermissionAllDecorator('hg.admin')
874 @HasPermissionAllDecorator('hg.admin')
849 @view_config(
875 @view_config(
850 route_name='edit_user_ssh_keys', request_method='GET',
876 route_name='edit_user_ssh_keys', request_method='GET',
851 renderer='rhodecode:templates/admin/users/user_edit.mako')
877 renderer='rhodecode:templates/admin/users/user_edit.mako')
852 def ssh_keys(self):
878 def ssh_keys(self):
853 _ = self.request.translate
879 _ = self.request.translate
854 c = self.load_default_context()
880 c = self.load_default_context()
855 c.user = self.db_user
881 c.user = self.db_user
856
882
857 c.active = 'ssh_keys'
883 c.active = 'ssh_keys'
858 c.default_key = self.request.GET.get('default_key')
884 c.default_key = self.request.GET.get('default_key')
859 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
885 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
860 return self._get_template_context(c)
886 return self._get_template_context(c)
861
887
862 @LoginRequired()
888 @LoginRequired()
863 @HasPermissionAllDecorator('hg.admin')
889 @HasPermissionAllDecorator('hg.admin')
864 @view_config(
890 @view_config(
865 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
891 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
866 renderer='rhodecode:templates/admin/users/user_edit.mako')
892 renderer='rhodecode:templates/admin/users/user_edit.mako')
867 def ssh_keys_generate_keypair(self):
893 def ssh_keys_generate_keypair(self):
868 _ = self.request.translate
894 _ = self.request.translate
869 c = self.load_default_context()
895 c = self.load_default_context()
870
896
871 c.user = self.db_user
897 c.user = self.db_user
872
898
873 c.active = 'ssh_keys_generate'
899 c.active = 'ssh_keys_generate'
874 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
900 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
875 private_format = self.request.GET.get('private_format') \
901 private_format = self.request.GET.get('private_format') \
876 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
902 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
877 c.private, c.public = SshKeyModel().generate_keypair(
903 c.private, c.public = SshKeyModel().generate_keypair(
878 comment=comment, private_format=private_format)
904 comment=comment, private_format=private_format)
879
905
880 return self._get_template_context(c)
906 return self._get_template_context(c)
881
907
882 @LoginRequired()
908 @LoginRequired()
883 @HasPermissionAllDecorator('hg.admin')
909 @HasPermissionAllDecorator('hg.admin')
884 @CSRFRequired()
910 @CSRFRequired()
885 @view_config(
911 @view_config(
886 route_name='edit_user_ssh_keys_add', request_method='POST')
912 route_name='edit_user_ssh_keys_add', request_method='POST')
887 def ssh_keys_add(self):
913 def ssh_keys_add(self):
888 _ = self.request.translate
914 _ = self.request.translate
889 c = self.load_default_context()
915 c = self.load_default_context()
890
916
891 user_id = self.db_user_id
917 user_id = self.db_user_id
892 c.user = self.db_user
918 c.user = self.db_user
893
919
894 user_data = c.user.get_api_data()
920 user_data = c.user.get_api_data()
895 key_data = self.request.POST.get('key_data')
921 key_data = self.request.POST.get('key_data')
896 description = self.request.POST.get('description')
922 description = self.request.POST.get('description')
897
923
898 fingerprint = 'unknown'
924 fingerprint = 'unknown'
899 try:
925 try:
900 if not key_data:
926 if not key_data:
901 raise ValueError('Please add a valid public key')
927 raise ValueError('Please add a valid public key')
902
928
903 key = SshKeyModel().parse_key(key_data.strip())
929 key = SshKeyModel().parse_key(key_data.strip())
904 fingerprint = key.hash_md5()
930 fingerprint = key.hash_md5()
905
931
906 ssh_key = SshKeyModel().create(
932 ssh_key = SshKeyModel().create(
907 c.user.user_id, fingerprint, key.keydata, description)
933 c.user.user_id, fingerprint, key.keydata, description)
908 ssh_key_data = ssh_key.get_api_data()
934 ssh_key_data = ssh_key.get_api_data()
909
935
910 audit_logger.store_web(
936 audit_logger.store_web(
911 'user.edit.ssh_key.add', action_data={
937 'user.edit.ssh_key.add', action_data={
912 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
938 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
913 user=self._rhodecode_user, )
939 user=self._rhodecode_user, )
914 Session().commit()
940 Session().commit()
915
941
916 # Trigger an event on change of keys.
942 # Trigger an event on change of keys.
917 trigger(SshKeyFileChangeEvent(), self.request.registry)
943 trigger(SshKeyFileChangeEvent(), self.request.registry)
918
944
919 h.flash(_("Ssh Key successfully created"), category='success')
945 h.flash(_("Ssh Key successfully created"), category='success')
920
946
921 except IntegrityError:
947 except IntegrityError:
922 log.exception("Exception during ssh key saving")
948 log.exception("Exception during ssh key saving")
923 err = 'Such key with fingerprint `{}` already exists, ' \
949 err = 'Such key with fingerprint `{}` already exists, ' \
924 'please use a different one'.format(fingerprint)
950 'please use a different one'.format(fingerprint)
925 h.flash(_('An error occurred during ssh key saving: {}').format(err),
951 h.flash(_('An error occurred during ssh key saving: {}').format(err),
926 category='error')
952 category='error')
927 except Exception as e:
953 except Exception as e:
928 log.exception("Exception during ssh key saving")
954 log.exception("Exception during ssh key saving")
929 h.flash(_('An error occurred during ssh key saving: {}').format(e),
955 h.flash(_('An error occurred during ssh key saving: {}').format(e),
930 category='error')
956 category='error')
931
957
932 return HTTPFound(
958 return HTTPFound(
933 h.route_path('edit_user_ssh_keys', user_id=user_id))
959 h.route_path('edit_user_ssh_keys', user_id=user_id))
934
960
935 @LoginRequired()
961 @LoginRequired()
936 @HasPermissionAllDecorator('hg.admin')
962 @HasPermissionAllDecorator('hg.admin')
937 @CSRFRequired()
963 @CSRFRequired()
938 @view_config(
964 @view_config(
939 route_name='edit_user_ssh_keys_delete', request_method='POST')
965 route_name='edit_user_ssh_keys_delete', request_method='POST')
940 def ssh_keys_delete(self):
966 def ssh_keys_delete(self):
941 _ = self.request.translate
967 _ = self.request.translate
942 c = self.load_default_context()
968 c = self.load_default_context()
943
969
944 user_id = self.db_user_id
970 user_id = self.db_user_id
945 c.user = self.db_user
971 c.user = self.db_user
946
972
947 user_data = c.user.get_api_data()
973 user_data = c.user.get_api_data()
948
974
949 del_ssh_key = self.request.POST.get('del_ssh_key')
975 del_ssh_key = self.request.POST.get('del_ssh_key')
950
976
951 if del_ssh_key:
977 if del_ssh_key:
952 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
978 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
953 ssh_key_data = ssh_key.get_api_data()
979 ssh_key_data = ssh_key.get_api_data()
954
980
955 SshKeyModel().delete(del_ssh_key, c.user.user_id)
981 SshKeyModel().delete(del_ssh_key, c.user.user_id)
956 audit_logger.store_web(
982 audit_logger.store_web(
957 'user.edit.ssh_key.delete', action_data={
983 'user.edit.ssh_key.delete', action_data={
958 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
984 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
959 user=self._rhodecode_user,)
985 user=self._rhodecode_user,)
960 Session().commit()
986 Session().commit()
961 # Trigger an event on change of keys.
987 # Trigger an event on change of keys.
962 trigger(SshKeyFileChangeEvent(), self.request.registry)
988 trigger(SshKeyFileChangeEvent(), self.request.registry)
963 h.flash(_("Ssh key successfully deleted"), category='success')
989 h.flash(_("Ssh key successfully deleted"), category='success')
964
990
965 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
991 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
966
992
967 @LoginRequired()
993 @LoginRequired()
968 @HasPermissionAllDecorator('hg.admin')
994 @HasPermissionAllDecorator('hg.admin')
969 @view_config(
995 @view_config(
970 route_name='edit_user_emails', request_method='GET',
996 route_name='edit_user_emails', request_method='GET',
971 renderer='rhodecode:templates/admin/users/user_edit.mako')
997 renderer='rhodecode:templates/admin/users/user_edit.mako')
972 def emails(self):
998 def emails(self):
973 _ = self.request.translate
999 _ = self.request.translate
974 c = self.load_default_context()
1000 c = self.load_default_context()
975 c.user = self.db_user
1001 c.user = self.db_user
976
1002
977 c.active = 'emails'
1003 c.active = 'emails'
978 c.user_email_map = UserEmailMap.query() \
1004 c.user_email_map = UserEmailMap.query() \
979 .filter(UserEmailMap.user == c.user).all()
1005 .filter(UserEmailMap.user == c.user).all()
980
1006
981 return self._get_template_context(c)
1007 return self._get_template_context(c)
982
1008
983 @LoginRequired()
1009 @LoginRequired()
984 @HasPermissionAllDecorator('hg.admin')
1010 @HasPermissionAllDecorator('hg.admin')
985 @CSRFRequired()
1011 @CSRFRequired()
986 @view_config(
1012 @view_config(
987 route_name='edit_user_emails_add', request_method='POST')
1013 route_name='edit_user_emails_add', request_method='POST')
988 def emails_add(self):
1014 def emails_add(self):
989 _ = self.request.translate
1015 _ = self.request.translate
990 c = self.load_default_context()
1016 c = self.load_default_context()
991
1017
992 user_id = self.db_user_id
1018 user_id = self.db_user_id
993 c.user = self.db_user
1019 c.user = self.db_user
994
1020
995 email = self.request.POST.get('new_email')
1021 email = self.request.POST.get('new_email')
996 user_data = c.user.get_api_data()
1022 user_data = c.user.get_api_data()
997 try:
1023 try:
998
1024
999 form = UserExtraEmailForm(self.request.translate)()
1025 form = UserExtraEmailForm(self.request.translate)()
1000 data = form.to_python({'email': email})
1026 data = form.to_python({'email': email})
1001 email = data['email']
1027 email = data['email']
1002
1028
1003 UserModel().add_extra_email(c.user.user_id, email)
1029 UserModel().add_extra_email(c.user.user_id, email)
1004 audit_logger.store_web(
1030 audit_logger.store_web(
1005 'user.edit.email.add',
1031 'user.edit.email.add',
1006 action_data={'email': email, 'user': user_data},
1032 action_data={'email': email, 'user': user_data},
1007 user=self._rhodecode_user)
1033 user=self._rhodecode_user)
1008 Session().commit()
1034 Session().commit()
1009 h.flash(_("Added new email address `%s` for user account") % email,
1035 h.flash(_("Added new email address `%s` for user account") % email,
1010 category='success')
1036 category='success')
1011 except formencode.Invalid as error:
1037 except formencode.Invalid as error:
1012 h.flash(h.escape(error.error_dict['email']), category='error')
1038 h.flash(h.escape(error.error_dict['email']), category='error')
1013 except IntegrityError:
1039 except IntegrityError:
1014 log.warning("Email %s already exists", email)
1040 log.warning("Email %s already exists", email)
1015 h.flash(_('Email `{}` is already registered for another user.').format(email),
1041 h.flash(_('Email `{}` is already registered for another user.').format(email),
1016 category='error')
1042 category='error')
1017 except Exception:
1043 except Exception:
1018 log.exception("Exception during email saving")
1044 log.exception("Exception during email saving")
1019 h.flash(_('An error occurred during email saving'),
1045 h.flash(_('An error occurred during email saving'),
1020 category='error')
1046 category='error')
1021 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1047 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1022
1048
1023 @LoginRequired()
1049 @LoginRequired()
1024 @HasPermissionAllDecorator('hg.admin')
1050 @HasPermissionAllDecorator('hg.admin')
1025 @CSRFRequired()
1051 @CSRFRequired()
1026 @view_config(
1052 @view_config(
1027 route_name='edit_user_emails_delete', request_method='POST')
1053 route_name='edit_user_emails_delete', request_method='POST')
1028 def emails_delete(self):
1054 def emails_delete(self):
1029 _ = self.request.translate
1055 _ = self.request.translate
1030 c = self.load_default_context()
1056 c = self.load_default_context()
1031
1057
1032 user_id = self.db_user_id
1058 user_id = self.db_user_id
1033 c.user = self.db_user
1059 c.user = self.db_user
1034
1060
1035 email_id = self.request.POST.get('del_email_id')
1061 email_id = self.request.POST.get('del_email_id')
1036 user_model = UserModel()
1062 user_model = UserModel()
1037
1063
1038 email = UserEmailMap.query().get(email_id).email
1064 email = UserEmailMap.query().get(email_id).email
1039 user_data = c.user.get_api_data()
1065 user_data = c.user.get_api_data()
1040 user_model.delete_extra_email(c.user.user_id, email_id)
1066 user_model.delete_extra_email(c.user.user_id, email_id)
1041 audit_logger.store_web(
1067 audit_logger.store_web(
1042 'user.edit.email.delete',
1068 'user.edit.email.delete',
1043 action_data={'email': email, 'user': user_data},
1069 action_data={'email': email, 'user': user_data},
1044 user=self._rhodecode_user)
1070 user=self._rhodecode_user)
1045 Session().commit()
1071 Session().commit()
1046 h.flash(_("Removed email address from user account"),
1072 h.flash(_("Removed email address from user account"),
1047 category='success')
1073 category='success')
1048 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1074 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1049
1075
1050 @LoginRequired()
1076 @LoginRequired()
1051 @HasPermissionAllDecorator('hg.admin')
1077 @HasPermissionAllDecorator('hg.admin')
1052 @view_config(
1078 @view_config(
1053 route_name='edit_user_ips', request_method='GET',
1079 route_name='edit_user_ips', request_method='GET',
1054 renderer='rhodecode:templates/admin/users/user_edit.mako')
1080 renderer='rhodecode:templates/admin/users/user_edit.mako')
1055 def ips(self):
1081 def ips(self):
1056 _ = self.request.translate
1082 _ = self.request.translate
1057 c = self.load_default_context()
1083 c = self.load_default_context()
1058 c.user = self.db_user
1084 c.user = self.db_user
1059
1085
1060 c.active = 'ips'
1086 c.active = 'ips'
1061 c.user_ip_map = UserIpMap.query() \
1087 c.user_ip_map = UserIpMap.query() \
1062 .filter(UserIpMap.user == c.user).all()
1088 .filter(UserIpMap.user == c.user).all()
1063
1089
1064 c.inherit_default_ips = c.user.inherit_default_permissions
1090 c.inherit_default_ips = c.user.inherit_default_permissions
1065 c.default_user_ip_map = UserIpMap.query() \
1091 c.default_user_ip_map = UserIpMap.query() \
1066 .filter(UserIpMap.user == User.get_default_user()).all()
1092 .filter(UserIpMap.user == User.get_default_user()).all()
1067
1093
1068 return self._get_template_context(c)
1094 return self._get_template_context(c)
1069
1095
1070 @LoginRequired()
1096 @LoginRequired()
1071 @HasPermissionAllDecorator('hg.admin')
1097 @HasPermissionAllDecorator('hg.admin')
1072 @CSRFRequired()
1098 @CSRFRequired()
1073 @view_config(
1099 @view_config(
1074 route_name='edit_user_ips_add', request_method='POST')
1100 route_name='edit_user_ips_add', request_method='POST')
1075 # NOTE(marcink): this view is allowed for default users, as we can
1101 # NOTE(marcink): this view is allowed for default users, as we can
1076 # edit their IP white list
1102 # edit their IP white list
1077 def ips_add(self):
1103 def ips_add(self):
1078 _ = self.request.translate
1104 _ = self.request.translate
1079 c = self.load_default_context()
1105 c = self.load_default_context()
1080
1106
1081 user_id = self.db_user_id
1107 user_id = self.db_user_id
1082 c.user = self.db_user
1108 c.user = self.db_user
1083
1109
1084 user_model = UserModel()
1110 user_model = UserModel()
1085 desc = self.request.POST.get('description')
1111 desc = self.request.POST.get('description')
1086 try:
1112 try:
1087 ip_list = user_model.parse_ip_range(
1113 ip_list = user_model.parse_ip_range(
1088 self.request.POST.get('new_ip'))
1114 self.request.POST.get('new_ip'))
1089 except Exception as e:
1115 except Exception as e:
1090 ip_list = []
1116 ip_list = []
1091 log.exception("Exception during ip saving")
1117 log.exception("Exception during ip saving")
1092 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1118 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1093 category='error')
1119 category='error')
1094 added = []
1120 added = []
1095 user_data = c.user.get_api_data()
1121 user_data = c.user.get_api_data()
1096 for ip in ip_list:
1122 for ip in ip_list:
1097 try:
1123 try:
1098 form = UserExtraIpForm(self.request.translate)()
1124 form = UserExtraIpForm(self.request.translate)()
1099 data = form.to_python({'ip': ip})
1125 data = form.to_python({'ip': ip})
1100 ip = data['ip']
1126 ip = data['ip']
1101
1127
1102 user_model.add_extra_ip(c.user.user_id, ip, desc)
1128 user_model.add_extra_ip(c.user.user_id, ip, desc)
1103 audit_logger.store_web(
1129 audit_logger.store_web(
1104 'user.edit.ip.add',
1130 'user.edit.ip.add',
1105 action_data={'ip': ip, 'user': user_data},
1131 action_data={'ip': ip, 'user': user_data},
1106 user=self._rhodecode_user)
1132 user=self._rhodecode_user)
1107 Session().commit()
1133 Session().commit()
1108 added.append(ip)
1134 added.append(ip)
1109 except formencode.Invalid as error:
1135 except formencode.Invalid as error:
1110 msg = error.error_dict['ip']
1136 msg = error.error_dict['ip']
1111 h.flash(msg, category='error')
1137 h.flash(msg, category='error')
1112 except Exception:
1138 except Exception:
1113 log.exception("Exception during ip saving")
1139 log.exception("Exception during ip saving")
1114 h.flash(_('An error occurred during ip saving'),
1140 h.flash(_('An error occurred during ip saving'),
1115 category='error')
1141 category='error')
1116 if added:
1142 if added:
1117 h.flash(
1143 h.flash(
1118 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1144 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1119 category='success')
1145 category='success')
1120 if 'default_user' in self.request.POST:
1146 if 'default_user' in self.request.POST:
1121 # case for editing global IP list we do it for 'DEFAULT' user
1147 # case for editing global IP list we do it for 'DEFAULT' user
1122 raise HTTPFound(h.route_path('admin_permissions_ips'))
1148 raise HTTPFound(h.route_path('admin_permissions_ips'))
1123 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1149 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1124
1150
1125 @LoginRequired()
1151 @LoginRequired()
1126 @HasPermissionAllDecorator('hg.admin')
1152 @HasPermissionAllDecorator('hg.admin')
1127 @CSRFRequired()
1153 @CSRFRequired()
1128 @view_config(
1154 @view_config(
1129 route_name='edit_user_ips_delete', request_method='POST')
1155 route_name='edit_user_ips_delete', request_method='POST')
1130 # NOTE(marcink): this view is allowed for default users, as we can
1156 # NOTE(marcink): this view is allowed for default users, as we can
1131 # edit their IP white list
1157 # edit their IP white list
1132 def ips_delete(self):
1158 def ips_delete(self):
1133 _ = self.request.translate
1159 _ = self.request.translate
1134 c = self.load_default_context()
1160 c = self.load_default_context()
1135
1161
1136 user_id = self.db_user_id
1162 user_id = self.db_user_id
1137 c.user = self.db_user
1163 c.user = self.db_user
1138
1164
1139 ip_id = self.request.POST.get('del_ip_id')
1165 ip_id = self.request.POST.get('del_ip_id')
1140 user_model = UserModel()
1166 user_model = UserModel()
1141 user_data = c.user.get_api_data()
1167 user_data = c.user.get_api_data()
1142 ip = UserIpMap.query().get(ip_id).ip_addr
1168 ip = UserIpMap.query().get(ip_id).ip_addr
1143 user_model.delete_extra_ip(c.user.user_id, ip_id)
1169 user_model.delete_extra_ip(c.user.user_id, ip_id)
1144 audit_logger.store_web(
1170 audit_logger.store_web(
1145 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1171 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1146 user=self._rhodecode_user)
1172 user=self._rhodecode_user)
1147 Session().commit()
1173 Session().commit()
1148 h.flash(_("Removed ip address from user whitelist"), category='success')
1174 h.flash(_("Removed ip address from user whitelist"), category='success')
1149
1175
1150 if 'default_user' in self.request.POST:
1176 if 'default_user' in self.request.POST:
1151 # case for editing global IP list we do it for 'DEFAULT' user
1177 # case for editing global IP list we do it for 'DEFAULT' user
1152 raise HTTPFound(h.route_path('admin_permissions_ips'))
1178 raise HTTPFound(h.route_path('admin_permissions_ips'))
1153 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1179 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1154
1180
1155 @LoginRequired()
1181 @LoginRequired()
1156 @HasPermissionAllDecorator('hg.admin')
1182 @HasPermissionAllDecorator('hg.admin')
1157 @view_config(
1183 @view_config(
1158 route_name='edit_user_groups_management', request_method='GET',
1184 route_name='edit_user_groups_management', request_method='GET',
1159 renderer='rhodecode:templates/admin/users/user_edit.mako')
1185 renderer='rhodecode:templates/admin/users/user_edit.mako')
1160 def groups_management(self):
1186 def groups_management(self):
1161 c = self.load_default_context()
1187 c = self.load_default_context()
1162 c.user = self.db_user
1188 c.user = self.db_user
1163 c.data = c.user.group_member
1189 c.data = c.user.group_member
1164
1190
1165 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1191 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1166 for group in c.user.group_member]
1192 for group in c.user.group_member]
1167 c.groups = json.dumps(groups)
1193 c.groups = json.dumps(groups)
1168 c.active = 'groups'
1194 c.active = 'groups'
1169
1195
1170 return self._get_template_context(c)
1196 return self._get_template_context(c)
1171
1197
1172 @LoginRequired()
1198 @LoginRequired()
1173 @HasPermissionAllDecorator('hg.admin')
1199 @HasPermissionAllDecorator('hg.admin')
1174 @CSRFRequired()
1200 @CSRFRequired()
1175 @view_config(
1201 @view_config(
1176 route_name='edit_user_groups_management_updates', request_method='POST')
1202 route_name='edit_user_groups_management_updates', request_method='POST')
1177 def groups_management_updates(self):
1203 def groups_management_updates(self):
1178 _ = self.request.translate
1204 _ = self.request.translate
1179 c = self.load_default_context()
1205 c = self.load_default_context()
1180
1206
1181 user_id = self.db_user_id
1207 user_id = self.db_user_id
1182 c.user = self.db_user
1208 c.user = self.db_user
1183
1209
1184 user_groups = set(self.request.POST.getall('users_group_id'))
1210 user_groups = set(self.request.POST.getall('users_group_id'))
1185 user_groups_objects = []
1211 user_groups_objects = []
1186
1212
1187 for ugid in user_groups:
1213 for ugid in user_groups:
1188 user_groups_objects.append(
1214 user_groups_objects.append(
1189 UserGroupModel().get_group(safe_int(ugid)))
1215 UserGroupModel().get_group(safe_int(ugid)))
1190 user_group_model = UserGroupModel()
1216 user_group_model = UserGroupModel()
1191 added_to_groups, removed_from_groups = \
1217 added_to_groups, removed_from_groups = \
1192 user_group_model.change_groups(c.user, user_groups_objects)
1218 user_group_model.change_groups(c.user, user_groups_objects)
1193
1219
1194 user_data = c.user.get_api_data()
1220 user_data = c.user.get_api_data()
1195 for user_group_id in added_to_groups:
1221 for user_group_id in added_to_groups:
1196 user_group = UserGroup.get(user_group_id)
1222 user_group = UserGroup.get(user_group_id)
1197 old_values = user_group.get_api_data()
1223 old_values = user_group.get_api_data()
1198 audit_logger.store_web(
1224 audit_logger.store_web(
1199 'user_group.edit.member.add',
1225 'user_group.edit.member.add',
1200 action_data={'user': user_data, 'old_data': old_values},
1226 action_data={'user': user_data, 'old_data': old_values},
1201 user=self._rhodecode_user)
1227 user=self._rhodecode_user)
1202
1228
1203 for user_group_id in removed_from_groups:
1229 for user_group_id in removed_from_groups:
1204 user_group = UserGroup.get(user_group_id)
1230 user_group = UserGroup.get(user_group_id)
1205 old_values = user_group.get_api_data()
1231 old_values = user_group.get_api_data()
1206 audit_logger.store_web(
1232 audit_logger.store_web(
1207 'user_group.edit.member.delete',
1233 'user_group.edit.member.delete',
1208 action_data={'user': user_data, 'old_data': old_values},
1234 action_data={'user': user_data, 'old_data': old_values},
1209 user=self._rhodecode_user)
1235 user=self._rhodecode_user)
1210
1236
1211 Session().commit()
1237 Session().commit()
1212 c.active = 'user_groups_management'
1238 c.active = 'user_groups_management'
1213 h.flash(_("Groups successfully changed"), category='success')
1239 h.flash(_("Groups successfully changed"), category='success')
1214
1240
1215 return HTTPFound(h.route_path(
1241 return HTTPFound(h.route_path(
1216 'edit_user_groups_management', user_id=user_id))
1242 'edit_user_groups_management', user_id=user_id))
1217
1243
1218 @LoginRequired()
1244 @LoginRequired()
1219 @HasPermissionAllDecorator('hg.admin')
1245 @HasPermissionAllDecorator('hg.admin')
1220 @view_config(
1246 @view_config(
1221 route_name='edit_user_audit_logs', request_method='GET',
1247 route_name='edit_user_audit_logs', request_method='GET',
1222 renderer='rhodecode:templates/admin/users/user_edit.mako')
1248 renderer='rhodecode:templates/admin/users/user_edit.mako')
1223 def user_audit_logs(self):
1249 def user_audit_logs(self):
1224 _ = self.request.translate
1250 _ = self.request.translate
1225 c = self.load_default_context()
1251 c = self.load_default_context()
1226 c.user = self.db_user
1252 c.user = self.db_user
1227
1253
1228 c.active = 'audit'
1254 c.active = 'audit'
1229
1255
1230 p = safe_int(self.request.GET.get('page', 1), 1)
1256 p = safe_int(self.request.GET.get('page', 1), 1)
1231
1257
1232 filter_term = self.request.GET.get('filter')
1258 filter_term = self.request.GET.get('filter')
1233 user_log = UserModel().get_user_log(c.user, filter_term)
1259 user_log = UserModel().get_user_log(c.user, filter_term)
1234
1260
1235 def url_generator(page_num):
1261 def url_generator(page_num):
1236 query_params = {
1262 query_params = {
1237 'page': page_num
1263 'page': page_num
1238 }
1264 }
1239 if filter_term:
1265 if filter_term:
1240 query_params['filter'] = filter_term
1266 query_params['filter'] = filter_term
1241 return self.request.current_route_path(_query=query_params)
1267 return self.request.current_route_path(_query=query_params)
1242
1268
1243 c.audit_logs = SqlPage(
1269 c.audit_logs = SqlPage(
1244 user_log, page=p, items_per_page=10, url_maker=url_generator)
1270 user_log, page=p, items_per_page=10, url_maker=url_generator)
1245 c.filter_term = filter_term
1271 c.filter_term = filter_term
1246 return self._get_template_context(c)
1272 return self._get_template_context(c)
1247
1273
1248 @LoginRequired()
1274 @LoginRequired()
1249 @HasPermissionAllDecorator('hg.admin')
1275 @HasPermissionAllDecorator('hg.admin')
1250 @view_config(
1276 @view_config(
1251 route_name='edit_user_audit_logs_download', request_method='GET',
1277 route_name='edit_user_audit_logs_download', request_method='GET',
1252 renderer='string')
1278 renderer='string')
1253 def user_audit_logs_download(self):
1279 def user_audit_logs_download(self):
1254 _ = self.request.translate
1280 _ = self.request.translate
1255 c = self.load_default_context()
1281 c = self.load_default_context()
1256 c.user = self.db_user
1282 c.user = self.db_user
1257
1283
1258 user_log = UserModel().get_user_log(c.user, filter_term=None)
1284 user_log = UserModel().get_user_log(c.user, filter_term=None)
1259
1285
1260 audit_log_data = {}
1286 audit_log_data = {}
1261 for entry in user_log:
1287 for entry in user_log:
1262 audit_log_data[entry.user_log_id] = entry.get_dict()
1288 audit_log_data[entry.user_log_id] = entry.get_dict()
1263
1289
1264 response = Response(json.dumps(audit_log_data, indent=4))
1290 response = Response(json.dumps(audit_log_data, indent=4))
1265 response.content_disposition = str(
1291 response.content_disposition = str(
1266 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1292 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1267 response.content_type = 'application/json'
1293 response.content_type = 'application/json'
1268
1294
1269 return response
1295 return response
1270
1296
1271 @LoginRequired()
1297 @LoginRequired()
1272 @HasPermissionAllDecorator('hg.admin')
1298 @HasPermissionAllDecorator('hg.admin')
1273 @view_config(
1299 @view_config(
1274 route_name='edit_user_perms_summary', request_method='GET',
1300 route_name='edit_user_perms_summary', request_method='GET',
1275 renderer='rhodecode:templates/admin/users/user_edit.mako')
1301 renderer='rhodecode:templates/admin/users/user_edit.mako')
1276 def user_perms_summary(self):
1302 def user_perms_summary(self):
1277 _ = self.request.translate
1303 _ = self.request.translate
1278 c = self.load_default_context()
1304 c = self.load_default_context()
1279 c.user = self.db_user
1305 c.user = self.db_user
1280
1306
1281 c.active = 'perms_summary'
1307 c.active = 'perms_summary'
1282 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1308 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1283
1309
1284 return self._get_template_context(c)
1310 return self._get_template_context(c)
1285
1311
1286 @LoginRequired()
1312 @LoginRequired()
1287 @HasPermissionAllDecorator('hg.admin')
1313 @HasPermissionAllDecorator('hg.admin')
1288 @view_config(
1314 @view_config(
1289 route_name='edit_user_perms_summary_json', request_method='GET',
1315 route_name='edit_user_perms_summary_json', request_method='GET',
1290 renderer='json_ext')
1316 renderer='json_ext')
1291 def user_perms_summary_json(self):
1317 def user_perms_summary_json(self):
1292 self.load_default_context()
1318 self.load_default_context()
1293 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1319 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1294
1320
1295 return perm_user.permissions
1321 return perm_user.permissions
1296
1322
1297 @LoginRequired()
1323 @LoginRequired()
1298 @HasPermissionAllDecorator('hg.admin')
1324 @HasPermissionAllDecorator('hg.admin')
1299 @view_config(
1325 @view_config(
1300 route_name='edit_user_caches', request_method='GET',
1326 route_name='edit_user_caches', request_method='GET',
1301 renderer='rhodecode:templates/admin/users/user_edit.mako')
1327 renderer='rhodecode:templates/admin/users/user_edit.mako')
1302 def user_caches(self):
1328 def user_caches(self):
1303 _ = self.request.translate
1329 _ = self.request.translate
1304 c = self.load_default_context()
1330 c = self.load_default_context()
1305 c.user = self.db_user
1331 c.user = self.db_user
1306
1332
1307 c.active = 'caches'
1333 c.active = 'caches'
1308 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1334 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1309
1335
1310 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1336 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1311 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1337 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1312 c.backend = c.region.backend
1338 c.backend = c.region.backend
1313 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1339 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1314
1340
1315 return self._get_template_context(c)
1341 return self._get_template_context(c)
1316
1342
1317 @LoginRequired()
1343 @LoginRequired()
1318 @HasPermissionAllDecorator('hg.admin')
1344 @HasPermissionAllDecorator('hg.admin')
1319 @CSRFRequired()
1345 @CSRFRequired()
1320 @view_config(
1346 @view_config(
1321 route_name='edit_user_caches_update', request_method='POST')
1347 route_name='edit_user_caches_update', request_method='POST')
1322 def user_caches_update(self):
1348 def user_caches_update(self):
1323 _ = self.request.translate
1349 _ = self.request.translate
1324 c = self.load_default_context()
1350 c = self.load_default_context()
1325 c.user = self.db_user
1351 c.user = self.db_user
1326
1352
1327 c.active = 'caches'
1353 c.active = 'caches'
1328 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1354 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1329
1355
1330 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1356 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1331 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1357 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1332
1358
1333 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1359 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1334
1360
1335 return HTTPFound(h.route_path(
1361 return HTTPFound(h.route_path(
1336 'edit_user_caches', user_id=c.user.user_id))
1362 'edit_user_caches', user_id=c.user.user_id))
@@ -1,2413 +1,2446 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26
27 import colander
26 import time
28 import time
27 import collections
29 import collections
28 import fnmatch
30 import fnmatch
29 import hashlib
31 import hashlib
30 import itertools
32 import itertools
31 import logging
33 import logging
32 import random
34 import random
33 import traceback
35 import traceback
34 from functools import wraps
36 from functools import wraps
35
37
36 import ipaddress
38 import ipaddress
37
39
38 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm import joinedload
42 from sqlalchemy.orm import joinedload
41 from zope.cachedescriptors.property import Lazy as LazyProperty
43 from zope.cachedescriptors.property import Lazy as LazyProperty
42
44
43 import rhodecode
45 import rhodecode
44 from rhodecode.model import meta
46 from rhodecode.model import meta
45 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
46 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
47 from rhodecode.model.db import (
49 from rhodecode.model.db import (
48 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
50 from rhodecode.lib import rc_cache
52 from rhodecode.lib import rc_cache
51 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils import (
54 from rhodecode.lib.utils import (
53 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 from rhodecode.lib.caching_query import FromCache
56 from rhodecode.lib.caching_query import FromCache
55
57
56
57 if rhodecode.is_unix:
58 if rhodecode.is_unix:
58 import bcrypt
59 import bcrypt
59
60
60 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
61
62
62 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
63
64
64
65
65 class PasswordGenerator(object):
66 class PasswordGenerator(object):
66 """
67 """
67 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
68 characters
69 characters
69 usage::
70 usage::
70 passwd_gen = PasswordGenerator()
71 passwd_gen = PasswordGenerator()
71 #print 8-letter password containing only big and small letters
72 #print 8-letter password containing only big and small letters
72 of alphabet
73 of alphabet
73 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
74 """
75 """
75 ALPHABETS_NUM = r'''1234567890'''
76 ALPHABETS_NUM = r'''1234567890'''
76 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
77 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
78 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
79 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
80 + ALPHABETS_NUM + ALPHABETS_SPECIAL
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
81 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
82 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
83 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
84 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
85
86
86 def __init__(self, passwd=''):
87 def __init__(self, passwd=''):
87 self.passwd = passwd
88 self.passwd = passwd
88
89
89 def gen_password(self, length, type_=None):
90 def gen_password(self, length, type_=None):
90 if type_ is None:
91 if type_ is None:
91 type_ = self.ALPHABETS_FULL
92 type_ = self.ALPHABETS_FULL
92 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
93 return self.passwd
94 return self.passwd
94
95
95
96
96 class _RhodeCodeCryptoBase(object):
97 class _RhodeCodeCryptoBase(object):
97 ENC_PREF = None
98 ENC_PREF = None
98
99
99 def hash_create(self, str_):
100 def hash_create(self, str_):
100 """
101 """
101 hash the string using
102 hash the string using
102
103
103 :param str_: password to hash
104 :param str_: password to hash
104 """
105 """
105 raise NotImplementedError
106 raise NotImplementedError
106
107
107 def hash_check_with_upgrade(self, password, hashed):
108 def hash_check_with_upgrade(self, password, hashed):
108 """
109 """
109 Returns tuple in which first element is boolean that states that
110 Returns tuple in which first element is boolean that states that
110 given password matches it's hashed version, and the second is new hash
111 given password matches it's hashed version, and the second is new hash
111 of the password, in case this password should be migrated to new
112 of the password, in case this password should be migrated to new
112 cipher.
113 cipher.
113 """
114 """
114 checked_hash = self.hash_check(password, hashed)
115 checked_hash = self.hash_check(password, hashed)
115 return checked_hash, None
116 return checked_hash, None
116
117
117 def hash_check(self, password, hashed):
118 def hash_check(self, password, hashed):
118 """
119 """
119 Checks matching password with it's hashed value.
120 Checks matching password with it's hashed value.
120
121
121 :param password: password
122 :param password: password
122 :param hashed: password in hashed form
123 :param hashed: password in hashed form
123 """
124 """
124 raise NotImplementedError
125 raise NotImplementedError
125
126
126 def _assert_bytes(self, value):
127 def _assert_bytes(self, value):
127 """
128 """
128 Passing in an `unicode` object can lead to hard to detect issues
129 Passing in an `unicode` object can lead to hard to detect issues
129 if passwords contain non-ascii characters. Doing a type check
130 if passwords contain non-ascii characters. Doing a type check
130 during runtime, so that such mistakes are detected early on.
131 during runtime, so that such mistakes are detected early on.
131 """
132 """
132 if not isinstance(value, str):
133 if not isinstance(value, str):
133 raise TypeError(
134 raise TypeError(
134 "Bytestring required as input, got %r." % (value, ))
135 "Bytestring required as input, got %r." % (value, ))
135
136
136
137
137 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
138 ENC_PREF = ('$2a$10', '$2b$10')
139 ENC_PREF = ('$2a$10', '$2b$10')
139
140
140 def hash_create(self, str_):
141 def hash_create(self, str_):
141 self._assert_bytes(str_)
142 self._assert_bytes(str_)
142 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
143
144
144 def hash_check_with_upgrade(self, password, hashed):
145 def hash_check_with_upgrade(self, password, hashed):
145 """
146 """
146 Returns tuple in which first element is boolean that states that
147 Returns tuple in which first element is boolean that states that
147 given password matches it's hashed version, and the second is new hash
148 given password matches it's hashed version, and the second is new hash
148 of the password, in case this password should be migrated to new
149 of the password, in case this password should be migrated to new
149 cipher.
150 cipher.
150
151
151 This implements special upgrade logic which works like that:
152 This implements special upgrade logic which works like that:
152 - check if the given password == bcrypted hash, if yes then we
153 - check if the given password == bcrypted hash, if yes then we
153 properly used password and it was already in bcrypt. Proceed
154 properly used password and it was already in bcrypt. Proceed
154 without any changes
155 without any changes
155 - if bcrypt hash check is not working try with sha256. If hash compare
156 - if bcrypt hash check is not working try with sha256. If hash compare
156 is ok, it means we using correct but old hashed password. indicate
157 is ok, it means we using correct but old hashed password. indicate
157 hash change and proceed
158 hash change and proceed
158 """
159 """
159
160
160 new_hash = None
161 new_hash = None
161
162
162 # regular pw check
163 # regular pw check
163 password_match_bcrypt = self.hash_check(password, hashed)
164 password_match_bcrypt = self.hash_check(password, hashed)
164
165
165 # now we want to know if the password was maybe from sha256
166 # now we want to know if the password was maybe from sha256
166 # basically calling _RhodeCodeCryptoSha256().hash_check()
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
167 if not password_match_bcrypt:
168 if not password_match_bcrypt:
168 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
169 new_hash = self.hash_create(password) # make new bcrypt hash
170 new_hash = self.hash_create(password) # make new bcrypt hash
170 password_match_bcrypt = True
171 password_match_bcrypt = True
171
172
172 return password_match_bcrypt, new_hash
173 return password_match_bcrypt, new_hash
173
174
174 def hash_check(self, password, hashed):
175 def hash_check(self, password, hashed):
175 """
176 """
176 Checks matching password with it's hashed value.
177 Checks matching password with it's hashed value.
177
178
178 :param password: password
179 :param password: password
179 :param hashed: password in hashed form
180 :param hashed: password in hashed form
180 """
181 """
181 self._assert_bytes(password)
182 self._assert_bytes(password)
182 try:
183 try:
183 return bcrypt.hashpw(password, hashed) == hashed
184 return bcrypt.hashpw(password, hashed) == hashed
184 except ValueError as e:
185 except ValueError as e:
185 # we're having a invalid salt here probably, we should not crash
186 # we're having a invalid salt here probably, we should not crash
186 # just return with False as it would be a wrong password.
187 # just return with False as it would be a wrong password.
187 log.debug('Failed to check password hash using bcrypt %s',
188 log.debug('Failed to check password hash using bcrypt %s',
188 safe_str(e))
189 safe_str(e))
189
190
190 return False
191 return False
191
192
192
193
193 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
194 ENC_PREF = '_'
195 ENC_PREF = '_'
195
196
196 def hash_create(self, str_):
197 def hash_create(self, str_):
197 self._assert_bytes(str_)
198 self._assert_bytes(str_)
198 return hashlib.sha256(str_).hexdigest()
199 return hashlib.sha256(str_).hexdigest()
199
200
200 def hash_check(self, password, hashed):
201 def hash_check(self, password, hashed):
201 """
202 """
202 Checks matching password with it's hashed value.
203 Checks matching password with it's hashed value.
203
204
204 :param password: password
205 :param password: password
205 :param hashed: password in hashed form
206 :param hashed: password in hashed form
206 """
207 """
207 self._assert_bytes(password)
208 self._assert_bytes(password)
208 return hashlib.sha256(password).hexdigest() == hashed
209 return hashlib.sha256(password).hexdigest() == hashed
209
210
210
211
211 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
212 ENC_PREF = '_'
213 ENC_PREF = '_'
213
214
214 def hash_create(self, str_):
215 def hash_create(self, str_):
215 self._assert_bytes(str_)
216 self._assert_bytes(str_)
216 return sha1(str_)
217 return sha1(str_)
217
218
218 def hash_check(self, password, hashed):
219 def hash_check(self, password, hashed):
219 """
220 """
220 Checks matching password with it's hashed value.
221 Checks matching password with it's hashed value.
221
222
222 :param password: password
223 :param password: password
223 :param hashed: password in hashed form
224 :param hashed: password in hashed form
224 """
225 """
225 self._assert_bytes(password)
226 self._assert_bytes(password)
226 return sha1(password) == hashed
227 return sha1(password) == hashed
227
228
228
229
229 def crypto_backend():
230 def crypto_backend():
230 """
231 """
231 Return the matching crypto backend.
232 Return the matching crypto backend.
232
233
233 Selection is based on if we run tests or not, we pick sha1-test backend to run
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
234 tests faster since BCRYPT is expensive to calculate
235 tests faster since BCRYPT is expensive to calculate
235 """
236 """
236 if rhodecode.is_test:
237 if rhodecode.is_test:
237 RhodeCodeCrypto = _RhodeCodeCryptoTest()
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
238 else:
239 else:
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240
241
241 return RhodeCodeCrypto
242 return RhodeCodeCrypto
242
243
243
244
244 def get_crypt_password(password):
245 def get_crypt_password(password):
245 """
246 """
246 Create the hash of `password` with the active crypto backend.
247 Create the hash of `password` with the active crypto backend.
247
248
248 :param password: The cleartext password.
249 :param password: The cleartext password.
249 :type password: unicode
250 :type password: unicode
250 """
251 """
251 password = safe_str(password)
252 password = safe_str(password)
252 return crypto_backend().hash_create(password)
253 return crypto_backend().hash_create(password)
253
254
254
255
255 def check_password(password, hashed):
256 def check_password(password, hashed):
256 """
257 """
257 Check if the value in `password` matches the hash in `hashed`.
258 Check if the value in `password` matches the hash in `hashed`.
258
259
259 :param password: The cleartext password.
260 :param password: The cleartext password.
260 :type password: unicode
261 :type password: unicode
261
262
262 :param hashed: The expected hashed version of the password.
263 :param hashed: The expected hashed version of the password.
263 :type hashed: The hash has to be passed in in text representation.
264 :type hashed: The hash has to be passed in in text representation.
264 """
265 """
265 password = safe_str(password)
266 password = safe_str(password)
266 return crypto_backend().hash_check(password, hashed)
267 return crypto_backend().hash_check(password, hashed)
267
268
268
269
269 def generate_auth_token(data, salt=None):
270 def generate_auth_token(data, salt=None):
270 """
271 """
271 Generates API KEY from given string
272 Generates API KEY from given string
272 """
273 """
273
274
274 if salt is None:
275 if salt is None:
275 salt = os.urandom(16)
276 salt = os.urandom(16)
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277
278
278
279
279 def get_came_from(request):
280 def get_came_from(request):
280 """
281 """
281 get query_string+path from request sanitized after removing auth_token
282 get query_string+path from request sanitized after removing auth_token
282 """
283 """
283 _req = request
284 _req = request
284
285
285 path = _req.path
286 path = _req.path
286 if 'auth_token' in _req.GET:
287 if 'auth_token' in _req.GET:
287 # sanitize the request and remove auth_token for redirection
288 # sanitize the request and remove auth_token for redirection
288 _req.GET.pop('auth_token')
289 _req.GET.pop('auth_token')
289 qs = _req.query_string
290 qs = _req.query_string
290 if qs:
291 if qs:
291 path += '?' + qs
292 path += '?' + qs
292
293
293 return path
294 return path
294
295
295
296
296 class CookieStoreWrapper(object):
297 class CookieStoreWrapper(object):
297
298
298 def __init__(self, cookie_store):
299 def __init__(self, cookie_store):
299 self.cookie_store = cookie_store
300 self.cookie_store = cookie_store
300
301
301 def __repr__(self):
302 def __repr__(self):
302 return 'CookieStore<%s>' % (self.cookie_store)
303 return 'CookieStore<%s>' % (self.cookie_store)
303
304
304 def get(self, key, other=None):
305 def get(self, key, other=None):
305 if isinstance(self.cookie_store, dict):
306 if isinstance(self.cookie_store, dict):
306 return self.cookie_store.get(key, other)
307 return self.cookie_store.get(key, other)
307 elif isinstance(self.cookie_store, AuthUser):
308 elif isinstance(self.cookie_store, AuthUser):
308 return self.cookie_store.__dict__.get(key, other)
309 return self.cookie_store.__dict__.get(key, other)
309
310
310
311
311 def _cached_perms_data(user_id, scope, user_is_admin,
312 def _cached_perms_data(user_id, scope, user_is_admin,
312 user_inherit_default_permissions, explicit, algo,
313 user_inherit_default_permissions, explicit, algo,
313 calculate_super_admin):
314 calculate_super_admin):
314
315
315 permissions = PermissionCalculator(
316 permissions = PermissionCalculator(
316 user_id, scope, user_is_admin, user_inherit_default_permissions,
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
317 explicit, algo, calculate_super_admin)
318 explicit, algo, calculate_super_admin)
318 return permissions.calculate()
319 return permissions.calculate()
319
320
320
321
321 class PermOrigin(object):
322 class PermOrigin(object):
322 SUPER_ADMIN = 'superadmin'
323 SUPER_ADMIN = 'superadmin'
323 ARCHIVED = 'archived'
324 ARCHIVED = 'archived'
324
325
325 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
326 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
328 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
331
332
332 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337
338
338 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343
344
344
345
345 class PermOriginDict(dict):
346 class PermOriginDict(dict):
346 """
347 """
347 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
348
349
349 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
351 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352
353
353 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
354 >>> perms['resource'] = 'read', 'default', 1
355 >>> perms['resource'] = 'read', 'default', 1
355 >>> perms['resource']
356 >>> perms['resource']
356 'read'
357 'read'
357 >>> perms['resource'] = 'write', 'admin', 2
358 >>> perms['resource'] = 'write', 'admin', 2
358 >>> perms['resource']
359 >>> perms['resource']
359 'write'
360 'write'
360 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
361 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
362 """
363 """
363
364
364 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
365 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
366 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
367
368
368 def __setitem__(self, key, (perm, origin, obj_id)):
369 def __setitem__(self, key, (perm, origin, obj_id)):
369 self.perm_origin_stack.setdefault(key, []).append(
370 self.perm_origin_stack.setdefault(key, []).append(
370 (perm, origin, obj_id))
371 (perm, origin, obj_id))
371 dict.__setitem__(self, key, perm)
372 dict.__setitem__(self, key, perm)
372
373
373
374
374 class BranchPermOriginDict(PermOriginDict):
375 class BranchPermOriginDict(PermOriginDict):
375 """
376 """
376 Dedicated branch permissions dict, with tracking of patterns and origins.
377 Dedicated branch permissions dict, with tracking of patterns and origins.
377
378
378 >>> perms = BranchPermOriginDict()
379 >>> perms = BranchPermOriginDict()
379 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource']
381 >>> perms['resource']
381 {'*pattern': 'read'}
382 {'*pattern': 'read'}
382 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource']
384 >>> perms['resource']
384 {'*pattern': 'write'}
385 {'*pattern': 'write'}
385 >>> perms.perm_origin_stack
386 >>> perms.perm_origin_stack
386 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 """
388 """
388 def __setitem__(self, key, (pattern, perm, origin)):
389 def __setitem__(self, key, (pattern, perm, origin)):
389
390
390 self.perm_origin_stack.setdefault(key, {}) \
391 self.perm_origin_stack.setdefault(key, {}) \
391 .setdefault(pattern, []).append((perm, origin))
392 .setdefault(pattern, []).append((perm, origin))
392
393
393 if key in self:
394 if key in self:
394 self[key].__setitem__(pattern, perm)
395 self[key].__setitem__(pattern, perm)
395 else:
396 else:
396 patterns = collections.OrderedDict()
397 patterns = collections.OrderedDict()
397 patterns[pattern] = perm
398 patterns[pattern] = perm
398 dict.__setitem__(self, key, patterns)
399 dict.__setitem__(self, key, patterns)
399
400
400
401
401 class PermissionCalculator(object):
402 class PermissionCalculator(object):
402
403
403 def __init__(
404 def __init__(
404 self, user_id, scope, user_is_admin,
405 self, user_id, scope, user_is_admin,
405 user_inherit_default_permissions, explicit, algo,
406 user_inherit_default_permissions, explicit, algo,
406 calculate_super_admin_as_user=False):
407 calculate_super_admin_as_user=False):
407
408
408 self.user_id = user_id
409 self.user_id = user_id
409 self.user_is_admin = user_is_admin
410 self.user_is_admin = user_is_admin
410 self.inherit_default_permissions = user_inherit_default_permissions
411 self.inherit_default_permissions = user_inherit_default_permissions
411 self.explicit = explicit
412 self.explicit = explicit
412 self.algo = algo
413 self.algo = algo
413 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414
415
415 scope = scope or {}
416 scope = scope or {}
416 self.scope_repo_id = scope.get('repo_id')
417 self.scope_repo_id = scope.get('repo_id')
417 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_user_group_id = scope.get('user_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
419
420
420 self.default_user_id = User.get_default_user(cache=True).user_id
421 self.default_user_id = User.get_default_user(cache=True).user_id
421
422
422 self.permissions_repositories = PermOriginDict()
423 self.permissions_repositories = PermOriginDict()
423 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_user_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
425 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_global = set()
427 self.permissions_global = set()
427
428
428 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_user_id, self.scope_repo_id)
430 self.default_user_id, self.scope_repo_id)
430 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_group_perms = \
433 self.default_user_group_perms = \
433 Permission.get_default_user_group_perms(
434 Permission.get_default_user_group_perms(
434 self.default_user_id, self.scope_user_group_id)
435 self.default_user_id, self.scope_user_group_id)
435
436
436 # default branch perms
437 # default branch perms
437 self.default_branch_repo_perms = \
438 self.default_branch_repo_perms = \
438 Permission.get_default_repo_branch_perms(
439 Permission.get_default_repo_branch_perms(
439 self.default_user_id, self.scope_repo_id)
440 self.default_user_id, self.scope_repo_id)
440
441
441 def calculate(self):
442 def calculate(self):
442 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 return self._calculate_admin_permissions()
444 return self._calculate_admin_permissions()
444
445
445 self._calculate_global_default_permissions()
446 self._calculate_global_default_permissions()
446 self._calculate_global_permissions()
447 self._calculate_global_permissions()
447 self._calculate_default_permissions()
448 self._calculate_default_permissions()
448 self._calculate_repository_permissions()
449 self._calculate_repository_permissions()
449 self._calculate_repository_branch_permissions()
450 self._calculate_repository_branch_permissions()
450 self._calculate_repository_group_permissions()
451 self._calculate_repository_group_permissions()
451 self._calculate_user_group_permissions()
452 self._calculate_user_group_permissions()
452 return self._permission_structure()
453 return self._permission_structure()
453
454
454 def _calculate_admin_permissions(self):
455 def _calculate_admin_permissions(self):
455 """
456 """
456 admin user have all default rights for repositories
457 admin user have all default rights for repositories
457 and groups set to admin
458 and groups set to admin
458 """
459 """
459 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461
462
462 # repositories
463 # repositories
463 for perm in self.default_repo_perms:
464 for perm in self.default_repo_perms:
464 r_k = perm.UserRepoToPerm.repository.repo_name
465 r_k = perm.UserRepoToPerm.repository.repo_name
465 obj_id = perm.UserRepoToPerm.repository.repo_id
466 obj_id = perm.UserRepoToPerm.repository.repo_id
466 archived = perm.UserRepoToPerm.repository.archived
467 archived = perm.UserRepoToPerm.repository.archived
467 p = 'repository.admin'
468 p = 'repository.admin'
468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
469 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
469 # special case for archived repositories, which we block still even for
470 # special case for archived repositories, which we block still even for
470 # super admins
471 # super admins
471 if archived:
472 if archived:
472 p = 'repository.read'
473 p = 'repository.read'
473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
474 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
474
475
475 # repository groups
476 # repository groups
476 for perm in self.default_repo_groups_perms:
477 for perm in self.default_repo_groups_perms:
477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 obj_id = perm.UserRepoGroupToPerm.group.group_id
479 obj_id = perm.UserRepoGroupToPerm.group.group_id
479 p = 'group.admin'
480 p = 'group.admin'
480 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
481 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
481
482
482 # user groups
483 # user groups
483 for perm in self.default_user_group_perms:
484 for perm in self.default_user_group_perms:
484 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
485 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
485 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
486 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
486 p = 'usergroup.admin'
487 p = 'usergroup.admin'
487 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
488 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
488
489
489 # branch permissions
490 # branch permissions
490 # since super-admin also can have custom rule permissions
491 # since super-admin also can have custom rule permissions
491 # we *always* need to calculate those inherited from default, and also explicit
492 # we *always* need to calculate those inherited from default, and also explicit
492 self._calculate_default_permissions_repository_branches(
493 self._calculate_default_permissions_repository_branches(
493 user_inherit_object_permissions=False)
494 user_inherit_object_permissions=False)
494 self._calculate_repository_branch_permissions()
495 self._calculate_repository_branch_permissions()
495
496
496 return self._permission_structure()
497 return self._permission_structure()
497
498
498 def _calculate_global_default_permissions(self):
499 def _calculate_global_default_permissions(self):
499 """
500 """
500 global permissions taken from the default user
501 global permissions taken from the default user
501 """
502 """
502 default_global_perms = UserToPerm.query()\
503 default_global_perms = UserToPerm.query()\
503 .filter(UserToPerm.user_id == self.default_user_id)\
504 .filter(UserToPerm.user_id == self.default_user_id)\
504 .options(joinedload(UserToPerm.permission))
505 .options(joinedload(UserToPerm.permission))
505
506
506 for perm in default_global_perms:
507 for perm in default_global_perms:
507 self.permissions_global.add(perm.permission.permission_name)
508 self.permissions_global.add(perm.permission.permission_name)
508
509
509 if self.user_is_admin:
510 if self.user_is_admin:
510 self.permissions_global.add('hg.admin')
511 self.permissions_global.add('hg.admin')
511 self.permissions_global.add('hg.create.write_on_repogroup.true')
512 self.permissions_global.add('hg.create.write_on_repogroup.true')
512
513
513 def _calculate_global_permissions(self):
514 def _calculate_global_permissions(self):
514 """
515 """
515 Set global system permissions with user permissions or permissions
516 Set global system permissions with user permissions or permissions
516 taken from the user groups of the current user.
517 taken from the user groups of the current user.
517
518
518 The permissions include repo creating, repo group creating, forking
519 The permissions include repo creating, repo group creating, forking
519 etc.
520 etc.
520 """
521 """
521
522
522 # now we read the defined permissions and overwrite what we have set
523 # now we read the defined permissions and overwrite what we have set
523 # before those can be configured from groups or users explicitly.
524 # before those can be configured from groups or users explicitly.
524
525
525 # In case we want to extend this list we should make sure
526 # In case we want to extend this list we should make sure
526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 _configurable = frozenset([
528 _configurable = frozenset([
528 'hg.fork.none', 'hg.fork.repository',
529 'hg.fork.none', 'hg.fork.repository',
529 'hg.create.none', 'hg.create.repository',
530 'hg.create.none', 'hg.create.repository',
530 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 'hg.repogroup.create.false', 'hg.repogroup.create.true',
532 'hg.repogroup.create.false', 'hg.repogroup.create.true',
532 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
533 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
533 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
534 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
534 ])
535 ])
535
536
536 # USER GROUPS comes first user group global permissions
537 # USER GROUPS comes first user group global permissions
537 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
538 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
538 .options(joinedload(UserGroupToPerm.permission))\
539 .options(joinedload(UserGroupToPerm.permission))\
539 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
540 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
540 UserGroupMember.users_group_id))\
541 UserGroupMember.users_group_id))\
541 .filter(UserGroupMember.user_id == self.user_id)\
542 .filter(UserGroupMember.user_id == self.user_id)\
542 .order_by(UserGroupToPerm.users_group_id)\
543 .order_by(UserGroupToPerm.users_group_id)\
543 .all()
544 .all()
544
545
545 # need to group here by groups since user can be in more than
546 # need to group here by groups since user can be in more than
546 # one group, so we get all groups
547 # one group, so we get all groups
547 _explicit_grouped_perms = [
548 _explicit_grouped_perms = [
548 [x, list(y)] for x, y in
549 [x, list(y)] for x, y in
549 itertools.groupby(user_perms_from_users_groups,
550 itertools.groupby(user_perms_from_users_groups,
550 lambda _x: _x.users_group)]
551 lambda _x: _x.users_group)]
551
552
552 for gr, perms in _explicit_grouped_perms:
553 for gr, perms in _explicit_grouped_perms:
553 # since user can be in multiple groups iterate over them and
554 # since user can be in multiple groups iterate over them and
554 # select the lowest permissions first (more explicit)
555 # select the lowest permissions first (more explicit)
555 # TODO(marcink): do this^^
556 # TODO(marcink): do this^^
556
557
557 # group doesn't inherit default permissions so we actually set them
558 # group doesn't inherit default permissions so we actually set them
558 if not gr.inherit_default_permissions:
559 if not gr.inherit_default_permissions:
559 # NEED TO IGNORE all previously set configurable permissions
560 # NEED TO IGNORE all previously set configurable permissions
560 # and replace them with explicitly set from this user
561 # and replace them with explicitly set from this user
561 # group permissions
562 # group permissions
562 self.permissions_global = self.permissions_global.difference(
563 self.permissions_global = self.permissions_global.difference(
563 _configurable)
564 _configurable)
564 for perm in perms:
565 for perm in perms:
565 self.permissions_global.add(perm.permission.permission_name)
566 self.permissions_global.add(perm.permission.permission_name)
566
567
567 # user explicit global permissions
568 # user explicit global permissions
568 user_perms = Session().query(UserToPerm)\
569 user_perms = Session().query(UserToPerm)\
569 .options(joinedload(UserToPerm.permission))\
570 .options(joinedload(UserToPerm.permission))\
570 .filter(UserToPerm.user_id == self.user_id).all()
571 .filter(UserToPerm.user_id == self.user_id).all()
571
572
572 if not self.inherit_default_permissions:
573 if not self.inherit_default_permissions:
573 # NEED TO IGNORE all configurable permissions and
574 # NEED TO IGNORE all configurable permissions and
574 # replace them with explicitly set from this user permissions
575 # replace them with explicitly set from this user permissions
575 self.permissions_global = self.permissions_global.difference(
576 self.permissions_global = self.permissions_global.difference(
576 _configurable)
577 _configurable)
577 for perm in user_perms:
578 for perm in user_perms:
578 self.permissions_global.add(perm.permission.permission_name)
579 self.permissions_global.add(perm.permission.permission_name)
579
580
580 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
581 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
581 for perm in self.default_repo_perms:
582 for perm in self.default_repo_perms:
582 r_k = perm.UserRepoToPerm.repository.repo_name
583 r_k = perm.UserRepoToPerm.repository.repo_name
583 obj_id = perm.UserRepoToPerm.repository.repo_id
584 obj_id = perm.UserRepoToPerm.repository.repo_id
584 archived = perm.UserRepoToPerm.repository.archived
585 archived = perm.UserRepoToPerm.repository.archived
585 p = perm.Permission.permission_name
586 p = perm.Permission.permission_name
586 o = PermOrigin.REPO_DEFAULT
587 o = PermOrigin.REPO_DEFAULT
587 self.permissions_repositories[r_k] = p, o, obj_id
588 self.permissions_repositories[r_k] = p, o, obj_id
588
589
589 # if we decide this user isn't inheriting permissions from
590 # if we decide this user isn't inheriting permissions from
590 # default user we set him to .none so only explicit
591 # default user we set him to .none so only explicit
591 # permissions work
592 # permissions work
592 if not user_inherit_object_permissions:
593 if not user_inherit_object_permissions:
593 p = 'repository.none'
594 p = 'repository.none'
594 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
595 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
595 self.permissions_repositories[r_k] = p, o, obj_id
596 self.permissions_repositories[r_k] = p, o, obj_id
596
597
597 if perm.Repository.private and not (
598 if perm.Repository.private and not (
598 perm.Repository.user_id == self.user_id):
599 perm.Repository.user_id == self.user_id):
599 # disable defaults for private repos,
600 # disable defaults for private repos,
600 p = 'repository.none'
601 p = 'repository.none'
601 o = PermOrigin.REPO_PRIVATE
602 o = PermOrigin.REPO_PRIVATE
602 self.permissions_repositories[r_k] = p, o, obj_id
603 self.permissions_repositories[r_k] = p, o, obj_id
603
604
604 elif perm.Repository.user_id == self.user_id:
605 elif perm.Repository.user_id == self.user_id:
605 # set admin if owner
606 # set admin if owner
606 p = 'repository.admin'
607 p = 'repository.admin'
607 o = PermOrigin.REPO_OWNER
608 o = PermOrigin.REPO_OWNER
608 self.permissions_repositories[r_k] = p, o, obj_id
609 self.permissions_repositories[r_k] = p, o, obj_id
609
610
610 if self.user_is_admin:
611 if self.user_is_admin:
611 p = 'repository.admin'
612 p = 'repository.admin'
612 o = PermOrigin.SUPER_ADMIN
613 o = PermOrigin.SUPER_ADMIN
613 self.permissions_repositories[r_k] = p, o, obj_id
614 self.permissions_repositories[r_k] = p, o, obj_id
614
615
615 # finally in case of archived repositories, we downgrade higher
616 # finally in case of archived repositories, we downgrade higher
616 # permissions to read
617 # permissions to read
617 if archived:
618 if archived:
618 current_perm = self.permissions_repositories[r_k]
619 current_perm = self.permissions_repositories[r_k]
619 if current_perm in ['repository.write', 'repository.admin']:
620 if current_perm in ['repository.write', 'repository.admin']:
620 p = 'repository.read'
621 p = 'repository.read'
621 o = PermOrigin.ARCHIVED
622 o = PermOrigin.ARCHIVED
622 self.permissions_repositories[r_k] = p, o, obj_id
623 self.permissions_repositories[r_k] = p, o, obj_id
623
624
624 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
625 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
625 for perm in self.default_branch_repo_perms:
626 for perm in self.default_branch_repo_perms:
626
627
627 r_k = perm.UserRepoToPerm.repository.repo_name
628 r_k = perm.UserRepoToPerm.repository.repo_name
628 p = perm.Permission.permission_name
629 p = perm.Permission.permission_name
629 pattern = perm.UserToRepoBranchPermission.branch_pattern
630 pattern = perm.UserToRepoBranchPermission.branch_pattern
630 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
631 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
631
632
632 if not self.explicit:
633 if not self.explicit:
633 cur_perm = self.permissions_repository_branches.get(r_k)
634 cur_perm = self.permissions_repository_branches.get(r_k)
634 if cur_perm:
635 if cur_perm:
635 cur_perm = cur_perm[pattern]
636 cur_perm = cur_perm[pattern]
636 cur_perm = cur_perm or 'branch.none'
637 cur_perm = cur_perm or 'branch.none'
637
638
638 p = self._choose_permission(p, cur_perm)
639 p = self._choose_permission(p, cur_perm)
639
640
640 # NOTE(marcink): register all pattern/perm instances in this
641 # NOTE(marcink): register all pattern/perm instances in this
641 # special dict that aggregates entries
642 # special dict that aggregates entries
642 self.permissions_repository_branches[r_k] = pattern, p, o
643 self.permissions_repository_branches[r_k] = pattern, p, o
643
644
644 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
645 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
645 for perm in self.default_repo_groups_perms:
646 for perm in self.default_repo_groups_perms:
646 rg_k = perm.UserRepoGroupToPerm.group.group_name
647 rg_k = perm.UserRepoGroupToPerm.group.group_name
647 obj_id = perm.UserRepoGroupToPerm.group.group_id
648 obj_id = perm.UserRepoGroupToPerm.group.group_id
648 p = perm.Permission.permission_name
649 p = perm.Permission.permission_name
649 o = PermOrigin.REPOGROUP_DEFAULT
650 o = PermOrigin.REPOGROUP_DEFAULT
650 self.permissions_repository_groups[rg_k] = p, o, obj_id
651 self.permissions_repository_groups[rg_k] = p, o, obj_id
651
652
652 # if we decide this user isn't inheriting permissions from default
653 # if we decide this user isn't inheriting permissions from default
653 # user we set him to .none so only explicit permissions work
654 # user we set him to .none so only explicit permissions work
654 if not user_inherit_object_permissions:
655 if not user_inherit_object_permissions:
655 p = 'group.none'
656 p = 'group.none'
656 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
657 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
657 self.permissions_repository_groups[rg_k] = p, o, obj_id
658 self.permissions_repository_groups[rg_k] = p, o, obj_id
658
659
659 if perm.RepoGroup.user_id == self.user_id:
660 if perm.RepoGroup.user_id == self.user_id:
660 # set admin if owner
661 # set admin if owner
661 p = 'group.admin'
662 p = 'group.admin'
662 o = PermOrigin.REPOGROUP_OWNER
663 o = PermOrigin.REPOGROUP_OWNER
663 self.permissions_repository_groups[rg_k] = p, o, obj_id
664 self.permissions_repository_groups[rg_k] = p, o, obj_id
664
665
665 if self.user_is_admin:
666 if self.user_is_admin:
666 p = 'group.admin'
667 p = 'group.admin'
667 o = PermOrigin.SUPER_ADMIN
668 o = PermOrigin.SUPER_ADMIN
668 self.permissions_repository_groups[rg_k] = p, o, obj_id
669 self.permissions_repository_groups[rg_k] = p, o, obj_id
669
670
670 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
671 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
671 for perm in self.default_user_group_perms:
672 for perm in self.default_user_group_perms:
672 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
673 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
673 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
674 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
674 p = perm.Permission.permission_name
675 p = perm.Permission.permission_name
675 o = PermOrigin.USERGROUP_DEFAULT
676 o = PermOrigin.USERGROUP_DEFAULT
676 self.permissions_user_groups[u_k] = p, o, obj_id
677 self.permissions_user_groups[u_k] = p, o, obj_id
677
678
678 # if we decide this user isn't inheriting permissions from default
679 # if we decide this user isn't inheriting permissions from default
679 # user we set him to .none so only explicit permissions work
680 # user we set him to .none so only explicit permissions work
680 if not user_inherit_object_permissions:
681 if not user_inherit_object_permissions:
681 p = 'usergroup.none'
682 p = 'usergroup.none'
682 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
683 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
683 self.permissions_user_groups[u_k] = p, o, obj_id
684 self.permissions_user_groups[u_k] = p, o, obj_id
684
685
685 if perm.UserGroup.user_id == self.user_id:
686 if perm.UserGroup.user_id == self.user_id:
686 # set admin if owner
687 # set admin if owner
687 p = 'usergroup.admin'
688 p = 'usergroup.admin'
688 o = PermOrigin.USERGROUP_OWNER
689 o = PermOrigin.USERGROUP_OWNER
689 self.permissions_user_groups[u_k] = p, o, obj_id
690 self.permissions_user_groups[u_k] = p, o, obj_id
690
691
691 if self.user_is_admin:
692 if self.user_is_admin:
692 p = 'usergroup.admin'
693 p = 'usergroup.admin'
693 o = PermOrigin.SUPER_ADMIN
694 o = PermOrigin.SUPER_ADMIN
694 self.permissions_user_groups[u_k] = p, o, obj_id
695 self.permissions_user_groups[u_k] = p, o, obj_id
695
696
696 def _calculate_default_permissions(self):
697 def _calculate_default_permissions(self):
697 """
698 """
698 Set default user permissions for repositories, repository branches,
699 Set default user permissions for repositories, repository branches,
699 repository groups, user groups taken from the default user.
700 repository groups, user groups taken from the default user.
700
701
701 Calculate inheritance of object permissions based on what we have now
702 Calculate inheritance of object permissions based on what we have now
702 in GLOBAL permissions. We check if .false is in GLOBAL since this is
703 in GLOBAL permissions. We check if .false is in GLOBAL since this is
703 explicitly set. Inherit is the opposite of .false being there.
704 explicitly set. Inherit is the opposite of .false being there.
704
705
705 .. note::
706 .. note::
706
707
707 the syntax is little bit odd but what we need to check here is
708 the syntax is little bit odd but what we need to check here is
708 the opposite of .false permission being in the list so even for
709 the opposite of .false permission being in the list so even for
709 inconsistent state when both .true/.false is there
710 inconsistent state when both .true/.false is there
710 .false is more important
711 .false is more important
711
712
712 """
713 """
713 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
714 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
714 in self.permissions_global)
715 in self.permissions_global)
715
716
716 # default permissions inherited from `default` user permissions
717 # default permissions inherited from `default` user permissions
717 self._calculate_default_permissions_repositories(
718 self._calculate_default_permissions_repositories(
718 user_inherit_object_permissions)
719 user_inherit_object_permissions)
719
720
720 self._calculate_default_permissions_repository_branches(
721 self._calculate_default_permissions_repository_branches(
721 user_inherit_object_permissions)
722 user_inherit_object_permissions)
722
723
723 self._calculate_default_permissions_repository_groups(
724 self._calculate_default_permissions_repository_groups(
724 user_inherit_object_permissions)
725 user_inherit_object_permissions)
725
726
726 self._calculate_default_permissions_user_groups(
727 self._calculate_default_permissions_user_groups(
727 user_inherit_object_permissions)
728 user_inherit_object_permissions)
728
729
729 def _calculate_repository_permissions(self):
730 def _calculate_repository_permissions(self):
730 """
731 """
731 Repository access permissions for the current user.
732 Repository access permissions for the current user.
732
733
733 Check if the user is part of user groups for this repository and
734 Check if the user is part of user groups for this repository and
734 fill in the permission from it. `_choose_permission` decides of which
735 fill in the permission from it. `_choose_permission` decides of which
735 permission should be selected based on selected method.
736 permission should be selected based on selected method.
736 """
737 """
737
738
738 # user group for repositories permissions
739 # user group for repositories permissions
739 user_repo_perms_from_user_group = Permission\
740 user_repo_perms_from_user_group = Permission\
740 .get_default_repo_perms_from_user_group(
741 .get_default_repo_perms_from_user_group(
741 self.user_id, self.scope_repo_id)
742 self.user_id, self.scope_repo_id)
742
743
743 multiple_counter = collections.defaultdict(int)
744 multiple_counter = collections.defaultdict(int)
744 for perm in user_repo_perms_from_user_group:
745 for perm in user_repo_perms_from_user_group:
745 r_k = perm.UserGroupRepoToPerm.repository.repo_name
746 r_k = perm.UserGroupRepoToPerm.repository.repo_name
746 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
747 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
747 multiple_counter[r_k] += 1
748 multiple_counter[r_k] += 1
748 p = perm.Permission.permission_name
749 p = perm.Permission.permission_name
749 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
750 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
750 .users_group.users_group_name
751 .users_group.users_group_name
751
752
752 if multiple_counter[r_k] > 1:
753 if multiple_counter[r_k] > 1:
753 cur_perm = self.permissions_repositories[r_k]
754 cur_perm = self.permissions_repositories[r_k]
754 p = self._choose_permission(p, cur_perm)
755 p = self._choose_permission(p, cur_perm)
755
756
756 self.permissions_repositories[r_k] = p, o, obj_id
757 self.permissions_repositories[r_k] = p, o, obj_id
757
758
758 if perm.Repository.user_id == self.user_id:
759 if perm.Repository.user_id == self.user_id:
759 # set admin if owner
760 # set admin if owner
760 p = 'repository.admin'
761 p = 'repository.admin'
761 o = PermOrigin.REPO_OWNER
762 o = PermOrigin.REPO_OWNER
762 self.permissions_repositories[r_k] = p, o, obj_id
763 self.permissions_repositories[r_k] = p, o, obj_id
763
764
764 if self.user_is_admin:
765 if self.user_is_admin:
765 p = 'repository.admin'
766 p = 'repository.admin'
766 o = PermOrigin.SUPER_ADMIN
767 o = PermOrigin.SUPER_ADMIN
767 self.permissions_repositories[r_k] = p, o, obj_id
768 self.permissions_repositories[r_k] = p, o, obj_id
768
769
769 # user explicit permissions for repositories, overrides any specified
770 # user explicit permissions for repositories, overrides any specified
770 # by the group permission
771 # by the group permission
771 user_repo_perms = Permission.get_default_repo_perms(
772 user_repo_perms = Permission.get_default_repo_perms(
772 self.user_id, self.scope_repo_id)
773 self.user_id, self.scope_repo_id)
773 for perm in user_repo_perms:
774 for perm in user_repo_perms:
774 r_k = perm.UserRepoToPerm.repository.repo_name
775 r_k = perm.UserRepoToPerm.repository.repo_name
775 obj_id = perm.UserRepoToPerm.repository.repo_id
776 obj_id = perm.UserRepoToPerm.repository.repo_id
776 p = perm.Permission.permission_name
777 p = perm.Permission.permission_name
777 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
778
779
779 if not self.explicit:
780 if not self.explicit:
780 cur_perm = self.permissions_repositories.get(
781 cur_perm = self.permissions_repositories.get(
781 r_k, 'repository.none')
782 r_k, 'repository.none')
782 p = self._choose_permission(p, cur_perm)
783 p = self._choose_permission(p, cur_perm)
783
784
784 self.permissions_repositories[r_k] = p, o, obj_id
785 self.permissions_repositories[r_k] = p, o, obj_id
785
786
786 if perm.Repository.user_id == self.user_id:
787 if perm.Repository.user_id == self.user_id:
787 # set admin if owner
788 # set admin if owner
788 p = 'repository.admin'
789 p = 'repository.admin'
789 o = PermOrigin.REPO_OWNER
790 o = PermOrigin.REPO_OWNER
790 self.permissions_repositories[r_k] = p, o, obj_id
791 self.permissions_repositories[r_k] = p, o, obj_id
791
792
792 if self.user_is_admin:
793 if self.user_is_admin:
793 p = 'repository.admin'
794 p = 'repository.admin'
794 o = PermOrigin.SUPER_ADMIN
795 o = PermOrigin.SUPER_ADMIN
795 self.permissions_repositories[r_k] = p, o, obj_id
796 self.permissions_repositories[r_k] = p, o, obj_id
796
797
797 def _calculate_repository_branch_permissions(self):
798 def _calculate_repository_branch_permissions(self):
798 # user group for repositories permissions
799 # user group for repositories permissions
799 user_repo_branch_perms_from_user_group = Permission\
800 user_repo_branch_perms_from_user_group = Permission\
800 .get_default_repo_branch_perms_from_user_group(
801 .get_default_repo_branch_perms_from_user_group(
801 self.user_id, self.scope_repo_id)
802 self.user_id, self.scope_repo_id)
802
803
803 multiple_counter = collections.defaultdict(int)
804 multiple_counter = collections.defaultdict(int)
804 for perm in user_repo_branch_perms_from_user_group:
805 for perm in user_repo_branch_perms_from_user_group:
805 r_k = perm.UserGroupRepoToPerm.repository.repo_name
806 r_k = perm.UserGroupRepoToPerm.repository.repo_name
806 p = perm.Permission.permission_name
807 p = perm.Permission.permission_name
807 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
808 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
808 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
809 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
809 .users_group.users_group_name
810 .users_group.users_group_name
810
811
811 multiple_counter[r_k] += 1
812 multiple_counter[r_k] += 1
812 if multiple_counter[r_k] > 1:
813 if multiple_counter[r_k] > 1:
813 cur_perm = self.permissions_repository_branches[r_k][pattern]
814 cur_perm = self.permissions_repository_branches[r_k][pattern]
814 p = self._choose_permission(p, cur_perm)
815 p = self._choose_permission(p, cur_perm)
815
816
816 self.permissions_repository_branches[r_k] = pattern, p, o
817 self.permissions_repository_branches[r_k] = pattern, p, o
817
818
818 # user explicit branch permissions for repositories, overrides
819 # user explicit branch permissions for repositories, overrides
819 # any specified by the group permission
820 # any specified by the group permission
820 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
821 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
821 self.user_id, self.scope_repo_id)
822 self.user_id, self.scope_repo_id)
822
823
823 for perm in user_repo_branch_perms:
824 for perm in user_repo_branch_perms:
824
825
825 r_k = perm.UserRepoToPerm.repository.repo_name
826 r_k = perm.UserRepoToPerm.repository.repo_name
826 p = perm.Permission.permission_name
827 p = perm.Permission.permission_name
827 pattern = perm.UserToRepoBranchPermission.branch_pattern
828 pattern = perm.UserToRepoBranchPermission.branch_pattern
828 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
829 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
829
830
830 if not self.explicit:
831 if not self.explicit:
831 cur_perm = self.permissions_repository_branches.get(r_k)
832 cur_perm = self.permissions_repository_branches.get(r_k)
832 if cur_perm:
833 if cur_perm:
833 cur_perm = cur_perm[pattern]
834 cur_perm = cur_perm[pattern]
834 cur_perm = cur_perm or 'branch.none'
835 cur_perm = cur_perm or 'branch.none'
835 p = self._choose_permission(p, cur_perm)
836 p = self._choose_permission(p, cur_perm)
836
837
837 # NOTE(marcink): register all pattern/perm instances in this
838 # NOTE(marcink): register all pattern/perm instances in this
838 # special dict that aggregates entries
839 # special dict that aggregates entries
839 self.permissions_repository_branches[r_k] = pattern, p, o
840 self.permissions_repository_branches[r_k] = pattern, p, o
840
841
841 def _calculate_repository_group_permissions(self):
842 def _calculate_repository_group_permissions(self):
842 """
843 """
843 Repository group permissions for the current user.
844 Repository group permissions for the current user.
844
845
845 Check if the user is part of user groups for repository groups and
846 Check if the user is part of user groups for repository groups and
846 fill in the permissions from it. `_choose_permission` decides of which
847 fill in the permissions from it. `_choose_permission` decides of which
847 permission should be selected based on selected method.
848 permission should be selected based on selected method.
848 """
849 """
849 # user group for repo groups permissions
850 # user group for repo groups permissions
850 user_repo_group_perms_from_user_group = Permission\
851 user_repo_group_perms_from_user_group = Permission\
851 .get_default_group_perms_from_user_group(
852 .get_default_group_perms_from_user_group(
852 self.user_id, self.scope_repo_group_id)
853 self.user_id, self.scope_repo_group_id)
853
854
854 multiple_counter = collections.defaultdict(int)
855 multiple_counter = collections.defaultdict(int)
855 for perm in user_repo_group_perms_from_user_group:
856 for perm in user_repo_group_perms_from_user_group:
856 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
857 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
857 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
858 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
858 multiple_counter[rg_k] += 1
859 multiple_counter[rg_k] += 1
859 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
860 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
860 .users_group.users_group_name
861 .users_group.users_group_name
861 p = perm.Permission.permission_name
862 p = perm.Permission.permission_name
862
863
863 if multiple_counter[rg_k] > 1:
864 if multiple_counter[rg_k] > 1:
864 cur_perm = self.permissions_repository_groups[rg_k]
865 cur_perm = self.permissions_repository_groups[rg_k]
865 p = self._choose_permission(p, cur_perm)
866 p = self._choose_permission(p, cur_perm)
866 self.permissions_repository_groups[rg_k] = p, o, obj_id
867 self.permissions_repository_groups[rg_k] = p, o, obj_id
867
868
868 if perm.RepoGroup.user_id == self.user_id:
869 if perm.RepoGroup.user_id == self.user_id:
869 # set admin if owner, even for member of other user group
870 # set admin if owner, even for member of other user group
870 p = 'group.admin'
871 p = 'group.admin'
871 o = PermOrigin.REPOGROUP_OWNER
872 o = PermOrigin.REPOGROUP_OWNER
872 self.permissions_repository_groups[rg_k] = p, o, obj_id
873 self.permissions_repository_groups[rg_k] = p, o, obj_id
873
874
874 if self.user_is_admin:
875 if self.user_is_admin:
875 p = 'group.admin'
876 p = 'group.admin'
876 o = PermOrigin.SUPER_ADMIN
877 o = PermOrigin.SUPER_ADMIN
877 self.permissions_repository_groups[rg_k] = p, o, obj_id
878 self.permissions_repository_groups[rg_k] = p, o, obj_id
878
879
879 # user explicit permissions for repository groups
880 # user explicit permissions for repository groups
880 user_repo_groups_perms = Permission.get_default_group_perms(
881 user_repo_groups_perms = Permission.get_default_group_perms(
881 self.user_id, self.scope_repo_group_id)
882 self.user_id, self.scope_repo_group_id)
882 for perm in user_repo_groups_perms:
883 for perm in user_repo_groups_perms:
883 rg_k = perm.UserRepoGroupToPerm.group.group_name
884 rg_k = perm.UserRepoGroupToPerm.group.group_name
884 obj_id = perm.UserRepoGroupToPerm.group.group_id
885 obj_id = perm.UserRepoGroupToPerm.group.group_id
885 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
886 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
886 .user.username
887 .user.username
887 p = perm.Permission.permission_name
888 p = perm.Permission.permission_name
888
889
889 if not self.explicit:
890 if not self.explicit:
890 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
891 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
891 p = self._choose_permission(p, cur_perm)
892 p = self._choose_permission(p, cur_perm)
892
893
893 self.permissions_repository_groups[rg_k] = p, o, obj_id
894 self.permissions_repository_groups[rg_k] = p, o, obj_id
894
895
895 if perm.RepoGroup.user_id == self.user_id:
896 if perm.RepoGroup.user_id == self.user_id:
896 # set admin if owner
897 # set admin if owner
897 p = 'group.admin'
898 p = 'group.admin'
898 o = PermOrigin.REPOGROUP_OWNER
899 o = PermOrigin.REPOGROUP_OWNER
899 self.permissions_repository_groups[rg_k] = p, o, obj_id
900 self.permissions_repository_groups[rg_k] = p, o, obj_id
900
901
901 if self.user_is_admin:
902 if self.user_is_admin:
902 p = 'group.admin'
903 p = 'group.admin'
903 o = PermOrigin.SUPER_ADMIN
904 o = PermOrigin.SUPER_ADMIN
904 self.permissions_repository_groups[rg_k] = p, o, obj_id
905 self.permissions_repository_groups[rg_k] = p, o, obj_id
905
906
906 def _calculate_user_group_permissions(self):
907 def _calculate_user_group_permissions(self):
907 """
908 """
908 User group permissions for the current user.
909 User group permissions for the current user.
909 """
910 """
910 # user group for user group permissions
911 # user group for user group permissions
911 user_group_from_user_group = Permission\
912 user_group_from_user_group = Permission\
912 .get_default_user_group_perms_from_user_group(
913 .get_default_user_group_perms_from_user_group(
913 self.user_id, self.scope_user_group_id)
914 self.user_id, self.scope_user_group_id)
914
915
915 multiple_counter = collections.defaultdict(int)
916 multiple_counter = collections.defaultdict(int)
916 for perm in user_group_from_user_group:
917 for perm in user_group_from_user_group:
917 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
918 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
918 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
919 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
919 multiple_counter[ug_k] += 1
920 multiple_counter[ug_k] += 1
920 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
921 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
921 .user_group.users_group_name
922 .user_group.users_group_name
922 p = perm.Permission.permission_name
923 p = perm.Permission.permission_name
923
924
924 if multiple_counter[ug_k] > 1:
925 if multiple_counter[ug_k] > 1:
925 cur_perm = self.permissions_user_groups[ug_k]
926 cur_perm = self.permissions_user_groups[ug_k]
926 p = self._choose_permission(p, cur_perm)
927 p = self._choose_permission(p, cur_perm)
927
928
928 self.permissions_user_groups[ug_k] = p, o, obj_id
929 self.permissions_user_groups[ug_k] = p, o, obj_id
929
930
930 if perm.UserGroup.user_id == self.user_id:
931 if perm.UserGroup.user_id == self.user_id:
931 # set admin if owner, even for member of other user group
932 # set admin if owner, even for member of other user group
932 p = 'usergroup.admin'
933 p = 'usergroup.admin'
933 o = PermOrigin.USERGROUP_OWNER
934 o = PermOrigin.USERGROUP_OWNER
934 self.permissions_user_groups[ug_k] = p, o, obj_id
935 self.permissions_user_groups[ug_k] = p, o, obj_id
935
936
936 if self.user_is_admin:
937 if self.user_is_admin:
937 p = 'usergroup.admin'
938 p = 'usergroup.admin'
938 o = PermOrigin.SUPER_ADMIN
939 o = PermOrigin.SUPER_ADMIN
939 self.permissions_user_groups[ug_k] = p, o, obj_id
940 self.permissions_user_groups[ug_k] = p, o, obj_id
940
941
941 # user explicit permission for user groups
942 # user explicit permission for user groups
942 user_user_groups_perms = Permission.get_default_user_group_perms(
943 user_user_groups_perms = Permission.get_default_user_group_perms(
943 self.user_id, self.scope_user_group_id)
944 self.user_id, self.scope_user_group_id)
944 for perm in user_user_groups_perms:
945 for perm in user_user_groups_perms:
945 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
946 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
946 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
947 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
947 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
948 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
948 .user.username
949 .user.username
949 p = perm.Permission.permission_name
950 p = perm.Permission.permission_name
950
951
951 if not self.explicit:
952 if not self.explicit:
952 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
953 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
953 p = self._choose_permission(p, cur_perm)
954 p = self._choose_permission(p, cur_perm)
954
955
955 self.permissions_user_groups[ug_k] = p, o, obj_id
956 self.permissions_user_groups[ug_k] = p, o, obj_id
956
957
957 if perm.UserGroup.user_id == self.user_id:
958 if perm.UserGroup.user_id == self.user_id:
958 # set admin if owner
959 # set admin if owner
959 p = 'usergroup.admin'
960 p = 'usergroup.admin'
960 o = PermOrigin.USERGROUP_OWNER
961 o = PermOrigin.USERGROUP_OWNER
961 self.permissions_user_groups[ug_k] = p, o, obj_id
962 self.permissions_user_groups[ug_k] = p, o, obj_id
962
963
963 if self.user_is_admin:
964 if self.user_is_admin:
964 p = 'usergroup.admin'
965 p = 'usergroup.admin'
965 o = PermOrigin.SUPER_ADMIN
966 o = PermOrigin.SUPER_ADMIN
966 self.permissions_user_groups[ug_k] = p, o, obj_id
967 self.permissions_user_groups[ug_k] = p, o, obj_id
967
968
968 def _choose_permission(self, new_perm, cur_perm):
969 def _choose_permission(self, new_perm, cur_perm):
969 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
970 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
970 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
971 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
971 if self.algo == 'higherwin':
972 if self.algo == 'higherwin':
972 if new_perm_val > cur_perm_val:
973 if new_perm_val > cur_perm_val:
973 return new_perm
974 return new_perm
974 return cur_perm
975 return cur_perm
975 elif self.algo == 'lowerwin':
976 elif self.algo == 'lowerwin':
976 if new_perm_val < cur_perm_val:
977 if new_perm_val < cur_perm_val:
977 return new_perm
978 return new_perm
978 return cur_perm
979 return cur_perm
979
980
980 def _permission_structure(self):
981 def _permission_structure(self):
981 return {
982 return {
982 'global': self.permissions_global,
983 'global': self.permissions_global,
983 'repositories': self.permissions_repositories,
984 'repositories': self.permissions_repositories,
984 'repository_branches': self.permissions_repository_branches,
985 'repository_branches': self.permissions_repository_branches,
985 'repositories_groups': self.permissions_repository_groups,
986 'repositories_groups': self.permissions_repository_groups,
986 'user_groups': self.permissions_user_groups,
987 'user_groups': self.permissions_user_groups,
987 }
988 }
988
989
989
990
990 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
991 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
991 """
992 """
992 Check if given controller_name is in whitelist of auth token access
993 Check if given controller_name is in whitelist of auth token access
993 """
994 """
994 if not whitelist:
995 if not whitelist:
995 from rhodecode import CONFIG
996 from rhodecode import CONFIG
996 whitelist = aslist(
997 whitelist = aslist(
997 CONFIG.get('api_access_controllers_whitelist'), sep=',')
998 CONFIG.get('api_access_controllers_whitelist'), sep=',')
998 # backward compat translation
999 # backward compat translation
999 compat = {
1000 compat = {
1000 # old controller, new VIEW
1001 # old controller, new VIEW
1001 'ChangesetController:*': 'RepoCommitsView:*',
1002 'ChangesetController:*': 'RepoCommitsView:*',
1002 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1003 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1003 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1004 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1004 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1005 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1005 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1006 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1006 'GistsController:*': 'GistView:*',
1007 'GistsController:*': 'GistView:*',
1007 }
1008 }
1008
1009
1009 log.debug(
1010 log.debug(
1010 'Allowed views for AUTH TOKEN access: %s', whitelist)
1011 'Allowed views for AUTH TOKEN access: %s', whitelist)
1011 auth_token_access_valid = False
1012 auth_token_access_valid = False
1012
1013
1013 for entry in whitelist:
1014 for entry in whitelist:
1014 token_match = True
1015 token_match = True
1015 if entry in compat:
1016 if entry in compat:
1016 # translate from old Controllers to Pyramid Views
1017 # translate from old Controllers to Pyramid Views
1017 entry = compat[entry]
1018 entry = compat[entry]
1018
1019
1019 if '@' in entry:
1020 if '@' in entry:
1020 # specific AuthToken
1021 # specific AuthToken
1021 entry, allowed_token = entry.split('@', 1)
1022 entry, allowed_token = entry.split('@', 1)
1022 token_match = auth_token == allowed_token
1023 token_match = auth_token == allowed_token
1023
1024
1024 if fnmatch.fnmatch(view_name, entry) and token_match:
1025 if fnmatch.fnmatch(view_name, entry) and token_match:
1025 auth_token_access_valid = True
1026 auth_token_access_valid = True
1026 break
1027 break
1027
1028
1028 if auth_token_access_valid:
1029 if auth_token_access_valid:
1029 log.debug('view: `%s` matches entry in whitelist: %s',
1030 log.debug('view: `%s` matches entry in whitelist: %s',
1030 view_name, whitelist)
1031 view_name, whitelist)
1031
1032
1032 else:
1033 else:
1033 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1034 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1034 % (view_name, whitelist))
1035 % (view_name, whitelist))
1035 if auth_token:
1036 if auth_token:
1036 # if we use auth token key and don't have access it's a warning
1037 # if we use auth token key and don't have access it's a warning
1037 log.warning(msg)
1038 log.warning(msg)
1038 else:
1039 else:
1039 log.debug(msg)
1040 log.debug(msg)
1040
1041
1041 return auth_token_access_valid
1042 return auth_token_access_valid
1042
1043
1043
1044
1044 class AuthUser(object):
1045 class AuthUser(object):
1045 """
1046 """
1046 A simple object that handles all attributes of user in RhodeCode
1047 A simple object that handles all attributes of user in RhodeCode
1047
1048
1048 It does lookup based on API key,given user, or user present in session
1049 It does lookup based on API key,given user, or user present in session
1049 Then it fills all required information for such user. It also checks if
1050 Then it fills all required information for such user. It also checks if
1050 anonymous access is enabled and if so, it returns default user as logged in
1051 anonymous access is enabled and if so, it returns default user as logged in
1051 """
1052 """
1052 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1053 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1053 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1054 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1054 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1055 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1055 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1056 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1056
1057
1057 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1058 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1058
1059
1059 self.user_id = user_id
1060 self.user_id = user_id
1060 self._api_key = api_key
1061 self._api_key = api_key
1061
1062
1062 self.api_key = None
1063 self.api_key = None
1063 self.username = username
1064 self.username = username
1064 self.ip_addr = ip_addr
1065 self.ip_addr = ip_addr
1065 self.name = ''
1066 self.name = ''
1066 self.lastname = ''
1067 self.lastname = ''
1067 self.first_name = ''
1068 self.first_name = ''
1068 self.last_name = ''
1069 self.last_name = ''
1069 self.email = ''
1070 self.email = ''
1070 self.is_authenticated = False
1071 self.is_authenticated = False
1071 self.admin = False
1072 self.admin = False
1072 self.inherit_default_permissions = False
1073 self.inherit_default_permissions = False
1073 self.password = ''
1074 self.password = ''
1074
1075
1075 self.anonymous_user = None # propagated on propagate_data
1076 self.anonymous_user = None # propagated on propagate_data
1076 self.propagate_data()
1077 self.propagate_data()
1077 self._instance = None
1078 self._instance = None
1078 self._permissions_scoped_cache = {} # used to bind scoped calculation
1079 self._permissions_scoped_cache = {} # used to bind scoped calculation
1079
1080
1080 @LazyProperty
1081 @LazyProperty
1081 def permissions(self):
1082 def permissions(self):
1082 return self.get_perms(user=self, cache=None)
1083 return self.get_perms(user=self, cache=None)
1083
1084
1084 @LazyProperty
1085 @LazyProperty
1085 def permissions_safe(self):
1086 def permissions_safe(self):
1086 """
1087 """
1087 Filtered permissions excluding not allowed repositories
1088 Filtered permissions excluding not allowed repositories
1088 """
1089 """
1089 perms = self.get_perms(user=self, cache=None)
1090 perms = self.get_perms(user=self, cache=None)
1090
1091
1091 perms['repositories'] = {
1092 perms['repositories'] = {
1092 k: v for k, v in perms['repositories'].items()
1093 k: v for k, v in perms['repositories'].items()
1093 if v != 'repository.none'}
1094 if v != 'repository.none'}
1094 perms['repositories_groups'] = {
1095 perms['repositories_groups'] = {
1095 k: v for k, v in perms['repositories_groups'].items()
1096 k: v for k, v in perms['repositories_groups'].items()
1096 if v != 'group.none'}
1097 if v != 'group.none'}
1097 perms['user_groups'] = {
1098 perms['user_groups'] = {
1098 k: v for k, v in perms['user_groups'].items()
1099 k: v for k, v in perms['user_groups'].items()
1099 if v != 'usergroup.none'}
1100 if v != 'usergroup.none'}
1100 perms['repository_branches'] = {
1101 perms['repository_branches'] = {
1101 k: v for k, v in perms['repository_branches'].iteritems()
1102 k: v for k, v in perms['repository_branches'].iteritems()
1102 if v != 'branch.none'}
1103 if v != 'branch.none'}
1103 return perms
1104 return perms
1104
1105
1105 @LazyProperty
1106 @LazyProperty
1106 def permissions_full_details(self):
1107 def permissions_full_details(self):
1107 return self.get_perms(
1108 return self.get_perms(
1108 user=self, cache=None, calculate_super_admin=True)
1109 user=self, cache=None, calculate_super_admin=True)
1109
1110
1110 def permissions_with_scope(self, scope):
1111 def permissions_with_scope(self, scope):
1111 """
1112 """
1112 Call the get_perms function with scoped data. The scope in that function
1113 Call the get_perms function with scoped data. The scope in that function
1113 narrows the SQL calls to the given ID of objects resulting in fetching
1114 narrows the SQL calls to the given ID of objects resulting in fetching
1114 Just particular permission we want to obtain. If scope is an empty dict
1115 Just particular permission we want to obtain. If scope is an empty dict
1115 then it basically narrows the scope to GLOBAL permissions only.
1116 then it basically narrows the scope to GLOBAL permissions only.
1116
1117
1117 :param scope: dict
1118 :param scope: dict
1118 """
1119 """
1119 if 'repo_name' in scope:
1120 if 'repo_name' in scope:
1120 obj = Repository.get_by_repo_name(scope['repo_name'])
1121 obj = Repository.get_by_repo_name(scope['repo_name'])
1121 if obj:
1122 if obj:
1122 scope['repo_id'] = obj.repo_id
1123 scope['repo_id'] = obj.repo_id
1123 _scope = collections.OrderedDict()
1124 _scope = collections.OrderedDict()
1124 _scope['repo_id'] = -1
1125 _scope['repo_id'] = -1
1125 _scope['user_group_id'] = -1
1126 _scope['user_group_id'] = -1
1126 _scope['repo_group_id'] = -1
1127 _scope['repo_group_id'] = -1
1127
1128
1128 for k in sorted(scope.keys()):
1129 for k in sorted(scope.keys()):
1129 _scope[k] = scope[k]
1130 _scope[k] = scope[k]
1130
1131
1131 # store in cache to mimic how the @LazyProperty works,
1132 # store in cache to mimic how the @LazyProperty works,
1132 # the difference here is that we use the unique key calculated
1133 # the difference here is that we use the unique key calculated
1133 # from params and values
1134 # from params and values
1134 return self.get_perms(user=self, cache=None, scope=_scope)
1135 return self.get_perms(user=self, cache=None, scope=_scope)
1135
1136
1136 def get_instance(self):
1137 def get_instance(self):
1137 return User.get(self.user_id)
1138 return User.get(self.user_id)
1138
1139
1139 def propagate_data(self):
1140 def propagate_data(self):
1140 """
1141 """
1141 Fills in user data and propagates values to this instance. Maps fetched
1142 Fills in user data and propagates values to this instance. Maps fetched
1142 user attributes to this class instance attributes
1143 user attributes to this class instance attributes
1143 """
1144 """
1144 log.debug('AuthUser: starting data propagation for new potential user')
1145 log.debug('AuthUser: starting data propagation for new potential user')
1145 user_model = UserModel()
1146 user_model = UserModel()
1146 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1147 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1147 is_user_loaded = False
1148 is_user_loaded = False
1148
1149
1149 # lookup by userid
1150 # lookup by userid
1150 if self.user_id is not None and self.user_id != anon_user.user_id:
1151 if self.user_id is not None and self.user_id != anon_user.user_id:
1151 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1152 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1152 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1153 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1153
1154
1154 # try go get user by api key
1155 # try go get user by api key
1155 elif self._api_key and self._api_key != anon_user.api_key:
1156 elif self._api_key and self._api_key != anon_user.api_key:
1156 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1157 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1157 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1158 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1158
1159
1159 # lookup by username
1160 # lookup by username
1160 elif self.username:
1161 elif self.username:
1161 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1162 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1162 is_user_loaded = user_model.fill_data(self, username=self.username)
1163 is_user_loaded = user_model.fill_data(self, username=self.username)
1163 else:
1164 else:
1164 log.debug('No data in %s that could been used to log in', self)
1165 log.debug('No data in %s that could been used to log in', self)
1165
1166
1166 if not is_user_loaded:
1167 if not is_user_loaded:
1167 log.debug(
1168 log.debug(
1168 'Failed to load user. Fallback to default user %s', anon_user)
1169 'Failed to load user. Fallback to default user %s', anon_user)
1169 # if we cannot authenticate user try anonymous
1170 # if we cannot authenticate user try anonymous
1170 if anon_user.active:
1171 if anon_user.active:
1171 log.debug('default user is active, using it as a session user')
1172 log.debug('default user is active, using it as a session user')
1172 user_model.fill_data(self, user_id=anon_user.user_id)
1173 user_model.fill_data(self, user_id=anon_user.user_id)
1173 # then we set this user is logged in
1174 # then we set this user is logged in
1174 self.is_authenticated = True
1175 self.is_authenticated = True
1175 else:
1176 else:
1176 log.debug('default user is NOT active')
1177 log.debug('default user is NOT active')
1177 # in case of disabled anonymous user we reset some of the
1178 # in case of disabled anonymous user we reset some of the
1178 # parameters so such user is "corrupted", skipping the fill_data
1179 # parameters so such user is "corrupted", skipping the fill_data
1179 for attr in ['user_id', 'username', 'admin', 'active']:
1180 for attr in ['user_id', 'username', 'admin', 'active']:
1180 setattr(self, attr, None)
1181 setattr(self, attr, None)
1181 self.is_authenticated = False
1182 self.is_authenticated = False
1182
1183
1183 if not self.username:
1184 if not self.username:
1184 self.username = 'None'
1185 self.username = 'None'
1185
1186
1186 log.debug('AuthUser: propagated user is now %s', self)
1187 log.debug('AuthUser: propagated user is now %s', self)
1187
1188
1188 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1189 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1189 calculate_super_admin=False, cache=None):
1190 calculate_super_admin=False, cache=None):
1190 """
1191 """
1191 Fills user permission attribute with permissions taken from database
1192 Fills user permission attribute with permissions taken from database
1192 works for permissions given for repositories, and for permissions that
1193 works for permissions given for repositories, and for permissions that
1193 are granted to groups
1194 are granted to groups
1194
1195
1195 :param user: instance of User object from database
1196 :param user: instance of User object from database
1196 :param explicit: In case there are permissions both for user and a group
1197 :param explicit: In case there are permissions both for user and a group
1197 that user is part of, explicit flag will defiine if user will
1198 that user is part of, explicit flag will defiine if user will
1198 explicitly override permissions from group, if it's False it will
1199 explicitly override permissions from group, if it's False it will
1199 make decision based on the algo
1200 make decision based on the algo
1200 :param algo: algorithm to decide what permission should be choose if
1201 :param algo: algorithm to decide what permission should be choose if
1201 it's multiple defined, eg user in two different groups. It also
1202 it's multiple defined, eg user in two different groups. It also
1202 decides if explicit flag is turned off how to specify the permission
1203 decides if explicit flag is turned off how to specify the permission
1203 for case when user is in a group + have defined separate permission
1204 for case when user is in a group + have defined separate permission
1204 :param calculate_super_admin: calculate permissions for super-admin in the
1205 :param calculate_super_admin: calculate permissions for super-admin in the
1205 same way as for regular user without speedups
1206 same way as for regular user without speedups
1206 :param cache: Use caching for calculation, None = let the cache backend decide
1207 :param cache: Use caching for calculation, None = let the cache backend decide
1207 """
1208 """
1208 user_id = user.user_id
1209 user_id = user.user_id
1209 user_is_admin = user.is_admin
1210 user_is_admin = user.is_admin
1210
1211
1211 # inheritance of global permissions like create repo/fork repo etc
1212 # inheritance of global permissions like create repo/fork repo etc
1212 user_inherit_default_permissions = user.inherit_default_permissions
1213 user_inherit_default_permissions = user.inherit_default_permissions
1213
1214
1214 cache_seconds = safe_int(
1215 cache_seconds = safe_int(
1215 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1216 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1216
1217
1217 if cache is None:
1218 if cache is None:
1218 # let the backend cache decide
1219 # let the backend cache decide
1219 cache_on = cache_seconds > 0
1220 cache_on = cache_seconds > 0
1220 else:
1221 else:
1221 cache_on = cache
1222 cache_on = cache
1222
1223
1223 log.debug(
1224 log.debug(
1224 'Computing PERMISSION tree for user %s scope `%s` '
1225 'Computing PERMISSION tree for user %s scope `%s` '
1225 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1226 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1226
1227
1227 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1228 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1228 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1229 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1229
1230
1230 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1231 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1231 condition=cache_on)
1232 condition=cache_on)
1232 def compute_perm_tree(cache_name, cache_ver,
1233 def compute_perm_tree(cache_name, cache_ver,
1233 user_id, scope, user_is_admin,user_inherit_default_permissions,
1234 user_id, scope, user_is_admin,user_inherit_default_permissions,
1234 explicit, algo, calculate_super_admin):
1235 explicit, algo, calculate_super_admin):
1235 return _cached_perms_data(
1236 return _cached_perms_data(
1236 user_id, scope, user_is_admin, user_inherit_default_permissions,
1237 user_id, scope, user_is_admin, user_inherit_default_permissions,
1237 explicit, algo, calculate_super_admin)
1238 explicit, algo, calculate_super_admin)
1238
1239
1239 start = time.time()
1240 start = time.time()
1240 result = compute_perm_tree(
1241 result = compute_perm_tree(
1241 'permissions', 'v1', user_id, scope, user_is_admin,
1242 'permissions', 'v1', user_id, scope, user_is_admin,
1242 user_inherit_default_permissions, explicit, algo,
1243 user_inherit_default_permissions, explicit, algo,
1243 calculate_super_admin)
1244 calculate_super_admin)
1244
1245
1245 result_repr = []
1246 result_repr = []
1246 for k in result:
1247 for k in result:
1247 result_repr.append((k, len(result[k])))
1248 result_repr.append((k, len(result[k])))
1248 total = time.time() - start
1249 total = time.time() - start
1249 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1250 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1250 user, total, result_repr)
1251 user, total, result_repr)
1251
1252
1252 return result
1253 return result
1253
1254
1254 @property
1255 @property
1255 def is_default(self):
1256 def is_default(self):
1256 return self.username == User.DEFAULT_USER
1257 return self.username == User.DEFAULT_USER
1257
1258
1258 @property
1259 @property
1259 def is_admin(self):
1260 def is_admin(self):
1260 return self.admin
1261 return self.admin
1261
1262
1262 @property
1263 @property
1263 def is_user_object(self):
1264 def is_user_object(self):
1264 return self.user_id is not None
1265 return self.user_id is not None
1265
1266
1266 @property
1267 @property
1267 def repositories_admin(self):
1268 def repositories_admin(self):
1268 """
1269 """
1269 Returns list of repositories you're an admin of
1270 Returns list of repositories you're an admin of
1270 """
1271 """
1271 return [
1272 return [
1272 x[0] for x in self.permissions['repositories'].items()
1273 x[0] for x in self.permissions['repositories'].items()
1273 if x[1] == 'repository.admin']
1274 if x[1] == 'repository.admin']
1274
1275
1275 @property
1276 @property
1276 def repository_groups_admin(self):
1277 def repository_groups_admin(self):
1277 """
1278 """
1278 Returns list of repository groups you're an admin of
1279 Returns list of repository groups you're an admin of
1279 """
1280 """
1280 return [
1281 return [
1281 x[0] for x in self.permissions['repositories_groups'].items()
1282 x[0] for x in self.permissions['repositories_groups'].items()
1282 if x[1] == 'group.admin']
1283 if x[1] == 'group.admin']
1283
1284
1284 @property
1285 @property
1285 def user_groups_admin(self):
1286 def user_groups_admin(self):
1286 """
1287 """
1287 Returns list of user groups you're an admin of
1288 Returns list of user groups you're an admin of
1288 """
1289 """
1289 return [
1290 return [
1290 x[0] for x in self.permissions['user_groups'].items()
1291 x[0] for x in self.permissions['user_groups'].items()
1291 if x[1] == 'usergroup.admin']
1292 if x[1] == 'usergroup.admin']
1292
1293
1293 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1294 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1294 if not perms:
1295 if not perms:
1295 perms = AuthUser.repo_read_perms
1296 perms = AuthUser.repo_read_perms
1296 allowed_ids = []
1297 allowed_ids = []
1297 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1298 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1298 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1299 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1299 if prefix_filter and not k.startswith(prefix_filter):
1300 if prefix_filter and not k.startswith(prefix_filter):
1300 continue
1301 continue
1301 if perm in perms:
1302 if perm in perms:
1302 allowed_ids.append(obj_id)
1303 allowed_ids.append(obj_id)
1303 return allowed_ids
1304 return allowed_ids
1304
1305
1305 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1306 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1306 """
1307 """
1307 Returns list of repository ids that user have access to based on given
1308 Returns list of repository ids that user have access to based on given
1308 perms. The cache flag should be only used in cases that are used for
1309 perms. The cache flag should be only used in cases that are used for
1309 display purposes, NOT IN ANY CASE for permission checks.
1310 display purposes, NOT IN ANY CASE for permission checks.
1310 """
1311 """
1311 from rhodecode.model.scm import RepoList
1312 from rhodecode.model.scm import RepoList
1312 if not perms:
1313 if not perms:
1313 perms = AuthUser.repo_read_perms
1314 perms = AuthUser.repo_read_perms
1314
1315
1315 def _cached_repo_acl(user_id, perm_def, _name_filter):
1316 def _cached_repo_acl(user_id, perm_def, _name_filter):
1316 qry = Repository.query()
1317 qry = Repository.query()
1317 if _name_filter:
1318 if _name_filter:
1318 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1319 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1319 qry = qry.filter(
1320 qry = qry.filter(
1320 Repository.repo_name.ilike(ilike_expression))
1321 Repository.repo_name.ilike(ilike_expression))
1321
1322
1322 return [x.repo_id for x in
1323 return [x.repo_id for x in
1323 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1324 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1324
1325
1325 return _cached_repo_acl(self.user_id, perms, name_filter)
1326 return _cached_repo_acl(self.user_id, perms, name_filter)
1326
1327
1327 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1328 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1328 if not perms:
1329 if not perms:
1329 perms = AuthUser.repo_group_read_perms
1330 perms = AuthUser.repo_group_read_perms
1330 allowed_ids = []
1331 allowed_ids = []
1331 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1332 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1332 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1333 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1333 if prefix_filter and not k.startswith(prefix_filter):
1334 if prefix_filter and not k.startswith(prefix_filter):
1334 continue
1335 continue
1335 if perm in perms:
1336 if perm in perms:
1336 allowed_ids.append(obj_id)
1337 allowed_ids.append(obj_id)
1337 return allowed_ids
1338 return allowed_ids
1338
1339
1339 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1340 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1340 """
1341 """
1341 Returns list of repository group ids that user have access to based on given
1342 Returns list of repository group ids that user have access to based on given
1342 perms. The cache flag should be only used in cases that are used for
1343 perms. The cache flag should be only used in cases that are used for
1343 display purposes, NOT IN ANY CASE for permission checks.
1344 display purposes, NOT IN ANY CASE for permission checks.
1344 """
1345 """
1345 from rhodecode.model.scm import RepoGroupList
1346 from rhodecode.model.scm import RepoGroupList
1346 if not perms:
1347 if not perms:
1347 perms = AuthUser.repo_group_read_perms
1348 perms = AuthUser.repo_group_read_perms
1348
1349
1349 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1350 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1350 qry = RepoGroup.query()
1351 qry = RepoGroup.query()
1351 if _name_filter:
1352 if _name_filter:
1352 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1353 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1353 qry = qry.filter(
1354 qry = qry.filter(
1354 RepoGroup.group_name.ilike(ilike_expression))
1355 RepoGroup.group_name.ilike(ilike_expression))
1355
1356
1356 return [x.group_id for x in
1357 return [x.group_id for x in
1357 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1358 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1358
1359
1359 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1360 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1360
1361
1361 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1362 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1362 if not perms:
1363 if not perms:
1363 perms = AuthUser.user_group_read_perms
1364 perms = AuthUser.user_group_read_perms
1364 allowed_ids = []
1365 allowed_ids = []
1365 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1366 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1366 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1367 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1367 if perm in perms:
1368 if perm in perms:
1368 allowed_ids.append(obj_id)
1369 allowed_ids.append(obj_id)
1369 return allowed_ids
1370 return allowed_ids
1370
1371
1371 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1372 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1372 """
1373 """
1373 Returns list of user group ids that user have access to based on given
1374 Returns list of user group ids that user have access to based on given
1374 perms. The cache flag should be only used in cases that are used for
1375 perms. The cache flag should be only used in cases that are used for
1375 display purposes, NOT IN ANY CASE for permission checks.
1376 display purposes, NOT IN ANY CASE for permission checks.
1376 """
1377 """
1377 from rhodecode.model.scm import UserGroupList
1378 from rhodecode.model.scm import UserGroupList
1378 if not perms:
1379 if not perms:
1379 perms = AuthUser.user_group_read_perms
1380 perms = AuthUser.user_group_read_perms
1380
1381
1381 def _cached_user_group_acl(user_id, perm_def, name_filter):
1382 def _cached_user_group_acl(user_id, perm_def, name_filter):
1382 qry = UserGroup.query()
1383 qry = UserGroup.query()
1383 if name_filter:
1384 if name_filter:
1384 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1385 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1385 qry = qry.filter(
1386 qry = qry.filter(
1386 UserGroup.users_group_name.ilike(ilike_expression))
1387 UserGroup.users_group_name.ilike(ilike_expression))
1387
1388
1388 return [x.users_group_id for x in
1389 return [x.users_group_id for x in
1389 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1390 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1390
1391
1391 return _cached_user_group_acl(self.user_id, perms, name_filter)
1392 return _cached_user_group_acl(self.user_id, perms, name_filter)
1392
1393
1393 @property
1394 @property
1394 def ip_allowed(self):
1395 def ip_allowed(self):
1395 """
1396 """
1396 Checks if ip_addr used in constructor is allowed from defined list of
1397 Checks if ip_addr used in constructor is allowed from defined list of
1397 allowed ip_addresses for user
1398 allowed ip_addresses for user
1398
1399
1399 :returns: boolean, True if ip is in allowed ip range
1400 :returns: boolean, True if ip is in allowed ip range
1400 """
1401 """
1401 # check IP
1402 # check IP
1402 inherit = self.inherit_default_permissions
1403 inherit = self.inherit_default_permissions
1403 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1404 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1404 inherit_from_default=inherit)
1405 inherit_from_default=inherit)
1405 @property
1406 @property
1406 def personal_repo_group(self):
1407 def personal_repo_group(self):
1407 return RepoGroup.get_user_personal_repo_group(self.user_id)
1408 return RepoGroup.get_user_personal_repo_group(self.user_id)
1408
1409
1409 @LazyProperty
1410 @LazyProperty
1410 def feed_token(self):
1411 def feed_token(self):
1411 return self.get_instance().feed_token
1412 return self.get_instance().feed_token
1412
1413
1413 @LazyProperty
1414 @LazyProperty
1414 def artifact_token(self):
1415 def artifact_token(self):
1415 return self.get_instance().artifact_token
1416 return self.get_instance().artifact_token
1416
1417
1417 @classmethod
1418 @classmethod
1418 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1419 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1419 allowed_ips = AuthUser.get_allowed_ips(
1420 allowed_ips = AuthUser.get_allowed_ips(
1420 user_id, cache=True, inherit_from_default=inherit_from_default)
1421 user_id, cache=True, inherit_from_default=inherit_from_default)
1421 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1422 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1422 log.debug('IP:%s for user %s is in range of %s',
1423 log.debug('IP:%s for user %s is in range of %s',
1423 ip_addr, user_id, allowed_ips)
1424 ip_addr, user_id, allowed_ips)
1424 return True
1425 return True
1425 else:
1426 else:
1426 log.info('Access for IP:%s forbidden for user %s, '
1427 log.info('Access for IP:%s forbidden for user %s, '
1427 'not in %s', ip_addr, user_id, allowed_ips)
1428 'not in %s', ip_addr, user_id, allowed_ips)
1428 return False
1429 return False
1429
1430
1430 def get_branch_permissions(self, repo_name, perms=None):
1431 def get_branch_permissions(self, repo_name, perms=None):
1431 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1432 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1432 branch_perms = perms.get('repository_branches', {})
1433 branch_perms = perms.get('repository_branches', {})
1433 if not branch_perms:
1434 if not branch_perms:
1434 return {}
1435 return {}
1435 repo_branch_perms = branch_perms.get(repo_name)
1436 repo_branch_perms = branch_perms.get(repo_name)
1436 return repo_branch_perms or {}
1437 return repo_branch_perms or {}
1437
1438
1438 def get_rule_and_branch_permission(self, repo_name, branch_name):
1439 def get_rule_and_branch_permission(self, repo_name, branch_name):
1439 """
1440 """
1440 Check if this AuthUser has defined any permissions for branches. If any of
1441 Check if this AuthUser has defined any permissions for branches. If any of
1441 the rules match in order, we return the matching permissions
1442 the rules match in order, we return the matching permissions
1442 """
1443 """
1443
1444
1444 rule = default_perm = ''
1445 rule = default_perm = ''
1445
1446
1446 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1447 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1447 if not repo_branch_perms:
1448 if not repo_branch_perms:
1448 return rule, default_perm
1449 return rule, default_perm
1449
1450
1450 # now calculate the permissions
1451 # now calculate the permissions
1451 for pattern, branch_perm in repo_branch_perms.items():
1452 for pattern, branch_perm in repo_branch_perms.items():
1452 if fnmatch.fnmatch(branch_name, pattern):
1453 if fnmatch.fnmatch(branch_name, pattern):
1453 rule = '`{}`=>{}'.format(pattern, branch_perm)
1454 rule = '`{}`=>{}'.format(pattern, branch_perm)
1454 return rule, branch_perm
1455 return rule, branch_perm
1455
1456
1456 return rule, default_perm
1457 return rule, default_perm
1457
1458
1459 def get_notice_messages(self):
1460
1461 notice_level = 'notice-error'
1462 notice_messages = []
1463 if self.is_default:
1464 return [], notice_level
1465
1466 notices = UserNotice.query()\
1467 .filter(UserNotice.user_id == self.user_id)\
1468 .filter(UserNotice.notice_read == false())\
1469 .all()
1470
1471 try:
1472 for entry in notices:
1473
1474 msg = {
1475 'msg_id': entry.user_notice_id,
1476 'level': entry.notification_level,
1477 'subject': entry.notice_subject,
1478 'body': entry.notice_body,
1479 }
1480 notice_messages.append(msg)
1481
1482 log.debug('Got user %s %s messages', self, len(notice_messages))
1483
1484 levels = [x['level'] for x in notice_messages]
1485 notice_level = 'notice-error' if 'error' in levels else 'notice-warning'
1486 except Exception:
1487 pass
1488
1489 return notice_messages, notice_level
1490
1458 def __repr__(self):
1491 def __repr__(self):
1459 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1492 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1460 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1493 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1461
1494
1462 def set_authenticated(self, authenticated=True):
1495 def set_authenticated(self, authenticated=True):
1463 if self.user_id != self.anonymous_user.user_id:
1496 if self.user_id != self.anonymous_user.user_id:
1464 self.is_authenticated = authenticated
1497 self.is_authenticated = authenticated
1465
1498
1466 def get_cookie_store(self):
1499 def get_cookie_store(self):
1467 return {
1500 return {
1468 'username': self.username,
1501 'username': self.username,
1469 'password': md5(self.password or ''),
1502 'password': md5(self.password or ''),
1470 'user_id': self.user_id,
1503 'user_id': self.user_id,
1471 'is_authenticated': self.is_authenticated
1504 'is_authenticated': self.is_authenticated
1472 }
1505 }
1473
1506
1474 @classmethod
1507 @classmethod
1475 def from_cookie_store(cls, cookie_store):
1508 def from_cookie_store(cls, cookie_store):
1476 """
1509 """
1477 Creates AuthUser from a cookie store
1510 Creates AuthUser from a cookie store
1478
1511
1479 :param cls:
1512 :param cls:
1480 :param cookie_store:
1513 :param cookie_store:
1481 """
1514 """
1482 user_id = cookie_store.get('user_id')
1515 user_id = cookie_store.get('user_id')
1483 username = cookie_store.get('username')
1516 username = cookie_store.get('username')
1484 api_key = cookie_store.get('api_key')
1517 api_key = cookie_store.get('api_key')
1485 return AuthUser(user_id, api_key, username)
1518 return AuthUser(user_id, api_key, username)
1486
1519
1487 @classmethod
1520 @classmethod
1488 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1521 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1489 _set = set()
1522 _set = set()
1490
1523
1491 if inherit_from_default:
1524 if inherit_from_default:
1492 def_user_id = User.get_default_user(cache=True).user_id
1525 def_user_id = User.get_default_user(cache=True).user_id
1493 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1526 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1494 if cache:
1527 if cache:
1495 default_ips = default_ips.options(
1528 default_ips = default_ips.options(
1496 FromCache("sql_cache_short", "get_user_ips_default"))
1529 FromCache("sql_cache_short", "get_user_ips_default"))
1497
1530
1498 # populate from default user
1531 # populate from default user
1499 for ip in default_ips:
1532 for ip in default_ips:
1500 try:
1533 try:
1501 _set.add(ip.ip_addr)
1534 _set.add(ip.ip_addr)
1502 except ObjectDeletedError:
1535 except ObjectDeletedError:
1503 # since we use heavy caching sometimes it happens that
1536 # since we use heavy caching sometimes it happens that
1504 # we get deleted objects here, we just skip them
1537 # we get deleted objects here, we just skip them
1505 pass
1538 pass
1506
1539
1507 # NOTE:(marcink) we don't want to load any rules for empty
1540 # NOTE:(marcink) we don't want to load any rules for empty
1508 # user_id which is the case of access of non logged users when anonymous
1541 # user_id which is the case of access of non logged users when anonymous
1509 # access is disabled
1542 # access is disabled
1510 user_ips = []
1543 user_ips = []
1511 if user_id:
1544 if user_id:
1512 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1545 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1513 if cache:
1546 if cache:
1514 user_ips = user_ips.options(
1547 user_ips = user_ips.options(
1515 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1548 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1516
1549
1517 for ip in user_ips:
1550 for ip in user_ips:
1518 try:
1551 try:
1519 _set.add(ip.ip_addr)
1552 _set.add(ip.ip_addr)
1520 except ObjectDeletedError:
1553 except ObjectDeletedError:
1521 # since we use heavy caching sometimes it happens that we get
1554 # since we use heavy caching sometimes it happens that we get
1522 # deleted objects here, we just skip them
1555 # deleted objects here, we just skip them
1523 pass
1556 pass
1524 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1557 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1525
1558
1526
1559
1527 def set_available_permissions(settings):
1560 def set_available_permissions(settings):
1528 """
1561 """
1529 This function will propagate pyramid settings with all available defined
1562 This function will propagate pyramid settings with all available defined
1530 permission given in db. We don't want to check each time from db for new
1563 permission given in db. We don't want to check each time from db for new
1531 permissions since adding a new permission also requires application restart
1564 permissions since adding a new permission also requires application restart
1532 ie. to decorate new views with the newly created permission
1565 ie. to decorate new views with the newly created permission
1533
1566
1534 :param settings: current pyramid registry.settings
1567 :param settings: current pyramid registry.settings
1535
1568
1536 """
1569 """
1537 log.debug('auth: getting information about all available permissions')
1570 log.debug('auth: getting information about all available permissions')
1538 try:
1571 try:
1539 sa = meta.Session
1572 sa = meta.Session
1540 all_perms = sa.query(Permission).all()
1573 all_perms = sa.query(Permission).all()
1541 settings.setdefault('available_permissions',
1574 settings.setdefault('available_permissions',
1542 [x.permission_name for x in all_perms])
1575 [x.permission_name for x in all_perms])
1543 log.debug('auth: set available permissions')
1576 log.debug('auth: set available permissions')
1544 except Exception:
1577 except Exception:
1545 log.exception('Failed to fetch permissions from the database.')
1578 log.exception('Failed to fetch permissions from the database.')
1546 raise
1579 raise
1547
1580
1548
1581
1549 def get_csrf_token(session, force_new=False, save_if_missing=True):
1582 def get_csrf_token(session, force_new=False, save_if_missing=True):
1550 """
1583 """
1551 Return the current authentication token, creating one if one doesn't
1584 Return the current authentication token, creating one if one doesn't
1552 already exist and the save_if_missing flag is present.
1585 already exist and the save_if_missing flag is present.
1553
1586
1554 :param session: pass in the pyramid session, else we use the global ones
1587 :param session: pass in the pyramid session, else we use the global ones
1555 :param force_new: force to re-generate the token and store it in session
1588 :param force_new: force to re-generate the token and store it in session
1556 :param save_if_missing: save the newly generated token if it's missing in
1589 :param save_if_missing: save the newly generated token if it's missing in
1557 session
1590 session
1558 """
1591 """
1559 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1592 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1560 # from pyramid.csrf import get_csrf_token
1593 # from pyramid.csrf import get_csrf_token
1561
1594
1562 if (csrf_token_key not in session and save_if_missing) or force_new:
1595 if (csrf_token_key not in session and save_if_missing) or force_new:
1563 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1596 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1564 session[csrf_token_key] = token
1597 session[csrf_token_key] = token
1565 if hasattr(session, 'save'):
1598 if hasattr(session, 'save'):
1566 session.save()
1599 session.save()
1567 return session.get(csrf_token_key)
1600 return session.get(csrf_token_key)
1568
1601
1569
1602
1570 def get_request(perm_class_instance):
1603 def get_request(perm_class_instance):
1571 from pyramid.threadlocal import get_current_request
1604 from pyramid.threadlocal import get_current_request
1572 pyramid_request = get_current_request()
1605 pyramid_request = get_current_request()
1573 return pyramid_request
1606 return pyramid_request
1574
1607
1575
1608
1576 # CHECK DECORATORS
1609 # CHECK DECORATORS
1577 class CSRFRequired(object):
1610 class CSRFRequired(object):
1578 """
1611 """
1579 Decorator for authenticating a form
1612 Decorator for authenticating a form
1580
1613
1581 This decorator uses an authorization token stored in the client's
1614 This decorator uses an authorization token stored in the client's
1582 session for prevention of certain Cross-site request forgery (CSRF)
1615 session for prevention of certain Cross-site request forgery (CSRF)
1583 attacks (See
1616 attacks (See
1584 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1617 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1585 information).
1618 information).
1586
1619
1587 For use with the ``secure_form`` helper functions.
1620 For use with the ``secure_form`` helper functions.
1588
1621
1589 """
1622 """
1590 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1623 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1591 self.token = token
1624 self.token = token
1592 self.header = header
1625 self.header = header
1593 self.except_methods = except_methods or []
1626 self.except_methods = except_methods or []
1594
1627
1595 def __call__(self, func):
1628 def __call__(self, func):
1596 return get_cython_compat_decorator(self.__wrapper, func)
1629 return get_cython_compat_decorator(self.__wrapper, func)
1597
1630
1598 def _get_csrf(self, _request):
1631 def _get_csrf(self, _request):
1599 return _request.POST.get(self.token, _request.headers.get(self.header))
1632 return _request.POST.get(self.token, _request.headers.get(self.header))
1600
1633
1601 def check_csrf(self, _request, cur_token):
1634 def check_csrf(self, _request, cur_token):
1602 supplied_token = self._get_csrf(_request)
1635 supplied_token = self._get_csrf(_request)
1603 return supplied_token and supplied_token == cur_token
1636 return supplied_token and supplied_token == cur_token
1604
1637
1605 def _get_request(self):
1638 def _get_request(self):
1606 return get_request(self)
1639 return get_request(self)
1607
1640
1608 def __wrapper(self, func, *fargs, **fkwargs):
1641 def __wrapper(self, func, *fargs, **fkwargs):
1609 request = self._get_request()
1642 request = self._get_request()
1610
1643
1611 if request.method in self.except_methods:
1644 if request.method in self.except_methods:
1612 return func(*fargs, **fkwargs)
1645 return func(*fargs, **fkwargs)
1613
1646
1614 cur_token = get_csrf_token(request.session, save_if_missing=False)
1647 cur_token = get_csrf_token(request.session, save_if_missing=False)
1615 if self.check_csrf(request, cur_token):
1648 if self.check_csrf(request, cur_token):
1616 if request.POST.get(self.token):
1649 if request.POST.get(self.token):
1617 del request.POST[self.token]
1650 del request.POST[self.token]
1618 return func(*fargs, **fkwargs)
1651 return func(*fargs, **fkwargs)
1619 else:
1652 else:
1620 reason = 'token-missing'
1653 reason = 'token-missing'
1621 supplied_token = self._get_csrf(request)
1654 supplied_token = self._get_csrf(request)
1622 if supplied_token and cur_token != supplied_token:
1655 if supplied_token and cur_token != supplied_token:
1623 reason = 'token-mismatch [%s:%s]' % (
1656 reason = 'token-mismatch [%s:%s]' % (
1624 cur_token or ''[:6], supplied_token or ''[:6])
1657 cur_token or ''[:6], supplied_token or ''[:6])
1625
1658
1626 csrf_message = \
1659 csrf_message = \
1627 ("Cross-site request forgery detected, request denied. See "
1660 ("Cross-site request forgery detected, request denied. See "
1628 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1661 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1629 "more information.")
1662 "more information.")
1630 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1663 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1631 'REMOTE_ADDR:%s, HEADERS:%s' % (
1664 'REMOTE_ADDR:%s, HEADERS:%s' % (
1632 request, reason, request.remote_addr, request.headers))
1665 request, reason, request.remote_addr, request.headers))
1633
1666
1634 raise HTTPForbidden(explanation=csrf_message)
1667 raise HTTPForbidden(explanation=csrf_message)
1635
1668
1636
1669
1637 class LoginRequired(object):
1670 class LoginRequired(object):
1638 """
1671 """
1639 Must be logged in to execute this function else
1672 Must be logged in to execute this function else
1640 redirect to login page
1673 redirect to login page
1641
1674
1642 :param api_access: if enabled this checks only for valid auth token
1675 :param api_access: if enabled this checks only for valid auth token
1643 and grants access based on valid token
1676 and grants access based on valid token
1644 """
1677 """
1645 def __init__(self, auth_token_access=None):
1678 def __init__(self, auth_token_access=None):
1646 self.auth_token_access = auth_token_access
1679 self.auth_token_access = auth_token_access
1647 if self.auth_token_access:
1680 if self.auth_token_access:
1648 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1681 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1649 if not valid_type:
1682 if not valid_type:
1650 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1683 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1651 UserApiKeys.ROLES, auth_token_access))
1684 UserApiKeys.ROLES, auth_token_access))
1652
1685
1653 def __call__(self, func):
1686 def __call__(self, func):
1654 return get_cython_compat_decorator(self.__wrapper, func)
1687 return get_cython_compat_decorator(self.__wrapper, func)
1655
1688
1656 def _get_request(self):
1689 def _get_request(self):
1657 return get_request(self)
1690 return get_request(self)
1658
1691
1659 def __wrapper(self, func, *fargs, **fkwargs):
1692 def __wrapper(self, func, *fargs, **fkwargs):
1660 from rhodecode.lib import helpers as h
1693 from rhodecode.lib import helpers as h
1661 cls = fargs[0]
1694 cls = fargs[0]
1662 user = cls._rhodecode_user
1695 user = cls._rhodecode_user
1663 request = self._get_request()
1696 request = self._get_request()
1664 _ = request.translate
1697 _ = request.translate
1665
1698
1666 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1699 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1667 log.debug('Starting login restriction checks for user: %s', user)
1700 log.debug('Starting login restriction checks for user: %s', user)
1668 # check if our IP is allowed
1701 # check if our IP is allowed
1669 ip_access_valid = True
1702 ip_access_valid = True
1670 if not user.ip_allowed:
1703 if not user.ip_allowed:
1671 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1704 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1672 category='warning')
1705 category='warning')
1673 ip_access_valid = False
1706 ip_access_valid = False
1674
1707
1675 # we used stored token that is extract from GET or URL param (if any)
1708 # we used stored token that is extract from GET or URL param (if any)
1676 _auth_token = request.user_auth_token
1709 _auth_token = request.user_auth_token
1677
1710
1678 # check if we used an AUTH_TOKEN and it's a valid one
1711 # check if we used an AUTH_TOKEN and it's a valid one
1679 # defined white-list of controllers which API access will be enabled
1712 # defined white-list of controllers which API access will be enabled
1680 whitelist = None
1713 whitelist = None
1681 if self.auth_token_access:
1714 if self.auth_token_access:
1682 # since this location is allowed by @LoginRequired decorator it's our
1715 # since this location is allowed by @LoginRequired decorator it's our
1683 # only whitelist
1716 # only whitelist
1684 whitelist = [loc]
1717 whitelist = [loc]
1685 auth_token_access_valid = allowed_auth_token_access(
1718 auth_token_access_valid = allowed_auth_token_access(
1686 loc, whitelist=whitelist, auth_token=_auth_token)
1719 loc, whitelist=whitelist, auth_token=_auth_token)
1687
1720
1688 # explicit controller is enabled or API is in our whitelist
1721 # explicit controller is enabled or API is in our whitelist
1689 if auth_token_access_valid:
1722 if auth_token_access_valid:
1690 log.debug('Checking AUTH TOKEN access for %s', cls)
1723 log.debug('Checking AUTH TOKEN access for %s', cls)
1691 db_user = user.get_instance()
1724 db_user = user.get_instance()
1692
1725
1693 if db_user:
1726 if db_user:
1694 if self.auth_token_access:
1727 if self.auth_token_access:
1695 roles = self.auth_token_access
1728 roles = self.auth_token_access
1696 else:
1729 else:
1697 roles = [UserApiKeys.ROLE_HTTP]
1730 roles = [UserApiKeys.ROLE_HTTP]
1698 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1731 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1699 db_user, roles)
1732 db_user, roles)
1700 token_match = db_user.authenticate_by_token(
1733 token_match = db_user.authenticate_by_token(
1701 _auth_token, roles=roles)
1734 _auth_token, roles=roles)
1702 else:
1735 else:
1703 log.debug('Unable to fetch db instance for auth user: %s', user)
1736 log.debug('Unable to fetch db instance for auth user: %s', user)
1704 token_match = False
1737 token_match = False
1705
1738
1706 if _auth_token and token_match:
1739 if _auth_token and token_match:
1707 auth_token_access_valid = True
1740 auth_token_access_valid = True
1708 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1741 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1709 else:
1742 else:
1710 auth_token_access_valid = False
1743 auth_token_access_valid = False
1711 if not _auth_token:
1744 if not _auth_token:
1712 log.debug("AUTH TOKEN *NOT* present in request")
1745 log.debug("AUTH TOKEN *NOT* present in request")
1713 else:
1746 else:
1714 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1747 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1715
1748
1716 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1749 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1717 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1750 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1718 else 'AUTH_TOKEN_AUTH'
1751 else 'AUTH_TOKEN_AUTH'
1719
1752
1720 if ip_access_valid and (
1753 if ip_access_valid and (
1721 user.is_authenticated or auth_token_access_valid):
1754 user.is_authenticated or auth_token_access_valid):
1722 log.info('user %s authenticating with:%s IS authenticated on func %s',
1755 log.info('user %s authenticating with:%s IS authenticated on func %s',
1723 user, reason, loc)
1756 user, reason, loc)
1724
1757
1725 return func(*fargs, **fkwargs)
1758 return func(*fargs, **fkwargs)
1726 else:
1759 else:
1727 log.warning(
1760 log.warning(
1728 'user %s authenticating with:%s NOT authenticated on '
1761 'user %s authenticating with:%s NOT authenticated on '
1729 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1762 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1730 user, reason, loc, ip_access_valid, auth_token_access_valid)
1763 user, reason, loc, ip_access_valid, auth_token_access_valid)
1731 # we preserve the get PARAM
1764 # we preserve the get PARAM
1732 came_from = get_came_from(request)
1765 came_from = get_came_from(request)
1733
1766
1734 log.debug('redirecting to login page with %s', came_from)
1767 log.debug('redirecting to login page with %s', came_from)
1735 raise HTTPFound(
1768 raise HTTPFound(
1736 h.route_path('login', _query={'came_from': came_from}))
1769 h.route_path('login', _query={'came_from': came_from}))
1737
1770
1738
1771
1739 class NotAnonymous(object):
1772 class NotAnonymous(object):
1740 """
1773 """
1741 Must be logged in to execute this function else
1774 Must be logged in to execute this function else
1742 redirect to login page
1775 redirect to login page
1743 """
1776 """
1744
1777
1745 def __call__(self, func):
1778 def __call__(self, func):
1746 return get_cython_compat_decorator(self.__wrapper, func)
1779 return get_cython_compat_decorator(self.__wrapper, func)
1747
1780
1748 def _get_request(self):
1781 def _get_request(self):
1749 return get_request(self)
1782 return get_request(self)
1750
1783
1751 def __wrapper(self, func, *fargs, **fkwargs):
1784 def __wrapper(self, func, *fargs, **fkwargs):
1752 import rhodecode.lib.helpers as h
1785 import rhodecode.lib.helpers as h
1753 cls = fargs[0]
1786 cls = fargs[0]
1754 self.user = cls._rhodecode_user
1787 self.user = cls._rhodecode_user
1755 request = self._get_request()
1788 request = self._get_request()
1756 _ = request.translate
1789 _ = request.translate
1757 log.debug('Checking if user is not anonymous @%s', cls)
1790 log.debug('Checking if user is not anonymous @%s', cls)
1758
1791
1759 anonymous = self.user.username == User.DEFAULT_USER
1792 anonymous = self.user.username == User.DEFAULT_USER
1760
1793
1761 if anonymous:
1794 if anonymous:
1762 came_from = get_came_from(request)
1795 came_from = get_came_from(request)
1763 h.flash(_('You need to be a registered user to '
1796 h.flash(_('You need to be a registered user to '
1764 'perform this action'),
1797 'perform this action'),
1765 category='warning')
1798 category='warning')
1766 raise HTTPFound(
1799 raise HTTPFound(
1767 h.route_path('login', _query={'came_from': came_from}))
1800 h.route_path('login', _query={'came_from': came_from}))
1768 else:
1801 else:
1769 return func(*fargs, **fkwargs)
1802 return func(*fargs, **fkwargs)
1770
1803
1771
1804
1772 class PermsDecorator(object):
1805 class PermsDecorator(object):
1773 """
1806 """
1774 Base class for controller decorators, we extract the current user from
1807 Base class for controller decorators, we extract the current user from
1775 the class itself, which has it stored in base controllers
1808 the class itself, which has it stored in base controllers
1776 """
1809 """
1777
1810
1778 def __init__(self, *required_perms):
1811 def __init__(self, *required_perms):
1779 self.required_perms = set(required_perms)
1812 self.required_perms = set(required_perms)
1780
1813
1781 def __call__(self, func):
1814 def __call__(self, func):
1782 return get_cython_compat_decorator(self.__wrapper, func)
1815 return get_cython_compat_decorator(self.__wrapper, func)
1783
1816
1784 def _get_request(self):
1817 def _get_request(self):
1785 return get_request(self)
1818 return get_request(self)
1786
1819
1787 def __wrapper(self, func, *fargs, **fkwargs):
1820 def __wrapper(self, func, *fargs, **fkwargs):
1788 import rhodecode.lib.helpers as h
1821 import rhodecode.lib.helpers as h
1789 cls = fargs[0]
1822 cls = fargs[0]
1790 _user = cls._rhodecode_user
1823 _user = cls._rhodecode_user
1791 request = self._get_request()
1824 request = self._get_request()
1792 _ = request.translate
1825 _ = request.translate
1793
1826
1794 log.debug('checking %s permissions %s for %s %s',
1827 log.debug('checking %s permissions %s for %s %s',
1795 self.__class__.__name__, self.required_perms, cls, _user)
1828 self.__class__.__name__, self.required_perms, cls, _user)
1796
1829
1797 if self.check_permissions(_user):
1830 if self.check_permissions(_user):
1798 log.debug('Permission granted for %s %s', cls, _user)
1831 log.debug('Permission granted for %s %s', cls, _user)
1799 return func(*fargs, **fkwargs)
1832 return func(*fargs, **fkwargs)
1800
1833
1801 else:
1834 else:
1802 log.debug('Permission denied for %s %s', cls, _user)
1835 log.debug('Permission denied for %s %s', cls, _user)
1803 anonymous = _user.username == User.DEFAULT_USER
1836 anonymous = _user.username == User.DEFAULT_USER
1804
1837
1805 if anonymous:
1838 if anonymous:
1806 came_from = get_came_from(self._get_request())
1839 came_from = get_came_from(self._get_request())
1807 h.flash(_('You need to be signed in to view this page'),
1840 h.flash(_('You need to be signed in to view this page'),
1808 category='warning')
1841 category='warning')
1809 raise HTTPFound(
1842 raise HTTPFound(
1810 h.route_path('login', _query={'came_from': came_from}))
1843 h.route_path('login', _query={'came_from': came_from}))
1811
1844
1812 else:
1845 else:
1813 # redirect with 404 to prevent resource discovery
1846 # redirect with 404 to prevent resource discovery
1814 raise HTTPNotFound()
1847 raise HTTPNotFound()
1815
1848
1816 def check_permissions(self, user):
1849 def check_permissions(self, user):
1817 """Dummy function for overriding"""
1850 """Dummy function for overriding"""
1818 raise NotImplementedError(
1851 raise NotImplementedError(
1819 'You have to write this function in child class')
1852 'You have to write this function in child class')
1820
1853
1821
1854
1822 class HasPermissionAllDecorator(PermsDecorator):
1855 class HasPermissionAllDecorator(PermsDecorator):
1823 """
1856 """
1824 Checks for access permission for all given predicates. All of them
1857 Checks for access permission for all given predicates. All of them
1825 have to be meet in order to fulfill the request
1858 have to be meet in order to fulfill the request
1826 """
1859 """
1827
1860
1828 def check_permissions(self, user):
1861 def check_permissions(self, user):
1829 perms = user.permissions_with_scope({})
1862 perms = user.permissions_with_scope({})
1830 if self.required_perms.issubset(perms['global']):
1863 if self.required_perms.issubset(perms['global']):
1831 return True
1864 return True
1832 return False
1865 return False
1833
1866
1834
1867
1835 class HasPermissionAnyDecorator(PermsDecorator):
1868 class HasPermissionAnyDecorator(PermsDecorator):
1836 """
1869 """
1837 Checks for access permission for any of given predicates. In order to
1870 Checks for access permission for any of given predicates. In order to
1838 fulfill the request any of predicates must be meet
1871 fulfill the request any of predicates must be meet
1839 """
1872 """
1840
1873
1841 def check_permissions(self, user):
1874 def check_permissions(self, user):
1842 perms = user.permissions_with_scope({})
1875 perms = user.permissions_with_scope({})
1843 if self.required_perms.intersection(perms['global']):
1876 if self.required_perms.intersection(perms['global']):
1844 return True
1877 return True
1845 return False
1878 return False
1846
1879
1847
1880
1848 class HasRepoPermissionAllDecorator(PermsDecorator):
1881 class HasRepoPermissionAllDecorator(PermsDecorator):
1849 """
1882 """
1850 Checks for access permission for all given predicates for specific
1883 Checks for access permission for all given predicates for specific
1851 repository. All of them have to be meet in order to fulfill the request
1884 repository. All of them have to be meet in order to fulfill the request
1852 """
1885 """
1853 def _get_repo_name(self):
1886 def _get_repo_name(self):
1854 _request = self._get_request()
1887 _request = self._get_request()
1855 return get_repo_slug(_request)
1888 return get_repo_slug(_request)
1856
1889
1857 def check_permissions(self, user):
1890 def check_permissions(self, user):
1858 perms = user.permissions
1891 perms = user.permissions
1859 repo_name = self._get_repo_name()
1892 repo_name = self._get_repo_name()
1860
1893
1861 try:
1894 try:
1862 user_perms = {perms['repositories'][repo_name]}
1895 user_perms = {perms['repositories'][repo_name]}
1863 except KeyError:
1896 except KeyError:
1864 log.debug('cannot locate repo with name: `%s` in permissions defs',
1897 log.debug('cannot locate repo with name: `%s` in permissions defs',
1865 repo_name)
1898 repo_name)
1866 return False
1899 return False
1867
1900
1868 log.debug('checking `%s` permissions for repo `%s`',
1901 log.debug('checking `%s` permissions for repo `%s`',
1869 user_perms, repo_name)
1902 user_perms, repo_name)
1870 if self.required_perms.issubset(user_perms):
1903 if self.required_perms.issubset(user_perms):
1871 return True
1904 return True
1872 return False
1905 return False
1873
1906
1874
1907
1875 class HasRepoPermissionAnyDecorator(PermsDecorator):
1908 class HasRepoPermissionAnyDecorator(PermsDecorator):
1876 """
1909 """
1877 Checks for access permission for any of given predicates for specific
1910 Checks for access permission for any of given predicates for specific
1878 repository. In order to fulfill the request any of predicates must be meet
1911 repository. In order to fulfill the request any of predicates must be meet
1879 """
1912 """
1880 def _get_repo_name(self):
1913 def _get_repo_name(self):
1881 _request = self._get_request()
1914 _request = self._get_request()
1882 return get_repo_slug(_request)
1915 return get_repo_slug(_request)
1883
1916
1884 def check_permissions(self, user):
1917 def check_permissions(self, user):
1885 perms = user.permissions
1918 perms = user.permissions
1886 repo_name = self._get_repo_name()
1919 repo_name = self._get_repo_name()
1887
1920
1888 try:
1921 try:
1889 user_perms = {perms['repositories'][repo_name]}
1922 user_perms = {perms['repositories'][repo_name]}
1890 except KeyError:
1923 except KeyError:
1891 log.debug(
1924 log.debug(
1892 'cannot locate repo with name: `%s` in permissions defs',
1925 'cannot locate repo with name: `%s` in permissions defs',
1893 repo_name)
1926 repo_name)
1894 return False
1927 return False
1895
1928
1896 log.debug('checking `%s` permissions for repo `%s`',
1929 log.debug('checking `%s` permissions for repo `%s`',
1897 user_perms, repo_name)
1930 user_perms, repo_name)
1898 if self.required_perms.intersection(user_perms):
1931 if self.required_perms.intersection(user_perms):
1899 return True
1932 return True
1900 return False
1933 return False
1901
1934
1902
1935
1903 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1936 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1904 """
1937 """
1905 Checks for access permission for all given predicates for specific
1938 Checks for access permission for all given predicates for specific
1906 repository group. All of them have to be meet in order to
1939 repository group. All of them have to be meet in order to
1907 fulfill the request
1940 fulfill the request
1908 """
1941 """
1909 def _get_repo_group_name(self):
1942 def _get_repo_group_name(self):
1910 _request = self._get_request()
1943 _request = self._get_request()
1911 return get_repo_group_slug(_request)
1944 return get_repo_group_slug(_request)
1912
1945
1913 def check_permissions(self, user):
1946 def check_permissions(self, user):
1914 perms = user.permissions
1947 perms = user.permissions
1915 group_name = self._get_repo_group_name()
1948 group_name = self._get_repo_group_name()
1916 try:
1949 try:
1917 user_perms = {perms['repositories_groups'][group_name]}
1950 user_perms = {perms['repositories_groups'][group_name]}
1918 except KeyError:
1951 except KeyError:
1919 log.debug(
1952 log.debug(
1920 'cannot locate repo group with name: `%s` in permissions defs',
1953 'cannot locate repo group with name: `%s` in permissions defs',
1921 group_name)
1954 group_name)
1922 return False
1955 return False
1923
1956
1924 log.debug('checking `%s` permissions for repo group `%s`',
1957 log.debug('checking `%s` permissions for repo group `%s`',
1925 user_perms, group_name)
1958 user_perms, group_name)
1926 if self.required_perms.issubset(user_perms):
1959 if self.required_perms.issubset(user_perms):
1927 return True
1960 return True
1928 return False
1961 return False
1929
1962
1930
1963
1931 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1964 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1932 """
1965 """
1933 Checks for access permission for any of given predicates for specific
1966 Checks for access permission for any of given predicates for specific
1934 repository group. In order to fulfill the request any
1967 repository group. In order to fulfill the request any
1935 of predicates must be met
1968 of predicates must be met
1936 """
1969 """
1937 def _get_repo_group_name(self):
1970 def _get_repo_group_name(self):
1938 _request = self._get_request()
1971 _request = self._get_request()
1939 return get_repo_group_slug(_request)
1972 return get_repo_group_slug(_request)
1940
1973
1941 def check_permissions(self, user):
1974 def check_permissions(self, user):
1942 perms = user.permissions
1975 perms = user.permissions
1943 group_name = self._get_repo_group_name()
1976 group_name = self._get_repo_group_name()
1944
1977
1945 try:
1978 try:
1946 user_perms = {perms['repositories_groups'][group_name]}
1979 user_perms = {perms['repositories_groups'][group_name]}
1947 except KeyError:
1980 except KeyError:
1948 log.debug(
1981 log.debug(
1949 'cannot locate repo group with name: `%s` in permissions defs',
1982 'cannot locate repo group with name: `%s` in permissions defs',
1950 group_name)
1983 group_name)
1951 return False
1984 return False
1952
1985
1953 log.debug('checking `%s` permissions for repo group `%s`',
1986 log.debug('checking `%s` permissions for repo group `%s`',
1954 user_perms, group_name)
1987 user_perms, group_name)
1955 if self.required_perms.intersection(user_perms):
1988 if self.required_perms.intersection(user_perms):
1956 return True
1989 return True
1957 return False
1990 return False
1958
1991
1959
1992
1960 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1993 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1961 """
1994 """
1962 Checks for access permission for all given predicates for specific
1995 Checks for access permission for all given predicates for specific
1963 user group. All of them have to be meet in order to fulfill the request
1996 user group. All of them have to be meet in order to fulfill the request
1964 """
1997 """
1965 def _get_user_group_name(self):
1998 def _get_user_group_name(self):
1966 _request = self._get_request()
1999 _request = self._get_request()
1967 return get_user_group_slug(_request)
2000 return get_user_group_slug(_request)
1968
2001
1969 def check_permissions(self, user):
2002 def check_permissions(self, user):
1970 perms = user.permissions
2003 perms = user.permissions
1971 group_name = self._get_user_group_name()
2004 group_name = self._get_user_group_name()
1972 try:
2005 try:
1973 user_perms = {perms['user_groups'][group_name]}
2006 user_perms = {perms['user_groups'][group_name]}
1974 except KeyError:
2007 except KeyError:
1975 return False
2008 return False
1976
2009
1977 if self.required_perms.issubset(user_perms):
2010 if self.required_perms.issubset(user_perms):
1978 return True
2011 return True
1979 return False
2012 return False
1980
2013
1981
2014
1982 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
2015 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1983 """
2016 """
1984 Checks for access permission for any of given predicates for specific
2017 Checks for access permission for any of given predicates for specific
1985 user group. In order to fulfill the request any of predicates must be meet
2018 user group. In order to fulfill the request any of predicates must be meet
1986 """
2019 """
1987 def _get_user_group_name(self):
2020 def _get_user_group_name(self):
1988 _request = self._get_request()
2021 _request = self._get_request()
1989 return get_user_group_slug(_request)
2022 return get_user_group_slug(_request)
1990
2023
1991 def check_permissions(self, user):
2024 def check_permissions(self, user):
1992 perms = user.permissions
2025 perms = user.permissions
1993 group_name = self._get_user_group_name()
2026 group_name = self._get_user_group_name()
1994 try:
2027 try:
1995 user_perms = {perms['user_groups'][group_name]}
2028 user_perms = {perms['user_groups'][group_name]}
1996 except KeyError:
2029 except KeyError:
1997 return False
2030 return False
1998
2031
1999 if self.required_perms.intersection(user_perms):
2032 if self.required_perms.intersection(user_perms):
2000 return True
2033 return True
2001 return False
2034 return False
2002
2035
2003
2036
2004 # CHECK FUNCTIONS
2037 # CHECK FUNCTIONS
2005 class PermsFunction(object):
2038 class PermsFunction(object):
2006 """Base function for other check functions"""
2039 """Base function for other check functions"""
2007
2040
2008 def __init__(self, *perms):
2041 def __init__(self, *perms):
2009 self.required_perms = set(perms)
2042 self.required_perms = set(perms)
2010 self.repo_name = None
2043 self.repo_name = None
2011 self.repo_group_name = None
2044 self.repo_group_name = None
2012 self.user_group_name = None
2045 self.user_group_name = None
2013
2046
2014 def __bool__(self):
2047 def __bool__(self):
2015 import inspect
2048 import inspect
2016 frame = inspect.currentframe()
2049 frame = inspect.currentframe()
2017 stack_trace = traceback.format_stack(frame)
2050 stack_trace = traceback.format_stack(frame)
2018 log.error('Checking bool value on a class instance of perm '
2051 log.error('Checking bool value on a class instance of perm '
2019 'function is not allowed: %s', ''.join(stack_trace))
2052 'function is not allowed: %s', ''.join(stack_trace))
2020 # rather than throwing errors, here we always return False so if by
2053 # rather than throwing errors, here we always return False so if by
2021 # accident someone checks truth for just an instance it will always end
2054 # accident someone checks truth for just an instance it will always end
2022 # up in returning False
2055 # up in returning False
2023 return False
2056 return False
2024 __nonzero__ = __bool__
2057 __nonzero__ = __bool__
2025
2058
2026 def __call__(self, check_location='', user=None):
2059 def __call__(self, check_location='', user=None):
2027 if not user:
2060 if not user:
2028 log.debug('Using user attribute from global request')
2061 log.debug('Using user attribute from global request')
2029 request = self._get_request()
2062 request = self._get_request()
2030 user = request.user
2063 user = request.user
2031
2064
2032 # init auth user if not already given
2065 # init auth user if not already given
2033 if not isinstance(user, AuthUser):
2066 if not isinstance(user, AuthUser):
2034 log.debug('Wrapping user %s into AuthUser', user)
2067 log.debug('Wrapping user %s into AuthUser', user)
2035 user = AuthUser(user.user_id)
2068 user = AuthUser(user.user_id)
2036
2069
2037 cls_name = self.__class__.__name__
2070 cls_name = self.__class__.__name__
2038 check_scope = self._get_check_scope(cls_name)
2071 check_scope = self._get_check_scope(cls_name)
2039 check_location = check_location or 'unspecified location'
2072 check_location = check_location or 'unspecified location'
2040
2073
2041 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2074 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2042 self.required_perms, user, check_scope, check_location)
2075 self.required_perms, user, check_scope, check_location)
2043 if not user:
2076 if not user:
2044 log.warning('Empty user given for permission check')
2077 log.warning('Empty user given for permission check')
2045 return False
2078 return False
2046
2079
2047 if self.check_permissions(user):
2080 if self.check_permissions(user):
2048 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2081 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2049 check_scope, user, check_location)
2082 check_scope, user, check_location)
2050 return True
2083 return True
2051
2084
2052 else:
2085 else:
2053 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2086 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2054 check_scope, user, check_location)
2087 check_scope, user, check_location)
2055 return False
2088 return False
2056
2089
2057 def _get_request(self):
2090 def _get_request(self):
2058 return get_request(self)
2091 return get_request(self)
2059
2092
2060 def _get_check_scope(self, cls_name):
2093 def _get_check_scope(self, cls_name):
2061 return {
2094 return {
2062 'HasPermissionAll': 'GLOBAL',
2095 'HasPermissionAll': 'GLOBAL',
2063 'HasPermissionAny': 'GLOBAL',
2096 'HasPermissionAny': 'GLOBAL',
2064 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2097 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2065 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2098 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2066 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2099 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2067 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2100 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2068 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2101 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2069 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2102 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2070 }.get(cls_name, '?:%s' % cls_name)
2103 }.get(cls_name, '?:%s' % cls_name)
2071
2104
2072 def check_permissions(self, user):
2105 def check_permissions(self, user):
2073 """Dummy function for overriding"""
2106 """Dummy function for overriding"""
2074 raise Exception('You have to write this function in child class')
2107 raise Exception('You have to write this function in child class')
2075
2108
2076
2109
2077 class HasPermissionAll(PermsFunction):
2110 class HasPermissionAll(PermsFunction):
2078 def check_permissions(self, user):
2111 def check_permissions(self, user):
2079 perms = user.permissions_with_scope({})
2112 perms = user.permissions_with_scope({})
2080 if self.required_perms.issubset(perms.get('global')):
2113 if self.required_perms.issubset(perms.get('global')):
2081 return True
2114 return True
2082 return False
2115 return False
2083
2116
2084
2117
2085 class HasPermissionAny(PermsFunction):
2118 class HasPermissionAny(PermsFunction):
2086 def check_permissions(self, user):
2119 def check_permissions(self, user):
2087 perms = user.permissions_with_scope({})
2120 perms = user.permissions_with_scope({})
2088 if self.required_perms.intersection(perms.get('global')):
2121 if self.required_perms.intersection(perms.get('global')):
2089 return True
2122 return True
2090 return False
2123 return False
2091
2124
2092
2125
2093 class HasRepoPermissionAll(PermsFunction):
2126 class HasRepoPermissionAll(PermsFunction):
2094 def __call__(self, repo_name=None, check_location='', user=None):
2127 def __call__(self, repo_name=None, check_location='', user=None):
2095 self.repo_name = repo_name
2128 self.repo_name = repo_name
2096 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2129 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2097
2130
2098 def _get_repo_name(self):
2131 def _get_repo_name(self):
2099 if not self.repo_name:
2132 if not self.repo_name:
2100 _request = self._get_request()
2133 _request = self._get_request()
2101 self.repo_name = get_repo_slug(_request)
2134 self.repo_name = get_repo_slug(_request)
2102 return self.repo_name
2135 return self.repo_name
2103
2136
2104 def check_permissions(self, user):
2137 def check_permissions(self, user):
2105 self.repo_name = self._get_repo_name()
2138 self.repo_name = self._get_repo_name()
2106 perms = user.permissions
2139 perms = user.permissions
2107 try:
2140 try:
2108 user_perms = {perms['repositories'][self.repo_name]}
2141 user_perms = {perms['repositories'][self.repo_name]}
2109 except KeyError:
2142 except KeyError:
2110 return False
2143 return False
2111 if self.required_perms.issubset(user_perms):
2144 if self.required_perms.issubset(user_perms):
2112 return True
2145 return True
2113 return False
2146 return False
2114
2147
2115
2148
2116 class HasRepoPermissionAny(PermsFunction):
2149 class HasRepoPermissionAny(PermsFunction):
2117 def __call__(self, repo_name=None, check_location='', user=None):
2150 def __call__(self, repo_name=None, check_location='', user=None):
2118 self.repo_name = repo_name
2151 self.repo_name = repo_name
2119 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2152 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2120
2153
2121 def _get_repo_name(self):
2154 def _get_repo_name(self):
2122 if not self.repo_name:
2155 if not self.repo_name:
2123 _request = self._get_request()
2156 _request = self._get_request()
2124 self.repo_name = get_repo_slug(_request)
2157 self.repo_name = get_repo_slug(_request)
2125 return self.repo_name
2158 return self.repo_name
2126
2159
2127 def check_permissions(self, user):
2160 def check_permissions(self, user):
2128 self.repo_name = self._get_repo_name()
2161 self.repo_name = self._get_repo_name()
2129 perms = user.permissions
2162 perms = user.permissions
2130 try:
2163 try:
2131 user_perms = {perms['repositories'][self.repo_name]}
2164 user_perms = {perms['repositories'][self.repo_name]}
2132 except KeyError:
2165 except KeyError:
2133 return False
2166 return False
2134 if self.required_perms.intersection(user_perms):
2167 if self.required_perms.intersection(user_perms):
2135 return True
2168 return True
2136 return False
2169 return False
2137
2170
2138
2171
2139 class HasRepoGroupPermissionAny(PermsFunction):
2172 class HasRepoGroupPermissionAny(PermsFunction):
2140 def __call__(self, group_name=None, check_location='', user=None):
2173 def __call__(self, group_name=None, check_location='', user=None):
2141 self.repo_group_name = group_name
2174 self.repo_group_name = group_name
2142 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2175 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2143
2176
2144 def check_permissions(self, user):
2177 def check_permissions(self, user):
2145 perms = user.permissions
2178 perms = user.permissions
2146 try:
2179 try:
2147 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2180 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2148 except KeyError:
2181 except KeyError:
2149 return False
2182 return False
2150 if self.required_perms.intersection(user_perms):
2183 if self.required_perms.intersection(user_perms):
2151 return True
2184 return True
2152 return False
2185 return False
2153
2186
2154
2187
2155 class HasRepoGroupPermissionAll(PermsFunction):
2188 class HasRepoGroupPermissionAll(PermsFunction):
2156 def __call__(self, group_name=None, check_location='', user=None):
2189 def __call__(self, group_name=None, check_location='', user=None):
2157 self.repo_group_name = group_name
2190 self.repo_group_name = group_name
2158 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2191 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2159
2192
2160 def check_permissions(self, user):
2193 def check_permissions(self, user):
2161 perms = user.permissions
2194 perms = user.permissions
2162 try:
2195 try:
2163 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2196 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2164 except KeyError:
2197 except KeyError:
2165 return False
2198 return False
2166 if self.required_perms.issubset(user_perms):
2199 if self.required_perms.issubset(user_perms):
2167 return True
2200 return True
2168 return False
2201 return False
2169
2202
2170
2203
2171 class HasUserGroupPermissionAny(PermsFunction):
2204 class HasUserGroupPermissionAny(PermsFunction):
2172 def __call__(self, user_group_name=None, check_location='', user=None):
2205 def __call__(self, user_group_name=None, check_location='', user=None):
2173 self.user_group_name = user_group_name
2206 self.user_group_name = user_group_name
2174 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2207 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2175
2208
2176 def check_permissions(self, user):
2209 def check_permissions(self, user):
2177 perms = user.permissions
2210 perms = user.permissions
2178 try:
2211 try:
2179 user_perms = {perms['user_groups'][self.user_group_name]}
2212 user_perms = {perms['user_groups'][self.user_group_name]}
2180 except KeyError:
2213 except KeyError:
2181 return False
2214 return False
2182 if self.required_perms.intersection(user_perms):
2215 if self.required_perms.intersection(user_perms):
2183 return True
2216 return True
2184 return False
2217 return False
2185
2218
2186
2219
2187 class HasUserGroupPermissionAll(PermsFunction):
2220 class HasUserGroupPermissionAll(PermsFunction):
2188 def __call__(self, user_group_name=None, check_location='', user=None):
2221 def __call__(self, user_group_name=None, check_location='', user=None):
2189 self.user_group_name = user_group_name
2222 self.user_group_name = user_group_name
2190 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2223 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2191
2224
2192 def check_permissions(self, user):
2225 def check_permissions(self, user):
2193 perms = user.permissions
2226 perms = user.permissions
2194 try:
2227 try:
2195 user_perms = {perms['user_groups'][self.user_group_name]}
2228 user_perms = {perms['user_groups'][self.user_group_name]}
2196 except KeyError:
2229 except KeyError:
2197 return False
2230 return False
2198 if self.required_perms.issubset(user_perms):
2231 if self.required_perms.issubset(user_perms):
2199 return True
2232 return True
2200 return False
2233 return False
2201
2234
2202
2235
2203 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2236 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2204 class HasPermissionAnyMiddleware(object):
2237 class HasPermissionAnyMiddleware(object):
2205 def __init__(self, *perms):
2238 def __init__(self, *perms):
2206 self.required_perms = set(perms)
2239 self.required_perms = set(perms)
2207
2240
2208 def __call__(self, auth_user, repo_name):
2241 def __call__(self, auth_user, repo_name):
2209 # repo_name MUST be unicode, since we handle keys in permission
2242 # repo_name MUST be unicode, since we handle keys in permission
2210 # dict by unicode
2243 # dict by unicode
2211 repo_name = safe_unicode(repo_name)
2244 repo_name = safe_unicode(repo_name)
2212 log.debug(
2245 log.debug(
2213 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2246 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2214 self.required_perms, auth_user, repo_name)
2247 self.required_perms, auth_user, repo_name)
2215
2248
2216 if self.check_permissions(auth_user, repo_name):
2249 if self.check_permissions(auth_user, repo_name):
2217 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2250 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2218 repo_name, auth_user, 'PermissionMiddleware')
2251 repo_name, auth_user, 'PermissionMiddleware')
2219 return True
2252 return True
2220
2253
2221 else:
2254 else:
2222 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2255 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2223 repo_name, auth_user, 'PermissionMiddleware')
2256 repo_name, auth_user, 'PermissionMiddleware')
2224 return False
2257 return False
2225
2258
2226 def check_permissions(self, user, repo_name):
2259 def check_permissions(self, user, repo_name):
2227 perms = user.permissions_with_scope({'repo_name': repo_name})
2260 perms = user.permissions_with_scope({'repo_name': repo_name})
2228
2261
2229 try:
2262 try:
2230 user_perms = {perms['repositories'][repo_name]}
2263 user_perms = {perms['repositories'][repo_name]}
2231 except Exception:
2264 except Exception:
2232 log.exception('Error while accessing user permissions')
2265 log.exception('Error while accessing user permissions')
2233 return False
2266 return False
2234
2267
2235 if self.required_perms.intersection(user_perms):
2268 if self.required_perms.intersection(user_perms):
2236 return True
2269 return True
2237 return False
2270 return False
2238
2271
2239
2272
2240 # SPECIAL VERSION TO HANDLE API AUTH
2273 # SPECIAL VERSION TO HANDLE API AUTH
2241 class _BaseApiPerm(object):
2274 class _BaseApiPerm(object):
2242 def __init__(self, *perms):
2275 def __init__(self, *perms):
2243 self.required_perms = set(perms)
2276 self.required_perms = set(perms)
2244
2277
2245 def __call__(self, check_location=None, user=None, repo_name=None,
2278 def __call__(self, check_location=None, user=None, repo_name=None,
2246 group_name=None, user_group_name=None):
2279 group_name=None, user_group_name=None):
2247 cls_name = self.__class__.__name__
2280 cls_name = self.__class__.__name__
2248 check_scope = 'global:%s' % (self.required_perms,)
2281 check_scope = 'global:%s' % (self.required_perms,)
2249 if repo_name:
2282 if repo_name:
2250 check_scope += ', repo_name:%s' % (repo_name,)
2283 check_scope += ', repo_name:%s' % (repo_name,)
2251
2284
2252 if group_name:
2285 if group_name:
2253 check_scope += ', repo_group_name:%s' % (group_name,)
2286 check_scope += ', repo_group_name:%s' % (group_name,)
2254
2287
2255 if user_group_name:
2288 if user_group_name:
2256 check_scope += ', user_group_name:%s' % (user_group_name,)
2289 check_scope += ', user_group_name:%s' % (user_group_name,)
2257
2290
2258 log.debug('checking cls:%s %s %s @ %s',
2291 log.debug('checking cls:%s %s %s @ %s',
2259 cls_name, self.required_perms, check_scope, check_location)
2292 cls_name, self.required_perms, check_scope, check_location)
2260 if not user:
2293 if not user:
2261 log.debug('Empty User passed into arguments')
2294 log.debug('Empty User passed into arguments')
2262 return False
2295 return False
2263
2296
2264 # process user
2297 # process user
2265 if not isinstance(user, AuthUser):
2298 if not isinstance(user, AuthUser):
2266 user = AuthUser(user.user_id)
2299 user = AuthUser(user.user_id)
2267 if not check_location:
2300 if not check_location:
2268 check_location = 'unspecified'
2301 check_location = 'unspecified'
2269 if self.check_permissions(user.permissions, repo_name, group_name,
2302 if self.check_permissions(user.permissions, repo_name, group_name,
2270 user_group_name):
2303 user_group_name):
2271 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2304 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2272 check_scope, user, check_location)
2305 check_scope, user, check_location)
2273 return True
2306 return True
2274
2307
2275 else:
2308 else:
2276 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2309 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2277 check_scope, user, check_location)
2310 check_scope, user, check_location)
2278 return False
2311 return False
2279
2312
2280 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2313 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2281 user_group_name=None):
2314 user_group_name=None):
2282 """
2315 """
2283 implement in child class should return True if permissions are ok,
2316 implement in child class should return True if permissions are ok,
2284 False otherwise
2317 False otherwise
2285
2318
2286 :param perm_defs: dict with permission definitions
2319 :param perm_defs: dict with permission definitions
2287 :param repo_name: repo name
2320 :param repo_name: repo name
2288 """
2321 """
2289 raise NotImplementedError()
2322 raise NotImplementedError()
2290
2323
2291
2324
2292 class HasPermissionAllApi(_BaseApiPerm):
2325 class HasPermissionAllApi(_BaseApiPerm):
2293 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2326 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2294 user_group_name=None):
2327 user_group_name=None):
2295 if self.required_perms.issubset(perm_defs.get('global')):
2328 if self.required_perms.issubset(perm_defs.get('global')):
2296 return True
2329 return True
2297 return False
2330 return False
2298
2331
2299
2332
2300 class HasPermissionAnyApi(_BaseApiPerm):
2333 class HasPermissionAnyApi(_BaseApiPerm):
2301 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2334 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2302 user_group_name=None):
2335 user_group_name=None):
2303 if self.required_perms.intersection(perm_defs.get('global')):
2336 if self.required_perms.intersection(perm_defs.get('global')):
2304 return True
2337 return True
2305 return False
2338 return False
2306
2339
2307
2340
2308 class HasRepoPermissionAllApi(_BaseApiPerm):
2341 class HasRepoPermissionAllApi(_BaseApiPerm):
2309 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2342 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2310 user_group_name=None):
2343 user_group_name=None):
2311 try:
2344 try:
2312 _user_perms = {perm_defs['repositories'][repo_name]}
2345 _user_perms = {perm_defs['repositories'][repo_name]}
2313 except KeyError:
2346 except KeyError:
2314 log.warning(traceback.format_exc())
2347 log.warning(traceback.format_exc())
2315 return False
2348 return False
2316 if self.required_perms.issubset(_user_perms):
2349 if self.required_perms.issubset(_user_perms):
2317 return True
2350 return True
2318 return False
2351 return False
2319
2352
2320
2353
2321 class HasRepoPermissionAnyApi(_BaseApiPerm):
2354 class HasRepoPermissionAnyApi(_BaseApiPerm):
2322 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2355 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2323 user_group_name=None):
2356 user_group_name=None):
2324 try:
2357 try:
2325 _user_perms = {perm_defs['repositories'][repo_name]}
2358 _user_perms = {perm_defs['repositories'][repo_name]}
2326 except KeyError:
2359 except KeyError:
2327 log.warning(traceback.format_exc())
2360 log.warning(traceback.format_exc())
2328 return False
2361 return False
2329 if self.required_perms.intersection(_user_perms):
2362 if self.required_perms.intersection(_user_perms):
2330 return True
2363 return True
2331 return False
2364 return False
2332
2365
2333
2366
2334 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2367 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2335 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2368 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2336 user_group_name=None):
2369 user_group_name=None):
2337 try:
2370 try:
2338 _user_perms = {perm_defs['repositories_groups'][group_name]}
2371 _user_perms = {perm_defs['repositories_groups'][group_name]}
2339 except KeyError:
2372 except KeyError:
2340 log.warning(traceback.format_exc())
2373 log.warning(traceback.format_exc())
2341 return False
2374 return False
2342 if self.required_perms.intersection(_user_perms):
2375 if self.required_perms.intersection(_user_perms):
2343 return True
2376 return True
2344 return False
2377 return False
2345
2378
2346
2379
2347 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2380 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2348 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2381 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2349 user_group_name=None):
2382 user_group_name=None):
2350 try:
2383 try:
2351 _user_perms = {perm_defs['repositories_groups'][group_name]}
2384 _user_perms = {perm_defs['repositories_groups'][group_name]}
2352 except KeyError:
2385 except KeyError:
2353 log.warning(traceback.format_exc())
2386 log.warning(traceback.format_exc())
2354 return False
2387 return False
2355 if self.required_perms.issubset(_user_perms):
2388 if self.required_perms.issubset(_user_perms):
2356 return True
2389 return True
2357 return False
2390 return False
2358
2391
2359
2392
2360 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2393 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2361 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2394 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2362 user_group_name=None):
2395 user_group_name=None):
2363 try:
2396 try:
2364 _user_perms = {perm_defs['user_groups'][user_group_name]}
2397 _user_perms = {perm_defs['user_groups'][user_group_name]}
2365 except KeyError:
2398 except KeyError:
2366 log.warning(traceback.format_exc())
2399 log.warning(traceback.format_exc())
2367 return False
2400 return False
2368 if self.required_perms.intersection(_user_perms):
2401 if self.required_perms.intersection(_user_perms):
2369 return True
2402 return True
2370 return False
2403 return False
2371
2404
2372
2405
2373 def check_ip_access(source_ip, allowed_ips=None):
2406 def check_ip_access(source_ip, allowed_ips=None):
2374 """
2407 """
2375 Checks if source_ip is a subnet of any of allowed_ips.
2408 Checks if source_ip is a subnet of any of allowed_ips.
2376
2409
2377 :param source_ip:
2410 :param source_ip:
2378 :param allowed_ips: list of allowed ips together with mask
2411 :param allowed_ips: list of allowed ips together with mask
2379 """
2412 """
2380 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2413 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2381 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2414 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2382 if isinstance(allowed_ips, (tuple, list, set)):
2415 if isinstance(allowed_ips, (tuple, list, set)):
2383 for ip in allowed_ips:
2416 for ip in allowed_ips:
2384 ip = safe_unicode(ip)
2417 ip = safe_unicode(ip)
2385 try:
2418 try:
2386 network_address = ipaddress.ip_network(ip, strict=False)
2419 network_address = ipaddress.ip_network(ip, strict=False)
2387 if source_ip_address in network_address:
2420 if source_ip_address in network_address:
2388 log.debug('IP %s is network %s', source_ip_address, network_address)
2421 log.debug('IP %s is network %s', source_ip_address, network_address)
2389 return True
2422 return True
2390 # for any case we cannot determine the IP, don't crash just
2423 # for any case we cannot determine the IP, don't crash just
2391 # skip it and log as error, we want to say forbidden still when
2424 # skip it and log as error, we want to say forbidden still when
2392 # sending bad IP
2425 # sending bad IP
2393 except Exception:
2426 except Exception:
2394 log.error(traceback.format_exc())
2427 log.error(traceback.format_exc())
2395 continue
2428 continue
2396 return False
2429 return False
2397
2430
2398
2431
2399 def get_cython_compat_decorator(wrapper, func):
2432 def get_cython_compat_decorator(wrapper, func):
2400 """
2433 """
2401 Creates a cython compatible decorator. The previously used
2434 Creates a cython compatible decorator. The previously used
2402 decorator.decorator() function seems to be incompatible with cython.
2435 decorator.decorator() function seems to be incompatible with cython.
2403
2436
2404 :param wrapper: __wrapper method of the decorator class
2437 :param wrapper: __wrapper method of the decorator class
2405 :param func: decorated function
2438 :param func: decorated function
2406 """
2439 """
2407 @wraps(func)
2440 @wraps(func)
2408 def local_wrapper(*args, **kwds):
2441 def local_wrapper(*args, **kwds):
2409 return wrapper(func, *args, **kwds)
2442 return wrapper(func, *args, **kwds)
2410 local_wrapper.__wrapped__ = func
2443 local_wrapper.__wrapped__ = func
2411 return local_wrapper
2444 return local_wrapper
2412
2445
2413
2446
@@ -1,5517 +1,5576 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_all_user_ids(cls, only_active=True):
1028 def get_all_user_ids(cls, only_active=True):
1029 """
1029 """
1030 Returns all users IDs
1030 Returns all users IDs
1031 """
1031 """
1032 qry = Session().query(User.user_id)
1032 qry = Session().query(User.user_id)
1033
1033
1034 if only_active:
1034 if only_active:
1035 qry = qry.filter(User.active == true())
1035 qry = qry.filter(User.active == true())
1036 return [x.user_id for x in qry]
1036 return [x.user_id for x in qry]
1037
1037
1038 @classmethod
1038 @classmethod
1039 def get_default_user(cls, cache=False, refresh=False):
1039 def get_default_user(cls, cache=False, refresh=False):
1040 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1040 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1041 if user is None:
1041 if user is None:
1042 raise Exception('FATAL: Missing default account!')
1042 raise Exception('FATAL: Missing default account!')
1043 if refresh:
1043 if refresh:
1044 # The default user might be based on outdated state which
1044 # The default user might be based on outdated state which
1045 # has been loaded from the cache.
1045 # has been loaded from the cache.
1046 # A call to refresh() ensures that the
1046 # A call to refresh() ensures that the
1047 # latest state from the database is used.
1047 # latest state from the database is used.
1048 Session().refresh(user)
1048 Session().refresh(user)
1049 return user
1049 return user
1050
1050
1051 def _get_default_perms(self, user, suffix=''):
1051 def _get_default_perms(self, user, suffix=''):
1052 from rhodecode.model.permission import PermissionModel
1052 from rhodecode.model.permission import PermissionModel
1053 return PermissionModel().get_default_perms(user.user_perms, suffix)
1053 return PermissionModel().get_default_perms(user.user_perms, suffix)
1054
1054
1055 def get_default_perms(self, suffix=''):
1055 def get_default_perms(self, suffix=''):
1056 return self._get_default_perms(self, suffix)
1056 return self._get_default_perms(self, suffix)
1057
1057
1058 def get_api_data(self, include_secrets=False, details='full'):
1058 def get_api_data(self, include_secrets=False, details='full'):
1059 """
1059 """
1060 Common function for generating user related data for API
1060 Common function for generating user related data for API
1061
1061
1062 :param include_secrets: By default secrets in the API data will be replaced
1062 :param include_secrets: By default secrets in the API data will be replaced
1063 by a placeholder value to prevent exposing this data by accident. In case
1063 by a placeholder value to prevent exposing this data by accident. In case
1064 this data shall be exposed, set this flag to ``True``.
1064 this data shall be exposed, set this flag to ``True``.
1065
1065
1066 :param details: details can be 'basic|full' basic gives only a subset of
1066 :param details: details can be 'basic|full' basic gives only a subset of
1067 the available user information that includes user_id, name and emails.
1067 the available user information that includes user_id, name and emails.
1068 """
1068 """
1069 user = self
1069 user = self
1070 user_data = self.user_data
1070 user_data = self.user_data
1071 data = {
1071 data = {
1072 'user_id': user.user_id,
1072 'user_id': user.user_id,
1073 'username': user.username,
1073 'username': user.username,
1074 'firstname': user.name,
1074 'firstname': user.name,
1075 'lastname': user.lastname,
1075 'lastname': user.lastname,
1076 'description': user.description,
1076 'description': user.description,
1077 'email': user.email,
1077 'email': user.email,
1078 'emails': user.emails,
1078 'emails': user.emails,
1079 }
1079 }
1080 if details == 'basic':
1080 if details == 'basic':
1081 return data
1081 return data
1082
1082
1083 auth_token_length = 40
1083 auth_token_length = 40
1084 auth_token_replacement = '*' * auth_token_length
1084 auth_token_replacement = '*' * auth_token_length
1085
1085
1086 extras = {
1086 extras = {
1087 'auth_tokens': [auth_token_replacement],
1087 'auth_tokens': [auth_token_replacement],
1088 'active': user.active,
1088 'active': user.active,
1089 'admin': user.admin,
1089 'admin': user.admin,
1090 'extern_type': user.extern_type,
1090 'extern_type': user.extern_type,
1091 'extern_name': user.extern_name,
1091 'extern_name': user.extern_name,
1092 'last_login': user.last_login,
1092 'last_login': user.last_login,
1093 'last_activity': user.last_activity,
1093 'last_activity': user.last_activity,
1094 'ip_addresses': user.ip_addresses,
1094 'ip_addresses': user.ip_addresses,
1095 'language': user_data.get('language')
1095 'language': user_data.get('language')
1096 }
1096 }
1097 data.update(extras)
1097 data.update(extras)
1098
1098
1099 if include_secrets:
1099 if include_secrets:
1100 data['auth_tokens'] = user.auth_tokens
1100 data['auth_tokens'] = user.auth_tokens
1101 return data
1101 return data
1102
1102
1103 def __json__(self):
1103 def __json__(self):
1104 data = {
1104 data = {
1105 'full_name': self.full_name,
1105 'full_name': self.full_name,
1106 'full_name_or_username': self.full_name_or_username,
1106 'full_name_or_username': self.full_name_or_username,
1107 'short_contact': self.short_contact,
1107 'short_contact': self.short_contact,
1108 'full_contact': self.full_contact,
1108 'full_contact': self.full_contact,
1109 }
1109 }
1110 data.update(self.get_api_data())
1110 data.update(self.get_api_data())
1111 return data
1111 return data
1112
1112
1113
1113
1114 class UserApiKeys(Base, BaseModel):
1114 class UserApiKeys(Base, BaseModel):
1115 __tablename__ = 'user_api_keys'
1115 __tablename__ = 'user_api_keys'
1116 __table_args__ = (
1116 __table_args__ = (
1117 Index('uak_api_key_idx', 'api_key'),
1117 Index('uak_api_key_idx', 'api_key'),
1118 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1118 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1119 base_table_args
1119 base_table_args
1120 )
1120 )
1121 __mapper_args__ = {}
1121 __mapper_args__ = {}
1122
1122
1123 # ApiKey role
1123 # ApiKey role
1124 ROLE_ALL = 'token_role_all'
1124 ROLE_ALL = 'token_role_all'
1125 ROLE_HTTP = 'token_role_http'
1125 ROLE_HTTP = 'token_role_http'
1126 ROLE_VCS = 'token_role_vcs'
1126 ROLE_VCS = 'token_role_vcs'
1127 ROLE_API = 'token_role_api'
1127 ROLE_API = 'token_role_api'
1128 ROLE_FEED = 'token_role_feed'
1128 ROLE_FEED = 'token_role_feed'
1129 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1129 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1130 ROLE_PASSWORD_RESET = 'token_password_reset'
1130 ROLE_PASSWORD_RESET = 'token_password_reset'
1131
1131
1132 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1132 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1133
1133
1134 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1134 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1136 api_key = Column("api_key", String(255), nullable=False, unique=True)
1136 api_key = Column("api_key", String(255), nullable=False, unique=True)
1137 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1137 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1138 expires = Column('expires', Float(53), nullable=False)
1138 expires = Column('expires', Float(53), nullable=False)
1139 role = Column('role', String(255), nullable=True)
1139 role = Column('role', String(255), nullable=True)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1141
1141
1142 # scope columns
1142 # scope columns
1143 repo_id = Column(
1143 repo_id = Column(
1144 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1144 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1145 nullable=True, unique=None, default=None)
1145 nullable=True, unique=None, default=None)
1146 repo = relationship('Repository', lazy='joined')
1146 repo = relationship('Repository', lazy='joined')
1147
1147
1148 repo_group_id = Column(
1148 repo_group_id = Column(
1149 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1149 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1150 nullable=True, unique=None, default=None)
1150 nullable=True, unique=None, default=None)
1151 repo_group = relationship('RepoGroup', lazy='joined')
1151 repo_group = relationship('RepoGroup', lazy='joined')
1152
1152
1153 user = relationship('User', lazy='joined')
1153 user = relationship('User', lazy='joined')
1154
1154
1155 def __unicode__(self):
1155 def __unicode__(self):
1156 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1156 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1157
1157
1158 def __json__(self):
1158 def __json__(self):
1159 data = {
1159 data = {
1160 'auth_token': self.api_key,
1160 'auth_token': self.api_key,
1161 'role': self.role,
1161 'role': self.role,
1162 'scope': self.scope_humanized,
1162 'scope': self.scope_humanized,
1163 'expired': self.expired
1163 'expired': self.expired
1164 }
1164 }
1165 return data
1165 return data
1166
1166
1167 def get_api_data(self, include_secrets=False):
1167 def get_api_data(self, include_secrets=False):
1168 data = self.__json__()
1168 data = self.__json__()
1169 if include_secrets:
1169 if include_secrets:
1170 return data
1170 return data
1171 else:
1171 else:
1172 data['auth_token'] = self.token_obfuscated
1172 data['auth_token'] = self.token_obfuscated
1173 return data
1173 return data
1174
1174
1175 @hybrid_property
1175 @hybrid_property
1176 def description_safe(self):
1176 def description_safe(self):
1177 from rhodecode.lib import helpers as h
1177 from rhodecode.lib import helpers as h
1178 return h.escape(self.description)
1178 return h.escape(self.description)
1179
1179
1180 @property
1180 @property
1181 def expired(self):
1181 def expired(self):
1182 if self.expires == -1:
1182 if self.expires == -1:
1183 return False
1183 return False
1184 return time.time() > self.expires
1184 return time.time() > self.expires
1185
1185
1186 @classmethod
1186 @classmethod
1187 def _get_role_name(cls, role):
1187 def _get_role_name(cls, role):
1188 return {
1188 return {
1189 cls.ROLE_ALL: _('all'),
1189 cls.ROLE_ALL: _('all'),
1190 cls.ROLE_HTTP: _('http/web interface'),
1190 cls.ROLE_HTTP: _('http/web interface'),
1191 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1191 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1192 cls.ROLE_API: _('api calls'),
1192 cls.ROLE_API: _('api calls'),
1193 cls.ROLE_FEED: _('feed access'),
1193 cls.ROLE_FEED: _('feed access'),
1194 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1194 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1195 }.get(role, role)
1195 }.get(role, role)
1196
1196
1197 @property
1197 @property
1198 def role_humanized(self):
1198 def role_humanized(self):
1199 return self._get_role_name(self.role)
1199 return self._get_role_name(self.role)
1200
1200
1201 def _get_scope(self):
1201 def _get_scope(self):
1202 if self.repo:
1202 if self.repo:
1203 return 'Repository: {}'.format(self.repo.repo_name)
1203 return 'Repository: {}'.format(self.repo.repo_name)
1204 if self.repo_group:
1204 if self.repo_group:
1205 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1205 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1206 return 'Global'
1206 return 'Global'
1207
1207
1208 @property
1208 @property
1209 def scope_humanized(self):
1209 def scope_humanized(self):
1210 return self._get_scope()
1210 return self._get_scope()
1211
1211
1212 @property
1212 @property
1213 def token_obfuscated(self):
1213 def token_obfuscated(self):
1214 if self.api_key:
1214 if self.api_key:
1215 return self.api_key[:4] + "****"
1215 return self.api_key[:4] + "****"
1216
1216
1217
1217
1218 class UserEmailMap(Base, BaseModel):
1218 class UserEmailMap(Base, BaseModel):
1219 __tablename__ = 'user_email_map'
1219 __tablename__ = 'user_email_map'
1220 __table_args__ = (
1220 __table_args__ = (
1221 Index('uem_email_idx', 'email'),
1221 Index('uem_email_idx', 'email'),
1222 UniqueConstraint('email'),
1222 UniqueConstraint('email'),
1223 base_table_args
1223 base_table_args
1224 )
1224 )
1225 __mapper_args__ = {}
1225 __mapper_args__ = {}
1226
1226
1227 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1227 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1229 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1229 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1230 user = relationship('User', lazy='joined')
1230 user = relationship('User', lazy='joined')
1231
1231
1232 @validates('_email')
1232 @validates('_email')
1233 def validate_email(self, key, email):
1233 def validate_email(self, key, email):
1234 # check if this email is not main one
1234 # check if this email is not main one
1235 main_email = Session().query(User).filter(User.email == email).scalar()
1235 main_email = Session().query(User).filter(User.email == email).scalar()
1236 if main_email is not None:
1236 if main_email is not None:
1237 raise AttributeError('email %s is present is user table' % email)
1237 raise AttributeError('email %s is present is user table' % email)
1238 return email
1238 return email
1239
1239
1240 @hybrid_property
1240 @hybrid_property
1241 def email(self):
1241 def email(self):
1242 return self._email
1242 return self._email
1243
1243
1244 @email.setter
1244 @email.setter
1245 def email(self, val):
1245 def email(self, val):
1246 self._email = val.lower() if val else None
1246 self._email = val.lower() if val else None
1247
1247
1248
1248
1249 class UserIpMap(Base, BaseModel):
1249 class UserIpMap(Base, BaseModel):
1250 __tablename__ = 'user_ip_map'
1250 __tablename__ = 'user_ip_map'
1251 __table_args__ = (
1251 __table_args__ = (
1252 UniqueConstraint('user_id', 'ip_addr'),
1252 UniqueConstraint('user_id', 'ip_addr'),
1253 base_table_args
1253 base_table_args
1254 )
1254 )
1255 __mapper_args__ = {}
1255 __mapper_args__ = {}
1256
1256
1257 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1259 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1259 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1260 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1260 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1261 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1261 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1262 user = relationship('User', lazy='joined')
1262 user = relationship('User', lazy='joined')
1263
1263
1264 @hybrid_property
1264 @hybrid_property
1265 def description_safe(self):
1265 def description_safe(self):
1266 from rhodecode.lib import helpers as h
1266 from rhodecode.lib import helpers as h
1267 return h.escape(self.description)
1267 return h.escape(self.description)
1268
1268
1269 @classmethod
1269 @classmethod
1270 def _get_ip_range(cls, ip_addr):
1270 def _get_ip_range(cls, ip_addr):
1271 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1271 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1272 return [str(net.network_address), str(net.broadcast_address)]
1272 return [str(net.network_address), str(net.broadcast_address)]
1273
1273
1274 def __json__(self):
1274 def __json__(self):
1275 return {
1275 return {
1276 'ip_addr': self.ip_addr,
1276 'ip_addr': self.ip_addr,
1277 'ip_range': self._get_ip_range(self.ip_addr),
1277 'ip_range': self._get_ip_range(self.ip_addr),
1278 }
1278 }
1279
1279
1280 def __unicode__(self):
1280 def __unicode__(self):
1281 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1281 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1282 self.user_id, self.ip_addr)
1282 self.user_id, self.ip_addr)
1283
1283
1284
1284
1285 class UserSshKeys(Base, BaseModel):
1285 class UserSshKeys(Base, BaseModel):
1286 __tablename__ = 'user_ssh_keys'
1286 __tablename__ = 'user_ssh_keys'
1287 __table_args__ = (
1287 __table_args__ = (
1288 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1288 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1289
1289
1290 UniqueConstraint('ssh_key_fingerprint'),
1290 UniqueConstraint('ssh_key_fingerprint'),
1291
1291
1292 base_table_args
1292 base_table_args
1293 )
1293 )
1294 __mapper_args__ = {}
1294 __mapper_args__ = {}
1295
1295
1296 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1296 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1297 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1297 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1298 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1298 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1299
1299
1300 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1300 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1301
1301
1302 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1302 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1303 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1303 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1305
1305
1306 user = relationship('User', lazy='joined')
1306 user = relationship('User', lazy='joined')
1307
1307
1308 def __json__(self):
1308 def __json__(self):
1309 data = {
1309 data = {
1310 'ssh_fingerprint': self.ssh_key_fingerprint,
1310 'ssh_fingerprint': self.ssh_key_fingerprint,
1311 'description': self.description,
1311 'description': self.description,
1312 'created_on': self.created_on
1312 'created_on': self.created_on
1313 }
1313 }
1314 return data
1314 return data
1315
1315
1316 def get_api_data(self):
1316 def get_api_data(self):
1317 data = self.__json__()
1317 data = self.__json__()
1318 return data
1318 return data
1319
1319
1320
1320
1321 class UserLog(Base, BaseModel):
1321 class UserLog(Base, BaseModel):
1322 __tablename__ = 'user_logs'
1322 __tablename__ = 'user_logs'
1323 __table_args__ = (
1323 __table_args__ = (
1324 base_table_args,
1324 base_table_args,
1325 )
1325 )
1326
1326
1327 VERSION_1 = 'v1'
1327 VERSION_1 = 'v1'
1328 VERSION_2 = 'v2'
1328 VERSION_2 = 'v2'
1329 VERSIONS = [VERSION_1, VERSION_2]
1329 VERSIONS = [VERSION_1, VERSION_2]
1330
1330
1331 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1333 username = Column("username", String(255), nullable=True, unique=None, default=None)
1333 username = Column("username", String(255), nullable=True, unique=None, default=None)
1334 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1334 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1335 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1335 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1336 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1336 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1337 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1337 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1338 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1338 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1339
1339
1340 version = Column("version", String(255), nullable=True, default=VERSION_1)
1340 version = Column("version", String(255), nullable=True, default=VERSION_1)
1341 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1341 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1342 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1342 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1343
1343
1344 def __unicode__(self):
1344 def __unicode__(self):
1345 return u"<%s('id:%s:%s')>" % (
1345 return u"<%s('id:%s:%s')>" % (
1346 self.__class__.__name__, self.repository_name, self.action)
1346 self.__class__.__name__, self.repository_name, self.action)
1347
1347
1348 def __json__(self):
1348 def __json__(self):
1349 return {
1349 return {
1350 'user_id': self.user_id,
1350 'user_id': self.user_id,
1351 'username': self.username,
1351 'username': self.username,
1352 'repository_id': self.repository_id,
1352 'repository_id': self.repository_id,
1353 'repository_name': self.repository_name,
1353 'repository_name': self.repository_name,
1354 'user_ip': self.user_ip,
1354 'user_ip': self.user_ip,
1355 'action_date': self.action_date,
1355 'action_date': self.action_date,
1356 'action': self.action,
1356 'action': self.action,
1357 }
1357 }
1358
1358
1359 @hybrid_property
1359 @hybrid_property
1360 def entry_id(self):
1360 def entry_id(self):
1361 return self.user_log_id
1361 return self.user_log_id
1362
1362
1363 @property
1363 @property
1364 def action_as_day(self):
1364 def action_as_day(self):
1365 return datetime.date(*self.action_date.timetuple()[:3])
1365 return datetime.date(*self.action_date.timetuple()[:3])
1366
1366
1367 user = relationship('User')
1367 user = relationship('User')
1368 repository = relationship('Repository', cascade='')
1368 repository = relationship('Repository', cascade='')
1369
1369
1370
1370
1371 class UserGroup(Base, BaseModel):
1371 class UserGroup(Base, BaseModel):
1372 __tablename__ = 'users_groups'
1372 __tablename__ = 'users_groups'
1373 __table_args__ = (
1373 __table_args__ = (
1374 base_table_args,
1374 base_table_args,
1375 )
1375 )
1376
1376
1377 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1377 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1378 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1378 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1379 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1379 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1380 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1380 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1381 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1381 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1382 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1382 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1383 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1383 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1384 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1384 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1385
1385
1386 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1386 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1387 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1387 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1388 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1388 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1389 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1389 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1390 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1390 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1391 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1391 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1392
1392
1393 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1393 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1394 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1394 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1395
1395
1396 @classmethod
1396 @classmethod
1397 def _load_group_data(cls, column):
1397 def _load_group_data(cls, column):
1398 if not column:
1398 if not column:
1399 return {}
1399 return {}
1400
1400
1401 try:
1401 try:
1402 return json.loads(column) or {}
1402 return json.loads(column) or {}
1403 except TypeError:
1403 except TypeError:
1404 return {}
1404 return {}
1405
1405
1406 @hybrid_property
1406 @hybrid_property
1407 def description_safe(self):
1407 def description_safe(self):
1408 from rhodecode.lib import helpers as h
1408 from rhodecode.lib import helpers as h
1409 return h.escape(self.user_group_description)
1409 return h.escape(self.user_group_description)
1410
1410
1411 @hybrid_property
1411 @hybrid_property
1412 def group_data(self):
1412 def group_data(self):
1413 return self._load_group_data(self._group_data)
1413 return self._load_group_data(self._group_data)
1414
1414
1415 @group_data.expression
1415 @group_data.expression
1416 def group_data(self, **kwargs):
1416 def group_data(self, **kwargs):
1417 return self._group_data
1417 return self._group_data
1418
1418
1419 @group_data.setter
1419 @group_data.setter
1420 def group_data(self, val):
1420 def group_data(self, val):
1421 try:
1421 try:
1422 self._group_data = json.dumps(val)
1422 self._group_data = json.dumps(val)
1423 except Exception:
1423 except Exception:
1424 log.error(traceback.format_exc())
1424 log.error(traceback.format_exc())
1425
1425
1426 @classmethod
1426 @classmethod
1427 def _load_sync(cls, group_data):
1427 def _load_sync(cls, group_data):
1428 if group_data:
1428 if group_data:
1429 return group_data.get('extern_type')
1429 return group_data.get('extern_type')
1430
1430
1431 @property
1431 @property
1432 def sync(self):
1432 def sync(self):
1433 return self._load_sync(self.group_data)
1433 return self._load_sync(self.group_data)
1434
1434
1435 def __unicode__(self):
1435 def __unicode__(self):
1436 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1436 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1437 self.users_group_id,
1437 self.users_group_id,
1438 self.users_group_name)
1438 self.users_group_name)
1439
1439
1440 @classmethod
1440 @classmethod
1441 def get_by_group_name(cls, group_name, cache=False,
1441 def get_by_group_name(cls, group_name, cache=False,
1442 case_insensitive=False):
1442 case_insensitive=False):
1443 if case_insensitive:
1443 if case_insensitive:
1444 q = cls.query().filter(func.lower(cls.users_group_name) ==
1444 q = cls.query().filter(func.lower(cls.users_group_name) ==
1445 func.lower(group_name))
1445 func.lower(group_name))
1446
1446
1447 else:
1447 else:
1448 q = cls.query().filter(cls.users_group_name == group_name)
1448 q = cls.query().filter(cls.users_group_name == group_name)
1449 if cache:
1449 if cache:
1450 q = q.options(
1450 q = q.options(
1451 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1451 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1452 return q.scalar()
1452 return q.scalar()
1453
1453
1454 @classmethod
1454 @classmethod
1455 def get(cls, user_group_id, cache=False):
1455 def get(cls, user_group_id, cache=False):
1456 if not user_group_id:
1456 if not user_group_id:
1457 return
1457 return
1458
1458
1459 user_group = cls.query()
1459 user_group = cls.query()
1460 if cache:
1460 if cache:
1461 user_group = user_group.options(
1461 user_group = user_group.options(
1462 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1462 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1463 return user_group.get(user_group_id)
1463 return user_group.get(user_group_id)
1464
1464
1465 def permissions(self, with_admins=True, with_owner=True,
1465 def permissions(self, with_admins=True, with_owner=True,
1466 expand_from_user_groups=False):
1466 expand_from_user_groups=False):
1467 """
1467 """
1468 Permissions for user groups
1468 Permissions for user groups
1469 """
1469 """
1470 _admin_perm = 'usergroup.admin'
1470 _admin_perm = 'usergroup.admin'
1471
1471
1472 owner_row = []
1472 owner_row = []
1473 if with_owner:
1473 if with_owner:
1474 usr = AttributeDict(self.user.get_dict())
1474 usr = AttributeDict(self.user.get_dict())
1475 usr.owner_row = True
1475 usr.owner_row = True
1476 usr.permission = _admin_perm
1476 usr.permission = _admin_perm
1477 owner_row.append(usr)
1477 owner_row.append(usr)
1478
1478
1479 super_admin_ids = []
1479 super_admin_ids = []
1480 super_admin_rows = []
1480 super_admin_rows = []
1481 if with_admins:
1481 if with_admins:
1482 for usr in User.get_all_super_admins():
1482 for usr in User.get_all_super_admins():
1483 super_admin_ids.append(usr.user_id)
1483 super_admin_ids.append(usr.user_id)
1484 # if this admin is also owner, don't double the record
1484 # if this admin is also owner, don't double the record
1485 if usr.user_id == owner_row[0].user_id:
1485 if usr.user_id == owner_row[0].user_id:
1486 owner_row[0].admin_row = True
1486 owner_row[0].admin_row = True
1487 else:
1487 else:
1488 usr = AttributeDict(usr.get_dict())
1488 usr = AttributeDict(usr.get_dict())
1489 usr.admin_row = True
1489 usr.admin_row = True
1490 usr.permission = _admin_perm
1490 usr.permission = _admin_perm
1491 super_admin_rows.append(usr)
1491 super_admin_rows.append(usr)
1492
1492
1493 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1493 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1494 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1494 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1495 joinedload(UserUserGroupToPerm.user),
1495 joinedload(UserUserGroupToPerm.user),
1496 joinedload(UserUserGroupToPerm.permission),)
1496 joinedload(UserUserGroupToPerm.permission),)
1497
1497
1498 # get owners and admins and permissions. We do a trick of re-writing
1498 # get owners and admins and permissions. We do a trick of re-writing
1499 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1499 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1500 # has a global reference and changing one object propagates to all
1500 # has a global reference and changing one object propagates to all
1501 # others. This means if admin is also an owner admin_row that change
1501 # others. This means if admin is also an owner admin_row that change
1502 # would propagate to both objects
1502 # would propagate to both objects
1503 perm_rows = []
1503 perm_rows = []
1504 for _usr in q.all():
1504 for _usr in q.all():
1505 usr = AttributeDict(_usr.user.get_dict())
1505 usr = AttributeDict(_usr.user.get_dict())
1506 # if this user is also owner/admin, mark as duplicate record
1506 # if this user is also owner/admin, mark as duplicate record
1507 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1507 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1508 usr.duplicate_perm = True
1508 usr.duplicate_perm = True
1509 usr.permission = _usr.permission.permission_name
1509 usr.permission = _usr.permission.permission_name
1510 perm_rows.append(usr)
1510 perm_rows.append(usr)
1511
1511
1512 # filter the perm rows by 'default' first and then sort them by
1512 # filter the perm rows by 'default' first and then sort them by
1513 # admin,write,read,none permissions sorted again alphabetically in
1513 # admin,write,read,none permissions sorted again alphabetically in
1514 # each group
1514 # each group
1515 perm_rows = sorted(perm_rows, key=display_user_sort)
1515 perm_rows = sorted(perm_rows, key=display_user_sort)
1516
1516
1517 user_groups_rows = []
1517 user_groups_rows = []
1518 if expand_from_user_groups:
1518 if expand_from_user_groups:
1519 for ug in self.permission_user_groups(with_members=True):
1519 for ug in self.permission_user_groups(with_members=True):
1520 for user_data in ug.members:
1520 for user_data in ug.members:
1521 user_groups_rows.append(user_data)
1521 user_groups_rows.append(user_data)
1522
1522
1523 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1523 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1524
1524
1525 def permission_user_groups(self, with_members=False):
1525 def permission_user_groups(self, with_members=False):
1526 q = UserGroupUserGroupToPerm.query()\
1526 q = UserGroupUserGroupToPerm.query()\
1527 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1527 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1528 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1528 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1529 joinedload(UserGroupUserGroupToPerm.target_user_group),
1529 joinedload(UserGroupUserGroupToPerm.target_user_group),
1530 joinedload(UserGroupUserGroupToPerm.permission),)
1530 joinedload(UserGroupUserGroupToPerm.permission),)
1531
1531
1532 perm_rows = []
1532 perm_rows = []
1533 for _user_group in q.all():
1533 for _user_group in q.all():
1534 entry = AttributeDict(_user_group.user_group.get_dict())
1534 entry = AttributeDict(_user_group.user_group.get_dict())
1535 entry.permission = _user_group.permission.permission_name
1535 entry.permission = _user_group.permission.permission_name
1536 if with_members:
1536 if with_members:
1537 entry.members = [x.user.get_dict()
1537 entry.members = [x.user.get_dict()
1538 for x in _user_group.user_group.members]
1538 for x in _user_group.user_group.members]
1539 perm_rows.append(entry)
1539 perm_rows.append(entry)
1540
1540
1541 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1541 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1542 return perm_rows
1542 return perm_rows
1543
1543
1544 def _get_default_perms(self, user_group, suffix=''):
1544 def _get_default_perms(self, user_group, suffix=''):
1545 from rhodecode.model.permission import PermissionModel
1545 from rhodecode.model.permission import PermissionModel
1546 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1546 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1547
1547
1548 def get_default_perms(self, suffix=''):
1548 def get_default_perms(self, suffix=''):
1549 return self._get_default_perms(self, suffix)
1549 return self._get_default_perms(self, suffix)
1550
1550
1551 def get_api_data(self, with_group_members=True, include_secrets=False):
1551 def get_api_data(self, with_group_members=True, include_secrets=False):
1552 """
1552 """
1553 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1553 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1554 basically forwarded.
1554 basically forwarded.
1555
1555
1556 """
1556 """
1557 user_group = self
1557 user_group = self
1558 data = {
1558 data = {
1559 'users_group_id': user_group.users_group_id,
1559 'users_group_id': user_group.users_group_id,
1560 'group_name': user_group.users_group_name,
1560 'group_name': user_group.users_group_name,
1561 'group_description': user_group.user_group_description,
1561 'group_description': user_group.user_group_description,
1562 'active': user_group.users_group_active,
1562 'active': user_group.users_group_active,
1563 'owner': user_group.user.username,
1563 'owner': user_group.user.username,
1564 'sync': user_group.sync,
1564 'sync': user_group.sync,
1565 'owner_email': user_group.user.email,
1565 'owner_email': user_group.user.email,
1566 }
1566 }
1567
1567
1568 if with_group_members:
1568 if with_group_members:
1569 users = []
1569 users = []
1570 for user in user_group.members:
1570 for user in user_group.members:
1571 user = user.user
1571 user = user.user
1572 users.append(user.get_api_data(include_secrets=include_secrets))
1572 users.append(user.get_api_data(include_secrets=include_secrets))
1573 data['users'] = users
1573 data['users'] = users
1574
1574
1575 return data
1575 return data
1576
1576
1577
1577
1578 class UserGroupMember(Base, BaseModel):
1578 class UserGroupMember(Base, BaseModel):
1579 __tablename__ = 'users_groups_members'
1579 __tablename__ = 'users_groups_members'
1580 __table_args__ = (
1580 __table_args__ = (
1581 base_table_args,
1581 base_table_args,
1582 )
1582 )
1583
1583
1584 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1584 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1587
1587
1588 user = relationship('User', lazy='joined')
1588 user = relationship('User', lazy='joined')
1589 users_group = relationship('UserGroup')
1589 users_group = relationship('UserGroup')
1590
1590
1591 def __init__(self, gr_id='', u_id=''):
1591 def __init__(self, gr_id='', u_id=''):
1592 self.users_group_id = gr_id
1592 self.users_group_id = gr_id
1593 self.user_id = u_id
1593 self.user_id = u_id
1594
1594
1595
1595
1596 class RepositoryField(Base, BaseModel):
1596 class RepositoryField(Base, BaseModel):
1597 __tablename__ = 'repositories_fields'
1597 __tablename__ = 'repositories_fields'
1598 __table_args__ = (
1598 __table_args__ = (
1599 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1599 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1600 base_table_args,
1600 base_table_args,
1601 )
1601 )
1602
1602
1603 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1603 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1604
1604
1605 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1605 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1607 field_key = Column("field_key", String(250))
1607 field_key = Column("field_key", String(250))
1608 field_label = Column("field_label", String(1024), nullable=False)
1608 field_label = Column("field_label", String(1024), nullable=False)
1609 field_value = Column("field_value", String(10000), nullable=False)
1609 field_value = Column("field_value", String(10000), nullable=False)
1610 field_desc = Column("field_desc", String(1024), nullable=False)
1610 field_desc = Column("field_desc", String(1024), nullable=False)
1611 field_type = Column("field_type", String(255), nullable=False, unique=None)
1611 field_type = Column("field_type", String(255), nullable=False, unique=None)
1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1613
1613
1614 repository = relationship('Repository')
1614 repository = relationship('Repository')
1615
1615
1616 @property
1616 @property
1617 def field_key_prefixed(self):
1617 def field_key_prefixed(self):
1618 return 'ex_%s' % self.field_key
1618 return 'ex_%s' % self.field_key
1619
1619
1620 @classmethod
1620 @classmethod
1621 def un_prefix_key(cls, key):
1621 def un_prefix_key(cls, key):
1622 if key.startswith(cls.PREFIX):
1622 if key.startswith(cls.PREFIX):
1623 return key[len(cls.PREFIX):]
1623 return key[len(cls.PREFIX):]
1624 return key
1624 return key
1625
1625
1626 @classmethod
1626 @classmethod
1627 def get_by_key_name(cls, key, repo):
1627 def get_by_key_name(cls, key, repo):
1628 row = cls.query()\
1628 row = cls.query()\
1629 .filter(cls.repository == repo)\
1629 .filter(cls.repository == repo)\
1630 .filter(cls.field_key == key).scalar()
1630 .filter(cls.field_key == key).scalar()
1631 return row
1631 return row
1632
1632
1633
1633
1634 class Repository(Base, BaseModel):
1634 class Repository(Base, BaseModel):
1635 __tablename__ = 'repositories'
1635 __tablename__ = 'repositories'
1636 __table_args__ = (
1636 __table_args__ = (
1637 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1637 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1638 base_table_args,
1638 base_table_args,
1639 )
1639 )
1640 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1640 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1641 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1641 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1642 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1642 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1643
1643
1644 STATE_CREATED = 'repo_state_created'
1644 STATE_CREATED = 'repo_state_created'
1645 STATE_PENDING = 'repo_state_pending'
1645 STATE_PENDING = 'repo_state_pending'
1646 STATE_ERROR = 'repo_state_error'
1646 STATE_ERROR = 'repo_state_error'
1647
1647
1648 LOCK_AUTOMATIC = 'lock_auto'
1648 LOCK_AUTOMATIC = 'lock_auto'
1649 LOCK_API = 'lock_api'
1649 LOCK_API = 'lock_api'
1650 LOCK_WEB = 'lock_web'
1650 LOCK_WEB = 'lock_web'
1651 LOCK_PULL = 'lock_pull'
1651 LOCK_PULL = 'lock_pull'
1652
1652
1653 NAME_SEP = URL_SEP
1653 NAME_SEP = URL_SEP
1654
1654
1655 repo_id = Column(
1655 repo_id = Column(
1656 "repo_id", Integer(), nullable=False, unique=True, default=None,
1656 "repo_id", Integer(), nullable=False, unique=True, default=None,
1657 primary_key=True)
1657 primary_key=True)
1658 _repo_name = Column(
1658 _repo_name = Column(
1659 "repo_name", Text(), nullable=False, default=None)
1659 "repo_name", Text(), nullable=False, default=None)
1660 repo_name_hash = Column(
1660 repo_name_hash = Column(
1661 "repo_name_hash", String(255), nullable=False, unique=True)
1661 "repo_name_hash", String(255), nullable=False, unique=True)
1662 repo_state = Column("repo_state", String(255), nullable=True)
1662 repo_state = Column("repo_state", String(255), nullable=True)
1663
1663
1664 clone_uri = Column(
1664 clone_uri = Column(
1665 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1665 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1666 default=None)
1666 default=None)
1667 push_uri = Column(
1667 push_uri = Column(
1668 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1668 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1669 default=None)
1669 default=None)
1670 repo_type = Column(
1670 repo_type = Column(
1671 "repo_type", String(255), nullable=False, unique=False, default=None)
1671 "repo_type", String(255), nullable=False, unique=False, default=None)
1672 user_id = Column(
1672 user_id = Column(
1673 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1673 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1674 unique=False, default=None)
1674 unique=False, default=None)
1675 private = Column(
1675 private = Column(
1676 "private", Boolean(), nullable=True, unique=None, default=None)
1676 "private", Boolean(), nullable=True, unique=None, default=None)
1677 archived = Column(
1677 archived = Column(
1678 "archived", Boolean(), nullable=True, unique=None, default=None)
1678 "archived", Boolean(), nullable=True, unique=None, default=None)
1679 enable_statistics = Column(
1679 enable_statistics = Column(
1680 "statistics", Boolean(), nullable=True, unique=None, default=True)
1680 "statistics", Boolean(), nullable=True, unique=None, default=True)
1681 enable_downloads = Column(
1681 enable_downloads = Column(
1682 "downloads", Boolean(), nullable=True, unique=None, default=True)
1682 "downloads", Boolean(), nullable=True, unique=None, default=True)
1683 description = Column(
1683 description = Column(
1684 "description", String(10000), nullable=True, unique=None, default=None)
1684 "description", String(10000), nullable=True, unique=None, default=None)
1685 created_on = Column(
1685 created_on = Column(
1686 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1686 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1687 default=datetime.datetime.now)
1687 default=datetime.datetime.now)
1688 updated_on = Column(
1688 updated_on = Column(
1689 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1689 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1690 default=datetime.datetime.now)
1690 default=datetime.datetime.now)
1691 _landing_revision = Column(
1691 _landing_revision = Column(
1692 "landing_revision", String(255), nullable=False, unique=False,
1692 "landing_revision", String(255), nullable=False, unique=False,
1693 default=None)
1693 default=None)
1694 enable_locking = Column(
1694 enable_locking = Column(
1695 "enable_locking", Boolean(), nullable=False, unique=None,
1695 "enable_locking", Boolean(), nullable=False, unique=None,
1696 default=False)
1696 default=False)
1697 _locked = Column(
1697 _locked = Column(
1698 "locked", String(255), nullable=True, unique=False, default=None)
1698 "locked", String(255), nullable=True, unique=False, default=None)
1699 _changeset_cache = Column(
1699 _changeset_cache = Column(
1700 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1700 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1701
1701
1702 fork_id = Column(
1702 fork_id = Column(
1703 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1703 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1704 nullable=True, unique=False, default=None)
1704 nullable=True, unique=False, default=None)
1705 group_id = Column(
1705 group_id = Column(
1706 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1706 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1707 unique=False, default=None)
1707 unique=False, default=None)
1708
1708
1709 user = relationship('User', lazy='joined')
1709 user = relationship('User', lazy='joined')
1710 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1710 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1711 group = relationship('RepoGroup', lazy='joined')
1711 group = relationship('RepoGroup', lazy='joined')
1712 repo_to_perm = relationship(
1712 repo_to_perm = relationship(
1713 'UserRepoToPerm', cascade='all',
1713 'UserRepoToPerm', cascade='all',
1714 order_by='UserRepoToPerm.repo_to_perm_id')
1714 order_by='UserRepoToPerm.repo_to_perm_id')
1715 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1715 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1716 stats = relationship('Statistics', cascade='all', uselist=False)
1716 stats = relationship('Statistics', cascade='all', uselist=False)
1717
1717
1718 followers = relationship(
1718 followers = relationship(
1719 'UserFollowing',
1719 'UserFollowing',
1720 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1720 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1721 cascade='all')
1721 cascade='all')
1722 extra_fields = relationship(
1722 extra_fields = relationship(
1723 'RepositoryField', cascade="all, delete-orphan")
1723 'RepositoryField', cascade="all, delete-orphan")
1724 logs = relationship('UserLog')
1724 logs = relationship('UserLog')
1725 comments = relationship(
1725 comments = relationship(
1726 'ChangesetComment', cascade="all, delete-orphan")
1726 'ChangesetComment', cascade="all, delete-orphan")
1727 pull_requests_source = relationship(
1727 pull_requests_source = relationship(
1728 'PullRequest',
1728 'PullRequest',
1729 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1729 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1730 cascade="all, delete-orphan")
1730 cascade="all, delete-orphan")
1731 pull_requests_target = relationship(
1731 pull_requests_target = relationship(
1732 'PullRequest',
1732 'PullRequest',
1733 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1733 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1734 cascade="all, delete-orphan")
1734 cascade="all, delete-orphan")
1735 ui = relationship('RepoRhodeCodeUi', cascade="all")
1735 ui = relationship('RepoRhodeCodeUi', cascade="all")
1736 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1736 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1737 integrations = relationship('Integration', cascade="all, delete-orphan")
1737 integrations = relationship('Integration', cascade="all, delete-orphan")
1738
1738
1739 scoped_tokens = relationship('UserApiKeys', cascade="all")
1739 scoped_tokens = relationship('UserApiKeys', cascade="all")
1740
1740
1741 # no cascade, set NULL
1741 # no cascade, set NULL
1742 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1742 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1743
1743
1744 def __unicode__(self):
1744 def __unicode__(self):
1745 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1745 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1746 safe_unicode(self.repo_name))
1746 safe_unicode(self.repo_name))
1747
1747
1748 @hybrid_property
1748 @hybrid_property
1749 def description_safe(self):
1749 def description_safe(self):
1750 from rhodecode.lib import helpers as h
1750 from rhodecode.lib import helpers as h
1751 return h.escape(self.description)
1751 return h.escape(self.description)
1752
1752
1753 @hybrid_property
1753 @hybrid_property
1754 def landing_rev(self):
1754 def landing_rev(self):
1755 # always should return [rev_type, rev]
1755 # always should return [rev_type, rev]
1756 if self._landing_revision:
1756 if self._landing_revision:
1757 _rev_info = self._landing_revision.split(':')
1757 _rev_info = self._landing_revision.split(':')
1758 if len(_rev_info) < 2:
1758 if len(_rev_info) < 2:
1759 _rev_info.insert(0, 'rev')
1759 _rev_info.insert(0, 'rev')
1760 return [_rev_info[0], _rev_info[1]]
1760 return [_rev_info[0], _rev_info[1]]
1761 return [None, None]
1761 return [None, None]
1762
1762
1763 @landing_rev.setter
1763 @landing_rev.setter
1764 def landing_rev(self, val):
1764 def landing_rev(self, val):
1765 if ':' not in val:
1765 if ':' not in val:
1766 raise ValueError('value must be delimited with `:` and consist '
1766 raise ValueError('value must be delimited with `:` and consist '
1767 'of <rev_type>:<rev>, got %s instead' % val)
1767 'of <rev_type>:<rev>, got %s instead' % val)
1768 self._landing_revision = val
1768 self._landing_revision = val
1769
1769
1770 @hybrid_property
1770 @hybrid_property
1771 def locked(self):
1771 def locked(self):
1772 if self._locked:
1772 if self._locked:
1773 user_id, timelocked, reason = self._locked.split(':')
1773 user_id, timelocked, reason = self._locked.split(':')
1774 lock_values = int(user_id), timelocked, reason
1774 lock_values = int(user_id), timelocked, reason
1775 else:
1775 else:
1776 lock_values = [None, None, None]
1776 lock_values = [None, None, None]
1777 return lock_values
1777 return lock_values
1778
1778
1779 @locked.setter
1779 @locked.setter
1780 def locked(self, val):
1780 def locked(self, val):
1781 if val and isinstance(val, (list, tuple)):
1781 if val and isinstance(val, (list, tuple)):
1782 self._locked = ':'.join(map(str, val))
1782 self._locked = ':'.join(map(str, val))
1783 else:
1783 else:
1784 self._locked = None
1784 self._locked = None
1785
1785
1786 @classmethod
1786 @classmethod
1787 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1787 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1788 from rhodecode.lib.vcs.backends.base import EmptyCommit
1788 from rhodecode.lib.vcs.backends.base import EmptyCommit
1789 dummy = EmptyCommit().__json__()
1789 dummy = EmptyCommit().__json__()
1790 if not changeset_cache_raw:
1790 if not changeset_cache_raw:
1791 dummy['source_repo_id'] = repo_id
1791 dummy['source_repo_id'] = repo_id
1792 return json.loads(json.dumps(dummy))
1792 return json.loads(json.dumps(dummy))
1793
1793
1794 try:
1794 try:
1795 return json.loads(changeset_cache_raw)
1795 return json.loads(changeset_cache_raw)
1796 except TypeError:
1796 except TypeError:
1797 return dummy
1797 return dummy
1798 except Exception:
1798 except Exception:
1799 log.error(traceback.format_exc())
1799 log.error(traceback.format_exc())
1800 return dummy
1800 return dummy
1801
1801
1802 @hybrid_property
1802 @hybrid_property
1803 def changeset_cache(self):
1803 def changeset_cache(self):
1804 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1804 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1805
1805
1806 @changeset_cache.setter
1806 @changeset_cache.setter
1807 def changeset_cache(self, val):
1807 def changeset_cache(self, val):
1808 try:
1808 try:
1809 self._changeset_cache = json.dumps(val)
1809 self._changeset_cache = json.dumps(val)
1810 except Exception:
1810 except Exception:
1811 log.error(traceback.format_exc())
1811 log.error(traceback.format_exc())
1812
1812
1813 @hybrid_property
1813 @hybrid_property
1814 def repo_name(self):
1814 def repo_name(self):
1815 return self._repo_name
1815 return self._repo_name
1816
1816
1817 @repo_name.setter
1817 @repo_name.setter
1818 def repo_name(self, value):
1818 def repo_name(self, value):
1819 self._repo_name = value
1819 self._repo_name = value
1820 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1820 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1821
1821
1822 @classmethod
1822 @classmethod
1823 def normalize_repo_name(cls, repo_name):
1823 def normalize_repo_name(cls, repo_name):
1824 """
1824 """
1825 Normalizes os specific repo_name to the format internally stored inside
1825 Normalizes os specific repo_name to the format internally stored inside
1826 database using URL_SEP
1826 database using URL_SEP
1827
1827
1828 :param cls:
1828 :param cls:
1829 :param repo_name:
1829 :param repo_name:
1830 """
1830 """
1831 return cls.NAME_SEP.join(repo_name.split(os.sep))
1831 return cls.NAME_SEP.join(repo_name.split(os.sep))
1832
1832
1833 @classmethod
1833 @classmethod
1834 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1834 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1835 session = Session()
1835 session = Session()
1836 q = session.query(cls).filter(cls.repo_name == repo_name)
1836 q = session.query(cls).filter(cls.repo_name == repo_name)
1837
1837
1838 if cache:
1838 if cache:
1839 if identity_cache:
1839 if identity_cache:
1840 val = cls.identity_cache(session, 'repo_name', repo_name)
1840 val = cls.identity_cache(session, 'repo_name', repo_name)
1841 if val:
1841 if val:
1842 return val
1842 return val
1843 else:
1843 else:
1844 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1844 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1845 q = q.options(
1845 q = q.options(
1846 FromCache("sql_cache_short", cache_key))
1846 FromCache("sql_cache_short", cache_key))
1847
1847
1848 return q.scalar()
1848 return q.scalar()
1849
1849
1850 @classmethod
1850 @classmethod
1851 def get_by_id_or_repo_name(cls, repoid):
1851 def get_by_id_or_repo_name(cls, repoid):
1852 if isinstance(repoid, (int, long)):
1852 if isinstance(repoid, (int, long)):
1853 try:
1853 try:
1854 repo = cls.get(repoid)
1854 repo = cls.get(repoid)
1855 except ValueError:
1855 except ValueError:
1856 repo = None
1856 repo = None
1857 else:
1857 else:
1858 repo = cls.get_by_repo_name(repoid)
1858 repo = cls.get_by_repo_name(repoid)
1859 return repo
1859 return repo
1860
1860
1861 @classmethod
1861 @classmethod
1862 def get_by_full_path(cls, repo_full_path):
1862 def get_by_full_path(cls, repo_full_path):
1863 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1863 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1864 repo_name = cls.normalize_repo_name(repo_name)
1864 repo_name = cls.normalize_repo_name(repo_name)
1865 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1865 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1866
1866
1867 @classmethod
1867 @classmethod
1868 def get_repo_forks(cls, repo_id):
1868 def get_repo_forks(cls, repo_id):
1869 return cls.query().filter(Repository.fork_id == repo_id)
1869 return cls.query().filter(Repository.fork_id == repo_id)
1870
1870
1871 @classmethod
1871 @classmethod
1872 def base_path(cls):
1872 def base_path(cls):
1873 """
1873 """
1874 Returns base path when all repos are stored
1874 Returns base path when all repos are stored
1875
1875
1876 :param cls:
1876 :param cls:
1877 """
1877 """
1878 q = Session().query(RhodeCodeUi)\
1878 q = Session().query(RhodeCodeUi)\
1879 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1879 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1880 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1880 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1881 return q.one().ui_value
1881 return q.one().ui_value
1882
1882
1883 @classmethod
1883 @classmethod
1884 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1884 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1885 case_insensitive=True, archived=False):
1885 case_insensitive=True, archived=False):
1886 q = Repository.query()
1886 q = Repository.query()
1887
1887
1888 if not archived:
1888 if not archived:
1889 q = q.filter(Repository.archived.isnot(true()))
1889 q = q.filter(Repository.archived.isnot(true()))
1890
1890
1891 if not isinstance(user_id, Optional):
1891 if not isinstance(user_id, Optional):
1892 q = q.filter(Repository.user_id == user_id)
1892 q = q.filter(Repository.user_id == user_id)
1893
1893
1894 if not isinstance(group_id, Optional):
1894 if not isinstance(group_id, Optional):
1895 q = q.filter(Repository.group_id == group_id)
1895 q = q.filter(Repository.group_id == group_id)
1896
1896
1897 if case_insensitive:
1897 if case_insensitive:
1898 q = q.order_by(func.lower(Repository.repo_name))
1898 q = q.order_by(func.lower(Repository.repo_name))
1899 else:
1899 else:
1900 q = q.order_by(Repository.repo_name)
1900 q = q.order_by(Repository.repo_name)
1901
1901
1902 return q.all()
1902 return q.all()
1903
1903
1904 @property
1904 @property
1905 def repo_uid(self):
1905 def repo_uid(self):
1906 return '_{}'.format(self.repo_id)
1906 return '_{}'.format(self.repo_id)
1907
1907
1908 @property
1908 @property
1909 def forks(self):
1909 def forks(self):
1910 """
1910 """
1911 Return forks of this repo
1911 Return forks of this repo
1912 """
1912 """
1913 return Repository.get_repo_forks(self.repo_id)
1913 return Repository.get_repo_forks(self.repo_id)
1914
1914
1915 @property
1915 @property
1916 def parent(self):
1916 def parent(self):
1917 """
1917 """
1918 Returns fork parent
1918 Returns fork parent
1919 """
1919 """
1920 return self.fork
1920 return self.fork
1921
1921
1922 @property
1922 @property
1923 def just_name(self):
1923 def just_name(self):
1924 return self.repo_name.split(self.NAME_SEP)[-1]
1924 return self.repo_name.split(self.NAME_SEP)[-1]
1925
1925
1926 @property
1926 @property
1927 def groups_with_parents(self):
1927 def groups_with_parents(self):
1928 groups = []
1928 groups = []
1929 if self.group is None:
1929 if self.group is None:
1930 return groups
1930 return groups
1931
1931
1932 cur_gr = self.group
1932 cur_gr = self.group
1933 groups.insert(0, cur_gr)
1933 groups.insert(0, cur_gr)
1934 while 1:
1934 while 1:
1935 gr = getattr(cur_gr, 'parent_group', None)
1935 gr = getattr(cur_gr, 'parent_group', None)
1936 cur_gr = cur_gr.parent_group
1936 cur_gr = cur_gr.parent_group
1937 if gr is None:
1937 if gr is None:
1938 break
1938 break
1939 groups.insert(0, gr)
1939 groups.insert(0, gr)
1940
1940
1941 return groups
1941 return groups
1942
1942
1943 @property
1943 @property
1944 def groups_and_repo(self):
1944 def groups_and_repo(self):
1945 return self.groups_with_parents, self
1945 return self.groups_with_parents, self
1946
1946
1947 @LazyProperty
1947 @LazyProperty
1948 def repo_path(self):
1948 def repo_path(self):
1949 """
1949 """
1950 Returns base full path for that repository means where it actually
1950 Returns base full path for that repository means where it actually
1951 exists on a filesystem
1951 exists on a filesystem
1952 """
1952 """
1953 q = Session().query(RhodeCodeUi).filter(
1953 q = Session().query(RhodeCodeUi).filter(
1954 RhodeCodeUi.ui_key == self.NAME_SEP)
1954 RhodeCodeUi.ui_key == self.NAME_SEP)
1955 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1955 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1956 return q.one().ui_value
1956 return q.one().ui_value
1957
1957
1958 @property
1958 @property
1959 def repo_full_path(self):
1959 def repo_full_path(self):
1960 p = [self.repo_path]
1960 p = [self.repo_path]
1961 # we need to split the name by / since this is how we store the
1961 # we need to split the name by / since this is how we store the
1962 # names in the database, but that eventually needs to be converted
1962 # names in the database, but that eventually needs to be converted
1963 # into a valid system path
1963 # into a valid system path
1964 p += self.repo_name.split(self.NAME_SEP)
1964 p += self.repo_name.split(self.NAME_SEP)
1965 return os.path.join(*map(safe_unicode, p))
1965 return os.path.join(*map(safe_unicode, p))
1966
1966
1967 @property
1967 @property
1968 def cache_keys(self):
1968 def cache_keys(self):
1969 """
1969 """
1970 Returns associated cache keys for that repo
1970 Returns associated cache keys for that repo
1971 """
1971 """
1972 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1972 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1973 repo_id=self.repo_id)
1973 repo_id=self.repo_id)
1974 return CacheKey.query()\
1974 return CacheKey.query()\
1975 .filter(CacheKey.cache_args == invalidation_namespace)\
1975 .filter(CacheKey.cache_args == invalidation_namespace)\
1976 .order_by(CacheKey.cache_key)\
1976 .order_by(CacheKey.cache_key)\
1977 .all()
1977 .all()
1978
1978
1979 @property
1979 @property
1980 def cached_diffs_relative_dir(self):
1980 def cached_diffs_relative_dir(self):
1981 """
1981 """
1982 Return a relative to the repository store path of cached diffs
1982 Return a relative to the repository store path of cached diffs
1983 used for safe display for users, who shouldn't know the absolute store
1983 used for safe display for users, who shouldn't know the absolute store
1984 path
1984 path
1985 """
1985 """
1986 return os.path.join(
1986 return os.path.join(
1987 os.path.dirname(self.repo_name),
1987 os.path.dirname(self.repo_name),
1988 self.cached_diffs_dir.split(os.path.sep)[-1])
1988 self.cached_diffs_dir.split(os.path.sep)[-1])
1989
1989
1990 @property
1990 @property
1991 def cached_diffs_dir(self):
1991 def cached_diffs_dir(self):
1992 path = self.repo_full_path
1992 path = self.repo_full_path
1993 return os.path.join(
1993 return os.path.join(
1994 os.path.dirname(path),
1994 os.path.dirname(path),
1995 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1995 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1996
1996
1997 def cached_diffs(self):
1997 def cached_diffs(self):
1998 diff_cache_dir = self.cached_diffs_dir
1998 diff_cache_dir = self.cached_diffs_dir
1999 if os.path.isdir(diff_cache_dir):
1999 if os.path.isdir(diff_cache_dir):
2000 return os.listdir(diff_cache_dir)
2000 return os.listdir(diff_cache_dir)
2001 return []
2001 return []
2002
2002
2003 def shadow_repos(self):
2003 def shadow_repos(self):
2004 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2004 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2005 return [
2005 return [
2006 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2006 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2007 if x.startswith(shadow_repos_pattern)]
2007 if x.startswith(shadow_repos_pattern)]
2008
2008
2009 def get_new_name(self, repo_name):
2009 def get_new_name(self, repo_name):
2010 """
2010 """
2011 returns new full repository name based on assigned group and new new
2011 returns new full repository name based on assigned group and new new
2012
2012
2013 :param group_name:
2013 :param group_name:
2014 """
2014 """
2015 path_prefix = self.group.full_path_splitted if self.group else []
2015 path_prefix = self.group.full_path_splitted if self.group else []
2016 return self.NAME_SEP.join(path_prefix + [repo_name])
2016 return self.NAME_SEP.join(path_prefix + [repo_name])
2017
2017
2018 @property
2018 @property
2019 def _config(self):
2019 def _config(self):
2020 """
2020 """
2021 Returns db based config object.
2021 Returns db based config object.
2022 """
2022 """
2023 from rhodecode.lib.utils import make_db_config
2023 from rhodecode.lib.utils import make_db_config
2024 return make_db_config(clear_session=False, repo=self)
2024 return make_db_config(clear_session=False, repo=self)
2025
2025
2026 def permissions(self, with_admins=True, with_owner=True,
2026 def permissions(self, with_admins=True, with_owner=True,
2027 expand_from_user_groups=False):
2027 expand_from_user_groups=False):
2028 """
2028 """
2029 Permissions for repositories
2029 Permissions for repositories
2030 """
2030 """
2031 _admin_perm = 'repository.admin'
2031 _admin_perm = 'repository.admin'
2032
2032
2033 owner_row = []
2033 owner_row = []
2034 if with_owner:
2034 if with_owner:
2035 usr = AttributeDict(self.user.get_dict())
2035 usr = AttributeDict(self.user.get_dict())
2036 usr.owner_row = True
2036 usr.owner_row = True
2037 usr.permission = _admin_perm
2037 usr.permission = _admin_perm
2038 usr.permission_id = None
2038 usr.permission_id = None
2039 owner_row.append(usr)
2039 owner_row.append(usr)
2040
2040
2041 super_admin_ids = []
2041 super_admin_ids = []
2042 super_admin_rows = []
2042 super_admin_rows = []
2043 if with_admins:
2043 if with_admins:
2044 for usr in User.get_all_super_admins():
2044 for usr in User.get_all_super_admins():
2045 super_admin_ids.append(usr.user_id)
2045 super_admin_ids.append(usr.user_id)
2046 # if this admin is also owner, don't double the record
2046 # if this admin is also owner, don't double the record
2047 if usr.user_id == owner_row[0].user_id:
2047 if usr.user_id == owner_row[0].user_id:
2048 owner_row[0].admin_row = True
2048 owner_row[0].admin_row = True
2049 else:
2049 else:
2050 usr = AttributeDict(usr.get_dict())
2050 usr = AttributeDict(usr.get_dict())
2051 usr.admin_row = True
2051 usr.admin_row = True
2052 usr.permission = _admin_perm
2052 usr.permission = _admin_perm
2053 usr.permission_id = None
2053 usr.permission_id = None
2054 super_admin_rows.append(usr)
2054 super_admin_rows.append(usr)
2055
2055
2056 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2056 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2057 q = q.options(joinedload(UserRepoToPerm.repository),
2057 q = q.options(joinedload(UserRepoToPerm.repository),
2058 joinedload(UserRepoToPerm.user),
2058 joinedload(UserRepoToPerm.user),
2059 joinedload(UserRepoToPerm.permission),)
2059 joinedload(UserRepoToPerm.permission),)
2060
2060
2061 # get owners and admins and permissions. We do a trick of re-writing
2061 # get owners and admins and permissions. We do a trick of re-writing
2062 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2062 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2063 # has a global reference and changing one object propagates to all
2063 # has a global reference and changing one object propagates to all
2064 # others. This means if admin is also an owner admin_row that change
2064 # others. This means if admin is also an owner admin_row that change
2065 # would propagate to both objects
2065 # would propagate to both objects
2066 perm_rows = []
2066 perm_rows = []
2067 for _usr in q.all():
2067 for _usr in q.all():
2068 usr = AttributeDict(_usr.user.get_dict())
2068 usr = AttributeDict(_usr.user.get_dict())
2069 # if this user is also owner/admin, mark as duplicate record
2069 # if this user is also owner/admin, mark as duplicate record
2070 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2070 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2071 usr.duplicate_perm = True
2071 usr.duplicate_perm = True
2072 # also check if this permission is maybe used by branch_permissions
2072 # also check if this permission is maybe used by branch_permissions
2073 if _usr.branch_perm_entry:
2073 if _usr.branch_perm_entry:
2074 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2074 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2075
2075
2076 usr.permission = _usr.permission.permission_name
2076 usr.permission = _usr.permission.permission_name
2077 usr.permission_id = _usr.repo_to_perm_id
2077 usr.permission_id = _usr.repo_to_perm_id
2078 perm_rows.append(usr)
2078 perm_rows.append(usr)
2079
2079
2080 # filter the perm rows by 'default' first and then sort them by
2080 # filter the perm rows by 'default' first and then sort them by
2081 # admin,write,read,none permissions sorted again alphabetically in
2081 # admin,write,read,none permissions sorted again alphabetically in
2082 # each group
2082 # each group
2083 perm_rows = sorted(perm_rows, key=display_user_sort)
2083 perm_rows = sorted(perm_rows, key=display_user_sort)
2084
2084
2085 user_groups_rows = []
2085 user_groups_rows = []
2086 if expand_from_user_groups:
2086 if expand_from_user_groups:
2087 for ug in self.permission_user_groups(with_members=True):
2087 for ug in self.permission_user_groups(with_members=True):
2088 for user_data in ug.members:
2088 for user_data in ug.members:
2089 user_groups_rows.append(user_data)
2089 user_groups_rows.append(user_data)
2090
2090
2091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2092
2092
2093 def permission_user_groups(self, with_members=True):
2093 def permission_user_groups(self, with_members=True):
2094 q = UserGroupRepoToPerm.query()\
2094 q = UserGroupRepoToPerm.query()\
2095 .filter(UserGroupRepoToPerm.repository == self)
2095 .filter(UserGroupRepoToPerm.repository == self)
2096 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2096 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2097 joinedload(UserGroupRepoToPerm.users_group),
2097 joinedload(UserGroupRepoToPerm.users_group),
2098 joinedload(UserGroupRepoToPerm.permission),)
2098 joinedload(UserGroupRepoToPerm.permission),)
2099
2099
2100 perm_rows = []
2100 perm_rows = []
2101 for _user_group in q.all():
2101 for _user_group in q.all():
2102 entry = AttributeDict(_user_group.users_group.get_dict())
2102 entry = AttributeDict(_user_group.users_group.get_dict())
2103 entry.permission = _user_group.permission.permission_name
2103 entry.permission = _user_group.permission.permission_name
2104 if with_members:
2104 if with_members:
2105 entry.members = [x.user.get_dict()
2105 entry.members = [x.user.get_dict()
2106 for x in _user_group.users_group.members]
2106 for x in _user_group.users_group.members]
2107 perm_rows.append(entry)
2107 perm_rows.append(entry)
2108
2108
2109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2110 return perm_rows
2110 return perm_rows
2111
2111
2112 def get_api_data(self, include_secrets=False):
2112 def get_api_data(self, include_secrets=False):
2113 """
2113 """
2114 Common function for generating repo api data
2114 Common function for generating repo api data
2115
2115
2116 :param include_secrets: See :meth:`User.get_api_data`.
2116 :param include_secrets: See :meth:`User.get_api_data`.
2117
2117
2118 """
2118 """
2119 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2119 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2120 # move this methods on models level.
2120 # move this methods on models level.
2121 from rhodecode.model.settings import SettingsModel
2121 from rhodecode.model.settings import SettingsModel
2122 from rhodecode.model.repo import RepoModel
2122 from rhodecode.model.repo import RepoModel
2123
2123
2124 repo = self
2124 repo = self
2125 _user_id, _time, _reason = self.locked
2125 _user_id, _time, _reason = self.locked
2126
2126
2127 data = {
2127 data = {
2128 'repo_id': repo.repo_id,
2128 'repo_id': repo.repo_id,
2129 'repo_name': repo.repo_name,
2129 'repo_name': repo.repo_name,
2130 'repo_type': repo.repo_type,
2130 'repo_type': repo.repo_type,
2131 'clone_uri': repo.clone_uri or '',
2131 'clone_uri': repo.clone_uri or '',
2132 'push_uri': repo.push_uri or '',
2132 'push_uri': repo.push_uri or '',
2133 'url': RepoModel().get_url(self),
2133 'url': RepoModel().get_url(self),
2134 'private': repo.private,
2134 'private': repo.private,
2135 'created_on': repo.created_on,
2135 'created_on': repo.created_on,
2136 'description': repo.description_safe,
2136 'description': repo.description_safe,
2137 'landing_rev': repo.landing_rev,
2137 'landing_rev': repo.landing_rev,
2138 'owner': repo.user.username,
2138 'owner': repo.user.username,
2139 'fork_of': repo.fork.repo_name if repo.fork else None,
2139 'fork_of': repo.fork.repo_name if repo.fork else None,
2140 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2140 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2141 'enable_statistics': repo.enable_statistics,
2141 'enable_statistics': repo.enable_statistics,
2142 'enable_locking': repo.enable_locking,
2142 'enable_locking': repo.enable_locking,
2143 'enable_downloads': repo.enable_downloads,
2143 'enable_downloads': repo.enable_downloads,
2144 'last_changeset': repo.changeset_cache,
2144 'last_changeset': repo.changeset_cache,
2145 'locked_by': User.get(_user_id).get_api_data(
2145 'locked_by': User.get(_user_id).get_api_data(
2146 include_secrets=include_secrets) if _user_id else None,
2146 include_secrets=include_secrets) if _user_id else None,
2147 'locked_date': time_to_datetime(_time) if _time else None,
2147 'locked_date': time_to_datetime(_time) if _time else None,
2148 'lock_reason': _reason if _reason else None,
2148 'lock_reason': _reason if _reason else None,
2149 }
2149 }
2150
2150
2151 # TODO: mikhail: should be per-repo settings here
2151 # TODO: mikhail: should be per-repo settings here
2152 rc_config = SettingsModel().get_all_settings()
2152 rc_config = SettingsModel().get_all_settings()
2153 repository_fields = str2bool(
2153 repository_fields = str2bool(
2154 rc_config.get('rhodecode_repository_fields'))
2154 rc_config.get('rhodecode_repository_fields'))
2155 if repository_fields:
2155 if repository_fields:
2156 for f in self.extra_fields:
2156 for f in self.extra_fields:
2157 data[f.field_key_prefixed] = f.field_value
2157 data[f.field_key_prefixed] = f.field_value
2158
2158
2159 return data
2159 return data
2160
2160
2161 @classmethod
2161 @classmethod
2162 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2162 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2163 if not lock_time:
2163 if not lock_time:
2164 lock_time = time.time()
2164 lock_time = time.time()
2165 if not lock_reason:
2165 if not lock_reason:
2166 lock_reason = cls.LOCK_AUTOMATIC
2166 lock_reason = cls.LOCK_AUTOMATIC
2167 repo.locked = [user_id, lock_time, lock_reason]
2167 repo.locked = [user_id, lock_time, lock_reason]
2168 Session().add(repo)
2168 Session().add(repo)
2169 Session().commit()
2169 Session().commit()
2170
2170
2171 @classmethod
2171 @classmethod
2172 def unlock(cls, repo):
2172 def unlock(cls, repo):
2173 repo.locked = None
2173 repo.locked = None
2174 Session().add(repo)
2174 Session().add(repo)
2175 Session().commit()
2175 Session().commit()
2176
2176
2177 @classmethod
2177 @classmethod
2178 def getlock(cls, repo):
2178 def getlock(cls, repo):
2179 return repo.locked
2179 return repo.locked
2180
2180
2181 def is_user_lock(self, user_id):
2181 def is_user_lock(self, user_id):
2182 if self.lock[0]:
2182 if self.lock[0]:
2183 lock_user_id = safe_int(self.lock[0])
2183 lock_user_id = safe_int(self.lock[0])
2184 user_id = safe_int(user_id)
2184 user_id = safe_int(user_id)
2185 # both are ints, and they are equal
2185 # both are ints, and they are equal
2186 return all([lock_user_id, user_id]) and lock_user_id == user_id
2186 return all([lock_user_id, user_id]) and lock_user_id == user_id
2187
2187
2188 return False
2188 return False
2189
2189
2190 def get_locking_state(self, action, user_id, only_when_enabled=True):
2190 def get_locking_state(self, action, user_id, only_when_enabled=True):
2191 """
2191 """
2192 Checks locking on this repository, if locking is enabled and lock is
2192 Checks locking on this repository, if locking is enabled and lock is
2193 present returns a tuple of make_lock, locked, locked_by.
2193 present returns a tuple of make_lock, locked, locked_by.
2194 make_lock can have 3 states None (do nothing) True, make lock
2194 make_lock can have 3 states None (do nothing) True, make lock
2195 False release lock, This value is later propagated to hooks, which
2195 False release lock, This value is later propagated to hooks, which
2196 do the locking. Think about this as signals passed to hooks what to do.
2196 do the locking. Think about this as signals passed to hooks what to do.
2197
2197
2198 """
2198 """
2199 # TODO: johbo: This is part of the business logic and should be moved
2199 # TODO: johbo: This is part of the business logic and should be moved
2200 # into the RepositoryModel.
2200 # into the RepositoryModel.
2201
2201
2202 if action not in ('push', 'pull'):
2202 if action not in ('push', 'pull'):
2203 raise ValueError("Invalid action value: %s" % repr(action))
2203 raise ValueError("Invalid action value: %s" % repr(action))
2204
2204
2205 # defines if locked error should be thrown to user
2205 # defines if locked error should be thrown to user
2206 currently_locked = False
2206 currently_locked = False
2207 # defines if new lock should be made, tri-state
2207 # defines if new lock should be made, tri-state
2208 make_lock = None
2208 make_lock = None
2209 repo = self
2209 repo = self
2210 user = User.get(user_id)
2210 user = User.get(user_id)
2211
2211
2212 lock_info = repo.locked
2212 lock_info = repo.locked
2213
2213
2214 if repo and (repo.enable_locking or not only_when_enabled):
2214 if repo and (repo.enable_locking or not only_when_enabled):
2215 if action == 'push':
2215 if action == 'push':
2216 # check if it's already locked !, if it is compare users
2216 # check if it's already locked !, if it is compare users
2217 locked_by_user_id = lock_info[0]
2217 locked_by_user_id = lock_info[0]
2218 if user.user_id == locked_by_user_id:
2218 if user.user_id == locked_by_user_id:
2219 log.debug(
2219 log.debug(
2220 'Got `push` action from user %s, now unlocking', user)
2220 'Got `push` action from user %s, now unlocking', user)
2221 # unlock if we have push from user who locked
2221 # unlock if we have push from user who locked
2222 make_lock = False
2222 make_lock = False
2223 else:
2223 else:
2224 # we're not the same user who locked, ban with
2224 # we're not the same user who locked, ban with
2225 # code defined in settings (default is 423 HTTP Locked) !
2225 # code defined in settings (default is 423 HTTP Locked) !
2226 log.debug('Repo %s is currently locked by %s', repo, user)
2226 log.debug('Repo %s is currently locked by %s', repo, user)
2227 currently_locked = True
2227 currently_locked = True
2228 elif action == 'pull':
2228 elif action == 'pull':
2229 # [0] user [1] date
2229 # [0] user [1] date
2230 if lock_info[0] and lock_info[1]:
2230 if lock_info[0] and lock_info[1]:
2231 log.debug('Repo %s is currently locked by %s', repo, user)
2231 log.debug('Repo %s is currently locked by %s', repo, user)
2232 currently_locked = True
2232 currently_locked = True
2233 else:
2233 else:
2234 log.debug('Setting lock on repo %s by %s', repo, user)
2234 log.debug('Setting lock on repo %s by %s', repo, user)
2235 make_lock = True
2235 make_lock = True
2236
2236
2237 else:
2237 else:
2238 log.debug('Repository %s do not have locking enabled', repo)
2238 log.debug('Repository %s do not have locking enabled', repo)
2239
2239
2240 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2240 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2241 make_lock, currently_locked, lock_info)
2241 make_lock, currently_locked, lock_info)
2242
2242
2243 from rhodecode.lib.auth import HasRepoPermissionAny
2243 from rhodecode.lib.auth import HasRepoPermissionAny
2244 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2244 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2245 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2245 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2246 # if we don't have at least write permission we cannot make a lock
2246 # if we don't have at least write permission we cannot make a lock
2247 log.debug('lock state reset back to FALSE due to lack '
2247 log.debug('lock state reset back to FALSE due to lack '
2248 'of at least read permission')
2248 'of at least read permission')
2249 make_lock = False
2249 make_lock = False
2250
2250
2251 return make_lock, currently_locked, lock_info
2251 return make_lock, currently_locked, lock_info
2252
2252
2253 @property
2253 @property
2254 def last_commit_cache_update_diff(self):
2254 def last_commit_cache_update_diff(self):
2255 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2255 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2256
2256
2257 @classmethod
2257 @classmethod
2258 def _load_commit_change(cls, last_commit_cache):
2258 def _load_commit_change(cls, last_commit_cache):
2259 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2259 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2260 empty_date = datetime.datetime.fromtimestamp(0)
2260 empty_date = datetime.datetime.fromtimestamp(0)
2261 date_latest = last_commit_cache.get('date', empty_date)
2261 date_latest = last_commit_cache.get('date', empty_date)
2262 try:
2262 try:
2263 return parse_datetime(date_latest)
2263 return parse_datetime(date_latest)
2264 except Exception:
2264 except Exception:
2265 return empty_date
2265 return empty_date
2266
2266
2267 @property
2267 @property
2268 def last_commit_change(self):
2268 def last_commit_change(self):
2269 return self._load_commit_change(self.changeset_cache)
2269 return self._load_commit_change(self.changeset_cache)
2270
2270
2271 @property
2271 @property
2272 def last_db_change(self):
2272 def last_db_change(self):
2273 return self.updated_on
2273 return self.updated_on
2274
2274
2275 @property
2275 @property
2276 def clone_uri_hidden(self):
2276 def clone_uri_hidden(self):
2277 clone_uri = self.clone_uri
2277 clone_uri = self.clone_uri
2278 if clone_uri:
2278 if clone_uri:
2279 import urlobject
2279 import urlobject
2280 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2280 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2281 if url_obj.password:
2281 if url_obj.password:
2282 clone_uri = url_obj.with_password('*****')
2282 clone_uri = url_obj.with_password('*****')
2283 return clone_uri
2283 return clone_uri
2284
2284
2285 @property
2285 @property
2286 def push_uri_hidden(self):
2286 def push_uri_hidden(self):
2287 push_uri = self.push_uri
2287 push_uri = self.push_uri
2288 if push_uri:
2288 if push_uri:
2289 import urlobject
2289 import urlobject
2290 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2290 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2291 if url_obj.password:
2291 if url_obj.password:
2292 push_uri = url_obj.with_password('*****')
2292 push_uri = url_obj.with_password('*****')
2293 return push_uri
2293 return push_uri
2294
2294
2295 def clone_url(self, **override):
2295 def clone_url(self, **override):
2296 from rhodecode.model.settings import SettingsModel
2296 from rhodecode.model.settings import SettingsModel
2297
2297
2298 uri_tmpl = None
2298 uri_tmpl = None
2299 if 'with_id' in override:
2299 if 'with_id' in override:
2300 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2300 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2301 del override['with_id']
2301 del override['with_id']
2302
2302
2303 if 'uri_tmpl' in override:
2303 if 'uri_tmpl' in override:
2304 uri_tmpl = override['uri_tmpl']
2304 uri_tmpl = override['uri_tmpl']
2305 del override['uri_tmpl']
2305 del override['uri_tmpl']
2306
2306
2307 ssh = False
2307 ssh = False
2308 if 'ssh' in override:
2308 if 'ssh' in override:
2309 ssh = True
2309 ssh = True
2310 del override['ssh']
2310 del override['ssh']
2311
2311
2312 # we didn't override our tmpl from **overrides
2312 # we didn't override our tmpl from **overrides
2313 request = get_current_request()
2313 request = get_current_request()
2314 if not uri_tmpl:
2314 if not uri_tmpl:
2315 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2315 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2316 rc_config = request.call_context.rc_config
2316 rc_config = request.call_context.rc_config
2317 else:
2317 else:
2318 rc_config = SettingsModel().get_all_settings(cache=True)
2318 rc_config = SettingsModel().get_all_settings(cache=True)
2319
2319
2320 if ssh:
2320 if ssh:
2321 uri_tmpl = rc_config.get(
2321 uri_tmpl = rc_config.get(
2322 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2322 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2323
2323
2324 else:
2324 else:
2325 uri_tmpl = rc_config.get(
2325 uri_tmpl = rc_config.get(
2326 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2326 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2327
2327
2328 return get_clone_url(request=request,
2328 return get_clone_url(request=request,
2329 uri_tmpl=uri_tmpl,
2329 uri_tmpl=uri_tmpl,
2330 repo_name=self.repo_name,
2330 repo_name=self.repo_name,
2331 repo_id=self.repo_id,
2331 repo_id=self.repo_id,
2332 repo_type=self.repo_type,
2332 repo_type=self.repo_type,
2333 **override)
2333 **override)
2334
2334
2335 def set_state(self, state):
2335 def set_state(self, state):
2336 self.repo_state = state
2336 self.repo_state = state
2337 Session().add(self)
2337 Session().add(self)
2338 #==========================================================================
2338 #==========================================================================
2339 # SCM PROPERTIES
2339 # SCM PROPERTIES
2340 #==========================================================================
2340 #==========================================================================
2341
2341
2342 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2342 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2343 return get_commit_safe(
2343 return get_commit_safe(
2344 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2344 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2345 maybe_unreachable=maybe_unreachable)
2345 maybe_unreachable=maybe_unreachable)
2346
2346
2347 def get_changeset(self, rev=None, pre_load=None):
2347 def get_changeset(self, rev=None, pre_load=None):
2348 warnings.warn("Use get_commit", DeprecationWarning)
2348 warnings.warn("Use get_commit", DeprecationWarning)
2349 commit_id = None
2349 commit_id = None
2350 commit_idx = None
2350 commit_idx = None
2351 if isinstance(rev, compat.string_types):
2351 if isinstance(rev, compat.string_types):
2352 commit_id = rev
2352 commit_id = rev
2353 else:
2353 else:
2354 commit_idx = rev
2354 commit_idx = rev
2355 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2355 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2356 pre_load=pre_load)
2356 pre_load=pre_load)
2357
2357
2358 def get_landing_commit(self):
2358 def get_landing_commit(self):
2359 """
2359 """
2360 Returns landing commit, or if that doesn't exist returns the tip
2360 Returns landing commit, or if that doesn't exist returns the tip
2361 """
2361 """
2362 _rev_type, _rev = self.landing_rev
2362 _rev_type, _rev = self.landing_rev
2363 commit = self.get_commit(_rev)
2363 commit = self.get_commit(_rev)
2364 if isinstance(commit, EmptyCommit):
2364 if isinstance(commit, EmptyCommit):
2365 return self.get_commit()
2365 return self.get_commit()
2366 return commit
2366 return commit
2367
2367
2368 def flush_commit_cache(self):
2368 def flush_commit_cache(self):
2369 self.update_commit_cache(cs_cache={'raw_id':'0'})
2369 self.update_commit_cache(cs_cache={'raw_id':'0'})
2370 self.update_commit_cache()
2370 self.update_commit_cache()
2371
2371
2372 def update_commit_cache(self, cs_cache=None, config=None):
2372 def update_commit_cache(self, cs_cache=None, config=None):
2373 """
2373 """
2374 Update cache of last commit for repository
2374 Update cache of last commit for repository
2375 cache_keys should be::
2375 cache_keys should be::
2376
2376
2377 source_repo_id
2377 source_repo_id
2378 short_id
2378 short_id
2379 raw_id
2379 raw_id
2380 revision
2380 revision
2381 parents
2381 parents
2382 message
2382 message
2383 date
2383 date
2384 author
2384 author
2385 updated_on
2385 updated_on
2386
2386
2387 """
2387 """
2388 from rhodecode.lib.vcs.backends.base import BaseChangeset
2388 from rhodecode.lib.vcs.backends.base import BaseChangeset
2389 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2389 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2390 empty_date = datetime.datetime.fromtimestamp(0)
2390 empty_date = datetime.datetime.fromtimestamp(0)
2391
2391
2392 if cs_cache is None:
2392 if cs_cache is None:
2393 # use no-cache version here
2393 # use no-cache version here
2394 try:
2394 try:
2395 scm_repo = self.scm_instance(cache=False, config=config)
2395 scm_repo = self.scm_instance(cache=False, config=config)
2396 except VCSError:
2396 except VCSError:
2397 scm_repo = None
2397 scm_repo = None
2398 empty = scm_repo is None or scm_repo.is_empty()
2398 empty = scm_repo is None or scm_repo.is_empty()
2399
2399
2400 if not empty:
2400 if not empty:
2401 cs_cache = scm_repo.get_commit(
2401 cs_cache = scm_repo.get_commit(
2402 pre_load=["author", "date", "message", "parents", "branch"])
2402 pre_load=["author", "date", "message", "parents", "branch"])
2403 else:
2403 else:
2404 cs_cache = EmptyCommit()
2404 cs_cache = EmptyCommit()
2405
2405
2406 if isinstance(cs_cache, BaseChangeset):
2406 if isinstance(cs_cache, BaseChangeset):
2407 cs_cache = cs_cache.__json__()
2407 cs_cache = cs_cache.__json__()
2408
2408
2409 def is_outdated(new_cs_cache):
2409 def is_outdated(new_cs_cache):
2410 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2410 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2411 new_cs_cache['revision'] != self.changeset_cache['revision']):
2411 new_cs_cache['revision'] != self.changeset_cache['revision']):
2412 return True
2412 return True
2413 return False
2413 return False
2414
2414
2415 # check if we have maybe already latest cached revision
2415 # check if we have maybe already latest cached revision
2416 if is_outdated(cs_cache) or not self.changeset_cache:
2416 if is_outdated(cs_cache) or not self.changeset_cache:
2417 _current_datetime = datetime.datetime.utcnow()
2417 _current_datetime = datetime.datetime.utcnow()
2418 last_change = cs_cache.get('date') or _current_datetime
2418 last_change = cs_cache.get('date') or _current_datetime
2419 # we check if last update is newer than the new value
2419 # we check if last update is newer than the new value
2420 # if yes, we use the current timestamp instead. Imagine you get
2420 # if yes, we use the current timestamp instead. Imagine you get
2421 # old commit pushed 1y ago, we'd set last update 1y to ago.
2421 # old commit pushed 1y ago, we'd set last update 1y to ago.
2422 last_change_timestamp = datetime_to_time(last_change)
2422 last_change_timestamp = datetime_to_time(last_change)
2423 current_timestamp = datetime_to_time(last_change)
2423 current_timestamp = datetime_to_time(last_change)
2424 if last_change_timestamp > current_timestamp and not empty:
2424 if last_change_timestamp > current_timestamp and not empty:
2425 cs_cache['date'] = _current_datetime
2425 cs_cache['date'] = _current_datetime
2426
2426
2427 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2427 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2428 cs_cache['updated_on'] = time.time()
2428 cs_cache['updated_on'] = time.time()
2429 self.changeset_cache = cs_cache
2429 self.changeset_cache = cs_cache
2430 self.updated_on = last_change
2430 self.updated_on = last_change
2431 Session().add(self)
2431 Session().add(self)
2432 Session().commit()
2432 Session().commit()
2433
2433
2434 else:
2434 else:
2435 if empty:
2435 if empty:
2436 cs_cache = EmptyCommit().__json__()
2436 cs_cache = EmptyCommit().__json__()
2437 else:
2437 else:
2438 cs_cache = self.changeset_cache
2438 cs_cache = self.changeset_cache
2439
2439
2440 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2440 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2441
2441
2442 cs_cache['updated_on'] = time.time()
2442 cs_cache['updated_on'] = time.time()
2443 self.changeset_cache = cs_cache
2443 self.changeset_cache = cs_cache
2444 self.updated_on = _date_latest
2444 self.updated_on = _date_latest
2445 Session().add(self)
2445 Session().add(self)
2446 Session().commit()
2446 Session().commit()
2447
2447
2448 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2448 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2449 self.repo_name, cs_cache, _date_latest)
2449 self.repo_name, cs_cache, _date_latest)
2450
2450
2451 @property
2451 @property
2452 def tip(self):
2452 def tip(self):
2453 return self.get_commit('tip')
2453 return self.get_commit('tip')
2454
2454
2455 @property
2455 @property
2456 def author(self):
2456 def author(self):
2457 return self.tip.author
2457 return self.tip.author
2458
2458
2459 @property
2459 @property
2460 def last_change(self):
2460 def last_change(self):
2461 return self.scm_instance().last_change
2461 return self.scm_instance().last_change
2462
2462
2463 def get_comments(self, revisions=None):
2463 def get_comments(self, revisions=None):
2464 """
2464 """
2465 Returns comments for this repository grouped by revisions
2465 Returns comments for this repository grouped by revisions
2466
2466
2467 :param revisions: filter query by revisions only
2467 :param revisions: filter query by revisions only
2468 """
2468 """
2469 cmts = ChangesetComment.query()\
2469 cmts = ChangesetComment.query()\
2470 .filter(ChangesetComment.repo == self)
2470 .filter(ChangesetComment.repo == self)
2471 if revisions:
2471 if revisions:
2472 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2472 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2473 grouped = collections.defaultdict(list)
2473 grouped = collections.defaultdict(list)
2474 for cmt in cmts.all():
2474 for cmt in cmts.all():
2475 grouped[cmt.revision].append(cmt)
2475 grouped[cmt.revision].append(cmt)
2476 return grouped
2476 return grouped
2477
2477
2478 def statuses(self, revisions=None):
2478 def statuses(self, revisions=None):
2479 """
2479 """
2480 Returns statuses for this repository
2480 Returns statuses for this repository
2481
2481
2482 :param revisions: list of revisions to get statuses for
2482 :param revisions: list of revisions to get statuses for
2483 """
2483 """
2484 statuses = ChangesetStatus.query()\
2484 statuses = ChangesetStatus.query()\
2485 .filter(ChangesetStatus.repo == self)\
2485 .filter(ChangesetStatus.repo == self)\
2486 .filter(ChangesetStatus.version == 0)
2486 .filter(ChangesetStatus.version == 0)
2487
2487
2488 if revisions:
2488 if revisions:
2489 # Try doing the filtering in chunks to avoid hitting limits
2489 # Try doing the filtering in chunks to avoid hitting limits
2490 size = 500
2490 size = 500
2491 status_results = []
2491 status_results = []
2492 for chunk in xrange(0, len(revisions), size):
2492 for chunk in xrange(0, len(revisions), size):
2493 status_results += statuses.filter(
2493 status_results += statuses.filter(
2494 ChangesetStatus.revision.in_(
2494 ChangesetStatus.revision.in_(
2495 revisions[chunk: chunk+size])
2495 revisions[chunk: chunk+size])
2496 ).all()
2496 ).all()
2497 else:
2497 else:
2498 status_results = statuses.all()
2498 status_results = statuses.all()
2499
2499
2500 grouped = {}
2500 grouped = {}
2501
2501
2502 # maybe we have open new pullrequest without a status?
2502 # maybe we have open new pullrequest without a status?
2503 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2503 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2504 status_lbl = ChangesetStatus.get_status_lbl(stat)
2504 status_lbl = ChangesetStatus.get_status_lbl(stat)
2505 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2505 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2506 for rev in pr.revisions:
2506 for rev in pr.revisions:
2507 pr_id = pr.pull_request_id
2507 pr_id = pr.pull_request_id
2508 pr_repo = pr.target_repo.repo_name
2508 pr_repo = pr.target_repo.repo_name
2509 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2509 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2510
2510
2511 for stat in status_results:
2511 for stat in status_results:
2512 pr_id = pr_repo = None
2512 pr_id = pr_repo = None
2513 if stat.pull_request:
2513 if stat.pull_request:
2514 pr_id = stat.pull_request.pull_request_id
2514 pr_id = stat.pull_request.pull_request_id
2515 pr_repo = stat.pull_request.target_repo.repo_name
2515 pr_repo = stat.pull_request.target_repo.repo_name
2516 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2516 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2517 pr_id, pr_repo]
2517 pr_id, pr_repo]
2518 return grouped
2518 return grouped
2519
2519
2520 # ==========================================================================
2520 # ==========================================================================
2521 # SCM CACHE INSTANCE
2521 # SCM CACHE INSTANCE
2522 # ==========================================================================
2522 # ==========================================================================
2523
2523
2524 def scm_instance(self, **kwargs):
2524 def scm_instance(self, **kwargs):
2525 import rhodecode
2525 import rhodecode
2526
2526
2527 # Passing a config will not hit the cache currently only used
2527 # Passing a config will not hit the cache currently only used
2528 # for repo2dbmapper
2528 # for repo2dbmapper
2529 config = kwargs.pop('config', None)
2529 config = kwargs.pop('config', None)
2530 cache = kwargs.pop('cache', None)
2530 cache = kwargs.pop('cache', None)
2531 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2531 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2532 if vcs_full_cache is not None:
2532 if vcs_full_cache is not None:
2533 # allows override global config
2533 # allows override global config
2534 full_cache = vcs_full_cache
2534 full_cache = vcs_full_cache
2535 else:
2535 else:
2536 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2536 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2537 # if cache is NOT defined use default global, else we have a full
2537 # if cache is NOT defined use default global, else we have a full
2538 # control over cache behaviour
2538 # control over cache behaviour
2539 if cache is None and full_cache and not config:
2539 if cache is None and full_cache and not config:
2540 log.debug('Initializing pure cached instance for %s', self.repo_path)
2540 log.debug('Initializing pure cached instance for %s', self.repo_path)
2541 return self._get_instance_cached()
2541 return self._get_instance_cached()
2542
2542
2543 # cache here is sent to the "vcs server"
2543 # cache here is sent to the "vcs server"
2544 return self._get_instance(cache=bool(cache), config=config)
2544 return self._get_instance(cache=bool(cache), config=config)
2545
2545
2546 def _get_instance_cached(self):
2546 def _get_instance_cached(self):
2547 from rhodecode.lib import rc_cache
2547 from rhodecode.lib import rc_cache
2548
2548
2549 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2549 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2550 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2550 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2551 repo_id=self.repo_id)
2551 repo_id=self.repo_id)
2552 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2552 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2553
2553
2554 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2554 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2555 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2555 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2556 return self._get_instance(repo_state_uid=_cache_state_uid)
2556 return self._get_instance(repo_state_uid=_cache_state_uid)
2557
2557
2558 # we must use thread scoped cache here,
2558 # we must use thread scoped cache here,
2559 # because each thread of gevent needs it's own not shared connection and cache
2559 # because each thread of gevent needs it's own not shared connection and cache
2560 # we also alter `args` so the cache key is individual for every green thread.
2560 # we also alter `args` so the cache key is individual for every green thread.
2561 inv_context_manager = rc_cache.InvalidationContext(
2561 inv_context_manager = rc_cache.InvalidationContext(
2562 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2562 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2563 thread_scoped=True)
2563 thread_scoped=True)
2564 with inv_context_manager as invalidation_context:
2564 with inv_context_manager as invalidation_context:
2565 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2565 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2566 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2566 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2567
2567
2568 # re-compute and store cache if we get invalidate signal
2568 # re-compute and store cache if we get invalidate signal
2569 if invalidation_context.should_invalidate():
2569 if invalidation_context.should_invalidate():
2570 instance = get_instance_cached.refresh(*args)
2570 instance = get_instance_cached.refresh(*args)
2571 else:
2571 else:
2572 instance = get_instance_cached(*args)
2572 instance = get_instance_cached(*args)
2573
2573
2574 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2574 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2575 return instance
2575 return instance
2576
2576
2577 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2577 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2578 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2578 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2579 self.repo_type, self.repo_path, cache)
2579 self.repo_type, self.repo_path, cache)
2580 config = config or self._config
2580 config = config or self._config
2581 custom_wire = {
2581 custom_wire = {
2582 'cache': cache, # controls the vcs.remote cache
2582 'cache': cache, # controls the vcs.remote cache
2583 'repo_state_uid': repo_state_uid
2583 'repo_state_uid': repo_state_uid
2584 }
2584 }
2585 repo = get_vcs_instance(
2585 repo = get_vcs_instance(
2586 repo_path=safe_str(self.repo_full_path),
2586 repo_path=safe_str(self.repo_full_path),
2587 config=config,
2587 config=config,
2588 with_wire=custom_wire,
2588 with_wire=custom_wire,
2589 create=False,
2589 create=False,
2590 _vcs_alias=self.repo_type)
2590 _vcs_alias=self.repo_type)
2591 if repo is not None:
2591 if repo is not None:
2592 repo.count() # cache rebuild
2592 repo.count() # cache rebuild
2593 return repo
2593 return repo
2594
2594
2595 def get_shadow_repository_path(self, workspace_id):
2595 def get_shadow_repository_path(self, workspace_id):
2596 from rhodecode.lib.vcs.backends.base import BaseRepository
2596 from rhodecode.lib.vcs.backends.base import BaseRepository
2597 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2597 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2598 self.repo_full_path, self.repo_id, workspace_id)
2598 self.repo_full_path, self.repo_id, workspace_id)
2599 return shadow_repo_path
2599 return shadow_repo_path
2600
2600
2601 def __json__(self):
2601 def __json__(self):
2602 return {'landing_rev': self.landing_rev}
2602 return {'landing_rev': self.landing_rev}
2603
2603
2604 def get_dict(self):
2604 def get_dict(self):
2605
2605
2606 # Since we transformed `repo_name` to a hybrid property, we need to
2606 # Since we transformed `repo_name` to a hybrid property, we need to
2607 # keep compatibility with the code which uses `repo_name` field.
2607 # keep compatibility with the code which uses `repo_name` field.
2608
2608
2609 result = super(Repository, self).get_dict()
2609 result = super(Repository, self).get_dict()
2610 result['repo_name'] = result.pop('_repo_name', None)
2610 result['repo_name'] = result.pop('_repo_name', None)
2611 return result
2611 return result
2612
2612
2613
2613
2614 class RepoGroup(Base, BaseModel):
2614 class RepoGroup(Base, BaseModel):
2615 __tablename__ = 'groups'
2615 __tablename__ = 'groups'
2616 __table_args__ = (
2616 __table_args__ = (
2617 UniqueConstraint('group_name', 'group_parent_id'),
2617 UniqueConstraint('group_name', 'group_parent_id'),
2618 base_table_args,
2618 base_table_args,
2619 )
2619 )
2620 __mapper_args__ = {'order_by': 'group_name'}
2620 __mapper_args__ = {'order_by': 'group_name'}
2621
2621
2622 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2622 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2623
2623
2624 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2624 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2625 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2625 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2626 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2626 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2627 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2627 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2628 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2628 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2629 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2629 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2633 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2633 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2634 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2634 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2635
2635
2636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2638 parent_group = relationship('RepoGroup', remote_side=group_id)
2638 parent_group = relationship('RepoGroup', remote_side=group_id)
2639 user = relationship('User')
2639 user = relationship('User')
2640 integrations = relationship('Integration', cascade="all, delete-orphan")
2640 integrations = relationship('Integration', cascade="all, delete-orphan")
2641
2641
2642 # no cascade, set NULL
2642 # no cascade, set NULL
2643 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2643 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2644
2644
2645 def __init__(self, group_name='', parent_group=None):
2645 def __init__(self, group_name='', parent_group=None):
2646 self.group_name = group_name
2646 self.group_name = group_name
2647 self.parent_group = parent_group
2647 self.parent_group = parent_group
2648
2648
2649 def __unicode__(self):
2649 def __unicode__(self):
2650 return u"<%s('id:%s:%s')>" % (
2650 return u"<%s('id:%s:%s')>" % (
2651 self.__class__.__name__, self.group_id, self.group_name)
2651 self.__class__.__name__, self.group_id, self.group_name)
2652
2652
2653 @hybrid_property
2653 @hybrid_property
2654 def group_name(self):
2654 def group_name(self):
2655 return self._group_name
2655 return self._group_name
2656
2656
2657 @group_name.setter
2657 @group_name.setter
2658 def group_name(self, value):
2658 def group_name(self, value):
2659 self._group_name = value
2659 self._group_name = value
2660 self.group_name_hash = self.hash_repo_group_name(value)
2660 self.group_name_hash = self.hash_repo_group_name(value)
2661
2661
2662 @classmethod
2662 @classmethod
2663 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2663 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2664 from rhodecode.lib.vcs.backends.base import EmptyCommit
2664 from rhodecode.lib.vcs.backends.base import EmptyCommit
2665 dummy = EmptyCommit().__json__()
2665 dummy = EmptyCommit().__json__()
2666 if not changeset_cache_raw:
2666 if not changeset_cache_raw:
2667 dummy['source_repo_id'] = repo_id
2667 dummy['source_repo_id'] = repo_id
2668 return json.loads(json.dumps(dummy))
2668 return json.loads(json.dumps(dummy))
2669
2669
2670 try:
2670 try:
2671 return json.loads(changeset_cache_raw)
2671 return json.loads(changeset_cache_raw)
2672 except TypeError:
2672 except TypeError:
2673 return dummy
2673 return dummy
2674 except Exception:
2674 except Exception:
2675 log.error(traceback.format_exc())
2675 log.error(traceback.format_exc())
2676 return dummy
2676 return dummy
2677
2677
2678 @hybrid_property
2678 @hybrid_property
2679 def changeset_cache(self):
2679 def changeset_cache(self):
2680 return self._load_changeset_cache('', self._changeset_cache)
2680 return self._load_changeset_cache('', self._changeset_cache)
2681
2681
2682 @changeset_cache.setter
2682 @changeset_cache.setter
2683 def changeset_cache(self, val):
2683 def changeset_cache(self, val):
2684 try:
2684 try:
2685 self._changeset_cache = json.dumps(val)
2685 self._changeset_cache = json.dumps(val)
2686 except Exception:
2686 except Exception:
2687 log.error(traceback.format_exc())
2687 log.error(traceback.format_exc())
2688
2688
2689 @validates('group_parent_id')
2689 @validates('group_parent_id')
2690 def validate_group_parent_id(self, key, val):
2690 def validate_group_parent_id(self, key, val):
2691 """
2691 """
2692 Check cycle references for a parent group to self
2692 Check cycle references for a parent group to self
2693 """
2693 """
2694 if self.group_id and val:
2694 if self.group_id and val:
2695 assert val != self.group_id
2695 assert val != self.group_id
2696
2696
2697 return val
2697 return val
2698
2698
2699 @hybrid_property
2699 @hybrid_property
2700 def description_safe(self):
2700 def description_safe(self):
2701 from rhodecode.lib import helpers as h
2701 from rhodecode.lib import helpers as h
2702 return h.escape(self.group_description)
2702 return h.escape(self.group_description)
2703
2703
2704 @classmethod
2704 @classmethod
2705 def hash_repo_group_name(cls, repo_group_name):
2705 def hash_repo_group_name(cls, repo_group_name):
2706 val = remove_formatting(repo_group_name)
2706 val = remove_formatting(repo_group_name)
2707 val = safe_str(val).lower()
2707 val = safe_str(val).lower()
2708 chars = []
2708 chars = []
2709 for c in val:
2709 for c in val:
2710 if c not in string.ascii_letters:
2710 if c not in string.ascii_letters:
2711 c = str(ord(c))
2711 c = str(ord(c))
2712 chars.append(c)
2712 chars.append(c)
2713
2713
2714 return ''.join(chars)
2714 return ''.join(chars)
2715
2715
2716 @classmethod
2716 @classmethod
2717 def _generate_choice(cls, repo_group):
2717 def _generate_choice(cls, repo_group):
2718 from webhelpers2.html import literal as _literal
2718 from webhelpers2.html import literal as _literal
2719 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2719 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2720 return repo_group.group_id, _name(repo_group.full_path_splitted)
2720 return repo_group.group_id, _name(repo_group.full_path_splitted)
2721
2721
2722 @classmethod
2722 @classmethod
2723 def groups_choices(cls, groups=None, show_empty_group=True):
2723 def groups_choices(cls, groups=None, show_empty_group=True):
2724 if not groups:
2724 if not groups:
2725 groups = cls.query().all()
2725 groups = cls.query().all()
2726
2726
2727 repo_groups = []
2727 repo_groups = []
2728 if show_empty_group:
2728 if show_empty_group:
2729 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2729 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2730
2730
2731 repo_groups.extend([cls._generate_choice(x) for x in groups])
2731 repo_groups.extend([cls._generate_choice(x) for x in groups])
2732
2732
2733 repo_groups = sorted(
2733 repo_groups = sorted(
2734 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2734 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2735 return repo_groups
2735 return repo_groups
2736
2736
2737 @classmethod
2737 @classmethod
2738 def url_sep(cls):
2738 def url_sep(cls):
2739 return URL_SEP
2739 return URL_SEP
2740
2740
2741 @classmethod
2741 @classmethod
2742 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2742 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2743 if case_insensitive:
2743 if case_insensitive:
2744 gr = cls.query().filter(func.lower(cls.group_name)
2744 gr = cls.query().filter(func.lower(cls.group_name)
2745 == func.lower(group_name))
2745 == func.lower(group_name))
2746 else:
2746 else:
2747 gr = cls.query().filter(cls.group_name == group_name)
2747 gr = cls.query().filter(cls.group_name == group_name)
2748 if cache:
2748 if cache:
2749 name_key = _hash_key(group_name)
2749 name_key = _hash_key(group_name)
2750 gr = gr.options(
2750 gr = gr.options(
2751 FromCache("sql_cache_short", "get_group_%s" % name_key))
2751 FromCache("sql_cache_short", "get_group_%s" % name_key))
2752 return gr.scalar()
2752 return gr.scalar()
2753
2753
2754 @classmethod
2754 @classmethod
2755 def get_user_personal_repo_group(cls, user_id):
2755 def get_user_personal_repo_group(cls, user_id):
2756 user = User.get(user_id)
2756 user = User.get(user_id)
2757 if user.username == User.DEFAULT_USER:
2757 if user.username == User.DEFAULT_USER:
2758 return None
2758 return None
2759
2759
2760 return cls.query()\
2760 return cls.query()\
2761 .filter(cls.personal == true()) \
2761 .filter(cls.personal == true()) \
2762 .filter(cls.user == user) \
2762 .filter(cls.user == user) \
2763 .order_by(cls.group_id.asc()) \
2763 .order_by(cls.group_id.asc()) \
2764 .first()
2764 .first()
2765
2765
2766 @classmethod
2766 @classmethod
2767 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2767 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2768 case_insensitive=True):
2768 case_insensitive=True):
2769 q = RepoGroup.query()
2769 q = RepoGroup.query()
2770
2770
2771 if not isinstance(user_id, Optional):
2771 if not isinstance(user_id, Optional):
2772 q = q.filter(RepoGroup.user_id == user_id)
2772 q = q.filter(RepoGroup.user_id == user_id)
2773
2773
2774 if not isinstance(group_id, Optional):
2774 if not isinstance(group_id, Optional):
2775 q = q.filter(RepoGroup.group_parent_id == group_id)
2775 q = q.filter(RepoGroup.group_parent_id == group_id)
2776
2776
2777 if case_insensitive:
2777 if case_insensitive:
2778 q = q.order_by(func.lower(RepoGroup.group_name))
2778 q = q.order_by(func.lower(RepoGroup.group_name))
2779 else:
2779 else:
2780 q = q.order_by(RepoGroup.group_name)
2780 q = q.order_by(RepoGroup.group_name)
2781 return q.all()
2781 return q.all()
2782
2782
2783 @property
2783 @property
2784 def parents(self, parents_recursion_limit=10):
2784 def parents(self, parents_recursion_limit=10):
2785 groups = []
2785 groups = []
2786 if self.parent_group is None:
2786 if self.parent_group is None:
2787 return groups
2787 return groups
2788 cur_gr = self.parent_group
2788 cur_gr = self.parent_group
2789 groups.insert(0, cur_gr)
2789 groups.insert(0, cur_gr)
2790 cnt = 0
2790 cnt = 0
2791 while 1:
2791 while 1:
2792 cnt += 1
2792 cnt += 1
2793 gr = getattr(cur_gr, 'parent_group', None)
2793 gr = getattr(cur_gr, 'parent_group', None)
2794 cur_gr = cur_gr.parent_group
2794 cur_gr = cur_gr.parent_group
2795 if gr is None:
2795 if gr is None:
2796 break
2796 break
2797 if cnt == parents_recursion_limit:
2797 if cnt == parents_recursion_limit:
2798 # this will prevent accidental infinit loops
2798 # this will prevent accidental infinit loops
2799 log.error('more than %s parents found for group %s, stopping '
2799 log.error('more than %s parents found for group %s, stopping '
2800 'recursive parent fetching', parents_recursion_limit, self)
2800 'recursive parent fetching', parents_recursion_limit, self)
2801 break
2801 break
2802
2802
2803 groups.insert(0, gr)
2803 groups.insert(0, gr)
2804 return groups
2804 return groups
2805
2805
2806 @property
2806 @property
2807 def last_commit_cache_update_diff(self):
2807 def last_commit_cache_update_diff(self):
2808 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2808 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2809
2809
2810 @classmethod
2810 @classmethod
2811 def _load_commit_change(cls, last_commit_cache):
2811 def _load_commit_change(cls, last_commit_cache):
2812 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2812 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2813 empty_date = datetime.datetime.fromtimestamp(0)
2813 empty_date = datetime.datetime.fromtimestamp(0)
2814 date_latest = last_commit_cache.get('date', empty_date)
2814 date_latest = last_commit_cache.get('date', empty_date)
2815 try:
2815 try:
2816 return parse_datetime(date_latest)
2816 return parse_datetime(date_latest)
2817 except Exception:
2817 except Exception:
2818 return empty_date
2818 return empty_date
2819
2819
2820 @property
2820 @property
2821 def last_commit_change(self):
2821 def last_commit_change(self):
2822 return self._load_commit_change(self.changeset_cache)
2822 return self._load_commit_change(self.changeset_cache)
2823
2823
2824 @property
2824 @property
2825 def last_db_change(self):
2825 def last_db_change(self):
2826 return self.updated_on
2826 return self.updated_on
2827
2827
2828 @property
2828 @property
2829 def children(self):
2829 def children(self):
2830 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2830 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2831
2831
2832 @property
2832 @property
2833 def name(self):
2833 def name(self):
2834 return self.group_name.split(RepoGroup.url_sep())[-1]
2834 return self.group_name.split(RepoGroup.url_sep())[-1]
2835
2835
2836 @property
2836 @property
2837 def full_path(self):
2837 def full_path(self):
2838 return self.group_name
2838 return self.group_name
2839
2839
2840 @property
2840 @property
2841 def full_path_splitted(self):
2841 def full_path_splitted(self):
2842 return self.group_name.split(RepoGroup.url_sep())
2842 return self.group_name.split(RepoGroup.url_sep())
2843
2843
2844 @property
2844 @property
2845 def repositories(self):
2845 def repositories(self):
2846 return Repository.query()\
2846 return Repository.query()\
2847 .filter(Repository.group == self)\
2847 .filter(Repository.group == self)\
2848 .order_by(Repository.repo_name)
2848 .order_by(Repository.repo_name)
2849
2849
2850 @property
2850 @property
2851 def repositories_recursive_count(self):
2851 def repositories_recursive_count(self):
2852 cnt = self.repositories.count()
2852 cnt = self.repositories.count()
2853
2853
2854 def children_count(group):
2854 def children_count(group):
2855 cnt = 0
2855 cnt = 0
2856 for child in group.children:
2856 for child in group.children:
2857 cnt += child.repositories.count()
2857 cnt += child.repositories.count()
2858 cnt += children_count(child)
2858 cnt += children_count(child)
2859 return cnt
2859 return cnt
2860
2860
2861 return cnt + children_count(self)
2861 return cnt + children_count(self)
2862
2862
2863 def _recursive_objects(self, include_repos=True, include_groups=True):
2863 def _recursive_objects(self, include_repos=True, include_groups=True):
2864 all_ = []
2864 all_ = []
2865
2865
2866 def _get_members(root_gr):
2866 def _get_members(root_gr):
2867 if include_repos:
2867 if include_repos:
2868 for r in root_gr.repositories:
2868 for r in root_gr.repositories:
2869 all_.append(r)
2869 all_.append(r)
2870 childs = root_gr.children.all()
2870 childs = root_gr.children.all()
2871 if childs:
2871 if childs:
2872 for gr in childs:
2872 for gr in childs:
2873 if include_groups:
2873 if include_groups:
2874 all_.append(gr)
2874 all_.append(gr)
2875 _get_members(gr)
2875 _get_members(gr)
2876
2876
2877 root_group = []
2877 root_group = []
2878 if include_groups:
2878 if include_groups:
2879 root_group = [self]
2879 root_group = [self]
2880
2880
2881 _get_members(self)
2881 _get_members(self)
2882 return root_group + all_
2882 return root_group + all_
2883
2883
2884 def recursive_groups_and_repos(self):
2884 def recursive_groups_and_repos(self):
2885 """
2885 """
2886 Recursive return all groups, with repositories in those groups
2886 Recursive return all groups, with repositories in those groups
2887 """
2887 """
2888 return self._recursive_objects()
2888 return self._recursive_objects()
2889
2889
2890 def recursive_groups(self):
2890 def recursive_groups(self):
2891 """
2891 """
2892 Returns all children groups for this group including children of children
2892 Returns all children groups for this group including children of children
2893 """
2893 """
2894 return self._recursive_objects(include_repos=False)
2894 return self._recursive_objects(include_repos=False)
2895
2895
2896 def recursive_repos(self):
2896 def recursive_repos(self):
2897 """
2897 """
2898 Returns all children repositories for this group
2898 Returns all children repositories for this group
2899 """
2899 """
2900 return self._recursive_objects(include_groups=False)
2900 return self._recursive_objects(include_groups=False)
2901
2901
2902 def get_new_name(self, group_name):
2902 def get_new_name(self, group_name):
2903 """
2903 """
2904 returns new full group name based on parent and new name
2904 returns new full group name based on parent and new name
2905
2905
2906 :param group_name:
2906 :param group_name:
2907 """
2907 """
2908 path_prefix = (self.parent_group.full_path_splitted if
2908 path_prefix = (self.parent_group.full_path_splitted if
2909 self.parent_group else [])
2909 self.parent_group else [])
2910 return RepoGroup.url_sep().join(path_prefix + [group_name])
2910 return RepoGroup.url_sep().join(path_prefix + [group_name])
2911
2911
2912 def update_commit_cache(self, config=None):
2912 def update_commit_cache(self, config=None):
2913 """
2913 """
2914 Update cache of last commit for newest repository inside this repository group.
2914 Update cache of last commit for newest repository inside this repository group.
2915 cache_keys should be::
2915 cache_keys should be::
2916
2916
2917 source_repo_id
2917 source_repo_id
2918 short_id
2918 short_id
2919 raw_id
2919 raw_id
2920 revision
2920 revision
2921 parents
2921 parents
2922 message
2922 message
2923 date
2923 date
2924 author
2924 author
2925
2925
2926 """
2926 """
2927 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2927 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2928 empty_date = datetime.datetime.fromtimestamp(0)
2928 empty_date = datetime.datetime.fromtimestamp(0)
2929
2929
2930 def repo_groups_and_repos(root_gr):
2930 def repo_groups_and_repos(root_gr):
2931 for _repo in root_gr.repositories:
2931 for _repo in root_gr.repositories:
2932 yield _repo
2932 yield _repo
2933 for child_group in root_gr.children.all():
2933 for child_group in root_gr.children.all():
2934 yield child_group
2934 yield child_group
2935
2935
2936 latest_repo_cs_cache = {}
2936 latest_repo_cs_cache = {}
2937 for obj in repo_groups_and_repos(self):
2937 for obj in repo_groups_and_repos(self):
2938 repo_cs_cache = obj.changeset_cache
2938 repo_cs_cache = obj.changeset_cache
2939 date_latest = latest_repo_cs_cache.get('date', empty_date)
2939 date_latest = latest_repo_cs_cache.get('date', empty_date)
2940 date_current = repo_cs_cache.get('date', empty_date)
2940 date_current = repo_cs_cache.get('date', empty_date)
2941 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2941 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2942 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2942 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2943 latest_repo_cs_cache = repo_cs_cache
2943 latest_repo_cs_cache = repo_cs_cache
2944 if hasattr(obj, 'repo_id'):
2944 if hasattr(obj, 'repo_id'):
2945 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2945 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2946 else:
2946 else:
2947 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2947 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2948
2948
2949 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2949 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2950
2950
2951 latest_repo_cs_cache['updated_on'] = time.time()
2951 latest_repo_cs_cache['updated_on'] = time.time()
2952 self.changeset_cache = latest_repo_cs_cache
2952 self.changeset_cache = latest_repo_cs_cache
2953 self.updated_on = _date_latest
2953 self.updated_on = _date_latest
2954 Session().add(self)
2954 Session().add(self)
2955 Session().commit()
2955 Session().commit()
2956
2956
2957 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2957 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2958 self.group_name, latest_repo_cs_cache, _date_latest)
2958 self.group_name, latest_repo_cs_cache, _date_latest)
2959
2959
2960 def permissions(self, with_admins=True, with_owner=True,
2960 def permissions(self, with_admins=True, with_owner=True,
2961 expand_from_user_groups=False):
2961 expand_from_user_groups=False):
2962 """
2962 """
2963 Permissions for repository groups
2963 Permissions for repository groups
2964 """
2964 """
2965 _admin_perm = 'group.admin'
2965 _admin_perm = 'group.admin'
2966
2966
2967 owner_row = []
2967 owner_row = []
2968 if with_owner:
2968 if with_owner:
2969 usr = AttributeDict(self.user.get_dict())
2969 usr = AttributeDict(self.user.get_dict())
2970 usr.owner_row = True
2970 usr.owner_row = True
2971 usr.permission = _admin_perm
2971 usr.permission = _admin_perm
2972 owner_row.append(usr)
2972 owner_row.append(usr)
2973
2973
2974 super_admin_ids = []
2974 super_admin_ids = []
2975 super_admin_rows = []
2975 super_admin_rows = []
2976 if with_admins:
2976 if with_admins:
2977 for usr in User.get_all_super_admins():
2977 for usr in User.get_all_super_admins():
2978 super_admin_ids.append(usr.user_id)
2978 super_admin_ids.append(usr.user_id)
2979 # if this admin is also owner, don't double the record
2979 # if this admin is also owner, don't double the record
2980 if usr.user_id == owner_row[0].user_id:
2980 if usr.user_id == owner_row[0].user_id:
2981 owner_row[0].admin_row = True
2981 owner_row[0].admin_row = True
2982 else:
2982 else:
2983 usr = AttributeDict(usr.get_dict())
2983 usr = AttributeDict(usr.get_dict())
2984 usr.admin_row = True
2984 usr.admin_row = True
2985 usr.permission = _admin_perm
2985 usr.permission = _admin_perm
2986 super_admin_rows.append(usr)
2986 super_admin_rows.append(usr)
2987
2987
2988 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2988 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2989 q = q.options(joinedload(UserRepoGroupToPerm.group),
2989 q = q.options(joinedload(UserRepoGroupToPerm.group),
2990 joinedload(UserRepoGroupToPerm.user),
2990 joinedload(UserRepoGroupToPerm.user),
2991 joinedload(UserRepoGroupToPerm.permission),)
2991 joinedload(UserRepoGroupToPerm.permission),)
2992
2992
2993 # get owners and admins and permissions. We do a trick of re-writing
2993 # get owners and admins and permissions. We do a trick of re-writing
2994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2995 # has a global reference and changing one object propagates to all
2995 # has a global reference and changing one object propagates to all
2996 # others. This means if admin is also an owner admin_row that change
2996 # others. This means if admin is also an owner admin_row that change
2997 # would propagate to both objects
2997 # would propagate to both objects
2998 perm_rows = []
2998 perm_rows = []
2999 for _usr in q.all():
2999 for _usr in q.all():
3000 usr = AttributeDict(_usr.user.get_dict())
3000 usr = AttributeDict(_usr.user.get_dict())
3001 # if this user is also owner/admin, mark as duplicate record
3001 # if this user is also owner/admin, mark as duplicate record
3002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3003 usr.duplicate_perm = True
3003 usr.duplicate_perm = True
3004 usr.permission = _usr.permission.permission_name
3004 usr.permission = _usr.permission.permission_name
3005 perm_rows.append(usr)
3005 perm_rows.append(usr)
3006
3006
3007 # filter the perm rows by 'default' first and then sort them by
3007 # filter the perm rows by 'default' first and then sort them by
3008 # admin,write,read,none permissions sorted again alphabetically in
3008 # admin,write,read,none permissions sorted again alphabetically in
3009 # each group
3009 # each group
3010 perm_rows = sorted(perm_rows, key=display_user_sort)
3010 perm_rows = sorted(perm_rows, key=display_user_sort)
3011
3011
3012 user_groups_rows = []
3012 user_groups_rows = []
3013 if expand_from_user_groups:
3013 if expand_from_user_groups:
3014 for ug in self.permission_user_groups(with_members=True):
3014 for ug in self.permission_user_groups(with_members=True):
3015 for user_data in ug.members:
3015 for user_data in ug.members:
3016 user_groups_rows.append(user_data)
3016 user_groups_rows.append(user_data)
3017
3017
3018 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3018 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3019
3019
3020 def permission_user_groups(self, with_members=False):
3020 def permission_user_groups(self, with_members=False):
3021 q = UserGroupRepoGroupToPerm.query()\
3021 q = UserGroupRepoGroupToPerm.query()\
3022 .filter(UserGroupRepoGroupToPerm.group == self)
3022 .filter(UserGroupRepoGroupToPerm.group == self)
3023 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3023 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3024 joinedload(UserGroupRepoGroupToPerm.users_group),
3024 joinedload(UserGroupRepoGroupToPerm.users_group),
3025 joinedload(UserGroupRepoGroupToPerm.permission),)
3025 joinedload(UserGroupRepoGroupToPerm.permission),)
3026
3026
3027 perm_rows = []
3027 perm_rows = []
3028 for _user_group in q.all():
3028 for _user_group in q.all():
3029 entry = AttributeDict(_user_group.users_group.get_dict())
3029 entry = AttributeDict(_user_group.users_group.get_dict())
3030 entry.permission = _user_group.permission.permission_name
3030 entry.permission = _user_group.permission.permission_name
3031 if with_members:
3031 if with_members:
3032 entry.members = [x.user.get_dict()
3032 entry.members = [x.user.get_dict()
3033 for x in _user_group.users_group.members]
3033 for x in _user_group.users_group.members]
3034 perm_rows.append(entry)
3034 perm_rows.append(entry)
3035
3035
3036 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3036 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3037 return perm_rows
3037 return perm_rows
3038
3038
3039 def get_api_data(self):
3039 def get_api_data(self):
3040 """
3040 """
3041 Common function for generating api data
3041 Common function for generating api data
3042
3042
3043 """
3043 """
3044 group = self
3044 group = self
3045 data = {
3045 data = {
3046 'group_id': group.group_id,
3046 'group_id': group.group_id,
3047 'group_name': group.group_name,
3047 'group_name': group.group_name,
3048 'group_description': group.description_safe,
3048 'group_description': group.description_safe,
3049 'parent_group': group.parent_group.group_name if group.parent_group else None,
3049 'parent_group': group.parent_group.group_name if group.parent_group else None,
3050 'repositories': [x.repo_name for x in group.repositories],
3050 'repositories': [x.repo_name for x in group.repositories],
3051 'owner': group.user.username,
3051 'owner': group.user.username,
3052 }
3052 }
3053 return data
3053 return data
3054
3054
3055 def get_dict(self):
3055 def get_dict(self):
3056 # Since we transformed `group_name` to a hybrid property, we need to
3056 # Since we transformed `group_name` to a hybrid property, we need to
3057 # keep compatibility with the code which uses `group_name` field.
3057 # keep compatibility with the code which uses `group_name` field.
3058 result = super(RepoGroup, self).get_dict()
3058 result = super(RepoGroup, self).get_dict()
3059 result['group_name'] = result.pop('_group_name', None)
3059 result['group_name'] = result.pop('_group_name', None)
3060 return result
3060 return result
3061
3061
3062
3062
3063 class Permission(Base, BaseModel):
3063 class Permission(Base, BaseModel):
3064 __tablename__ = 'permissions'
3064 __tablename__ = 'permissions'
3065 __table_args__ = (
3065 __table_args__ = (
3066 Index('p_perm_name_idx', 'permission_name'),
3066 Index('p_perm_name_idx', 'permission_name'),
3067 base_table_args,
3067 base_table_args,
3068 )
3068 )
3069
3069
3070 PERMS = [
3070 PERMS = [
3071 ('hg.admin', _('RhodeCode Super Administrator')),
3071 ('hg.admin', _('RhodeCode Super Administrator')),
3072
3072
3073 ('repository.none', _('Repository no access')),
3073 ('repository.none', _('Repository no access')),
3074 ('repository.read', _('Repository read access')),
3074 ('repository.read', _('Repository read access')),
3075 ('repository.write', _('Repository write access')),
3075 ('repository.write', _('Repository write access')),
3076 ('repository.admin', _('Repository admin access')),
3076 ('repository.admin', _('Repository admin access')),
3077
3077
3078 ('group.none', _('Repository group no access')),
3078 ('group.none', _('Repository group no access')),
3079 ('group.read', _('Repository group read access')),
3079 ('group.read', _('Repository group read access')),
3080 ('group.write', _('Repository group write access')),
3080 ('group.write', _('Repository group write access')),
3081 ('group.admin', _('Repository group admin access')),
3081 ('group.admin', _('Repository group admin access')),
3082
3082
3083 ('usergroup.none', _('User group no access')),
3083 ('usergroup.none', _('User group no access')),
3084 ('usergroup.read', _('User group read access')),
3084 ('usergroup.read', _('User group read access')),
3085 ('usergroup.write', _('User group write access')),
3085 ('usergroup.write', _('User group write access')),
3086 ('usergroup.admin', _('User group admin access')),
3086 ('usergroup.admin', _('User group admin access')),
3087
3087
3088 ('branch.none', _('Branch no permissions')),
3088 ('branch.none', _('Branch no permissions')),
3089 ('branch.merge', _('Branch access by web merge')),
3089 ('branch.merge', _('Branch access by web merge')),
3090 ('branch.push', _('Branch access by push')),
3090 ('branch.push', _('Branch access by push')),
3091 ('branch.push_force', _('Branch access by push with force')),
3091 ('branch.push_force', _('Branch access by push with force')),
3092
3092
3093 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3093 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3094 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3094 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3095
3095
3096 ('hg.usergroup.create.false', _('User Group creation disabled')),
3096 ('hg.usergroup.create.false', _('User Group creation disabled')),
3097 ('hg.usergroup.create.true', _('User Group creation enabled')),
3097 ('hg.usergroup.create.true', _('User Group creation enabled')),
3098
3098
3099 ('hg.create.none', _('Repository creation disabled')),
3099 ('hg.create.none', _('Repository creation disabled')),
3100 ('hg.create.repository', _('Repository creation enabled')),
3100 ('hg.create.repository', _('Repository creation enabled')),
3101 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3101 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3102 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3102 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3103
3103
3104 ('hg.fork.none', _('Repository forking disabled')),
3104 ('hg.fork.none', _('Repository forking disabled')),
3105 ('hg.fork.repository', _('Repository forking enabled')),
3105 ('hg.fork.repository', _('Repository forking enabled')),
3106
3106
3107 ('hg.register.none', _('Registration disabled')),
3107 ('hg.register.none', _('Registration disabled')),
3108 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3108 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3109 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3109 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3110
3110
3111 ('hg.password_reset.enabled', _('Password reset enabled')),
3111 ('hg.password_reset.enabled', _('Password reset enabled')),
3112 ('hg.password_reset.hidden', _('Password reset hidden')),
3112 ('hg.password_reset.hidden', _('Password reset hidden')),
3113 ('hg.password_reset.disabled', _('Password reset disabled')),
3113 ('hg.password_reset.disabled', _('Password reset disabled')),
3114
3114
3115 ('hg.extern_activate.manual', _('Manual activation of external account')),
3115 ('hg.extern_activate.manual', _('Manual activation of external account')),
3116 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3116 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3117
3117
3118 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3118 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3119 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3119 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3120 ]
3120 ]
3121
3121
3122 # definition of system default permissions for DEFAULT user, created on
3122 # definition of system default permissions for DEFAULT user, created on
3123 # system setup
3123 # system setup
3124 DEFAULT_USER_PERMISSIONS = [
3124 DEFAULT_USER_PERMISSIONS = [
3125 # object perms
3125 # object perms
3126 'repository.read',
3126 'repository.read',
3127 'group.read',
3127 'group.read',
3128 'usergroup.read',
3128 'usergroup.read',
3129 # branch, for backward compat we need same value as before so forced pushed
3129 # branch, for backward compat we need same value as before so forced pushed
3130 'branch.push_force',
3130 'branch.push_force',
3131 # global
3131 # global
3132 'hg.create.repository',
3132 'hg.create.repository',
3133 'hg.repogroup.create.false',
3133 'hg.repogroup.create.false',
3134 'hg.usergroup.create.false',
3134 'hg.usergroup.create.false',
3135 'hg.create.write_on_repogroup.true',
3135 'hg.create.write_on_repogroup.true',
3136 'hg.fork.repository',
3136 'hg.fork.repository',
3137 'hg.register.manual_activate',
3137 'hg.register.manual_activate',
3138 'hg.password_reset.enabled',
3138 'hg.password_reset.enabled',
3139 'hg.extern_activate.auto',
3139 'hg.extern_activate.auto',
3140 'hg.inherit_default_perms.true',
3140 'hg.inherit_default_perms.true',
3141 ]
3141 ]
3142
3142
3143 # defines which permissions are more important higher the more important
3143 # defines which permissions are more important higher the more important
3144 # Weight defines which permissions are more important.
3144 # Weight defines which permissions are more important.
3145 # The higher number the more important.
3145 # The higher number the more important.
3146 PERM_WEIGHTS = {
3146 PERM_WEIGHTS = {
3147 'repository.none': 0,
3147 'repository.none': 0,
3148 'repository.read': 1,
3148 'repository.read': 1,
3149 'repository.write': 3,
3149 'repository.write': 3,
3150 'repository.admin': 4,
3150 'repository.admin': 4,
3151
3151
3152 'group.none': 0,
3152 'group.none': 0,
3153 'group.read': 1,
3153 'group.read': 1,
3154 'group.write': 3,
3154 'group.write': 3,
3155 'group.admin': 4,
3155 'group.admin': 4,
3156
3156
3157 'usergroup.none': 0,
3157 'usergroup.none': 0,
3158 'usergroup.read': 1,
3158 'usergroup.read': 1,
3159 'usergroup.write': 3,
3159 'usergroup.write': 3,
3160 'usergroup.admin': 4,
3160 'usergroup.admin': 4,
3161
3161
3162 'branch.none': 0,
3162 'branch.none': 0,
3163 'branch.merge': 1,
3163 'branch.merge': 1,
3164 'branch.push': 3,
3164 'branch.push': 3,
3165 'branch.push_force': 4,
3165 'branch.push_force': 4,
3166
3166
3167 'hg.repogroup.create.false': 0,
3167 'hg.repogroup.create.false': 0,
3168 'hg.repogroup.create.true': 1,
3168 'hg.repogroup.create.true': 1,
3169
3169
3170 'hg.usergroup.create.false': 0,
3170 'hg.usergroup.create.false': 0,
3171 'hg.usergroup.create.true': 1,
3171 'hg.usergroup.create.true': 1,
3172
3172
3173 'hg.fork.none': 0,
3173 'hg.fork.none': 0,
3174 'hg.fork.repository': 1,
3174 'hg.fork.repository': 1,
3175 'hg.create.none': 0,
3175 'hg.create.none': 0,
3176 'hg.create.repository': 1
3176 'hg.create.repository': 1
3177 }
3177 }
3178
3178
3179 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3179 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3180 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3180 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3181 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3181 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3182
3182
3183 def __unicode__(self):
3183 def __unicode__(self):
3184 return u"<%s('%s:%s')>" % (
3184 return u"<%s('%s:%s')>" % (
3185 self.__class__.__name__, self.permission_id, self.permission_name
3185 self.__class__.__name__, self.permission_id, self.permission_name
3186 )
3186 )
3187
3187
3188 @classmethod
3188 @classmethod
3189 def get_by_key(cls, key):
3189 def get_by_key(cls, key):
3190 return cls.query().filter(cls.permission_name == key).scalar()
3190 return cls.query().filter(cls.permission_name == key).scalar()
3191
3191
3192 @classmethod
3192 @classmethod
3193 def get_default_repo_perms(cls, user_id, repo_id=None):
3193 def get_default_repo_perms(cls, user_id, repo_id=None):
3194 q = Session().query(UserRepoToPerm, Repository, Permission)\
3194 q = Session().query(UserRepoToPerm, Repository, Permission)\
3195 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3195 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3196 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3196 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3197 .filter(UserRepoToPerm.user_id == user_id)
3197 .filter(UserRepoToPerm.user_id == user_id)
3198 if repo_id:
3198 if repo_id:
3199 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3199 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3200 return q.all()
3200 return q.all()
3201
3201
3202 @classmethod
3202 @classmethod
3203 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3203 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3204 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3204 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3205 .join(
3205 .join(
3206 Permission,
3206 Permission,
3207 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3207 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3208 .join(
3208 .join(
3209 UserRepoToPerm,
3209 UserRepoToPerm,
3210 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3210 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3211 .filter(UserRepoToPerm.user_id == user_id)
3211 .filter(UserRepoToPerm.user_id == user_id)
3212
3212
3213 if repo_id:
3213 if repo_id:
3214 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3214 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3215 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3215 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3216
3216
3217 @classmethod
3217 @classmethod
3218 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3218 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3219 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3219 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3220 .join(
3220 .join(
3221 Permission,
3221 Permission,
3222 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3222 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3223 .join(
3223 .join(
3224 Repository,
3224 Repository,
3225 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3225 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3226 .join(
3226 .join(
3227 UserGroup,
3227 UserGroup,
3228 UserGroupRepoToPerm.users_group_id ==
3228 UserGroupRepoToPerm.users_group_id ==
3229 UserGroup.users_group_id)\
3229 UserGroup.users_group_id)\
3230 .join(
3230 .join(
3231 UserGroupMember,
3231 UserGroupMember,
3232 UserGroupRepoToPerm.users_group_id ==
3232 UserGroupRepoToPerm.users_group_id ==
3233 UserGroupMember.users_group_id)\
3233 UserGroupMember.users_group_id)\
3234 .filter(
3234 .filter(
3235 UserGroupMember.user_id == user_id,
3235 UserGroupMember.user_id == user_id,
3236 UserGroup.users_group_active == true())
3236 UserGroup.users_group_active == true())
3237 if repo_id:
3237 if repo_id:
3238 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3238 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3239 return q.all()
3239 return q.all()
3240
3240
3241 @classmethod
3241 @classmethod
3242 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3242 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3243 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3243 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3244 .join(
3244 .join(
3245 Permission,
3245 Permission,
3246 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3246 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3247 .join(
3247 .join(
3248 UserGroupRepoToPerm,
3248 UserGroupRepoToPerm,
3249 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3249 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3250 .join(
3250 .join(
3251 UserGroup,
3251 UserGroup,
3252 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3252 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3253 .join(
3253 .join(
3254 UserGroupMember,
3254 UserGroupMember,
3255 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3255 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3256 .filter(
3256 .filter(
3257 UserGroupMember.user_id == user_id,
3257 UserGroupMember.user_id == user_id,
3258 UserGroup.users_group_active == true())
3258 UserGroup.users_group_active == true())
3259
3259
3260 if repo_id:
3260 if repo_id:
3261 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3261 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3262 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3262 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3263
3263
3264 @classmethod
3264 @classmethod
3265 def get_default_group_perms(cls, user_id, repo_group_id=None):
3265 def get_default_group_perms(cls, user_id, repo_group_id=None):
3266 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3266 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3267 .join(
3267 .join(
3268 Permission,
3268 Permission,
3269 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3269 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3270 .join(
3270 .join(
3271 RepoGroup,
3271 RepoGroup,
3272 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3272 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3273 .filter(UserRepoGroupToPerm.user_id == user_id)
3273 .filter(UserRepoGroupToPerm.user_id == user_id)
3274 if repo_group_id:
3274 if repo_group_id:
3275 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3275 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3276 return q.all()
3276 return q.all()
3277
3277
3278 @classmethod
3278 @classmethod
3279 def get_default_group_perms_from_user_group(
3279 def get_default_group_perms_from_user_group(
3280 cls, user_id, repo_group_id=None):
3280 cls, user_id, repo_group_id=None):
3281 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3281 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3282 .join(
3282 .join(
3283 Permission,
3283 Permission,
3284 UserGroupRepoGroupToPerm.permission_id ==
3284 UserGroupRepoGroupToPerm.permission_id ==
3285 Permission.permission_id)\
3285 Permission.permission_id)\
3286 .join(
3286 .join(
3287 RepoGroup,
3287 RepoGroup,
3288 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3288 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3289 .join(
3289 .join(
3290 UserGroup,
3290 UserGroup,
3291 UserGroupRepoGroupToPerm.users_group_id ==
3291 UserGroupRepoGroupToPerm.users_group_id ==
3292 UserGroup.users_group_id)\
3292 UserGroup.users_group_id)\
3293 .join(
3293 .join(
3294 UserGroupMember,
3294 UserGroupMember,
3295 UserGroupRepoGroupToPerm.users_group_id ==
3295 UserGroupRepoGroupToPerm.users_group_id ==
3296 UserGroupMember.users_group_id)\
3296 UserGroupMember.users_group_id)\
3297 .filter(
3297 .filter(
3298 UserGroupMember.user_id == user_id,
3298 UserGroupMember.user_id == user_id,
3299 UserGroup.users_group_active == true())
3299 UserGroup.users_group_active == true())
3300 if repo_group_id:
3300 if repo_group_id:
3301 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3301 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3302 return q.all()
3302 return q.all()
3303
3303
3304 @classmethod
3304 @classmethod
3305 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3305 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3306 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3306 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3307 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3307 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3308 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3308 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3309 .filter(UserUserGroupToPerm.user_id == user_id)
3309 .filter(UserUserGroupToPerm.user_id == user_id)
3310 if user_group_id:
3310 if user_group_id:
3311 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3311 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3312 return q.all()
3312 return q.all()
3313
3313
3314 @classmethod
3314 @classmethod
3315 def get_default_user_group_perms_from_user_group(
3315 def get_default_user_group_perms_from_user_group(
3316 cls, user_id, user_group_id=None):
3316 cls, user_id, user_group_id=None):
3317 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3317 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3318 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3318 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3319 .join(
3319 .join(
3320 Permission,
3320 Permission,
3321 UserGroupUserGroupToPerm.permission_id ==
3321 UserGroupUserGroupToPerm.permission_id ==
3322 Permission.permission_id)\
3322 Permission.permission_id)\
3323 .join(
3323 .join(
3324 TargetUserGroup,
3324 TargetUserGroup,
3325 UserGroupUserGroupToPerm.target_user_group_id ==
3325 UserGroupUserGroupToPerm.target_user_group_id ==
3326 TargetUserGroup.users_group_id)\
3326 TargetUserGroup.users_group_id)\
3327 .join(
3327 .join(
3328 UserGroup,
3328 UserGroup,
3329 UserGroupUserGroupToPerm.user_group_id ==
3329 UserGroupUserGroupToPerm.user_group_id ==
3330 UserGroup.users_group_id)\
3330 UserGroup.users_group_id)\
3331 .join(
3331 .join(
3332 UserGroupMember,
3332 UserGroupMember,
3333 UserGroupUserGroupToPerm.user_group_id ==
3333 UserGroupUserGroupToPerm.user_group_id ==
3334 UserGroupMember.users_group_id)\
3334 UserGroupMember.users_group_id)\
3335 .filter(
3335 .filter(
3336 UserGroupMember.user_id == user_id,
3336 UserGroupMember.user_id == user_id,
3337 UserGroup.users_group_active == true())
3337 UserGroup.users_group_active == true())
3338 if user_group_id:
3338 if user_group_id:
3339 q = q.filter(
3339 q = q.filter(
3340 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3340 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3341
3341
3342 return q.all()
3342 return q.all()
3343
3343
3344
3344
3345 class UserRepoToPerm(Base, BaseModel):
3345 class UserRepoToPerm(Base, BaseModel):
3346 __tablename__ = 'repo_to_perm'
3346 __tablename__ = 'repo_to_perm'
3347 __table_args__ = (
3347 __table_args__ = (
3348 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3348 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3349 base_table_args
3349 base_table_args
3350 )
3350 )
3351
3351
3352 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3352 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3354 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3354 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3355 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3355 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3356
3356
3357 user = relationship('User')
3357 user = relationship('User')
3358 repository = relationship('Repository')
3358 repository = relationship('Repository')
3359 permission = relationship('Permission')
3359 permission = relationship('Permission')
3360
3360
3361 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3361 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3362
3362
3363 @classmethod
3363 @classmethod
3364 def create(cls, user, repository, permission):
3364 def create(cls, user, repository, permission):
3365 n = cls()
3365 n = cls()
3366 n.user = user
3366 n.user = user
3367 n.repository = repository
3367 n.repository = repository
3368 n.permission = permission
3368 n.permission = permission
3369 Session().add(n)
3369 Session().add(n)
3370 return n
3370 return n
3371
3371
3372 def __unicode__(self):
3372 def __unicode__(self):
3373 return u'<%s => %s >' % (self.user, self.repository)
3373 return u'<%s => %s >' % (self.user, self.repository)
3374
3374
3375
3375
3376 class UserUserGroupToPerm(Base, BaseModel):
3376 class UserUserGroupToPerm(Base, BaseModel):
3377 __tablename__ = 'user_user_group_to_perm'
3377 __tablename__ = 'user_user_group_to_perm'
3378 __table_args__ = (
3378 __table_args__ = (
3379 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3379 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3380 base_table_args
3380 base_table_args
3381 )
3381 )
3382
3382
3383 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3383 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3385 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3386 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3387
3387
3388 user = relationship('User')
3388 user = relationship('User')
3389 user_group = relationship('UserGroup')
3389 user_group = relationship('UserGroup')
3390 permission = relationship('Permission')
3390 permission = relationship('Permission')
3391
3391
3392 @classmethod
3392 @classmethod
3393 def create(cls, user, user_group, permission):
3393 def create(cls, user, user_group, permission):
3394 n = cls()
3394 n = cls()
3395 n.user = user
3395 n.user = user
3396 n.user_group = user_group
3396 n.user_group = user_group
3397 n.permission = permission
3397 n.permission = permission
3398 Session().add(n)
3398 Session().add(n)
3399 return n
3399 return n
3400
3400
3401 def __unicode__(self):
3401 def __unicode__(self):
3402 return u'<%s => %s >' % (self.user, self.user_group)
3402 return u'<%s => %s >' % (self.user, self.user_group)
3403
3403
3404
3404
3405 class UserToPerm(Base, BaseModel):
3405 class UserToPerm(Base, BaseModel):
3406 __tablename__ = 'user_to_perm'
3406 __tablename__ = 'user_to_perm'
3407 __table_args__ = (
3407 __table_args__ = (
3408 UniqueConstraint('user_id', 'permission_id'),
3408 UniqueConstraint('user_id', 'permission_id'),
3409 base_table_args
3409 base_table_args
3410 )
3410 )
3411
3411
3412 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3412 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3415
3415
3416 user = relationship('User')
3416 user = relationship('User')
3417 permission = relationship('Permission', lazy='joined')
3417 permission = relationship('Permission', lazy='joined')
3418
3418
3419 def __unicode__(self):
3419 def __unicode__(self):
3420 return u'<%s => %s >' % (self.user, self.permission)
3420 return u'<%s => %s >' % (self.user, self.permission)
3421
3421
3422
3422
3423 class UserGroupRepoToPerm(Base, BaseModel):
3423 class UserGroupRepoToPerm(Base, BaseModel):
3424 __tablename__ = 'users_group_repo_to_perm'
3424 __tablename__ = 'users_group_repo_to_perm'
3425 __table_args__ = (
3425 __table_args__ = (
3426 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3426 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3427 base_table_args
3427 base_table_args
3428 )
3428 )
3429
3429
3430 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3430 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3431 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3431 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3432 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3432 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3433 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3433 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3434
3434
3435 users_group = relationship('UserGroup')
3435 users_group = relationship('UserGroup')
3436 permission = relationship('Permission')
3436 permission = relationship('Permission')
3437 repository = relationship('Repository')
3437 repository = relationship('Repository')
3438 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3438 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3439
3439
3440 @classmethod
3440 @classmethod
3441 def create(cls, users_group, repository, permission):
3441 def create(cls, users_group, repository, permission):
3442 n = cls()
3442 n = cls()
3443 n.users_group = users_group
3443 n.users_group = users_group
3444 n.repository = repository
3444 n.repository = repository
3445 n.permission = permission
3445 n.permission = permission
3446 Session().add(n)
3446 Session().add(n)
3447 return n
3447 return n
3448
3448
3449 def __unicode__(self):
3449 def __unicode__(self):
3450 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3450 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3451
3451
3452
3452
3453 class UserGroupUserGroupToPerm(Base, BaseModel):
3453 class UserGroupUserGroupToPerm(Base, BaseModel):
3454 __tablename__ = 'user_group_user_group_to_perm'
3454 __tablename__ = 'user_group_user_group_to_perm'
3455 __table_args__ = (
3455 __table_args__ = (
3456 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3456 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3457 CheckConstraint('target_user_group_id != user_group_id'),
3457 CheckConstraint('target_user_group_id != user_group_id'),
3458 base_table_args
3458 base_table_args
3459 )
3459 )
3460
3460
3461 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)
3461 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)
3462 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3462 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3464 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3464 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3465
3465
3466 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3466 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3467 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3467 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3468 permission = relationship('Permission')
3468 permission = relationship('Permission')
3469
3469
3470 @classmethod
3470 @classmethod
3471 def create(cls, target_user_group, user_group, permission):
3471 def create(cls, target_user_group, user_group, permission):
3472 n = cls()
3472 n = cls()
3473 n.target_user_group = target_user_group
3473 n.target_user_group = target_user_group
3474 n.user_group = user_group
3474 n.user_group = user_group
3475 n.permission = permission
3475 n.permission = permission
3476 Session().add(n)
3476 Session().add(n)
3477 return n
3477 return n
3478
3478
3479 def __unicode__(self):
3479 def __unicode__(self):
3480 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3480 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3481
3481
3482
3482
3483 class UserGroupToPerm(Base, BaseModel):
3483 class UserGroupToPerm(Base, BaseModel):
3484 __tablename__ = 'users_group_to_perm'
3484 __tablename__ = 'users_group_to_perm'
3485 __table_args__ = (
3485 __table_args__ = (
3486 UniqueConstraint('users_group_id', 'permission_id',),
3486 UniqueConstraint('users_group_id', 'permission_id',),
3487 base_table_args
3487 base_table_args
3488 )
3488 )
3489
3489
3490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3493
3493
3494 users_group = relationship('UserGroup')
3494 users_group = relationship('UserGroup')
3495 permission = relationship('Permission')
3495 permission = relationship('Permission')
3496
3496
3497
3497
3498 class UserRepoGroupToPerm(Base, BaseModel):
3498 class UserRepoGroupToPerm(Base, BaseModel):
3499 __tablename__ = 'user_repo_group_to_perm'
3499 __tablename__ = 'user_repo_group_to_perm'
3500 __table_args__ = (
3500 __table_args__ = (
3501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3502 base_table_args
3502 base_table_args
3503 )
3503 )
3504
3504
3505 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3505 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3509
3509
3510 user = relationship('User')
3510 user = relationship('User')
3511 group = relationship('RepoGroup')
3511 group = relationship('RepoGroup')
3512 permission = relationship('Permission')
3512 permission = relationship('Permission')
3513
3513
3514 @classmethod
3514 @classmethod
3515 def create(cls, user, repository_group, permission):
3515 def create(cls, user, repository_group, permission):
3516 n = cls()
3516 n = cls()
3517 n.user = user
3517 n.user = user
3518 n.group = repository_group
3518 n.group = repository_group
3519 n.permission = permission
3519 n.permission = permission
3520 Session().add(n)
3520 Session().add(n)
3521 return n
3521 return n
3522
3522
3523
3523
3524 class UserGroupRepoGroupToPerm(Base, BaseModel):
3524 class UserGroupRepoGroupToPerm(Base, BaseModel):
3525 __tablename__ = 'users_group_repo_group_to_perm'
3525 __tablename__ = 'users_group_repo_group_to_perm'
3526 __table_args__ = (
3526 __table_args__ = (
3527 UniqueConstraint('users_group_id', 'group_id'),
3527 UniqueConstraint('users_group_id', 'group_id'),
3528 base_table_args
3528 base_table_args
3529 )
3529 )
3530
3530
3531 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)
3531 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)
3532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3533 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3533 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3534 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3534 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3535
3535
3536 users_group = relationship('UserGroup')
3536 users_group = relationship('UserGroup')
3537 permission = relationship('Permission')
3537 permission = relationship('Permission')
3538 group = relationship('RepoGroup')
3538 group = relationship('RepoGroup')
3539
3539
3540 @classmethod
3540 @classmethod
3541 def create(cls, user_group, repository_group, permission):
3541 def create(cls, user_group, repository_group, permission):
3542 n = cls()
3542 n = cls()
3543 n.users_group = user_group
3543 n.users_group = user_group
3544 n.group = repository_group
3544 n.group = repository_group
3545 n.permission = permission
3545 n.permission = permission
3546 Session().add(n)
3546 Session().add(n)
3547 return n
3547 return n
3548
3548
3549 def __unicode__(self):
3549 def __unicode__(self):
3550 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3550 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3551
3551
3552
3552
3553 class Statistics(Base, BaseModel):
3553 class Statistics(Base, BaseModel):
3554 __tablename__ = 'statistics'
3554 __tablename__ = 'statistics'
3555 __table_args__ = (
3555 __table_args__ = (
3556 base_table_args
3556 base_table_args
3557 )
3557 )
3558
3558
3559 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3559 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3560 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3560 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3561 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3561 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3562 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3562 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3563 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3563 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3564 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3564 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3565
3565
3566 repository = relationship('Repository', single_parent=True)
3566 repository = relationship('Repository', single_parent=True)
3567
3567
3568
3568
3569 class UserFollowing(Base, BaseModel):
3569 class UserFollowing(Base, BaseModel):
3570 __tablename__ = 'user_followings'
3570 __tablename__ = 'user_followings'
3571 __table_args__ = (
3571 __table_args__ = (
3572 UniqueConstraint('user_id', 'follows_repository_id'),
3572 UniqueConstraint('user_id', 'follows_repository_id'),
3573 UniqueConstraint('user_id', 'follows_user_id'),
3573 UniqueConstraint('user_id', 'follows_user_id'),
3574 base_table_args
3574 base_table_args
3575 )
3575 )
3576
3576
3577 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3577 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3579 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3579 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3580 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3580 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3581 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3581 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3582
3582
3583 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3583 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3584
3584
3585 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3585 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3586 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3586 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3587
3587
3588 @classmethod
3588 @classmethod
3589 def get_repo_followers(cls, repo_id):
3589 def get_repo_followers(cls, repo_id):
3590 return cls.query().filter(cls.follows_repo_id == repo_id)
3590 return cls.query().filter(cls.follows_repo_id == repo_id)
3591
3591
3592
3592
3593 class CacheKey(Base, BaseModel):
3593 class CacheKey(Base, BaseModel):
3594 __tablename__ = 'cache_invalidation'
3594 __tablename__ = 'cache_invalidation'
3595 __table_args__ = (
3595 __table_args__ = (
3596 UniqueConstraint('cache_key'),
3596 UniqueConstraint('cache_key'),
3597 Index('key_idx', 'cache_key'),
3597 Index('key_idx', 'cache_key'),
3598 base_table_args,
3598 base_table_args,
3599 )
3599 )
3600
3600
3601 CACHE_TYPE_FEED = 'FEED'
3601 CACHE_TYPE_FEED = 'FEED'
3602
3602
3603 # namespaces used to register process/thread aware caches
3603 # namespaces used to register process/thread aware caches
3604 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3604 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3605 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3605 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3606
3606
3607 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3607 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3608 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3608 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3609 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3609 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3610 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3610 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3611 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3611 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3612
3612
3613 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3613 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3614 self.cache_key = cache_key
3614 self.cache_key = cache_key
3615 self.cache_args = cache_args
3615 self.cache_args = cache_args
3616 self.cache_active = False
3616 self.cache_active = False
3617 # first key should be same for all entries, since all workers should share it
3617 # first key should be same for all entries, since all workers should share it
3618 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3618 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3619
3619
3620 def __unicode__(self):
3620 def __unicode__(self):
3621 return u"<%s('%s:%s[%s]')>" % (
3621 return u"<%s('%s:%s[%s]')>" % (
3622 self.__class__.__name__,
3622 self.__class__.__name__,
3623 self.cache_id, self.cache_key, self.cache_active)
3623 self.cache_id, self.cache_key, self.cache_active)
3624
3624
3625 def _cache_key_partition(self):
3625 def _cache_key_partition(self):
3626 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3626 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3627 return prefix, repo_name, suffix
3627 return prefix, repo_name, suffix
3628
3628
3629 def get_prefix(self):
3629 def get_prefix(self):
3630 """
3630 """
3631 Try to extract prefix from existing cache key. The key could consist
3631 Try to extract prefix from existing cache key. The key could consist
3632 of prefix, repo_name, suffix
3632 of prefix, repo_name, suffix
3633 """
3633 """
3634 # this returns prefix, repo_name, suffix
3634 # this returns prefix, repo_name, suffix
3635 return self._cache_key_partition()[0]
3635 return self._cache_key_partition()[0]
3636
3636
3637 def get_suffix(self):
3637 def get_suffix(self):
3638 """
3638 """
3639 get suffix that might have been used in _get_cache_key to
3639 get suffix that might have been used in _get_cache_key to
3640 generate self.cache_key. Only used for informational purposes
3640 generate self.cache_key. Only used for informational purposes
3641 in repo_edit.mako.
3641 in repo_edit.mako.
3642 """
3642 """
3643 # prefix, repo_name, suffix
3643 # prefix, repo_name, suffix
3644 return self._cache_key_partition()[2]
3644 return self._cache_key_partition()[2]
3645
3645
3646 @classmethod
3646 @classmethod
3647 def generate_new_state_uid(cls, based_on=None):
3647 def generate_new_state_uid(cls, based_on=None):
3648 if based_on:
3648 if based_on:
3649 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3649 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3650 else:
3650 else:
3651 return str(uuid.uuid4())
3651 return str(uuid.uuid4())
3652
3652
3653 @classmethod
3653 @classmethod
3654 def delete_all_cache(cls):
3654 def delete_all_cache(cls):
3655 """
3655 """
3656 Delete all cache keys from database.
3656 Delete all cache keys from database.
3657 Should only be run when all instances are down and all entries
3657 Should only be run when all instances are down and all entries
3658 thus stale.
3658 thus stale.
3659 """
3659 """
3660 cls.query().delete()
3660 cls.query().delete()
3661 Session().commit()
3661 Session().commit()
3662
3662
3663 @classmethod
3663 @classmethod
3664 def set_invalidate(cls, cache_uid, delete=False):
3664 def set_invalidate(cls, cache_uid, delete=False):
3665 """
3665 """
3666 Mark all caches of a repo as invalid in the database.
3666 Mark all caches of a repo as invalid in the database.
3667 """
3667 """
3668
3668
3669 try:
3669 try:
3670 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3670 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3671 if delete:
3671 if delete:
3672 qry.delete()
3672 qry.delete()
3673 log.debug('cache objects deleted for cache args %s',
3673 log.debug('cache objects deleted for cache args %s',
3674 safe_str(cache_uid))
3674 safe_str(cache_uid))
3675 else:
3675 else:
3676 qry.update({"cache_active": False,
3676 qry.update({"cache_active": False,
3677 "cache_state_uid": cls.generate_new_state_uid()})
3677 "cache_state_uid": cls.generate_new_state_uid()})
3678 log.debug('cache objects marked as invalid for cache args %s',
3678 log.debug('cache objects marked as invalid for cache args %s',
3679 safe_str(cache_uid))
3679 safe_str(cache_uid))
3680
3680
3681 Session().commit()
3681 Session().commit()
3682 except Exception:
3682 except Exception:
3683 log.exception(
3683 log.exception(
3684 'Cache key invalidation failed for cache args %s',
3684 'Cache key invalidation failed for cache args %s',
3685 safe_str(cache_uid))
3685 safe_str(cache_uid))
3686 Session().rollback()
3686 Session().rollback()
3687
3687
3688 @classmethod
3688 @classmethod
3689 def get_active_cache(cls, cache_key):
3689 def get_active_cache(cls, cache_key):
3690 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3690 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3691 if inv_obj:
3691 if inv_obj:
3692 return inv_obj
3692 return inv_obj
3693 return None
3693 return None
3694
3694
3695 @classmethod
3695 @classmethod
3696 def get_namespace_map(cls, namespace):
3696 def get_namespace_map(cls, namespace):
3697 return {
3697 return {
3698 x.cache_key: x
3698 x.cache_key: x
3699 for x in cls.query().filter(cls.cache_args == namespace)}
3699 for x in cls.query().filter(cls.cache_args == namespace)}
3700
3700
3701
3701
3702 class ChangesetComment(Base, BaseModel):
3702 class ChangesetComment(Base, BaseModel):
3703 __tablename__ = 'changeset_comments'
3703 __tablename__ = 'changeset_comments'
3704 __table_args__ = (
3704 __table_args__ = (
3705 Index('cc_revision_idx', 'revision'),
3705 Index('cc_revision_idx', 'revision'),
3706 base_table_args,
3706 base_table_args,
3707 )
3707 )
3708
3708
3709 COMMENT_OUTDATED = u'comment_outdated'
3709 COMMENT_OUTDATED = u'comment_outdated'
3710 COMMENT_TYPE_NOTE = u'note'
3710 COMMENT_TYPE_NOTE = u'note'
3711 COMMENT_TYPE_TODO = u'todo'
3711 COMMENT_TYPE_TODO = u'todo'
3712 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3712 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3713
3713
3714 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3714 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3715 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3715 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3716 revision = Column('revision', String(40), nullable=True)
3716 revision = Column('revision', String(40), nullable=True)
3717 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3717 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3718 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3718 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3719 line_no = Column('line_no', Unicode(10), nullable=True)
3719 line_no = Column('line_no', Unicode(10), nullable=True)
3720 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3720 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3721 f_path = Column('f_path', Unicode(1000), nullable=True)
3721 f_path = Column('f_path', Unicode(1000), nullable=True)
3722 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3722 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3723 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3723 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3724 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3724 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3725 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3725 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3726 renderer = Column('renderer', Unicode(64), nullable=True)
3726 renderer = Column('renderer', Unicode(64), nullable=True)
3727 display_state = Column('display_state', Unicode(128), nullable=True)
3727 display_state = Column('display_state', Unicode(128), nullable=True)
3728
3728
3729 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3729 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3730 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3730 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3731
3731
3732 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3732 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3733 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3733 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3734
3734
3735 author = relationship('User', lazy='joined')
3735 author = relationship('User', lazy='joined')
3736 repo = relationship('Repository')
3736 repo = relationship('Repository')
3737 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3737 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3738 pull_request = relationship('PullRequest', lazy='joined')
3738 pull_request = relationship('PullRequest', lazy='joined')
3739 pull_request_version = relationship('PullRequestVersion')
3739 pull_request_version = relationship('PullRequestVersion')
3740
3740
3741 @classmethod
3741 @classmethod
3742 def get_users(cls, revision=None, pull_request_id=None):
3742 def get_users(cls, revision=None, pull_request_id=None):
3743 """
3743 """
3744 Returns user associated with this ChangesetComment. ie those
3744 Returns user associated with this ChangesetComment. ie those
3745 who actually commented
3745 who actually commented
3746
3746
3747 :param cls:
3747 :param cls:
3748 :param revision:
3748 :param revision:
3749 """
3749 """
3750 q = Session().query(User)\
3750 q = Session().query(User)\
3751 .join(ChangesetComment.author)
3751 .join(ChangesetComment.author)
3752 if revision:
3752 if revision:
3753 q = q.filter(cls.revision == revision)
3753 q = q.filter(cls.revision == revision)
3754 elif pull_request_id:
3754 elif pull_request_id:
3755 q = q.filter(cls.pull_request_id == pull_request_id)
3755 q = q.filter(cls.pull_request_id == pull_request_id)
3756 return q.all()
3756 return q.all()
3757
3757
3758 @classmethod
3758 @classmethod
3759 def get_index_from_version(cls, pr_version, versions):
3759 def get_index_from_version(cls, pr_version, versions):
3760 num_versions = [x.pull_request_version_id for x in versions]
3760 num_versions = [x.pull_request_version_id for x in versions]
3761 try:
3761 try:
3762 return num_versions.index(pr_version) +1
3762 return num_versions.index(pr_version) +1
3763 except (IndexError, ValueError):
3763 except (IndexError, ValueError):
3764 return
3764 return
3765
3765
3766 @property
3766 @property
3767 def outdated(self):
3767 def outdated(self):
3768 return self.display_state == self.COMMENT_OUTDATED
3768 return self.display_state == self.COMMENT_OUTDATED
3769
3769
3770 def outdated_at_version(self, version):
3770 def outdated_at_version(self, version):
3771 """
3771 """
3772 Checks if comment is outdated for given pull request version
3772 Checks if comment is outdated for given pull request version
3773 """
3773 """
3774 return self.outdated and self.pull_request_version_id != version
3774 return self.outdated and self.pull_request_version_id != version
3775
3775
3776 def older_than_version(self, version):
3776 def older_than_version(self, version):
3777 """
3777 """
3778 Checks if comment is made from previous version than given
3778 Checks if comment is made from previous version than given
3779 """
3779 """
3780 if version is None:
3780 if version is None:
3781 return self.pull_request_version_id is not None
3781 return self.pull_request_version_id is not None
3782
3782
3783 return self.pull_request_version_id < version
3783 return self.pull_request_version_id < version
3784
3784
3785 @property
3785 @property
3786 def resolved(self):
3786 def resolved(self):
3787 return self.resolved_by[0] if self.resolved_by else None
3787 return self.resolved_by[0] if self.resolved_by else None
3788
3788
3789 @property
3789 @property
3790 def is_todo(self):
3790 def is_todo(self):
3791 return self.comment_type == self.COMMENT_TYPE_TODO
3791 return self.comment_type == self.COMMENT_TYPE_TODO
3792
3792
3793 @property
3793 @property
3794 def is_inline(self):
3794 def is_inline(self):
3795 return self.line_no and self.f_path
3795 return self.line_no and self.f_path
3796
3796
3797 def get_index_version(self, versions):
3797 def get_index_version(self, versions):
3798 return self.get_index_from_version(
3798 return self.get_index_from_version(
3799 self.pull_request_version_id, versions)
3799 self.pull_request_version_id, versions)
3800
3800
3801 def __repr__(self):
3801 def __repr__(self):
3802 if self.comment_id:
3802 if self.comment_id:
3803 return '<DB:Comment #%s>' % self.comment_id
3803 return '<DB:Comment #%s>' % self.comment_id
3804 else:
3804 else:
3805 return '<DB:Comment at %#x>' % id(self)
3805 return '<DB:Comment at %#x>' % id(self)
3806
3806
3807 def get_api_data(self):
3807 def get_api_data(self):
3808 comment = self
3808 comment = self
3809 data = {
3809 data = {
3810 'comment_id': comment.comment_id,
3810 'comment_id': comment.comment_id,
3811 'comment_type': comment.comment_type,
3811 'comment_type': comment.comment_type,
3812 'comment_text': comment.text,
3812 'comment_text': comment.text,
3813 'comment_status': comment.status_change,
3813 'comment_status': comment.status_change,
3814 'comment_f_path': comment.f_path,
3814 'comment_f_path': comment.f_path,
3815 'comment_lineno': comment.line_no,
3815 'comment_lineno': comment.line_no,
3816 'comment_author': comment.author,
3816 'comment_author': comment.author,
3817 'comment_created_on': comment.created_on,
3817 'comment_created_on': comment.created_on,
3818 'comment_resolved_by': self.resolved
3818 'comment_resolved_by': self.resolved
3819 }
3819 }
3820 return data
3820 return data
3821
3821
3822 def __json__(self):
3822 def __json__(self):
3823 data = dict()
3823 data = dict()
3824 data.update(self.get_api_data())
3824 data.update(self.get_api_data())
3825 return data
3825 return data
3826
3826
3827
3827
3828 class ChangesetStatus(Base, BaseModel):
3828 class ChangesetStatus(Base, BaseModel):
3829 __tablename__ = 'changeset_statuses'
3829 __tablename__ = 'changeset_statuses'
3830 __table_args__ = (
3830 __table_args__ = (
3831 Index('cs_revision_idx', 'revision'),
3831 Index('cs_revision_idx', 'revision'),
3832 Index('cs_version_idx', 'version'),
3832 Index('cs_version_idx', 'version'),
3833 UniqueConstraint('repo_id', 'revision', 'version'),
3833 UniqueConstraint('repo_id', 'revision', 'version'),
3834 base_table_args
3834 base_table_args
3835 )
3835 )
3836
3836
3837 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3837 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3838 STATUS_APPROVED = 'approved'
3838 STATUS_APPROVED = 'approved'
3839 STATUS_REJECTED = 'rejected'
3839 STATUS_REJECTED = 'rejected'
3840 STATUS_UNDER_REVIEW = 'under_review'
3840 STATUS_UNDER_REVIEW = 'under_review'
3841
3841
3842 STATUSES = [
3842 STATUSES = [
3843 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3843 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3844 (STATUS_APPROVED, _("Approved")),
3844 (STATUS_APPROVED, _("Approved")),
3845 (STATUS_REJECTED, _("Rejected")),
3845 (STATUS_REJECTED, _("Rejected")),
3846 (STATUS_UNDER_REVIEW, _("Under Review")),
3846 (STATUS_UNDER_REVIEW, _("Under Review")),
3847 ]
3847 ]
3848
3848
3849 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3849 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3850 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3850 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3852 revision = Column('revision', String(40), nullable=False)
3852 revision = Column('revision', String(40), nullable=False)
3853 status = Column('status', String(128), nullable=False, default=DEFAULT)
3853 status = Column('status', String(128), nullable=False, default=DEFAULT)
3854 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3854 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3855 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3855 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3856 version = Column('version', Integer(), nullable=False, default=0)
3856 version = Column('version', Integer(), nullable=False, default=0)
3857 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3857 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3858
3858
3859 author = relationship('User', lazy='joined')
3859 author = relationship('User', lazy='joined')
3860 repo = relationship('Repository')
3860 repo = relationship('Repository')
3861 comment = relationship('ChangesetComment', lazy='joined')
3861 comment = relationship('ChangesetComment', lazy='joined')
3862 pull_request = relationship('PullRequest', lazy='joined')
3862 pull_request = relationship('PullRequest', lazy='joined')
3863
3863
3864 def __unicode__(self):
3864 def __unicode__(self):
3865 return u"<%s('%s[v%s]:%s')>" % (
3865 return u"<%s('%s[v%s]:%s')>" % (
3866 self.__class__.__name__,
3866 self.__class__.__name__,
3867 self.status, self.version, self.author
3867 self.status, self.version, self.author
3868 )
3868 )
3869
3869
3870 @classmethod
3870 @classmethod
3871 def get_status_lbl(cls, value):
3871 def get_status_lbl(cls, value):
3872 return dict(cls.STATUSES).get(value)
3872 return dict(cls.STATUSES).get(value)
3873
3873
3874 @property
3874 @property
3875 def status_lbl(self):
3875 def status_lbl(self):
3876 return ChangesetStatus.get_status_lbl(self.status)
3876 return ChangesetStatus.get_status_lbl(self.status)
3877
3877
3878 def get_api_data(self):
3878 def get_api_data(self):
3879 status = self
3879 status = self
3880 data = {
3880 data = {
3881 'status_id': status.changeset_status_id,
3881 'status_id': status.changeset_status_id,
3882 'status': status.status,
3882 'status': status.status,
3883 }
3883 }
3884 return data
3884 return data
3885
3885
3886 def __json__(self):
3886 def __json__(self):
3887 data = dict()
3887 data = dict()
3888 data.update(self.get_api_data())
3888 data.update(self.get_api_data())
3889 return data
3889 return data
3890
3890
3891
3891
3892 class _SetState(object):
3892 class _SetState(object):
3893 """
3893 """
3894 Context processor allowing changing state for sensitive operation such as
3894 Context processor allowing changing state for sensitive operation such as
3895 pull request update or merge
3895 pull request update or merge
3896 """
3896 """
3897
3897
3898 def __init__(self, pull_request, pr_state, back_state=None):
3898 def __init__(self, pull_request, pr_state, back_state=None):
3899 self._pr = pull_request
3899 self._pr = pull_request
3900 self._org_state = back_state or pull_request.pull_request_state
3900 self._org_state = back_state or pull_request.pull_request_state
3901 self._pr_state = pr_state
3901 self._pr_state = pr_state
3902 self._current_state = None
3902 self._current_state = None
3903
3903
3904 def __enter__(self):
3904 def __enter__(self):
3905 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3905 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3906 self._pr, self._pr_state)
3906 self._pr, self._pr_state)
3907 self.set_pr_state(self._pr_state)
3907 self.set_pr_state(self._pr_state)
3908 return self
3908 return self
3909
3909
3910 def __exit__(self, exc_type, exc_val, exc_tb):
3910 def __exit__(self, exc_type, exc_val, exc_tb):
3911 if exc_val is not None:
3911 if exc_val is not None:
3912 log.error(traceback.format_exc(exc_tb))
3912 log.error(traceback.format_exc(exc_tb))
3913 return None
3913 return None
3914
3914
3915 self.set_pr_state(self._org_state)
3915 self.set_pr_state(self._org_state)
3916 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3916 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3917 self._pr, self._org_state)
3917 self._pr, self._org_state)
3918
3918
3919 @property
3919 @property
3920 def state(self):
3920 def state(self):
3921 return self._current_state
3921 return self._current_state
3922
3922
3923 def set_pr_state(self, pr_state):
3923 def set_pr_state(self, pr_state):
3924 try:
3924 try:
3925 self._pr.pull_request_state = pr_state
3925 self._pr.pull_request_state = pr_state
3926 Session().add(self._pr)
3926 Session().add(self._pr)
3927 Session().commit()
3927 Session().commit()
3928 self._current_state = pr_state
3928 self._current_state = pr_state
3929 except Exception:
3929 except Exception:
3930 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3930 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3931 raise
3931 raise
3932
3932
3933
3933
3934 class _PullRequestBase(BaseModel):
3934 class _PullRequestBase(BaseModel):
3935 """
3935 """
3936 Common attributes of pull request and version entries.
3936 Common attributes of pull request and version entries.
3937 """
3937 """
3938
3938
3939 # .status values
3939 # .status values
3940 STATUS_NEW = u'new'
3940 STATUS_NEW = u'new'
3941 STATUS_OPEN = u'open'
3941 STATUS_OPEN = u'open'
3942 STATUS_CLOSED = u'closed'
3942 STATUS_CLOSED = u'closed'
3943
3943
3944 # available states
3944 # available states
3945 STATE_CREATING = u'creating'
3945 STATE_CREATING = u'creating'
3946 STATE_UPDATING = u'updating'
3946 STATE_UPDATING = u'updating'
3947 STATE_MERGING = u'merging'
3947 STATE_MERGING = u'merging'
3948 STATE_CREATED = u'created'
3948 STATE_CREATED = u'created'
3949
3949
3950 title = Column('title', Unicode(255), nullable=True)
3950 title = Column('title', Unicode(255), nullable=True)
3951 description = Column(
3951 description = Column(
3952 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3952 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3953 nullable=True)
3953 nullable=True)
3954 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3954 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3955
3955
3956 # new/open/closed status of pull request (not approve/reject/etc)
3956 # new/open/closed status of pull request (not approve/reject/etc)
3957 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3957 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3958 created_on = Column(
3958 created_on = Column(
3959 'created_on', DateTime(timezone=False), nullable=False,
3959 'created_on', DateTime(timezone=False), nullable=False,
3960 default=datetime.datetime.now)
3960 default=datetime.datetime.now)
3961 updated_on = Column(
3961 updated_on = Column(
3962 'updated_on', DateTime(timezone=False), nullable=False,
3962 'updated_on', DateTime(timezone=False), nullable=False,
3963 default=datetime.datetime.now)
3963 default=datetime.datetime.now)
3964
3964
3965 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3965 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3966
3966
3967 @declared_attr
3967 @declared_attr
3968 def user_id(cls):
3968 def user_id(cls):
3969 return Column(
3969 return Column(
3970 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3970 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3971 unique=None)
3971 unique=None)
3972
3972
3973 # 500 revisions max
3973 # 500 revisions max
3974 _revisions = Column(
3974 _revisions = Column(
3975 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3975 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3976
3976
3977 @declared_attr
3977 @declared_attr
3978 def source_repo_id(cls):
3978 def source_repo_id(cls):
3979 # TODO: dan: rename column to source_repo_id
3979 # TODO: dan: rename column to source_repo_id
3980 return Column(
3980 return Column(
3981 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3981 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3982 nullable=False)
3982 nullable=False)
3983
3983
3984 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3984 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3985
3985
3986 @hybrid_property
3986 @hybrid_property
3987 def source_ref(self):
3987 def source_ref(self):
3988 return self._source_ref
3988 return self._source_ref
3989
3989
3990 @source_ref.setter
3990 @source_ref.setter
3991 def source_ref(self, val):
3991 def source_ref(self, val):
3992 parts = (val or '').split(':')
3992 parts = (val or '').split(':')
3993 if len(parts) != 3:
3993 if len(parts) != 3:
3994 raise ValueError(
3994 raise ValueError(
3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3996 self._source_ref = safe_unicode(val)
3996 self._source_ref = safe_unicode(val)
3997
3997
3998 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3998 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3999
3999
4000 @hybrid_property
4000 @hybrid_property
4001 def target_ref(self):
4001 def target_ref(self):
4002 return self._target_ref
4002 return self._target_ref
4003
4003
4004 @target_ref.setter
4004 @target_ref.setter
4005 def target_ref(self, val):
4005 def target_ref(self, val):
4006 parts = (val or '').split(':')
4006 parts = (val or '').split(':')
4007 if len(parts) != 3:
4007 if len(parts) != 3:
4008 raise ValueError(
4008 raise ValueError(
4009 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4009 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4010 self._target_ref = safe_unicode(val)
4010 self._target_ref = safe_unicode(val)
4011
4011
4012 @declared_attr
4012 @declared_attr
4013 def target_repo_id(cls):
4013 def target_repo_id(cls):
4014 # TODO: dan: rename column to target_repo_id
4014 # TODO: dan: rename column to target_repo_id
4015 return Column(
4015 return Column(
4016 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4016 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4017 nullable=False)
4017 nullable=False)
4018
4018
4019 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4019 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4020
4020
4021 # TODO: dan: rename column to last_merge_source_rev
4021 # TODO: dan: rename column to last_merge_source_rev
4022 _last_merge_source_rev = Column(
4022 _last_merge_source_rev = Column(
4023 'last_merge_org_rev', String(40), nullable=True)
4023 'last_merge_org_rev', String(40), nullable=True)
4024 # TODO: dan: rename column to last_merge_target_rev
4024 # TODO: dan: rename column to last_merge_target_rev
4025 _last_merge_target_rev = Column(
4025 _last_merge_target_rev = Column(
4026 'last_merge_other_rev', String(40), nullable=True)
4026 'last_merge_other_rev', String(40), nullable=True)
4027 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4027 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4028 last_merge_metadata = Column(
4028 last_merge_metadata = Column(
4029 'last_merge_metadata', MutationObj.as_mutable(
4029 'last_merge_metadata', MutationObj.as_mutable(
4030 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4030 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4031
4031
4032 merge_rev = Column('merge_rev', String(40), nullable=True)
4032 merge_rev = Column('merge_rev', String(40), nullable=True)
4033
4033
4034 reviewer_data = Column(
4034 reviewer_data = Column(
4035 'reviewer_data_json', MutationObj.as_mutable(
4035 'reviewer_data_json', MutationObj.as_mutable(
4036 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4036 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4037
4037
4038 @property
4038 @property
4039 def reviewer_data_json(self):
4039 def reviewer_data_json(self):
4040 return json.dumps(self.reviewer_data)
4040 return json.dumps(self.reviewer_data)
4041
4041
4042 @property
4042 @property
4043 def work_in_progress(self):
4043 def work_in_progress(self):
4044 """checks if pull request is work in progress by checking the title"""
4044 """checks if pull request is work in progress by checking the title"""
4045 title = self.title.upper()
4045 title = self.title.upper()
4046 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4046 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4047 return True
4047 return True
4048 return False
4048 return False
4049
4049
4050 @hybrid_property
4050 @hybrid_property
4051 def description_safe(self):
4051 def description_safe(self):
4052 from rhodecode.lib import helpers as h
4052 from rhodecode.lib import helpers as h
4053 return h.escape(self.description)
4053 return h.escape(self.description)
4054
4054
4055 @hybrid_property
4055 @hybrid_property
4056 def revisions(self):
4056 def revisions(self):
4057 return self._revisions.split(':') if self._revisions else []
4057 return self._revisions.split(':') if self._revisions else []
4058
4058
4059 @revisions.setter
4059 @revisions.setter
4060 def revisions(self, val):
4060 def revisions(self, val):
4061 self._revisions = u':'.join(val)
4061 self._revisions = u':'.join(val)
4062
4062
4063 @hybrid_property
4063 @hybrid_property
4064 def last_merge_status(self):
4064 def last_merge_status(self):
4065 return safe_int(self._last_merge_status)
4065 return safe_int(self._last_merge_status)
4066
4066
4067 @last_merge_status.setter
4067 @last_merge_status.setter
4068 def last_merge_status(self, val):
4068 def last_merge_status(self, val):
4069 self._last_merge_status = val
4069 self._last_merge_status = val
4070
4070
4071 @declared_attr
4071 @declared_attr
4072 def author(cls):
4072 def author(cls):
4073 return relationship('User', lazy='joined')
4073 return relationship('User', lazy='joined')
4074
4074
4075 @declared_attr
4075 @declared_attr
4076 def source_repo(cls):
4076 def source_repo(cls):
4077 return relationship(
4077 return relationship(
4078 'Repository',
4078 'Repository',
4079 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4079 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4080
4080
4081 @property
4081 @property
4082 def source_ref_parts(self):
4082 def source_ref_parts(self):
4083 return self.unicode_to_reference(self.source_ref)
4083 return self.unicode_to_reference(self.source_ref)
4084
4084
4085 @declared_attr
4085 @declared_attr
4086 def target_repo(cls):
4086 def target_repo(cls):
4087 return relationship(
4087 return relationship(
4088 'Repository',
4088 'Repository',
4089 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4089 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4090
4090
4091 @property
4091 @property
4092 def target_ref_parts(self):
4092 def target_ref_parts(self):
4093 return self.unicode_to_reference(self.target_ref)
4093 return self.unicode_to_reference(self.target_ref)
4094
4094
4095 @property
4095 @property
4096 def shadow_merge_ref(self):
4096 def shadow_merge_ref(self):
4097 return self.unicode_to_reference(self._shadow_merge_ref)
4097 return self.unicode_to_reference(self._shadow_merge_ref)
4098
4098
4099 @shadow_merge_ref.setter
4099 @shadow_merge_ref.setter
4100 def shadow_merge_ref(self, ref):
4100 def shadow_merge_ref(self, ref):
4101 self._shadow_merge_ref = self.reference_to_unicode(ref)
4101 self._shadow_merge_ref = self.reference_to_unicode(ref)
4102
4102
4103 @staticmethod
4103 @staticmethod
4104 def unicode_to_reference(raw):
4104 def unicode_to_reference(raw):
4105 """
4105 """
4106 Convert a unicode (or string) to a reference object.
4106 Convert a unicode (or string) to a reference object.
4107 If unicode evaluates to False it returns None.
4107 If unicode evaluates to False it returns None.
4108 """
4108 """
4109 if raw:
4109 if raw:
4110 refs = raw.split(':')
4110 refs = raw.split(':')
4111 return Reference(*refs)
4111 return Reference(*refs)
4112 else:
4112 else:
4113 return None
4113 return None
4114
4114
4115 @staticmethod
4115 @staticmethod
4116 def reference_to_unicode(ref):
4116 def reference_to_unicode(ref):
4117 """
4117 """
4118 Convert a reference object to unicode.
4118 Convert a reference object to unicode.
4119 If reference is None it returns None.
4119 If reference is None it returns None.
4120 """
4120 """
4121 if ref:
4121 if ref:
4122 return u':'.join(ref)
4122 return u':'.join(ref)
4123 else:
4123 else:
4124 return None
4124 return None
4125
4125
4126 def get_api_data(self, with_merge_state=True):
4126 def get_api_data(self, with_merge_state=True):
4127 from rhodecode.model.pull_request import PullRequestModel
4127 from rhodecode.model.pull_request import PullRequestModel
4128
4128
4129 pull_request = self
4129 pull_request = self
4130 if with_merge_state:
4130 if with_merge_state:
4131 merge_response, merge_status, msg = \
4131 merge_response, merge_status, msg = \
4132 PullRequestModel().merge_status(pull_request)
4132 PullRequestModel().merge_status(pull_request)
4133 merge_state = {
4133 merge_state = {
4134 'status': merge_status,
4134 'status': merge_status,
4135 'message': safe_unicode(msg),
4135 'message': safe_unicode(msg),
4136 }
4136 }
4137 else:
4137 else:
4138 merge_state = {'status': 'not_available',
4138 merge_state = {'status': 'not_available',
4139 'message': 'not_available'}
4139 'message': 'not_available'}
4140
4140
4141 merge_data = {
4141 merge_data = {
4142 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4142 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4143 'reference': (
4143 'reference': (
4144 pull_request.shadow_merge_ref._asdict()
4144 pull_request.shadow_merge_ref._asdict()
4145 if pull_request.shadow_merge_ref else None),
4145 if pull_request.shadow_merge_ref else None),
4146 }
4146 }
4147
4147
4148 data = {
4148 data = {
4149 'pull_request_id': pull_request.pull_request_id,
4149 'pull_request_id': pull_request.pull_request_id,
4150 'url': PullRequestModel().get_url(pull_request),
4150 'url': PullRequestModel().get_url(pull_request),
4151 'title': pull_request.title,
4151 'title': pull_request.title,
4152 'description': pull_request.description,
4152 'description': pull_request.description,
4153 'status': pull_request.status,
4153 'status': pull_request.status,
4154 'state': pull_request.pull_request_state,
4154 'state': pull_request.pull_request_state,
4155 'created_on': pull_request.created_on,
4155 'created_on': pull_request.created_on,
4156 'updated_on': pull_request.updated_on,
4156 'updated_on': pull_request.updated_on,
4157 'commit_ids': pull_request.revisions,
4157 'commit_ids': pull_request.revisions,
4158 'review_status': pull_request.calculated_review_status(),
4158 'review_status': pull_request.calculated_review_status(),
4159 'mergeable': merge_state,
4159 'mergeable': merge_state,
4160 'source': {
4160 'source': {
4161 'clone_url': pull_request.source_repo.clone_url(),
4161 'clone_url': pull_request.source_repo.clone_url(),
4162 'repository': pull_request.source_repo.repo_name,
4162 'repository': pull_request.source_repo.repo_name,
4163 'reference': {
4163 'reference': {
4164 'name': pull_request.source_ref_parts.name,
4164 'name': pull_request.source_ref_parts.name,
4165 'type': pull_request.source_ref_parts.type,
4165 'type': pull_request.source_ref_parts.type,
4166 'commit_id': pull_request.source_ref_parts.commit_id,
4166 'commit_id': pull_request.source_ref_parts.commit_id,
4167 },
4167 },
4168 },
4168 },
4169 'target': {
4169 'target': {
4170 'clone_url': pull_request.target_repo.clone_url(),
4170 'clone_url': pull_request.target_repo.clone_url(),
4171 'repository': pull_request.target_repo.repo_name,
4171 'repository': pull_request.target_repo.repo_name,
4172 'reference': {
4172 'reference': {
4173 'name': pull_request.target_ref_parts.name,
4173 'name': pull_request.target_ref_parts.name,
4174 'type': pull_request.target_ref_parts.type,
4174 'type': pull_request.target_ref_parts.type,
4175 'commit_id': pull_request.target_ref_parts.commit_id,
4175 'commit_id': pull_request.target_ref_parts.commit_id,
4176 },
4176 },
4177 },
4177 },
4178 'merge': merge_data,
4178 'merge': merge_data,
4179 'author': pull_request.author.get_api_data(include_secrets=False,
4179 'author': pull_request.author.get_api_data(include_secrets=False,
4180 details='basic'),
4180 details='basic'),
4181 'reviewers': [
4181 'reviewers': [
4182 {
4182 {
4183 'user': reviewer.get_api_data(include_secrets=False,
4183 'user': reviewer.get_api_data(include_secrets=False,
4184 details='basic'),
4184 details='basic'),
4185 'reasons': reasons,
4185 'reasons': reasons,
4186 'review_status': st[0][1].status if st else 'not_reviewed',
4186 'review_status': st[0][1].status if st else 'not_reviewed',
4187 }
4187 }
4188 for obj, reviewer, reasons, mandatory, st in
4188 for obj, reviewer, reasons, mandatory, st in
4189 pull_request.reviewers_statuses()
4189 pull_request.reviewers_statuses()
4190 ]
4190 ]
4191 }
4191 }
4192
4192
4193 return data
4193 return data
4194
4194
4195 def set_state(self, pull_request_state, final_state=None):
4195 def set_state(self, pull_request_state, final_state=None):
4196 """
4196 """
4197 # goes from initial state to updating to initial state.
4197 # goes from initial state to updating to initial state.
4198 # initial state can be changed by specifying back_state=
4198 # initial state can be changed by specifying back_state=
4199 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4199 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4200 pull_request.merge()
4200 pull_request.merge()
4201
4201
4202 :param pull_request_state:
4202 :param pull_request_state:
4203 :param final_state:
4203 :param final_state:
4204
4204
4205 """
4205 """
4206
4206
4207 return _SetState(self, pull_request_state, back_state=final_state)
4207 return _SetState(self, pull_request_state, back_state=final_state)
4208
4208
4209
4209
4210 class PullRequest(Base, _PullRequestBase):
4210 class PullRequest(Base, _PullRequestBase):
4211 __tablename__ = 'pull_requests'
4211 __tablename__ = 'pull_requests'
4212 __table_args__ = (
4212 __table_args__ = (
4213 base_table_args,
4213 base_table_args,
4214 )
4214 )
4215
4215
4216 pull_request_id = Column(
4216 pull_request_id = Column(
4217 'pull_request_id', Integer(), nullable=False, primary_key=True)
4217 'pull_request_id', Integer(), nullable=False, primary_key=True)
4218
4218
4219 def __repr__(self):
4219 def __repr__(self):
4220 if self.pull_request_id:
4220 if self.pull_request_id:
4221 return '<DB:PullRequest #%s>' % self.pull_request_id
4221 return '<DB:PullRequest #%s>' % self.pull_request_id
4222 else:
4222 else:
4223 return '<DB:PullRequest at %#x>' % id(self)
4223 return '<DB:PullRequest at %#x>' % id(self)
4224
4224
4225 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4225 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4226 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4226 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4227 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4227 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4228 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4228 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4229 lazy='dynamic')
4229 lazy='dynamic')
4230
4230
4231 @classmethod
4231 @classmethod
4232 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4232 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4233 internal_methods=None):
4233 internal_methods=None):
4234
4234
4235 class PullRequestDisplay(object):
4235 class PullRequestDisplay(object):
4236 """
4236 """
4237 Special object wrapper for showing PullRequest data via Versions
4237 Special object wrapper for showing PullRequest data via Versions
4238 It mimics PR object as close as possible. This is read only object
4238 It mimics PR object as close as possible. This is read only object
4239 just for display
4239 just for display
4240 """
4240 """
4241
4241
4242 def __init__(self, attrs, internal=None):
4242 def __init__(self, attrs, internal=None):
4243 self.attrs = attrs
4243 self.attrs = attrs
4244 # internal have priority over the given ones via attrs
4244 # internal have priority over the given ones via attrs
4245 self.internal = internal or ['versions']
4245 self.internal = internal or ['versions']
4246
4246
4247 def __getattr__(self, item):
4247 def __getattr__(self, item):
4248 if item in self.internal:
4248 if item in self.internal:
4249 return getattr(self, item)
4249 return getattr(self, item)
4250 try:
4250 try:
4251 return self.attrs[item]
4251 return self.attrs[item]
4252 except KeyError:
4252 except KeyError:
4253 raise AttributeError(
4253 raise AttributeError(
4254 '%s object has no attribute %s' % (self, item))
4254 '%s object has no attribute %s' % (self, item))
4255
4255
4256 def __repr__(self):
4256 def __repr__(self):
4257 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4257 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4258
4258
4259 def versions(self):
4259 def versions(self):
4260 return pull_request_obj.versions.order_by(
4260 return pull_request_obj.versions.order_by(
4261 PullRequestVersion.pull_request_version_id).all()
4261 PullRequestVersion.pull_request_version_id).all()
4262
4262
4263 def is_closed(self):
4263 def is_closed(self):
4264 return pull_request_obj.is_closed()
4264 return pull_request_obj.is_closed()
4265
4265
4266 def is_state_changing(self):
4266 def is_state_changing(self):
4267 return pull_request_obj.is_state_changing()
4267 return pull_request_obj.is_state_changing()
4268
4268
4269 @property
4269 @property
4270 def pull_request_version_id(self):
4270 def pull_request_version_id(self):
4271 return getattr(pull_request_obj, 'pull_request_version_id', None)
4271 return getattr(pull_request_obj, 'pull_request_version_id', None)
4272
4272
4273 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4273 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4274
4274
4275 attrs.author = StrictAttributeDict(
4275 attrs.author = StrictAttributeDict(
4276 pull_request_obj.author.get_api_data())
4276 pull_request_obj.author.get_api_data())
4277 if pull_request_obj.target_repo:
4277 if pull_request_obj.target_repo:
4278 attrs.target_repo = StrictAttributeDict(
4278 attrs.target_repo = StrictAttributeDict(
4279 pull_request_obj.target_repo.get_api_data())
4279 pull_request_obj.target_repo.get_api_data())
4280 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4280 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4281
4281
4282 if pull_request_obj.source_repo:
4282 if pull_request_obj.source_repo:
4283 attrs.source_repo = StrictAttributeDict(
4283 attrs.source_repo = StrictAttributeDict(
4284 pull_request_obj.source_repo.get_api_data())
4284 pull_request_obj.source_repo.get_api_data())
4285 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4285 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4286
4286
4287 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4287 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4288 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4288 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4289 attrs.revisions = pull_request_obj.revisions
4289 attrs.revisions = pull_request_obj.revisions
4290
4290
4291 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4291 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4292 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4292 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4293 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4293 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4294
4294
4295 return PullRequestDisplay(attrs, internal=internal_methods)
4295 return PullRequestDisplay(attrs, internal=internal_methods)
4296
4296
4297 def is_closed(self):
4297 def is_closed(self):
4298 return self.status == self.STATUS_CLOSED
4298 return self.status == self.STATUS_CLOSED
4299
4299
4300 def is_state_changing(self):
4300 def is_state_changing(self):
4301 return self.pull_request_state != PullRequest.STATE_CREATED
4301 return self.pull_request_state != PullRequest.STATE_CREATED
4302
4302
4303 def __json__(self):
4303 def __json__(self):
4304 return {
4304 return {
4305 'revisions': self.revisions,
4305 'revisions': self.revisions,
4306 'versions': self.versions_count
4306 'versions': self.versions_count
4307 }
4307 }
4308
4308
4309 def calculated_review_status(self):
4309 def calculated_review_status(self):
4310 from rhodecode.model.changeset_status import ChangesetStatusModel
4310 from rhodecode.model.changeset_status import ChangesetStatusModel
4311 return ChangesetStatusModel().calculated_review_status(self)
4311 return ChangesetStatusModel().calculated_review_status(self)
4312
4312
4313 def reviewers_statuses(self):
4313 def reviewers_statuses(self):
4314 from rhodecode.model.changeset_status import ChangesetStatusModel
4314 from rhodecode.model.changeset_status import ChangesetStatusModel
4315 return ChangesetStatusModel().reviewers_statuses(self)
4315 return ChangesetStatusModel().reviewers_statuses(self)
4316
4316
4317 @property
4317 @property
4318 def workspace_id(self):
4318 def workspace_id(self):
4319 from rhodecode.model.pull_request import PullRequestModel
4319 from rhodecode.model.pull_request import PullRequestModel
4320 return PullRequestModel()._workspace_id(self)
4320 return PullRequestModel()._workspace_id(self)
4321
4321
4322 def get_shadow_repo(self):
4322 def get_shadow_repo(self):
4323 workspace_id = self.workspace_id
4323 workspace_id = self.workspace_id
4324 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4324 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4325 if os.path.isdir(shadow_repository_path):
4325 if os.path.isdir(shadow_repository_path):
4326 vcs_obj = self.target_repo.scm_instance()
4326 vcs_obj = self.target_repo.scm_instance()
4327 return vcs_obj.get_shadow_instance(shadow_repository_path)
4327 return vcs_obj.get_shadow_instance(shadow_repository_path)
4328
4328
4329 @property
4329 @property
4330 def versions_count(self):
4330 def versions_count(self):
4331 """
4331 """
4332 return number of versions this PR have, e.g a PR that once been
4332 return number of versions this PR have, e.g a PR that once been
4333 updated will have 2 versions
4333 updated will have 2 versions
4334 """
4334 """
4335 return self.versions.count() + 1
4335 return self.versions.count() + 1
4336
4336
4337
4337
4338 class PullRequestVersion(Base, _PullRequestBase):
4338 class PullRequestVersion(Base, _PullRequestBase):
4339 __tablename__ = 'pull_request_versions'
4339 __tablename__ = 'pull_request_versions'
4340 __table_args__ = (
4340 __table_args__ = (
4341 base_table_args,
4341 base_table_args,
4342 )
4342 )
4343
4343
4344 pull_request_version_id = Column(
4344 pull_request_version_id = Column(
4345 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4345 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4346 pull_request_id = Column(
4346 pull_request_id = Column(
4347 'pull_request_id', Integer(),
4347 'pull_request_id', Integer(),
4348 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4348 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4349 pull_request = relationship('PullRequest')
4349 pull_request = relationship('PullRequest')
4350
4350
4351 def __repr__(self):
4351 def __repr__(self):
4352 if self.pull_request_version_id:
4352 if self.pull_request_version_id:
4353 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4353 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4354 else:
4354 else:
4355 return '<DB:PullRequestVersion at %#x>' % id(self)
4355 return '<DB:PullRequestVersion at %#x>' % id(self)
4356
4356
4357 @property
4357 @property
4358 def reviewers(self):
4358 def reviewers(self):
4359 return self.pull_request.reviewers
4359 return self.pull_request.reviewers
4360
4360
4361 @property
4361 @property
4362 def versions(self):
4362 def versions(self):
4363 return self.pull_request.versions
4363 return self.pull_request.versions
4364
4364
4365 def is_closed(self):
4365 def is_closed(self):
4366 # calculate from original
4366 # calculate from original
4367 return self.pull_request.status == self.STATUS_CLOSED
4367 return self.pull_request.status == self.STATUS_CLOSED
4368
4368
4369 def is_state_changing(self):
4369 def is_state_changing(self):
4370 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4370 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4371
4371
4372 def calculated_review_status(self):
4372 def calculated_review_status(self):
4373 return self.pull_request.calculated_review_status()
4373 return self.pull_request.calculated_review_status()
4374
4374
4375 def reviewers_statuses(self):
4375 def reviewers_statuses(self):
4376 return self.pull_request.reviewers_statuses()
4376 return self.pull_request.reviewers_statuses()
4377
4377
4378
4378
4379 class PullRequestReviewers(Base, BaseModel):
4379 class PullRequestReviewers(Base, BaseModel):
4380 __tablename__ = 'pull_request_reviewers'
4380 __tablename__ = 'pull_request_reviewers'
4381 __table_args__ = (
4381 __table_args__ = (
4382 base_table_args,
4382 base_table_args,
4383 )
4383 )
4384
4384
4385 @hybrid_property
4385 @hybrid_property
4386 def reasons(self):
4386 def reasons(self):
4387 if not self._reasons:
4387 if not self._reasons:
4388 return []
4388 return []
4389 return self._reasons
4389 return self._reasons
4390
4390
4391 @reasons.setter
4391 @reasons.setter
4392 def reasons(self, val):
4392 def reasons(self, val):
4393 val = val or []
4393 val = val or []
4394 if any(not isinstance(x, compat.string_types) for x in val):
4394 if any(not isinstance(x, compat.string_types) for x in val):
4395 raise Exception('invalid reasons type, must be list of strings')
4395 raise Exception('invalid reasons type, must be list of strings')
4396 self._reasons = val
4396 self._reasons = val
4397
4397
4398 pull_requests_reviewers_id = Column(
4398 pull_requests_reviewers_id = Column(
4399 'pull_requests_reviewers_id', Integer(), nullable=False,
4399 'pull_requests_reviewers_id', Integer(), nullable=False,
4400 primary_key=True)
4400 primary_key=True)
4401 pull_request_id = Column(
4401 pull_request_id = Column(
4402 "pull_request_id", Integer(),
4402 "pull_request_id", Integer(),
4403 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4403 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4404 user_id = Column(
4404 user_id = Column(
4405 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4405 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4406 _reasons = Column(
4406 _reasons = Column(
4407 'reason', MutationList.as_mutable(
4407 'reason', MutationList.as_mutable(
4408 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4408 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4409
4409
4410 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4410 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4411 user = relationship('User')
4411 user = relationship('User')
4412 pull_request = relationship('PullRequest')
4412 pull_request = relationship('PullRequest')
4413
4413
4414 rule_data = Column(
4414 rule_data = Column(
4415 'rule_data_json',
4415 'rule_data_json',
4416 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4416 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4417
4417
4418 def rule_user_group_data(self):
4418 def rule_user_group_data(self):
4419 """
4419 """
4420 Returns the voting user group rule data for this reviewer
4420 Returns the voting user group rule data for this reviewer
4421 """
4421 """
4422
4422
4423 if self.rule_data and 'vote_rule' in self.rule_data:
4423 if self.rule_data and 'vote_rule' in self.rule_data:
4424 user_group_data = {}
4424 user_group_data = {}
4425 if 'rule_user_group_entry_id' in self.rule_data:
4425 if 'rule_user_group_entry_id' in self.rule_data:
4426 # means a group with voting rules !
4426 # means a group with voting rules !
4427 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4427 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4428 user_group_data['name'] = self.rule_data['rule_name']
4428 user_group_data['name'] = self.rule_data['rule_name']
4429 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4429 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4430
4430
4431 return user_group_data
4431 return user_group_data
4432
4432
4433 def __unicode__(self):
4433 def __unicode__(self):
4434 return u"<%s('id:%s')>" % (self.__class__.__name__,
4434 return u"<%s('id:%s')>" % (self.__class__.__name__,
4435 self.pull_requests_reviewers_id)
4435 self.pull_requests_reviewers_id)
4436
4436
4437
4437
4438 class Notification(Base, BaseModel):
4438 class Notification(Base, BaseModel):
4439 __tablename__ = 'notifications'
4439 __tablename__ = 'notifications'
4440 __table_args__ = (
4440 __table_args__ = (
4441 Index('notification_type_idx', 'type'),
4441 Index('notification_type_idx', 'type'),
4442 base_table_args,
4442 base_table_args,
4443 )
4443 )
4444
4444
4445 TYPE_CHANGESET_COMMENT = u'cs_comment'
4445 TYPE_CHANGESET_COMMENT = u'cs_comment'
4446 TYPE_MESSAGE = u'message'
4446 TYPE_MESSAGE = u'message'
4447 TYPE_MENTION = u'mention'
4447 TYPE_MENTION = u'mention'
4448 TYPE_REGISTRATION = u'registration'
4448 TYPE_REGISTRATION = u'registration'
4449 TYPE_PULL_REQUEST = u'pull_request'
4449 TYPE_PULL_REQUEST = u'pull_request'
4450 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4450 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4451 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4451 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4452
4452
4453 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4453 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4454 subject = Column('subject', Unicode(512), nullable=True)
4454 subject = Column('subject', Unicode(512), nullable=True)
4455 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4455 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4456 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4456 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4457 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4457 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4458 type_ = Column('type', Unicode(255))
4458 type_ = Column('type', Unicode(255))
4459
4459
4460 created_by_user = relationship('User')
4460 created_by_user = relationship('User')
4461 notifications_to_users = relationship('UserNotification', lazy='joined',
4461 notifications_to_users = relationship('UserNotification', lazy='joined',
4462 cascade="all, delete-orphan")
4462 cascade="all, delete-orphan")
4463
4463
4464 @property
4464 @property
4465 def recipients(self):
4465 def recipients(self):
4466 return [x.user for x in UserNotification.query()\
4466 return [x.user for x in UserNotification.query()\
4467 .filter(UserNotification.notification == self)\
4467 .filter(UserNotification.notification == self)\
4468 .order_by(UserNotification.user_id.asc()).all()]
4468 .order_by(UserNotification.user_id.asc()).all()]
4469
4469
4470 @classmethod
4470 @classmethod
4471 def create(cls, created_by, subject, body, recipients, type_=None):
4471 def create(cls, created_by, subject, body, recipients, type_=None):
4472 if type_ is None:
4472 if type_ is None:
4473 type_ = Notification.TYPE_MESSAGE
4473 type_ = Notification.TYPE_MESSAGE
4474
4474
4475 notification = cls()
4475 notification = cls()
4476 notification.created_by_user = created_by
4476 notification.created_by_user = created_by
4477 notification.subject = subject
4477 notification.subject = subject
4478 notification.body = body
4478 notification.body = body
4479 notification.type_ = type_
4479 notification.type_ = type_
4480 notification.created_on = datetime.datetime.now()
4480 notification.created_on = datetime.datetime.now()
4481
4481
4482 # For each recipient link the created notification to his account
4482 # For each recipient link the created notification to his account
4483 for u in recipients:
4483 for u in recipients:
4484 assoc = UserNotification()
4484 assoc = UserNotification()
4485 assoc.user_id = u.user_id
4485 assoc.user_id = u.user_id
4486 assoc.notification = notification
4486 assoc.notification = notification
4487
4487
4488 # if created_by is inside recipients mark his notification
4488 # if created_by is inside recipients mark his notification
4489 # as read
4489 # as read
4490 if u.user_id == created_by.user_id:
4490 if u.user_id == created_by.user_id:
4491 assoc.read = True
4491 assoc.read = True
4492 Session().add(assoc)
4492 Session().add(assoc)
4493
4493
4494 Session().add(notification)
4494 Session().add(notification)
4495
4495
4496 return notification
4496 return notification
4497
4497
4498
4498
4499 class UserNotification(Base, BaseModel):
4499 class UserNotification(Base, BaseModel):
4500 __tablename__ = 'user_to_notification'
4500 __tablename__ = 'user_to_notification'
4501 __table_args__ = (
4501 __table_args__ = (
4502 UniqueConstraint('user_id', 'notification_id'),
4502 UniqueConstraint('user_id', 'notification_id'),
4503 base_table_args
4503 base_table_args
4504 )
4504 )
4505
4505
4506 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4506 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4507 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4507 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4508 read = Column('read', Boolean, default=False)
4508 read = Column('read', Boolean, default=False)
4509 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4509 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4510
4510
4511 user = relationship('User', lazy="joined")
4511 user = relationship('User', lazy="joined")
4512 notification = relationship('Notification', lazy="joined",
4512 notification = relationship('Notification', lazy="joined",
4513 order_by=lambda: Notification.created_on.desc(),)
4513 order_by=lambda: Notification.created_on.desc(),)
4514
4514
4515 def mark_as_read(self):
4515 def mark_as_read(self):
4516 self.read = True
4516 self.read = True
4517 Session().add(self)
4517 Session().add(self)
4518
4518
4519
4519
4520 class UserNotice(Base, BaseModel):
4521 __tablename__ = 'user_notices'
4522 __table_args__ = (
4523 base_table_args
4524 )
4525
4526 NOTIFICATION_TYPE_MESSAGE = 'message'
4527 NOTIFICATION_TYPE_NOTICE = 'notice'
4528
4529 NOTIFICATION_LEVEL_INFO = 'info'
4530 NOTIFICATION_LEVEL_WARNING = 'warning'
4531 NOTIFICATION_LEVEL_ERROR = 'error'
4532
4533 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4534
4535 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4536 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4537
4538 notice_read = Column('notice_read', Boolean, default=False)
4539
4540 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4541 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4542
4543 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4544 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4545
4546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4547 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4548
4549 @classmethod
4550 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4551
4552 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4553 cls.NOTIFICATION_LEVEL_WARNING,
4554 cls.NOTIFICATION_LEVEL_INFO]:
4555 return
4556
4557 from rhodecode.model.user import UserModel
4558 user = UserModel().get_user(user)
4559
4560 new_notice = UserNotice()
4561 if not allow_duplicate:
4562 existing_msg = UserNotice().query() \
4563 .filter(UserNotice.user == user) \
4564 .filter(UserNotice.notice_body == body) \
4565 .filter(UserNotice.notice_read == false()) \
4566 .scalar()
4567 if existing_msg:
4568 log.warning('Ignoring duplicate notice for user %s', user)
4569 return
4570
4571 new_notice.user = user
4572 new_notice.notice_subject = subject
4573 new_notice.notice_body = body
4574 new_notice.notification_level = notice_level
4575 Session().add(new_notice)
4576 Session().commit()
4577
4578
4520 class Gist(Base, BaseModel):
4579 class Gist(Base, BaseModel):
4521 __tablename__ = 'gists'
4580 __tablename__ = 'gists'
4522 __table_args__ = (
4581 __table_args__ = (
4523 Index('g_gist_access_id_idx', 'gist_access_id'),
4582 Index('g_gist_access_id_idx', 'gist_access_id'),
4524 Index('g_created_on_idx', 'created_on'),
4583 Index('g_created_on_idx', 'created_on'),
4525 base_table_args
4584 base_table_args
4526 )
4585 )
4527
4586
4528 GIST_PUBLIC = u'public'
4587 GIST_PUBLIC = u'public'
4529 GIST_PRIVATE = u'private'
4588 GIST_PRIVATE = u'private'
4530 DEFAULT_FILENAME = u'gistfile1.txt'
4589 DEFAULT_FILENAME = u'gistfile1.txt'
4531
4590
4532 ACL_LEVEL_PUBLIC = u'acl_public'
4591 ACL_LEVEL_PUBLIC = u'acl_public'
4533 ACL_LEVEL_PRIVATE = u'acl_private'
4592 ACL_LEVEL_PRIVATE = u'acl_private'
4534
4593
4535 gist_id = Column('gist_id', Integer(), primary_key=True)
4594 gist_id = Column('gist_id', Integer(), primary_key=True)
4536 gist_access_id = Column('gist_access_id', Unicode(250))
4595 gist_access_id = Column('gist_access_id', Unicode(250))
4537 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4596 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4538 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4597 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4539 gist_expires = Column('gist_expires', Float(53), nullable=False)
4598 gist_expires = Column('gist_expires', Float(53), nullable=False)
4540 gist_type = Column('gist_type', Unicode(128), nullable=False)
4599 gist_type = Column('gist_type', Unicode(128), nullable=False)
4541 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4600 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4542 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4601 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4543 acl_level = Column('acl_level', Unicode(128), nullable=True)
4602 acl_level = Column('acl_level', Unicode(128), nullable=True)
4544
4603
4545 owner = relationship('User')
4604 owner = relationship('User')
4546
4605
4547 def __repr__(self):
4606 def __repr__(self):
4548 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4607 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4549
4608
4550 @hybrid_property
4609 @hybrid_property
4551 def description_safe(self):
4610 def description_safe(self):
4552 from rhodecode.lib import helpers as h
4611 from rhodecode.lib import helpers as h
4553 return h.escape(self.gist_description)
4612 return h.escape(self.gist_description)
4554
4613
4555 @classmethod
4614 @classmethod
4556 def get_or_404(cls, id_):
4615 def get_or_404(cls, id_):
4557 from pyramid.httpexceptions import HTTPNotFound
4616 from pyramid.httpexceptions import HTTPNotFound
4558
4617
4559 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4618 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4560 if not res:
4619 if not res:
4561 raise HTTPNotFound()
4620 raise HTTPNotFound()
4562 return res
4621 return res
4563
4622
4564 @classmethod
4623 @classmethod
4565 def get_by_access_id(cls, gist_access_id):
4624 def get_by_access_id(cls, gist_access_id):
4566 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4625 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4567
4626
4568 def gist_url(self):
4627 def gist_url(self):
4569 from rhodecode.model.gist import GistModel
4628 from rhodecode.model.gist import GistModel
4570 return GistModel().get_url(self)
4629 return GistModel().get_url(self)
4571
4630
4572 @classmethod
4631 @classmethod
4573 def base_path(cls):
4632 def base_path(cls):
4574 """
4633 """
4575 Returns base path when all gists are stored
4634 Returns base path when all gists are stored
4576
4635
4577 :param cls:
4636 :param cls:
4578 """
4637 """
4579 from rhodecode.model.gist import GIST_STORE_LOC
4638 from rhodecode.model.gist import GIST_STORE_LOC
4580 q = Session().query(RhodeCodeUi)\
4639 q = Session().query(RhodeCodeUi)\
4581 .filter(RhodeCodeUi.ui_key == URL_SEP)
4640 .filter(RhodeCodeUi.ui_key == URL_SEP)
4582 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4641 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4583 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4642 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4584
4643
4585 def get_api_data(self):
4644 def get_api_data(self):
4586 """
4645 """
4587 Common function for generating gist related data for API
4646 Common function for generating gist related data for API
4588 """
4647 """
4589 gist = self
4648 gist = self
4590 data = {
4649 data = {
4591 'gist_id': gist.gist_id,
4650 'gist_id': gist.gist_id,
4592 'type': gist.gist_type,
4651 'type': gist.gist_type,
4593 'access_id': gist.gist_access_id,
4652 'access_id': gist.gist_access_id,
4594 'description': gist.gist_description,
4653 'description': gist.gist_description,
4595 'url': gist.gist_url(),
4654 'url': gist.gist_url(),
4596 'expires': gist.gist_expires,
4655 'expires': gist.gist_expires,
4597 'created_on': gist.created_on,
4656 'created_on': gist.created_on,
4598 'modified_at': gist.modified_at,
4657 'modified_at': gist.modified_at,
4599 'content': None,
4658 'content': None,
4600 'acl_level': gist.acl_level,
4659 'acl_level': gist.acl_level,
4601 }
4660 }
4602 return data
4661 return data
4603
4662
4604 def __json__(self):
4663 def __json__(self):
4605 data = dict(
4664 data = dict(
4606 )
4665 )
4607 data.update(self.get_api_data())
4666 data.update(self.get_api_data())
4608 return data
4667 return data
4609 # SCM functions
4668 # SCM functions
4610
4669
4611 def scm_instance(self, **kwargs):
4670 def scm_instance(self, **kwargs):
4612 """
4671 """
4613 Get an instance of VCS Repository
4672 Get an instance of VCS Repository
4614
4673
4615 :param kwargs:
4674 :param kwargs:
4616 """
4675 """
4617 from rhodecode.model.gist import GistModel
4676 from rhodecode.model.gist import GistModel
4618 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4677 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4619 return get_vcs_instance(
4678 return get_vcs_instance(
4620 repo_path=safe_str(full_repo_path), create=False,
4679 repo_path=safe_str(full_repo_path), create=False,
4621 _vcs_alias=GistModel.vcs_backend)
4680 _vcs_alias=GistModel.vcs_backend)
4622
4681
4623
4682
4624 class ExternalIdentity(Base, BaseModel):
4683 class ExternalIdentity(Base, BaseModel):
4625 __tablename__ = 'external_identities'
4684 __tablename__ = 'external_identities'
4626 __table_args__ = (
4685 __table_args__ = (
4627 Index('local_user_id_idx', 'local_user_id'),
4686 Index('local_user_id_idx', 'local_user_id'),
4628 Index('external_id_idx', 'external_id'),
4687 Index('external_id_idx', 'external_id'),
4629 base_table_args
4688 base_table_args
4630 )
4689 )
4631
4690
4632 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4691 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4633 external_username = Column('external_username', Unicode(1024), default=u'')
4692 external_username = Column('external_username', Unicode(1024), default=u'')
4634 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4693 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4635 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4694 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4636 access_token = Column('access_token', String(1024), default=u'')
4695 access_token = Column('access_token', String(1024), default=u'')
4637 alt_token = Column('alt_token', String(1024), default=u'')
4696 alt_token = Column('alt_token', String(1024), default=u'')
4638 token_secret = Column('token_secret', String(1024), default=u'')
4697 token_secret = Column('token_secret', String(1024), default=u'')
4639
4698
4640 @classmethod
4699 @classmethod
4641 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4700 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4642 """
4701 """
4643 Returns ExternalIdentity instance based on search params
4702 Returns ExternalIdentity instance based on search params
4644
4703
4645 :param external_id:
4704 :param external_id:
4646 :param provider_name:
4705 :param provider_name:
4647 :return: ExternalIdentity
4706 :return: ExternalIdentity
4648 """
4707 """
4649 query = cls.query()
4708 query = cls.query()
4650 query = query.filter(cls.external_id == external_id)
4709 query = query.filter(cls.external_id == external_id)
4651 query = query.filter(cls.provider_name == provider_name)
4710 query = query.filter(cls.provider_name == provider_name)
4652 if local_user_id:
4711 if local_user_id:
4653 query = query.filter(cls.local_user_id == local_user_id)
4712 query = query.filter(cls.local_user_id == local_user_id)
4654 return query.first()
4713 return query.first()
4655
4714
4656 @classmethod
4715 @classmethod
4657 def user_by_external_id_and_provider(cls, external_id, provider_name):
4716 def user_by_external_id_and_provider(cls, external_id, provider_name):
4658 """
4717 """
4659 Returns User instance based on search params
4718 Returns User instance based on search params
4660
4719
4661 :param external_id:
4720 :param external_id:
4662 :param provider_name:
4721 :param provider_name:
4663 :return: User
4722 :return: User
4664 """
4723 """
4665 query = User.query()
4724 query = User.query()
4666 query = query.filter(cls.external_id == external_id)
4725 query = query.filter(cls.external_id == external_id)
4667 query = query.filter(cls.provider_name == provider_name)
4726 query = query.filter(cls.provider_name == provider_name)
4668 query = query.filter(User.user_id == cls.local_user_id)
4727 query = query.filter(User.user_id == cls.local_user_id)
4669 return query.first()
4728 return query.first()
4670
4729
4671 @classmethod
4730 @classmethod
4672 def by_local_user_id(cls, local_user_id):
4731 def by_local_user_id(cls, local_user_id):
4673 """
4732 """
4674 Returns all tokens for user
4733 Returns all tokens for user
4675
4734
4676 :param local_user_id:
4735 :param local_user_id:
4677 :return: ExternalIdentity
4736 :return: ExternalIdentity
4678 """
4737 """
4679 query = cls.query()
4738 query = cls.query()
4680 query = query.filter(cls.local_user_id == local_user_id)
4739 query = query.filter(cls.local_user_id == local_user_id)
4681 return query
4740 return query
4682
4741
4683 @classmethod
4742 @classmethod
4684 def load_provider_plugin(cls, plugin_id):
4743 def load_provider_plugin(cls, plugin_id):
4685 from rhodecode.authentication.base import loadplugin
4744 from rhodecode.authentication.base import loadplugin
4686 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4745 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4687 auth_plugin = loadplugin(_plugin_id)
4746 auth_plugin = loadplugin(_plugin_id)
4688 return auth_plugin
4747 return auth_plugin
4689
4748
4690
4749
4691 class Integration(Base, BaseModel):
4750 class Integration(Base, BaseModel):
4692 __tablename__ = 'integrations'
4751 __tablename__ = 'integrations'
4693 __table_args__ = (
4752 __table_args__ = (
4694 base_table_args
4753 base_table_args
4695 )
4754 )
4696
4755
4697 integration_id = Column('integration_id', Integer(), primary_key=True)
4756 integration_id = Column('integration_id', Integer(), primary_key=True)
4698 integration_type = Column('integration_type', String(255))
4757 integration_type = Column('integration_type', String(255))
4699 enabled = Column('enabled', Boolean(), nullable=False)
4758 enabled = Column('enabled', Boolean(), nullable=False)
4700 name = Column('name', String(255), nullable=False)
4759 name = Column('name', String(255), nullable=False)
4701 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4760 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4702 default=False)
4761 default=False)
4703
4762
4704 settings = Column(
4763 settings = Column(
4705 'settings_json', MutationObj.as_mutable(
4764 'settings_json', MutationObj.as_mutable(
4706 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4765 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4707 repo_id = Column(
4766 repo_id = Column(
4708 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4767 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4709 nullable=True, unique=None, default=None)
4768 nullable=True, unique=None, default=None)
4710 repo = relationship('Repository', lazy='joined')
4769 repo = relationship('Repository', lazy='joined')
4711
4770
4712 repo_group_id = Column(
4771 repo_group_id = Column(
4713 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4772 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4714 nullable=True, unique=None, default=None)
4773 nullable=True, unique=None, default=None)
4715 repo_group = relationship('RepoGroup', lazy='joined')
4774 repo_group = relationship('RepoGroup', lazy='joined')
4716
4775
4717 @property
4776 @property
4718 def scope(self):
4777 def scope(self):
4719 if self.repo:
4778 if self.repo:
4720 return repr(self.repo)
4779 return repr(self.repo)
4721 if self.repo_group:
4780 if self.repo_group:
4722 if self.child_repos_only:
4781 if self.child_repos_only:
4723 return repr(self.repo_group) + ' (child repos only)'
4782 return repr(self.repo_group) + ' (child repos only)'
4724 else:
4783 else:
4725 return repr(self.repo_group) + ' (recursive)'
4784 return repr(self.repo_group) + ' (recursive)'
4726 if self.child_repos_only:
4785 if self.child_repos_only:
4727 return 'root_repos'
4786 return 'root_repos'
4728 return 'global'
4787 return 'global'
4729
4788
4730 def __repr__(self):
4789 def __repr__(self):
4731 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4790 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4732
4791
4733
4792
4734 class RepoReviewRuleUser(Base, BaseModel):
4793 class RepoReviewRuleUser(Base, BaseModel):
4735 __tablename__ = 'repo_review_rules_users'
4794 __tablename__ = 'repo_review_rules_users'
4736 __table_args__ = (
4795 __table_args__ = (
4737 base_table_args
4796 base_table_args
4738 )
4797 )
4739
4798
4740 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4799 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4741 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4800 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4742 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4801 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4743 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4802 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4744 user = relationship('User')
4803 user = relationship('User')
4745
4804
4746 def rule_data(self):
4805 def rule_data(self):
4747 return {
4806 return {
4748 'mandatory': self.mandatory
4807 'mandatory': self.mandatory
4749 }
4808 }
4750
4809
4751
4810
4752 class RepoReviewRuleUserGroup(Base, BaseModel):
4811 class RepoReviewRuleUserGroup(Base, BaseModel):
4753 __tablename__ = 'repo_review_rules_users_groups'
4812 __tablename__ = 'repo_review_rules_users_groups'
4754 __table_args__ = (
4813 __table_args__ = (
4755 base_table_args
4814 base_table_args
4756 )
4815 )
4757
4816
4758 VOTE_RULE_ALL = -1
4817 VOTE_RULE_ALL = -1
4759
4818
4760 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4819 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4761 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4820 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4762 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4821 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4763 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4822 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4764 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4823 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4765 users_group = relationship('UserGroup')
4824 users_group = relationship('UserGroup')
4766
4825
4767 def rule_data(self):
4826 def rule_data(self):
4768 return {
4827 return {
4769 'mandatory': self.mandatory,
4828 'mandatory': self.mandatory,
4770 'vote_rule': self.vote_rule
4829 'vote_rule': self.vote_rule
4771 }
4830 }
4772
4831
4773 @property
4832 @property
4774 def vote_rule_label(self):
4833 def vote_rule_label(self):
4775 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4834 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4776 return 'all must vote'
4835 return 'all must vote'
4777 else:
4836 else:
4778 return 'min. vote {}'.format(self.vote_rule)
4837 return 'min. vote {}'.format(self.vote_rule)
4779
4838
4780
4839
4781 class RepoReviewRule(Base, BaseModel):
4840 class RepoReviewRule(Base, BaseModel):
4782 __tablename__ = 'repo_review_rules'
4841 __tablename__ = 'repo_review_rules'
4783 __table_args__ = (
4842 __table_args__ = (
4784 base_table_args
4843 base_table_args
4785 )
4844 )
4786
4845
4787 repo_review_rule_id = Column(
4846 repo_review_rule_id = Column(
4788 'repo_review_rule_id', Integer(), primary_key=True)
4847 'repo_review_rule_id', Integer(), primary_key=True)
4789 repo_id = Column(
4848 repo_id = Column(
4790 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4849 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4791 repo = relationship('Repository', backref='review_rules')
4850 repo = relationship('Repository', backref='review_rules')
4792
4851
4793 review_rule_name = Column('review_rule_name', String(255))
4852 review_rule_name = Column('review_rule_name', String(255))
4794 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4853 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4795 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4854 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4796 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4855 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4797
4856
4798 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4857 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4799 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4858 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4800 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4859 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4801 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4860 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4802
4861
4803 rule_users = relationship('RepoReviewRuleUser')
4862 rule_users = relationship('RepoReviewRuleUser')
4804 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4863 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4805
4864
4806 def _validate_pattern(self, value):
4865 def _validate_pattern(self, value):
4807 re.compile('^' + glob2re(value) + '$')
4866 re.compile('^' + glob2re(value) + '$')
4808
4867
4809 @hybrid_property
4868 @hybrid_property
4810 def source_branch_pattern(self):
4869 def source_branch_pattern(self):
4811 return self._branch_pattern or '*'
4870 return self._branch_pattern or '*'
4812
4871
4813 @source_branch_pattern.setter
4872 @source_branch_pattern.setter
4814 def source_branch_pattern(self, value):
4873 def source_branch_pattern(self, value):
4815 self._validate_pattern(value)
4874 self._validate_pattern(value)
4816 self._branch_pattern = value or '*'
4875 self._branch_pattern = value or '*'
4817
4876
4818 @hybrid_property
4877 @hybrid_property
4819 def target_branch_pattern(self):
4878 def target_branch_pattern(self):
4820 return self._target_branch_pattern or '*'
4879 return self._target_branch_pattern or '*'
4821
4880
4822 @target_branch_pattern.setter
4881 @target_branch_pattern.setter
4823 def target_branch_pattern(self, value):
4882 def target_branch_pattern(self, value):
4824 self._validate_pattern(value)
4883 self._validate_pattern(value)
4825 self._target_branch_pattern = value or '*'
4884 self._target_branch_pattern = value or '*'
4826
4885
4827 @hybrid_property
4886 @hybrid_property
4828 def file_pattern(self):
4887 def file_pattern(self):
4829 return self._file_pattern or '*'
4888 return self._file_pattern or '*'
4830
4889
4831 @file_pattern.setter
4890 @file_pattern.setter
4832 def file_pattern(self, value):
4891 def file_pattern(self, value):
4833 self._validate_pattern(value)
4892 self._validate_pattern(value)
4834 self._file_pattern = value or '*'
4893 self._file_pattern = value or '*'
4835
4894
4836 def matches(self, source_branch, target_branch, files_changed):
4895 def matches(self, source_branch, target_branch, files_changed):
4837 """
4896 """
4838 Check if this review rule matches a branch/files in a pull request
4897 Check if this review rule matches a branch/files in a pull request
4839
4898
4840 :param source_branch: source branch name for the commit
4899 :param source_branch: source branch name for the commit
4841 :param target_branch: target branch name for the commit
4900 :param target_branch: target branch name for the commit
4842 :param files_changed: list of file paths changed in the pull request
4901 :param files_changed: list of file paths changed in the pull request
4843 """
4902 """
4844
4903
4845 source_branch = source_branch or ''
4904 source_branch = source_branch or ''
4846 target_branch = target_branch or ''
4905 target_branch = target_branch or ''
4847 files_changed = files_changed or []
4906 files_changed = files_changed or []
4848
4907
4849 branch_matches = True
4908 branch_matches = True
4850 if source_branch or target_branch:
4909 if source_branch or target_branch:
4851 if self.source_branch_pattern == '*':
4910 if self.source_branch_pattern == '*':
4852 source_branch_match = True
4911 source_branch_match = True
4853 else:
4912 else:
4854 if self.source_branch_pattern.startswith('re:'):
4913 if self.source_branch_pattern.startswith('re:'):
4855 source_pattern = self.source_branch_pattern[3:]
4914 source_pattern = self.source_branch_pattern[3:]
4856 else:
4915 else:
4857 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4916 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4858 source_branch_regex = re.compile(source_pattern)
4917 source_branch_regex = re.compile(source_pattern)
4859 source_branch_match = bool(source_branch_regex.search(source_branch))
4918 source_branch_match = bool(source_branch_regex.search(source_branch))
4860 if self.target_branch_pattern == '*':
4919 if self.target_branch_pattern == '*':
4861 target_branch_match = True
4920 target_branch_match = True
4862 else:
4921 else:
4863 if self.target_branch_pattern.startswith('re:'):
4922 if self.target_branch_pattern.startswith('re:'):
4864 target_pattern = self.target_branch_pattern[3:]
4923 target_pattern = self.target_branch_pattern[3:]
4865 else:
4924 else:
4866 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4925 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4867 target_branch_regex = re.compile(target_pattern)
4926 target_branch_regex = re.compile(target_pattern)
4868 target_branch_match = bool(target_branch_regex.search(target_branch))
4927 target_branch_match = bool(target_branch_regex.search(target_branch))
4869
4928
4870 branch_matches = source_branch_match and target_branch_match
4929 branch_matches = source_branch_match and target_branch_match
4871
4930
4872 files_matches = True
4931 files_matches = True
4873 if self.file_pattern != '*':
4932 if self.file_pattern != '*':
4874 files_matches = False
4933 files_matches = False
4875 if self.file_pattern.startswith('re:'):
4934 if self.file_pattern.startswith('re:'):
4876 file_pattern = self.file_pattern[3:]
4935 file_pattern = self.file_pattern[3:]
4877 else:
4936 else:
4878 file_pattern = glob2re(self.file_pattern)
4937 file_pattern = glob2re(self.file_pattern)
4879 file_regex = re.compile(file_pattern)
4938 file_regex = re.compile(file_pattern)
4880 for filename in files_changed:
4939 for filename in files_changed:
4881 if file_regex.search(filename):
4940 if file_regex.search(filename):
4882 files_matches = True
4941 files_matches = True
4883 break
4942 break
4884
4943
4885 return branch_matches and files_matches
4944 return branch_matches and files_matches
4886
4945
4887 @property
4946 @property
4888 def review_users(self):
4947 def review_users(self):
4889 """ Returns the users which this rule applies to """
4948 """ Returns the users which this rule applies to """
4890
4949
4891 users = collections.OrderedDict()
4950 users = collections.OrderedDict()
4892
4951
4893 for rule_user in self.rule_users:
4952 for rule_user in self.rule_users:
4894 if rule_user.user.active:
4953 if rule_user.user.active:
4895 if rule_user.user not in users:
4954 if rule_user.user not in users:
4896 users[rule_user.user.username] = {
4955 users[rule_user.user.username] = {
4897 'user': rule_user.user,
4956 'user': rule_user.user,
4898 'source': 'user',
4957 'source': 'user',
4899 'source_data': {},
4958 'source_data': {},
4900 'data': rule_user.rule_data()
4959 'data': rule_user.rule_data()
4901 }
4960 }
4902
4961
4903 for rule_user_group in self.rule_user_groups:
4962 for rule_user_group in self.rule_user_groups:
4904 source_data = {
4963 source_data = {
4905 'user_group_id': rule_user_group.users_group.users_group_id,
4964 'user_group_id': rule_user_group.users_group.users_group_id,
4906 'name': rule_user_group.users_group.users_group_name,
4965 'name': rule_user_group.users_group.users_group_name,
4907 'members': len(rule_user_group.users_group.members)
4966 'members': len(rule_user_group.users_group.members)
4908 }
4967 }
4909 for member in rule_user_group.users_group.members:
4968 for member in rule_user_group.users_group.members:
4910 if member.user.active:
4969 if member.user.active:
4911 key = member.user.username
4970 key = member.user.username
4912 if key in users:
4971 if key in users:
4913 # skip this member as we have him already
4972 # skip this member as we have him already
4914 # this prevents from override the "first" matched
4973 # this prevents from override the "first" matched
4915 # users with duplicates in multiple groups
4974 # users with duplicates in multiple groups
4916 continue
4975 continue
4917
4976
4918 users[key] = {
4977 users[key] = {
4919 'user': member.user,
4978 'user': member.user,
4920 'source': 'user_group',
4979 'source': 'user_group',
4921 'source_data': source_data,
4980 'source_data': source_data,
4922 'data': rule_user_group.rule_data()
4981 'data': rule_user_group.rule_data()
4923 }
4982 }
4924
4983
4925 return users
4984 return users
4926
4985
4927 def user_group_vote_rule(self, user_id):
4986 def user_group_vote_rule(self, user_id):
4928
4987
4929 rules = []
4988 rules = []
4930 if not self.rule_user_groups:
4989 if not self.rule_user_groups:
4931 return rules
4990 return rules
4932
4991
4933 for user_group in self.rule_user_groups:
4992 for user_group in self.rule_user_groups:
4934 user_group_members = [x.user_id for x in user_group.users_group.members]
4993 user_group_members = [x.user_id for x in user_group.users_group.members]
4935 if user_id in user_group_members:
4994 if user_id in user_group_members:
4936 rules.append(user_group)
4995 rules.append(user_group)
4937 return rules
4996 return rules
4938
4997
4939 def __repr__(self):
4998 def __repr__(self):
4940 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4999 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4941 self.repo_review_rule_id, self.repo)
5000 self.repo_review_rule_id, self.repo)
4942
5001
4943
5002
4944 class ScheduleEntry(Base, BaseModel):
5003 class ScheduleEntry(Base, BaseModel):
4945 __tablename__ = 'schedule_entries'
5004 __tablename__ = 'schedule_entries'
4946 __table_args__ = (
5005 __table_args__ = (
4947 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5006 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4948 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5007 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4949 base_table_args,
5008 base_table_args,
4950 )
5009 )
4951
5010
4952 schedule_types = ['crontab', 'timedelta', 'integer']
5011 schedule_types = ['crontab', 'timedelta', 'integer']
4953 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5012 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4954
5013
4955 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5014 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4956 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5015 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4957 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5016 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4958
5017
4959 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5018 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4960 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5019 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4961
5020
4962 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5021 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4963 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5022 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4964
5023
4965 # task
5024 # task
4966 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5025 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4967 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5026 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4968 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5027 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4969 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5028 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4970
5029
4971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5030 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4972 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5031 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4973
5032
4974 @hybrid_property
5033 @hybrid_property
4975 def schedule_type(self):
5034 def schedule_type(self):
4976 return self._schedule_type
5035 return self._schedule_type
4977
5036
4978 @schedule_type.setter
5037 @schedule_type.setter
4979 def schedule_type(self, val):
5038 def schedule_type(self, val):
4980 if val not in self.schedule_types:
5039 if val not in self.schedule_types:
4981 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5040 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4982 val, self.schedule_type))
5041 val, self.schedule_type))
4983
5042
4984 self._schedule_type = val
5043 self._schedule_type = val
4985
5044
4986 @classmethod
5045 @classmethod
4987 def get_uid(cls, obj):
5046 def get_uid(cls, obj):
4988 args = obj.task_args
5047 args = obj.task_args
4989 kwargs = obj.task_kwargs
5048 kwargs = obj.task_kwargs
4990 if isinstance(args, JsonRaw):
5049 if isinstance(args, JsonRaw):
4991 try:
5050 try:
4992 args = json.loads(args)
5051 args = json.loads(args)
4993 except ValueError:
5052 except ValueError:
4994 args = tuple()
5053 args = tuple()
4995
5054
4996 if isinstance(kwargs, JsonRaw):
5055 if isinstance(kwargs, JsonRaw):
4997 try:
5056 try:
4998 kwargs = json.loads(kwargs)
5057 kwargs = json.loads(kwargs)
4999 except ValueError:
5058 except ValueError:
5000 kwargs = dict()
5059 kwargs = dict()
5001
5060
5002 dot_notation = obj.task_dot_notation
5061 dot_notation = obj.task_dot_notation
5003 val = '.'.join(map(safe_str, [
5062 val = '.'.join(map(safe_str, [
5004 sorted(dot_notation), args, sorted(kwargs.items())]))
5063 sorted(dot_notation), args, sorted(kwargs.items())]))
5005 return hashlib.sha1(val).hexdigest()
5064 return hashlib.sha1(val).hexdigest()
5006
5065
5007 @classmethod
5066 @classmethod
5008 def get_by_schedule_name(cls, schedule_name):
5067 def get_by_schedule_name(cls, schedule_name):
5009 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5068 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5010
5069
5011 @classmethod
5070 @classmethod
5012 def get_by_schedule_id(cls, schedule_id):
5071 def get_by_schedule_id(cls, schedule_id):
5013 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5072 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5014
5073
5015 @property
5074 @property
5016 def task(self):
5075 def task(self):
5017 return self.task_dot_notation
5076 return self.task_dot_notation
5018
5077
5019 @property
5078 @property
5020 def schedule(self):
5079 def schedule(self):
5021 from rhodecode.lib.celerylib.utils import raw_2_schedule
5080 from rhodecode.lib.celerylib.utils import raw_2_schedule
5022 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5081 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5023 return schedule
5082 return schedule
5024
5083
5025 @property
5084 @property
5026 def args(self):
5085 def args(self):
5027 try:
5086 try:
5028 return list(self.task_args or [])
5087 return list(self.task_args or [])
5029 except ValueError:
5088 except ValueError:
5030 return list()
5089 return list()
5031
5090
5032 @property
5091 @property
5033 def kwargs(self):
5092 def kwargs(self):
5034 try:
5093 try:
5035 return dict(self.task_kwargs or {})
5094 return dict(self.task_kwargs or {})
5036 except ValueError:
5095 except ValueError:
5037 return dict()
5096 return dict()
5038
5097
5039 def _as_raw(self, val):
5098 def _as_raw(self, val):
5040 if hasattr(val, 'de_coerce'):
5099 if hasattr(val, 'de_coerce'):
5041 val = val.de_coerce()
5100 val = val.de_coerce()
5042 if val:
5101 if val:
5043 val = json.dumps(val)
5102 val = json.dumps(val)
5044
5103
5045 return val
5104 return val
5046
5105
5047 @property
5106 @property
5048 def schedule_definition_raw(self):
5107 def schedule_definition_raw(self):
5049 return self._as_raw(self.schedule_definition)
5108 return self._as_raw(self.schedule_definition)
5050
5109
5051 @property
5110 @property
5052 def args_raw(self):
5111 def args_raw(self):
5053 return self._as_raw(self.task_args)
5112 return self._as_raw(self.task_args)
5054
5113
5055 @property
5114 @property
5056 def kwargs_raw(self):
5115 def kwargs_raw(self):
5057 return self._as_raw(self.task_kwargs)
5116 return self._as_raw(self.task_kwargs)
5058
5117
5059 def __repr__(self):
5118 def __repr__(self):
5060 return '<DB:ScheduleEntry({}:{})>'.format(
5119 return '<DB:ScheduleEntry({}:{})>'.format(
5061 self.schedule_entry_id, self.schedule_name)
5120 self.schedule_entry_id, self.schedule_name)
5062
5121
5063
5122
5064 @event.listens_for(ScheduleEntry, 'before_update')
5123 @event.listens_for(ScheduleEntry, 'before_update')
5065 def update_task_uid(mapper, connection, target):
5124 def update_task_uid(mapper, connection, target):
5066 target.task_uid = ScheduleEntry.get_uid(target)
5125 target.task_uid = ScheduleEntry.get_uid(target)
5067
5126
5068
5127
5069 @event.listens_for(ScheduleEntry, 'before_insert')
5128 @event.listens_for(ScheduleEntry, 'before_insert')
5070 def set_task_uid(mapper, connection, target):
5129 def set_task_uid(mapper, connection, target):
5071 target.task_uid = ScheduleEntry.get_uid(target)
5130 target.task_uid = ScheduleEntry.get_uid(target)
5072
5131
5073
5132
5074 class _BaseBranchPerms(BaseModel):
5133 class _BaseBranchPerms(BaseModel):
5075 @classmethod
5134 @classmethod
5076 def compute_hash(cls, value):
5135 def compute_hash(cls, value):
5077 return sha1_safe(value)
5136 return sha1_safe(value)
5078
5137
5079 @hybrid_property
5138 @hybrid_property
5080 def branch_pattern(self):
5139 def branch_pattern(self):
5081 return self._branch_pattern or '*'
5140 return self._branch_pattern or '*'
5082
5141
5083 @hybrid_property
5142 @hybrid_property
5084 def branch_hash(self):
5143 def branch_hash(self):
5085 return self._branch_hash
5144 return self._branch_hash
5086
5145
5087 def _validate_glob(self, value):
5146 def _validate_glob(self, value):
5088 re.compile('^' + glob2re(value) + '$')
5147 re.compile('^' + glob2re(value) + '$')
5089
5148
5090 @branch_pattern.setter
5149 @branch_pattern.setter
5091 def branch_pattern(self, value):
5150 def branch_pattern(self, value):
5092 self._validate_glob(value)
5151 self._validate_glob(value)
5093 self._branch_pattern = value or '*'
5152 self._branch_pattern = value or '*'
5094 # set the Hash when setting the branch pattern
5153 # set the Hash when setting the branch pattern
5095 self._branch_hash = self.compute_hash(self._branch_pattern)
5154 self._branch_hash = self.compute_hash(self._branch_pattern)
5096
5155
5097 def matches(self, branch):
5156 def matches(self, branch):
5098 """
5157 """
5099 Check if this the branch matches entry
5158 Check if this the branch matches entry
5100
5159
5101 :param branch: branch name for the commit
5160 :param branch: branch name for the commit
5102 """
5161 """
5103
5162
5104 branch = branch or ''
5163 branch = branch or ''
5105
5164
5106 branch_matches = True
5165 branch_matches = True
5107 if branch:
5166 if branch:
5108 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5167 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5109 branch_matches = bool(branch_regex.search(branch))
5168 branch_matches = bool(branch_regex.search(branch))
5110
5169
5111 return branch_matches
5170 return branch_matches
5112
5171
5113
5172
5114 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5173 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5115 __tablename__ = 'user_to_repo_branch_permissions'
5174 __tablename__ = 'user_to_repo_branch_permissions'
5116 __table_args__ = (
5175 __table_args__ = (
5117 base_table_args
5176 base_table_args
5118 )
5177 )
5119
5178
5120 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5179 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5121
5180
5122 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5181 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5123 repo = relationship('Repository', backref='user_branch_perms')
5182 repo = relationship('Repository', backref='user_branch_perms')
5124
5183
5125 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5184 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5126 permission = relationship('Permission')
5185 permission = relationship('Permission')
5127
5186
5128 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5187 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5129 user_repo_to_perm = relationship('UserRepoToPerm')
5188 user_repo_to_perm = relationship('UserRepoToPerm')
5130
5189
5131 rule_order = Column('rule_order', Integer(), nullable=False)
5190 rule_order = Column('rule_order', Integer(), nullable=False)
5132 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5191 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5133 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5192 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5134
5193
5135 def __unicode__(self):
5194 def __unicode__(self):
5136 return u'<UserBranchPermission(%s => %r)>' % (
5195 return u'<UserBranchPermission(%s => %r)>' % (
5137 self.user_repo_to_perm, self.branch_pattern)
5196 self.user_repo_to_perm, self.branch_pattern)
5138
5197
5139
5198
5140 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5199 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5141 __tablename__ = 'user_group_to_repo_branch_permissions'
5200 __tablename__ = 'user_group_to_repo_branch_permissions'
5142 __table_args__ = (
5201 __table_args__ = (
5143 base_table_args
5202 base_table_args
5144 )
5203 )
5145
5204
5146 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5205 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5147
5206
5148 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5207 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5149 repo = relationship('Repository', backref='user_group_branch_perms')
5208 repo = relationship('Repository', backref='user_group_branch_perms')
5150
5209
5151 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5210 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5152 permission = relationship('Permission')
5211 permission = relationship('Permission')
5153
5212
5154 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5213 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5155 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5214 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5156
5215
5157 rule_order = Column('rule_order', Integer(), nullable=False)
5216 rule_order = Column('rule_order', Integer(), nullable=False)
5158 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5217 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5159 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5218 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5160
5219
5161 def __unicode__(self):
5220 def __unicode__(self):
5162 return u'<UserBranchPermission(%s => %r)>' % (
5221 return u'<UserBranchPermission(%s => %r)>' % (
5163 self.user_group_repo_to_perm, self.branch_pattern)
5222 self.user_group_repo_to_perm, self.branch_pattern)
5164
5223
5165
5224
5166 class UserBookmark(Base, BaseModel):
5225 class UserBookmark(Base, BaseModel):
5167 __tablename__ = 'user_bookmarks'
5226 __tablename__ = 'user_bookmarks'
5168 __table_args__ = (
5227 __table_args__ = (
5169 UniqueConstraint('user_id', 'bookmark_repo_id'),
5228 UniqueConstraint('user_id', 'bookmark_repo_id'),
5170 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5229 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5171 UniqueConstraint('user_id', 'bookmark_position'),
5230 UniqueConstraint('user_id', 'bookmark_position'),
5172 base_table_args
5231 base_table_args
5173 )
5232 )
5174
5233
5175 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5234 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5176 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5235 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5177 position = Column("bookmark_position", Integer(), nullable=False)
5236 position = Column("bookmark_position", Integer(), nullable=False)
5178 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5237 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5179 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5238 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5180 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5239 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5181
5240
5182 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5241 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5183 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5242 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5184
5243
5185 user = relationship("User")
5244 user = relationship("User")
5186
5245
5187 repository = relationship("Repository")
5246 repository = relationship("Repository")
5188 repository_group = relationship("RepoGroup")
5247 repository_group = relationship("RepoGroup")
5189
5248
5190 @classmethod
5249 @classmethod
5191 def get_by_position_for_user(cls, position, user_id):
5250 def get_by_position_for_user(cls, position, user_id):
5192 return cls.query() \
5251 return cls.query() \
5193 .filter(UserBookmark.user_id == user_id) \
5252 .filter(UserBookmark.user_id == user_id) \
5194 .filter(UserBookmark.position == position).scalar()
5253 .filter(UserBookmark.position == position).scalar()
5195
5254
5196 @classmethod
5255 @classmethod
5197 def get_bookmarks_for_user(cls, user_id, cache=True):
5256 def get_bookmarks_for_user(cls, user_id, cache=True):
5198 bookmarks = cls.query() \
5257 bookmarks = cls.query() \
5199 .filter(UserBookmark.user_id == user_id) \
5258 .filter(UserBookmark.user_id == user_id) \
5200 .options(joinedload(UserBookmark.repository)) \
5259 .options(joinedload(UserBookmark.repository)) \
5201 .options(joinedload(UserBookmark.repository_group)) \
5260 .options(joinedload(UserBookmark.repository_group)) \
5202 .order_by(UserBookmark.position.asc())
5261 .order_by(UserBookmark.position.asc())
5203
5262
5204 if cache:
5263 if cache:
5205 bookmarks = bookmarks.options(
5264 bookmarks = bookmarks.options(
5206 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5265 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5207 )
5266 )
5208
5267
5209 return bookmarks.all()
5268 return bookmarks.all()
5210
5269
5211 def __unicode__(self):
5270 def __unicode__(self):
5212 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5271 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5213
5272
5214
5273
5215 class FileStore(Base, BaseModel):
5274 class FileStore(Base, BaseModel):
5216 __tablename__ = 'file_store'
5275 __tablename__ = 'file_store'
5217 __table_args__ = (
5276 __table_args__ = (
5218 base_table_args
5277 base_table_args
5219 )
5278 )
5220
5279
5221 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5280 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5222 file_uid = Column('file_uid', String(1024), nullable=False)
5281 file_uid = Column('file_uid', String(1024), nullable=False)
5223 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5282 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5224 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5283 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5225 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5284 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5226
5285
5227 # sha256 hash
5286 # sha256 hash
5228 file_hash = Column('file_hash', String(512), nullable=False)
5287 file_hash = Column('file_hash', String(512), nullable=False)
5229 file_size = Column('file_size', BigInteger(), nullable=False)
5288 file_size = Column('file_size', BigInteger(), nullable=False)
5230
5289
5231 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5290 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5232 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5291 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5233 accessed_count = Column('accessed_count', Integer(), default=0)
5292 accessed_count = Column('accessed_count', Integer(), default=0)
5234
5293
5235 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5294 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5236
5295
5237 # if repo/repo_group reference is set, check for permissions
5296 # if repo/repo_group reference is set, check for permissions
5238 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5297 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5239
5298
5240 # hidden defines an attachment that should be hidden from showing in artifact listing
5299 # hidden defines an attachment that should be hidden from showing in artifact listing
5241 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5300 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5242
5301
5243 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5302 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5244 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5303 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5245
5304
5246 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5305 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5247
5306
5248 # scope limited to user, which requester have access to
5307 # scope limited to user, which requester have access to
5249 scope_user_id = Column(
5308 scope_user_id = Column(
5250 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5309 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5251 nullable=True, unique=None, default=None)
5310 nullable=True, unique=None, default=None)
5252 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5311 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5253
5312
5254 # scope limited to user group, which requester have access to
5313 # scope limited to user group, which requester have access to
5255 scope_user_group_id = Column(
5314 scope_user_group_id = Column(
5256 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5315 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5257 nullable=True, unique=None, default=None)
5316 nullable=True, unique=None, default=None)
5258 user_group = relationship('UserGroup', lazy='joined')
5317 user_group = relationship('UserGroup', lazy='joined')
5259
5318
5260 # scope limited to repo, which requester have access to
5319 # scope limited to repo, which requester have access to
5261 scope_repo_id = Column(
5320 scope_repo_id = Column(
5262 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5321 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5263 nullable=True, unique=None, default=None)
5322 nullable=True, unique=None, default=None)
5264 repo = relationship('Repository', lazy='joined')
5323 repo = relationship('Repository', lazy='joined')
5265
5324
5266 # scope limited to repo group, which requester have access to
5325 # scope limited to repo group, which requester have access to
5267 scope_repo_group_id = Column(
5326 scope_repo_group_id = Column(
5268 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5327 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5269 nullable=True, unique=None, default=None)
5328 nullable=True, unique=None, default=None)
5270 repo_group = relationship('RepoGroup', lazy='joined')
5329 repo_group = relationship('RepoGroup', lazy='joined')
5271
5330
5272 @classmethod
5331 @classmethod
5273 def get_by_store_uid(cls, file_store_uid):
5332 def get_by_store_uid(cls, file_store_uid):
5274 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5333 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5275
5334
5276 @classmethod
5335 @classmethod
5277 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5336 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5278 file_description='', enabled=True, hidden=False, check_acl=True,
5337 file_description='', enabled=True, hidden=False, check_acl=True,
5279 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5338 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5280
5339
5281 store_entry = FileStore()
5340 store_entry = FileStore()
5282 store_entry.file_uid = file_uid
5341 store_entry.file_uid = file_uid
5283 store_entry.file_display_name = file_display_name
5342 store_entry.file_display_name = file_display_name
5284 store_entry.file_org_name = filename
5343 store_entry.file_org_name = filename
5285 store_entry.file_size = file_size
5344 store_entry.file_size = file_size
5286 store_entry.file_hash = file_hash
5345 store_entry.file_hash = file_hash
5287 store_entry.file_description = file_description
5346 store_entry.file_description = file_description
5288
5347
5289 store_entry.check_acl = check_acl
5348 store_entry.check_acl = check_acl
5290 store_entry.enabled = enabled
5349 store_entry.enabled = enabled
5291 store_entry.hidden = hidden
5350 store_entry.hidden = hidden
5292
5351
5293 store_entry.user_id = user_id
5352 store_entry.user_id = user_id
5294 store_entry.scope_user_id = scope_user_id
5353 store_entry.scope_user_id = scope_user_id
5295 store_entry.scope_repo_id = scope_repo_id
5354 store_entry.scope_repo_id = scope_repo_id
5296 store_entry.scope_repo_group_id = scope_repo_group_id
5355 store_entry.scope_repo_group_id = scope_repo_group_id
5297
5356
5298 return store_entry
5357 return store_entry
5299
5358
5300 @classmethod
5359 @classmethod
5301 def store_metadata(cls, file_store_id, args, commit=True):
5360 def store_metadata(cls, file_store_id, args, commit=True):
5302 file_store = FileStore.get(file_store_id)
5361 file_store = FileStore.get(file_store_id)
5303 if file_store is None:
5362 if file_store is None:
5304 return
5363 return
5305
5364
5306 for section, key, value, value_type in args:
5365 for section, key, value, value_type in args:
5307 has_key = FileStoreMetadata().query() \
5366 has_key = FileStoreMetadata().query() \
5308 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5367 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5309 .filter(FileStoreMetadata.file_store_meta_section == section) \
5368 .filter(FileStoreMetadata.file_store_meta_section == section) \
5310 .filter(FileStoreMetadata.file_store_meta_key == key) \
5369 .filter(FileStoreMetadata.file_store_meta_key == key) \
5311 .scalar()
5370 .scalar()
5312 if has_key:
5371 if has_key:
5313 msg = 'key `{}` already defined under section `{}` for this file.'\
5372 msg = 'key `{}` already defined under section `{}` for this file.'\
5314 .format(key, section)
5373 .format(key, section)
5315 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5374 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5316
5375
5317 # NOTE(marcink): raises ArtifactMetadataBadValueType
5376 # NOTE(marcink): raises ArtifactMetadataBadValueType
5318 FileStoreMetadata.valid_value_type(value_type)
5377 FileStoreMetadata.valid_value_type(value_type)
5319
5378
5320 meta_entry = FileStoreMetadata()
5379 meta_entry = FileStoreMetadata()
5321 meta_entry.file_store = file_store
5380 meta_entry.file_store = file_store
5322 meta_entry.file_store_meta_section = section
5381 meta_entry.file_store_meta_section = section
5323 meta_entry.file_store_meta_key = key
5382 meta_entry.file_store_meta_key = key
5324 meta_entry.file_store_meta_value_type = value_type
5383 meta_entry.file_store_meta_value_type = value_type
5325 meta_entry.file_store_meta_value = value
5384 meta_entry.file_store_meta_value = value
5326
5385
5327 Session().add(meta_entry)
5386 Session().add(meta_entry)
5328
5387
5329 try:
5388 try:
5330 if commit:
5389 if commit:
5331 Session().commit()
5390 Session().commit()
5332 except IntegrityError:
5391 except IntegrityError:
5333 Session().rollback()
5392 Session().rollback()
5334 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5393 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5335
5394
5336 @classmethod
5395 @classmethod
5337 def bump_access_counter(cls, file_uid, commit=True):
5396 def bump_access_counter(cls, file_uid, commit=True):
5338 FileStore().query()\
5397 FileStore().query()\
5339 .filter(FileStore.file_uid == file_uid)\
5398 .filter(FileStore.file_uid == file_uid)\
5340 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5399 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5341 FileStore.accessed_on: datetime.datetime.now()})
5400 FileStore.accessed_on: datetime.datetime.now()})
5342 if commit:
5401 if commit:
5343 Session().commit()
5402 Session().commit()
5344
5403
5345 def __json__(self):
5404 def __json__(self):
5346 data = {
5405 data = {
5347 'filename': self.file_display_name,
5406 'filename': self.file_display_name,
5348 'filename_org': self.file_org_name,
5407 'filename_org': self.file_org_name,
5349 'file_uid': self.file_uid,
5408 'file_uid': self.file_uid,
5350 'description': self.file_description,
5409 'description': self.file_description,
5351 'hidden': self.hidden,
5410 'hidden': self.hidden,
5352 'size': self.file_size,
5411 'size': self.file_size,
5353 'created_on': self.created_on,
5412 'created_on': self.created_on,
5354 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5413 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5355 'downloaded_times': self.accessed_count,
5414 'downloaded_times': self.accessed_count,
5356 'sha256': self.file_hash,
5415 'sha256': self.file_hash,
5357 'metadata': self.file_metadata,
5416 'metadata': self.file_metadata,
5358 }
5417 }
5359
5418
5360 return data
5419 return data
5361
5420
5362 def __repr__(self):
5421 def __repr__(self):
5363 return '<FileStore({})>'.format(self.file_store_id)
5422 return '<FileStore({})>'.format(self.file_store_id)
5364
5423
5365
5424
5366 class FileStoreMetadata(Base, BaseModel):
5425 class FileStoreMetadata(Base, BaseModel):
5367 __tablename__ = 'file_store_metadata'
5426 __tablename__ = 'file_store_metadata'
5368 __table_args__ = (
5427 __table_args__ = (
5369 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5428 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5370 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5429 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5371 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5430 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5372 base_table_args
5431 base_table_args
5373 )
5432 )
5374 SETTINGS_TYPES = {
5433 SETTINGS_TYPES = {
5375 'str': safe_str,
5434 'str': safe_str,
5376 'int': safe_int,
5435 'int': safe_int,
5377 'unicode': safe_unicode,
5436 'unicode': safe_unicode,
5378 'bool': str2bool,
5437 'bool': str2bool,
5379 'list': functools.partial(aslist, sep=',')
5438 'list': functools.partial(aslist, sep=',')
5380 }
5439 }
5381
5440
5382 file_store_meta_id = Column(
5441 file_store_meta_id = Column(
5383 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5442 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5384 primary_key=True)
5443 primary_key=True)
5385 _file_store_meta_section = Column(
5444 _file_store_meta_section = Column(
5386 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5445 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5387 nullable=True, unique=None, default=None)
5446 nullable=True, unique=None, default=None)
5388 _file_store_meta_section_hash = Column(
5447 _file_store_meta_section_hash = Column(
5389 "file_store_meta_section_hash", String(255),
5448 "file_store_meta_section_hash", String(255),
5390 nullable=True, unique=None, default=None)
5449 nullable=True, unique=None, default=None)
5391 _file_store_meta_key = Column(
5450 _file_store_meta_key = Column(
5392 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5451 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5393 nullable=True, unique=None, default=None)
5452 nullable=True, unique=None, default=None)
5394 _file_store_meta_key_hash = Column(
5453 _file_store_meta_key_hash = Column(
5395 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5454 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5396 _file_store_meta_value = Column(
5455 _file_store_meta_value = Column(
5397 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5456 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5398 nullable=True, unique=None, default=None)
5457 nullable=True, unique=None, default=None)
5399 _file_store_meta_value_type = Column(
5458 _file_store_meta_value_type = Column(
5400 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5459 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5401 default='unicode')
5460 default='unicode')
5402
5461
5403 file_store_id = Column(
5462 file_store_id = Column(
5404 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5463 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5405 nullable=True, unique=None, default=None)
5464 nullable=True, unique=None, default=None)
5406
5465
5407 file_store = relationship('FileStore', lazy='joined')
5466 file_store = relationship('FileStore', lazy='joined')
5408
5467
5409 @classmethod
5468 @classmethod
5410 def valid_value_type(cls, value):
5469 def valid_value_type(cls, value):
5411 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5470 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5412 raise ArtifactMetadataBadValueType(
5471 raise ArtifactMetadataBadValueType(
5413 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5472 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5414
5473
5415 @hybrid_property
5474 @hybrid_property
5416 def file_store_meta_section(self):
5475 def file_store_meta_section(self):
5417 return self._file_store_meta_section
5476 return self._file_store_meta_section
5418
5477
5419 @file_store_meta_section.setter
5478 @file_store_meta_section.setter
5420 def file_store_meta_section(self, value):
5479 def file_store_meta_section(self, value):
5421 self._file_store_meta_section = value
5480 self._file_store_meta_section = value
5422 self._file_store_meta_section_hash = _hash_key(value)
5481 self._file_store_meta_section_hash = _hash_key(value)
5423
5482
5424 @hybrid_property
5483 @hybrid_property
5425 def file_store_meta_key(self):
5484 def file_store_meta_key(self):
5426 return self._file_store_meta_key
5485 return self._file_store_meta_key
5427
5486
5428 @file_store_meta_key.setter
5487 @file_store_meta_key.setter
5429 def file_store_meta_key(self, value):
5488 def file_store_meta_key(self, value):
5430 self._file_store_meta_key = value
5489 self._file_store_meta_key = value
5431 self._file_store_meta_key_hash = _hash_key(value)
5490 self._file_store_meta_key_hash = _hash_key(value)
5432
5491
5433 @hybrid_property
5492 @hybrid_property
5434 def file_store_meta_value(self):
5493 def file_store_meta_value(self):
5435 val = self._file_store_meta_value
5494 val = self._file_store_meta_value
5436
5495
5437 if self._file_store_meta_value_type:
5496 if self._file_store_meta_value_type:
5438 # e.g unicode.encrypted == unicode
5497 # e.g unicode.encrypted == unicode
5439 _type = self._file_store_meta_value_type.split('.')[0]
5498 _type = self._file_store_meta_value_type.split('.')[0]
5440 # decode the encrypted value if it's encrypted field type
5499 # decode the encrypted value if it's encrypted field type
5441 if '.encrypted' in self._file_store_meta_value_type:
5500 if '.encrypted' in self._file_store_meta_value_type:
5442 cipher = EncryptedTextValue()
5501 cipher = EncryptedTextValue()
5443 val = safe_unicode(cipher.process_result_value(val, None))
5502 val = safe_unicode(cipher.process_result_value(val, None))
5444 # do final type conversion
5503 # do final type conversion
5445 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5504 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5446 val = converter(val)
5505 val = converter(val)
5447
5506
5448 return val
5507 return val
5449
5508
5450 @file_store_meta_value.setter
5509 @file_store_meta_value.setter
5451 def file_store_meta_value(self, val):
5510 def file_store_meta_value(self, val):
5452 val = safe_unicode(val)
5511 val = safe_unicode(val)
5453 # encode the encrypted value
5512 # encode the encrypted value
5454 if '.encrypted' in self.file_store_meta_value_type:
5513 if '.encrypted' in self.file_store_meta_value_type:
5455 cipher = EncryptedTextValue()
5514 cipher = EncryptedTextValue()
5456 val = safe_unicode(cipher.process_bind_param(val, None))
5515 val = safe_unicode(cipher.process_bind_param(val, None))
5457 self._file_store_meta_value = val
5516 self._file_store_meta_value = val
5458
5517
5459 @hybrid_property
5518 @hybrid_property
5460 def file_store_meta_value_type(self):
5519 def file_store_meta_value_type(self):
5461 return self._file_store_meta_value_type
5520 return self._file_store_meta_value_type
5462
5521
5463 @file_store_meta_value_type.setter
5522 @file_store_meta_value_type.setter
5464 def file_store_meta_value_type(self, val):
5523 def file_store_meta_value_type(self, val):
5465 # e.g unicode.encrypted
5524 # e.g unicode.encrypted
5466 self.valid_value_type(val)
5525 self.valid_value_type(val)
5467 self._file_store_meta_value_type = val
5526 self._file_store_meta_value_type = val
5468
5527
5469 def __json__(self):
5528 def __json__(self):
5470 data = {
5529 data = {
5471 'artifact': self.file_store.file_uid,
5530 'artifact': self.file_store.file_uid,
5472 'section': self.file_store_meta_section,
5531 'section': self.file_store_meta_section,
5473 'key': self.file_store_meta_key,
5532 'key': self.file_store_meta_key,
5474 'value': self.file_store_meta_value,
5533 'value': self.file_store_meta_value,
5475 }
5534 }
5476
5535
5477 return data
5536 return data
5478
5537
5479 def __repr__(self):
5538 def __repr__(self):
5480 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5539 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5481 self.file_store_meta_key, self.file_store_meta_value)
5540 self.file_store_meta_key, self.file_store_meta_value)
5482
5541
5483
5542
5484 class DbMigrateVersion(Base, BaseModel):
5543 class DbMigrateVersion(Base, BaseModel):
5485 __tablename__ = 'db_migrate_version'
5544 __tablename__ = 'db_migrate_version'
5486 __table_args__ = (
5545 __table_args__ = (
5487 base_table_args,
5546 base_table_args,
5488 )
5547 )
5489
5548
5490 repository_id = Column('repository_id', String(250), primary_key=True)
5549 repository_id = Column('repository_id', String(250), primary_key=True)
5491 repository_path = Column('repository_path', Text)
5550 repository_path = Column('repository_path', Text)
5492 version = Column('version', Integer)
5551 version = Column('version', Integer)
5493
5552
5494 @classmethod
5553 @classmethod
5495 def set_version(cls, version):
5554 def set_version(cls, version):
5496 """
5555 """
5497 Helper for forcing a different version, usually for debugging purposes via ishell.
5556 Helper for forcing a different version, usually for debugging purposes via ishell.
5498 """
5557 """
5499 ver = DbMigrateVersion.query().first()
5558 ver = DbMigrateVersion.query().first()
5500 ver.version = version
5559 ver.version = version
5501 Session().commit()
5560 Session().commit()
5502
5561
5503
5562
5504 class DbSession(Base, BaseModel):
5563 class DbSession(Base, BaseModel):
5505 __tablename__ = 'db_session'
5564 __tablename__ = 'db_session'
5506 __table_args__ = (
5565 __table_args__ = (
5507 base_table_args,
5566 base_table_args,
5508 )
5567 )
5509
5568
5510 def __repr__(self):
5569 def __repr__(self):
5511 return '<DB:DbSession({})>'.format(self.id)
5570 return '<DB:DbSession({})>'.format(self.id)
5512
5571
5513 id = Column('id', Integer())
5572 id = Column('id', Integer())
5514 namespace = Column('namespace', String(255), primary_key=True)
5573 namespace = Column('namespace', String(255), primary_key=True)
5515 accessed = Column('accessed', DateTime, nullable=False)
5574 accessed = Column('accessed', DateTime, nullable=False)
5516 created = Column('created', DateTime, nullable=False)
5575 created = Column('created', DateTime, nullable=False)
5517 data = Column('data', PickleType, nullable=False)
5576 data = Column('data', PickleType, nullable=False)
@@ -1,3048 +1,3054 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29 @import 'tooltips';
29 @import 'tooltips';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-size: 120%;
38 font-size: 120%;
39 color: white;
39 color: white;
40 background-color: @alert2;
40 background-color: @alert2;
41 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
42 font-weight: @text-semibold-weight;
42 font-weight: @text-semibold-weight;
43 font-family: @text-semibold;
43 font-family: @text-semibold;
44 }
44 }
45
45
46 html {
46 html {
47 display: table;
47 display: table;
48 height: 100%;
48 height: 100%;
49 width: 100%;
49 width: 100%;
50 }
50 }
51
51
52 body {
52 body {
53 display: table-cell;
53 display: table-cell;
54 width: 100%;
54 width: 100%;
55 }
55 }
56
56
57 //--- LAYOUT ------------------//
57 //--- LAYOUT ------------------//
58
58
59 .hidden{
59 .hidden{
60 display: none !important;
60 display: none !important;
61 }
61 }
62
62
63 .box{
63 .box{
64 float: left;
64 float: left;
65 width: 100%;
65 width: 100%;
66 }
66 }
67
67
68 .browser-header {
68 .browser-header {
69 clear: both;
69 clear: both;
70 }
70 }
71 .main {
71 .main {
72 clear: both;
72 clear: both;
73 padding:0 0 @pagepadding;
73 padding:0 0 @pagepadding;
74 height: auto;
74 height: auto;
75
75
76 &:after { //clearfix
76 &:after { //clearfix
77 content:"";
77 content:"";
78 clear:both;
78 clear:both;
79 width:100%;
79 width:100%;
80 display:block;
80 display:block;
81 }
81 }
82 }
82 }
83
83
84 .action-link{
84 .action-link{
85 margin-left: @padding;
85 margin-left: @padding;
86 padding-left: @padding;
86 padding-left: @padding;
87 border-left: @border-thickness solid @border-default-color;
87 border-left: @border-thickness solid @border-default-color;
88 }
88 }
89
89
90 .cursor-pointer {
90 .cursor-pointer {
91 cursor: pointer;
91 cursor: pointer;
92 }
92 }
93
93
94 input + .action-link, .action-link.first{
94 input + .action-link, .action-link.first{
95 border-left: none;
95 border-left: none;
96 }
96 }
97
97
98 .action-link.last{
98 .action-link.last{
99 margin-right: @padding;
99 margin-right: @padding;
100 padding-right: @padding;
100 padding-right: @padding;
101 }
101 }
102
102
103 .action-link.active,
103 .action-link.active,
104 .action-link.active a{
104 .action-link.active a{
105 color: @grey4;
105 color: @grey4;
106 }
106 }
107
107
108 .action-link.disabled {
108 .action-link.disabled {
109 color: @grey4;
109 color: @grey4;
110 cursor: inherit;
110 cursor: inherit;
111 }
111 }
112
112
113
113
114 .clipboard-action {
114 .clipboard-action {
115 cursor: pointer;
115 cursor: pointer;
116 margin-left: 5px;
116 margin-left: 5px;
117
117
118 &:not(.no-grey) {
118 &:not(.no-grey) {
119
119
120 &:hover {
120 &:hover {
121 color: @grey2;
121 color: @grey2;
122 }
122 }
123 color: @grey4;
123 color: @grey4;
124 }
124 }
125 }
125 }
126
126
127 ul.simple-list{
127 ul.simple-list{
128 list-style: none;
128 list-style: none;
129 margin: 0;
129 margin: 0;
130 padding: 0;
130 padding: 0;
131 }
131 }
132
132
133 .main-content {
133 .main-content {
134 padding-bottom: @pagepadding;
134 padding-bottom: @pagepadding;
135 }
135 }
136
136
137 .wide-mode-wrapper {
137 .wide-mode-wrapper {
138 max-width:4000px !important;
138 max-width:4000px !important;
139 }
139 }
140
140
141 .wrapper {
141 .wrapper {
142 position: relative;
142 position: relative;
143 max-width: @wrapper-maxwidth;
143 max-width: @wrapper-maxwidth;
144 margin: 0 auto;
144 margin: 0 auto;
145 }
145 }
146
146
147 #content {
147 #content {
148 clear: both;
148 clear: both;
149 padding: 0 @contentpadding;
149 padding: 0 @contentpadding;
150 }
150 }
151
151
152 .advanced-settings-fields{
152 .advanced-settings-fields{
153 input{
153 input{
154 margin-left: @textmargin;
154 margin-left: @textmargin;
155 margin-right: @padding/2;
155 margin-right: @padding/2;
156 }
156 }
157 }
157 }
158
158
159 .cs_files_title {
159 .cs_files_title {
160 margin: @pagepadding 0 0;
160 margin: @pagepadding 0 0;
161 }
161 }
162
162
163 input.inline[type="file"] {
163 input.inline[type="file"] {
164 display: inline;
164 display: inline;
165 }
165 }
166
166
167 .error_page {
167 .error_page {
168 margin: 10% auto;
168 margin: 10% auto;
169
169
170 h1 {
170 h1 {
171 color: @grey2;
171 color: @grey2;
172 }
172 }
173
173
174 .alert {
174 .alert {
175 margin: @padding 0;
175 margin: @padding 0;
176 }
176 }
177
177
178 .error-branding {
178 .error-branding {
179 color: @grey4;
179 color: @grey4;
180 font-weight: @text-semibold-weight;
180 font-weight: @text-semibold-weight;
181 font-family: @text-semibold;
181 font-family: @text-semibold;
182 }
182 }
183
183
184 .error_message {
184 .error_message {
185 font-family: @text-regular;
185 font-family: @text-regular;
186 }
186 }
187
187
188 .sidebar {
188 .sidebar {
189 min-height: 275px;
189 min-height: 275px;
190 margin: 0;
190 margin: 0;
191 padding: 0 0 @sidebarpadding @sidebarpadding;
191 padding: 0 0 @sidebarpadding @sidebarpadding;
192 border: none;
192 border: none;
193 }
193 }
194
194
195 .main-content {
195 .main-content {
196 position: relative;
196 position: relative;
197 margin: 0 @sidebarpadding @sidebarpadding;
197 margin: 0 @sidebarpadding @sidebarpadding;
198 padding: 0 0 0 @sidebarpadding;
198 padding: 0 0 0 @sidebarpadding;
199 border-left: @border-thickness solid @grey5;
199 border-left: @border-thickness solid @grey5;
200
200
201 @media (max-width:767px) {
201 @media (max-width:767px) {
202 clear: both;
202 clear: both;
203 width: 100%;
203 width: 100%;
204 margin: 0;
204 margin: 0;
205 border: none;
205 border: none;
206 }
206 }
207 }
207 }
208
208
209 .inner-column {
209 .inner-column {
210 float: left;
210 float: left;
211 width: 29.75%;
211 width: 29.75%;
212 min-height: 150px;
212 min-height: 150px;
213 margin: @sidebarpadding 2% 0 0;
213 margin: @sidebarpadding 2% 0 0;
214 padding: 0 2% 0 0;
214 padding: 0 2% 0 0;
215 border-right: @border-thickness solid @grey5;
215 border-right: @border-thickness solid @grey5;
216
216
217 @media (max-width:767px) {
217 @media (max-width:767px) {
218 clear: both;
218 clear: both;
219 width: 100%;
219 width: 100%;
220 border: none;
220 border: none;
221 }
221 }
222
222
223 ul {
223 ul {
224 padding-left: 1.25em;
224 padding-left: 1.25em;
225 }
225 }
226
226
227 &:last-child {
227 &:last-child {
228 margin: @sidebarpadding 0 0;
228 margin: @sidebarpadding 0 0;
229 border: none;
229 border: none;
230 }
230 }
231
231
232 h4 {
232 h4 {
233 margin: 0 0 @padding;
233 margin: 0 0 @padding;
234 font-weight: @text-semibold-weight;
234 font-weight: @text-semibold-weight;
235 font-family: @text-semibold;
235 font-family: @text-semibold;
236 }
236 }
237 }
237 }
238 }
238 }
239 .error-page-logo {
239 .error-page-logo {
240 width: 130px;
240 width: 130px;
241 height: 160px;
241 height: 160px;
242 }
242 }
243
243
244 // HEADER
244 // HEADER
245 .header {
245 .header {
246
246
247 // TODO: johbo: Fix login pages, so that they work without a min-height
247 // TODO: johbo: Fix login pages, so that they work without a min-height
248 // for the header and then remove the min-height. I chose a smaller value
248 // for the header and then remove the min-height. I chose a smaller value
249 // intentionally here to avoid rendering issues in the main navigation.
249 // intentionally here to avoid rendering issues in the main navigation.
250 min-height: 49px;
250 min-height: 49px;
251 min-width: 1024px;
251 min-width: 1024px;
252
252
253 position: relative;
253 position: relative;
254 vertical-align: bottom;
254 vertical-align: bottom;
255 padding: 0 @header-padding;
255 padding: 0 @header-padding;
256 background-color: @grey1;
256 background-color: @grey1;
257 color: @grey5;
257 color: @grey5;
258
258
259 .title {
259 .title {
260 overflow: visible;
260 overflow: visible;
261 }
261 }
262
262
263 &:before,
263 &:before,
264 &:after {
264 &:after {
265 content: "";
265 content: "";
266 clear: both;
266 clear: both;
267 width: 100%;
267 width: 100%;
268 }
268 }
269
269
270 // TODO: johbo: Avoids breaking "Repositories" chooser
270 // TODO: johbo: Avoids breaking "Repositories" chooser
271 .select2-container .select2-choice .select2-arrow {
271 .select2-container .select2-choice .select2-arrow {
272 display: none;
272 display: none;
273 }
273 }
274 }
274 }
275
275
276 #header-inner {
276 #header-inner {
277 &.title {
277 &.title {
278 margin: 0;
278 margin: 0;
279 }
279 }
280 &:before,
280 &:before,
281 &:after {
281 &:after {
282 content: "";
282 content: "";
283 clear: both;
283 clear: both;
284 }
284 }
285 }
285 }
286
286
287 // Gists
287 // Gists
288 #files_data {
288 #files_data {
289 clear: both; //for firefox
289 clear: both; //for firefox
290 padding-top: 10px;
290 padding-top: 10px;
291 }
291 }
292
292
293 #gistid {
293 #gistid {
294 margin-right: @padding;
294 margin-right: @padding;
295 }
295 }
296
296
297 // Global Settings Editor
297 // Global Settings Editor
298 .textarea.editor {
298 .textarea.editor {
299 float: left;
299 float: left;
300 position: relative;
300 position: relative;
301 max-width: @texteditor-width;
301 max-width: @texteditor-width;
302
302
303 select {
303 select {
304 position: absolute;
304 position: absolute;
305 top:10px;
305 top:10px;
306 right:0;
306 right:0;
307 }
307 }
308
308
309 .CodeMirror {
309 .CodeMirror {
310 margin: 0;
310 margin: 0;
311 }
311 }
312
312
313 .help-block {
313 .help-block {
314 margin: 0 0 @padding;
314 margin: 0 0 @padding;
315 padding:.5em;
315 padding:.5em;
316 background-color: @grey6;
316 background-color: @grey6;
317 &.pre-formatting {
317 &.pre-formatting {
318 white-space: pre;
318 white-space: pre;
319 }
319 }
320 }
320 }
321 }
321 }
322
322
323 ul.auth_plugins {
323 ul.auth_plugins {
324 margin: @padding 0 @padding @legend-width;
324 margin: @padding 0 @padding @legend-width;
325 padding: 0;
325 padding: 0;
326
326
327 li {
327 li {
328 margin-bottom: @padding;
328 margin-bottom: @padding;
329 line-height: 1em;
329 line-height: 1em;
330 list-style-type: none;
330 list-style-type: none;
331
331
332 .auth_buttons .btn {
332 .auth_buttons .btn {
333 margin-right: @padding;
333 margin-right: @padding;
334 }
334 }
335
335
336 }
336 }
337 }
337 }
338
338
339
339
340 // My Account PR list
340 // My Account PR list
341
341
342 #show_closed {
342 #show_closed {
343 margin: 0 1em 0 0;
343 margin: 0 1em 0 0;
344 }
344 }
345
345
346 #pull_request_list_table {
346 #pull_request_list_table {
347 .closed {
347 .closed {
348 background-color: @grey6;
348 background-color: @grey6;
349 }
349 }
350
350
351 .state-creating,
351 .state-creating,
352 .state-updating,
352 .state-updating,
353 .state-merging
353 .state-merging
354 {
354 {
355 background-color: @grey6;
355 background-color: @grey6;
356 }
356 }
357
357
358 .td-status {
358 .td-status {
359 padding-left: .5em;
359 padding-left: .5em;
360 }
360 }
361 .log-container .truncate {
361 .log-container .truncate {
362 height: 2.75em;
362 height: 2.75em;
363 white-space: pre-line;
363 white-space: pre-line;
364 }
364 }
365 table.rctable .user {
365 table.rctable .user {
366 padding-left: 0;
366 padding-left: 0;
367 }
367 }
368 table.rctable {
368 table.rctable {
369 td.td-description,
369 td.td-description,
370 .rc-user {
370 .rc-user {
371 min-width: auto;
371 min-width: auto;
372 }
372 }
373 }
373 }
374 }
374 }
375
375
376 // Pull Requests
376 // Pull Requests
377
377
378 .pullrequests_section_head {
378 .pullrequests_section_head {
379 display: block;
379 display: block;
380 clear: both;
380 clear: both;
381 margin: @padding 0;
381 margin: @padding 0;
382 font-weight: @text-bold-weight;
382 font-weight: @text-bold-weight;
383 font-family: @text-bold;
383 font-family: @text-bold;
384 }
384 }
385
385
386 .pr-commit-flow {
386 .pr-commit-flow {
387 position: relative;
387 position: relative;
388 font-weight: 600;
388 font-weight: 600;
389
389
390 .tag {
390 .tag {
391 display: inline-block;
391 display: inline-block;
392 margin: 0 1em .5em 0;
392 margin: 0 1em .5em 0;
393 }
393 }
394
394
395 .clone-url {
395 .clone-url {
396 display: inline-block;
396 display: inline-block;
397 margin: 0 0 .5em 0;
397 margin: 0 0 .5em 0;
398 padding: 0;
398 padding: 0;
399 line-height: 1.2em;
399 line-height: 1.2em;
400 }
400 }
401 }
401 }
402
402
403 .pr-mergeinfo {
403 .pr-mergeinfo {
404 min-width: 95% !important;
404 min-width: 95% !important;
405 padding: 0 !important;
405 padding: 0 !important;
406 border: 0;
406 border: 0;
407 }
407 }
408 .pr-mergeinfo-copy {
408 .pr-mergeinfo-copy {
409 padding: 0 0;
409 padding: 0 0;
410 }
410 }
411
411
412 .pr-pullinfo {
412 .pr-pullinfo {
413 min-width: 95% !important;
413 min-width: 95% !important;
414 padding: 0 !important;
414 padding: 0 !important;
415 border: 0;
415 border: 0;
416 }
416 }
417 .pr-pullinfo-copy {
417 .pr-pullinfo-copy {
418 padding: 0 0;
418 padding: 0 0;
419 }
419 }
420
420
421 .pr-title-input {
421 .pr-title-input {
422 width: 100%;
422 width: 100%;
423 font-size: 18px;
423 font-size: 18px;
424 margin: 0 0 4px 0;
424 margin: 0 0 4px 0;
425 padding: 0;
425 padding: 0;
426 line-height: 1.7em;
426 line-height: 1.7em;
427 color: @text-color;
427 color: @text-color;
428 letter-spacing: .02em;
428 letter-spacing: .02em;
429 font-weight: @text-bold-weight;
429 font-weight: @text-bold-weight;
430 font-family: @text-bold;
430 font-family: @text-bold;
431
431
432 &:hover {
432 &:hover {
433 box-shadow: none;
433 box-shadow: none;
434 }
434 }
435 }
435 }
436
436
437 #pr-title {
437 #pr-title {
438 input {
438 input {
439 border: 1px transparent;
439 border: 1px transparent;
440 color: black;
440 color: black;
441 opacity: 1;
441 opacity: 1;
442 background: #fff;
442 background: #fff;
443 font-size: 18px;
443 font-size: 18px;
444 }
444 }
445 }
445 }
446
446
447 .pr-title-closed-tag {
447 .pr-title-closed-tag {
448 font-size: 16px;
448 font-size: 16px;
449 }
449 }
450
450
451 #pr-desc {
451 #pr-desc {
452 padding: 10px 0;
452 padding: 10px 0;
453
453
454 .markdown-block {
454 .markdown-block {
455 padding: 0;
455 padding: 0;
456 margin-bottom: -30px;
456 margin-bottom: -30px;
457 }
457 }
458 }
458 }
459
459
460 #pullrequest_title {
460 #pullrequest_title {
461 width: 100%;
461 width: 100%;
462 box-sizing: border-box;
462 box-sizing: border-box;
463 }
463 }
464
464
465 #pr_open_message {
465 #pr_open_message {
466 border: @border-thickness solid #fff;
466 border: @border-thickness solid #fff;
467 border-radius: @border-radius;
467 border-radius: @border-radius;
468 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
468 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
469 text-align: left;
469 text-align: left;
470 overflow: hidden;
470 overflow: hidden;
471 }
471 }
472
472
473 .pr-details-title {
473 .pr-details-title {
474 height: 16px
474 height: 16px
475 }
475 }
476
476
477 .pr-details-title-author-pref {
477 .pr-details-title-author-pref {
478 padding-right: 10px
478 padding-right: 10px
479 }
479 }
480
480
481 .label-pr-detail {
481 .label-pr-detail {
482 display: table-cell;
482 display: table-cell;
483 width: 120px;
483 width: 120px;
484 padding-top: 7.5px;
484 padding-top: 7.5px;
485 padding-bottom: 7.5px;
485 padding-bottom: 7.5px;
486 padding-right: 7.5px;
486 padding-right: 7.5px;
487 }
487 }
488
488
489 .source-details ul {
489 .source-details ul {
490 padding: 10px 16px;
490 padding: 10px 16px;
491 }
491 }
492
492
493 .source-details-action {
493 .source-details-action {
494 color: @grey4;
494 color: @grey4;
495 font-size: 11px
495 font-size: 11px
496 }
496 }
497
497
498 .pr-submit-button {
498 .pr-submit-button {
499 float: right;
499 float: right;
500 margin: 0 0 0 5px;
500 margin: 0 0 0 5px;
501 }
501 }
502
502
503 .pr-spacing-container {
503 .pr-spacing-container {
504 padding: 20px;
504 padding: 20px;
505 clear: both
505 clear: both
506 }
506 }
507
507
508 #pr-description-input {
508 #pr-description-input {
509 margin-bottom: 0;
509 margin-bottom: 0;
510 }
510 }
511
511
512 .pr-description-label {
512 .pr-description-label {
513 vertical-align: top;
513 vertical-align: top;
514 }
514 }
515
515
516 #open_edit_pullrequest {
516 #open_edit_pullrequest {
517 padding: 0;
517 padding: 0;
518 }
518 }
519
519
520 #close_edit_pullrequest {
520 #close_edit_pullrequest {
521
521
522 }
522 }
523
523
524 #delete_pullrequest {
524 #delete_pullrequest {
525 clear: inherit;
525 clear: inherit;
526
526
527 form {
527 form {
528 display: inline;
528 display: inline;
529 }
529 }
530
530
531 }
531 }
532
532
533 .perms_section_head {
533 .perms_section_head {
534 min-width: 625px;
534 min-width: 625px;
535
535
536 h2 {
536 h2 {
537 margin-bottom: 0;
537 margin-bottom: 0;
538 }
538 }
539
539
540 .label-checkbox {
540 .label-checkbox {
541 float: left;
541 float: left;
542 }
542 }
543
543
544 &.field {
544 &.field {
545 margin: @space 0 @padding;
545 margin: @space 0 @padding;
546 }
546 }
547
547
548 &:first-child.field {
548 &:first-child.field {
549 margin-top: 0;
549 margin-top: 0;
550
550
551 .label {
551 .label {
552 margin-top: 0;
552 margin-top: 0;
553 padding-top: 0;
553 padding-top: 0;
554 }
554 }
555
555
556 .radios {
556 .radios {
557 padding-top: 0;
557 padding-top: 0;
558 }
558 }
559 }
559 }
560
560
561 .radios {
561 .radios {
562 position: relative;
562 position: relative;
563 width: 505px;
563 width: 505px;
564 }
564 }
565 }
565 }
566
566
567 //--- MODULES ------------------//
567 //--- MODULES ------------------//
568
568
569
569
570 // Server Announcement
570 // Server Announcement
571 #server-announcement {
571 #server-announcement {
572 width: 95%;
572 width: 95%;
573 margin: @padding auto;
573 margin: @padding auto;
574 padding: @padding;
574 padding: @padding;
575 border-width: 2px;
575 border-width: 2px;
576 border-style: solid;
576 border-style: solid;
577 .border-radius(2px);
577 .border-radius(2px);
578 font-weight: @text-bold-weight;
578 font-weight: @text-bold-weight;
579 font-family: @text-bold;
579 font-family: @text-bold;
580
580
581 &.info { border-color: @alert4; background-color: @alert4-inner; }
581 &.info { border-color: @alert4; background-color: @alert4-inner; }
582 &.warning { border-color: @alert3; background-color: @alert3-inner; }
582 &.warning { border-color: @alert3; background-color: @alert3-inner; }
583 &.error { border-color: @alert2; background-color: @alert2-inner; }
583 &.error { border-color: @alert2; background-color: @alert2-inner; }
584 &.success { border-color: @alert1; background-color: @alert1-inner; }
584 &.success { border-color: @alert1; background-color: @alert1-inner; }
585 &.neutral { border-color: @grey3; background-color: @grey6; }
585 &.neutral { border-color: @grey3; background-color: @grey6; }
586 }
586 }
587
587
588 // Fixed Sidebar Column
588 // Fixed Sidebar Column
589 .sidebar-col-wrapper {
589 .sidebar-col-wrapper {
590 padding-left: @sidebar-all-width;
590 padding-left: @sidebar-all-width;
591
591
592 .sidebar {
592 .sidebar {
593 width: @sidebar-width;
593 width: @sidebar-width;
594 margin-left: -@sidebar-all-width;
594 margin-left: -@sidebar-all-width;
595 }
595 }
596 }
596 }
597
597
598 .sidebar-col-wrapper.scw-small {
598 .sidebar-col-wrapper.scw-small {
599 padding-left: @sidebar-small-all-width;
599 padding-left: @sidebar-small-all-width;
600
600
601 .sidebar {
601 .sidebar {
602 width: @sidebar-small-width;
602 width: @sidebar-small-width;
603 margin-left: -@sidebar-small-all-width;
603 margin-left: -@sidebar-small-all-width;
604 }
604 }
605 }
605 }
606
606
607
607
608 // FOOTER
608 // FOOTER
609 #footer {
609 #footer {
610 padding: 0;
610 padding: 0;
611 text-align: center;
611 text-align: center;
612 vertical-align: middle;
612 vertical-align: middle;
613 color: @grey2;
613 color: @grey2;
614 font-size: 11px;
614 font-size: 11px;
615
615
616 p {
616 p {
617 margin: 0;
617 margin: 0;
618 padding: 1em;
618 padding: 1em;
619 line-height: 1em;
619 line-height: 1em;
620 }
620 }
621
621
622 .server-instance { //server instance
622 .server-instance { //server instance
623 display: none;
623 display: none;
624 }
624 }
625
625
626 .title {
626 .title {
627 float: none;
627 float: none;
628 margin: 0 auto;
628 margin: 0 auto;
629 }
629 }
630 }
630 }
631
631
632 button.close {
632 button.close {
633 padding: 0;
633 padding: 0;
634 cursor: pointer;
634 cursor: pointer;
635 background: transparent;
635 background: transparent;
636 border: 0;
636 border: 0;
637 .box-shadow(none);
637 .box-shadow(none);
638 -webkit-appearance: none;
638 -webkit-appearance: none;
639 }
639 }
640
640
641 .close {
641 .close {
642 float: right;
642 float: right;
643 font-size: 21px;
643 font-size: 21px;
644 font-family: @text-bootstrap;
644 font-family: @text-bootstrap;
645 line-height: 1em;
645 line-height: 1em;
646 font-weight: bold;
646 font-weight: bold;
647 color: @grey2;
647 color: @grey2;
648
648
649 &:hover,
649 &:hover,
650 &:focus {
650 &:focus {
651 color: @grey1;
651 color: @grey1;
652 text-decoration: none;
652 text-decoration: none;
653 cursor: pointer;
653 cursor: pointer;
654 }
654 }
655 }
655 }
656
656
657 // GRID
657 // GRID
658 .sorting,
658 .sorting,
659 .sorting_desc,
659 .sorting_desc,
660 .sorting_asc {
660 .sorting_asc {
661 cursor: pointer;
661 cursor: pointer;
662 }
662 }
663 .sorting_desc:after {
663 .sorting_desc:after {
664 content: "\00A0\25B2";
664 content: "\00A0\25B2";
665 font-size: .75em;
665 font-size: .75em;
666 }
666 }
667 .sorting_asc:after {
667 .sorting_asc:after {
668 content: "\00A0\25BC";
668 content: "\00A0\25BC";
669 font-size: .68em;
669 font-size: .68em;
670 }
670 }
671
671
672
672
673 .user_auth_tokens {
673 .user_auth_tokens {
674
674
675 &.truncate {
675 &.truncate {
676 white-space: nowrap;
676 white-space: nowrap;
677 overflow: hidden;
677 overflow: hidden;
678 text-overflow: ellipsis;
678 text-overflow: ellipsis;
679 }
679 }
680
680
681 .fields .field .input {
681 .fields .field .input {
682 margin: 0;
682 margin: 0;
683 }
683 }
684
684
685 input#description {
685 input#description {
686 width: 100px;
686 width: 100px;
687 margin: 0;
687 margin: 0;
688 }
688 }
689
689
690 .drop-menu {
690 .drop-menu {
691 // TODO: johbo: Remove this, should work out of the box when
691 // TODO: johbo: Remove this, should work out of the box when
692 // having multiple inputs inline
692 // having multiple inputs inline
693 margin: 0 0 0 5px;
693 margin: 0 0 0 5px;
694 }
694 }
695 }
695 }
696 #user_list_table {
696 #user_list_table {
697 .closed {
697 .closed {
698 background-color: @grey6;
698 background-color: @grey6;
699 }
699 }
700 }
700 }
701
701
702
702
703 input, textarea {
703 input, textarea {
704 &.disabled {
704 &.disabled {
705 opacity: .5;
705 opacity: .5;
706 }
706 }
707
707
708 &:hover {
708 &:hover {
709 border-color: @grey3;
709 border-color: @grey3;
710 box-shadow: @button-shadow;
710 box-shadow: @button-shadow;
711 }
711 }
712
712
713 &:focus {
713 &:focus {
714 border-color: @rcblue;
714 border-color: @rcblue;
715 box-shadow: @button-shadow;
715 box-shadow: @button-shadow;
716 }
716 }
717 }
717 }
718
718
719 // remove extra padding in firefox
719 // remove extra padding in firefox
720 input::-moz-focus-inner { border:0; padding:0 }
720 input::-moz-focus-inner { border:0; padding:0 }
721
721
722 .adjacent input {
722 .adjacent input {
723 margin-bottom: @padding;
723 margin-bottom: @padding;
724 }
724 }
725
725
726 .permissions_boxes {
726 .permissions_boxes {
727 display: block;
727 display: block;
728 }
728 }
729
729
730 //FORMS
730 //FORMS
731
731
732 .medium-inline,
732 .medium-inline,
733 input#description.medium-inline {
733 input#description.medium-inline {
734 display: inline;
734 display: inline;
735 width: @medium-inline-input-width;
735 width: @medium-inline-input-width;
736 min-width: 100px;
736 min-width: 100px;
737 }
737 }
738
738
739 select {
739 select {
740 //reset
740 //reset
741 -webkit-appearance: none;
741 -webkit-appearance: none;
742 -moz-appearance: none;
742 -moz-appearance: none;
743
743
744 display: inline-block;
744 display: inline-block;
745 height: 28px;
745 height: 28px;
746 width: auto;
746 width: auto;
747 margin: 0 @padding @padding 0;
747 margin: 0 @padding @padding 0;
748 padding: 0 18px 0 8px;
748 padding: 0 18px 0 8px;
749 line-height:1em;
749 line-height:1em;
750 font-size: @basefontsize;
750 font-size: @basefontsize;
751 border: @border-thickness solid @grey5;
751 border: @border-thickness solid @grey5;
752 border-radius: @border-radius;
752 border-radius: @border-radius;
753 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
753 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
754 color: @grey4;
754 color: @grey4;
755 box-shadow: @button-shadow;
755 box-shadow: @button-shadow;
756
756
757 &:after {
757 &:after {
758 content: "\00A0\25BE";
758 content: "\00A0\25BE";
759 }
759 }
760
760
761 &:focus, &:hover {
761 &:focus, &:hover {
762 outline: none;
762 outline: none;
763 border-color: @grey4;
763 border-color: @grey4;
764 color: @rcdarkblue;
764 color: @rcdarkblue;
765 }
765 }
766 }
766 }
767
767
768 option {
768 option {
769 &:focus {
769 &:focus {
770 outline: none;
770 outline: none;
771 }
771 }
772 }
772 }
773
773
774 input,
774 input,
775 textarea {
775 textarea {
776 padding: @input-padding;
776 padding: @input-padding;
777 border: @input-border-thickness solid @border-highlight-color;
777 border: @input-border-thickness solid @border-highlight-color;
778 .border-radius (@border-radius);
778 .border-radius (@border-radius);
779 font-family: @text-light;
779 font-family: @text-light;
780 font-size: @basefontsize;
780 font-size: @basefontsize;
781
781
782 &.input-sm {
782 &.input-sm {
783 padding: 5px;
783 padding: 5px;
784 }
784 }
785
785
786 &#description {
786 &#description {
787 min-width: @input-description-minwidth;
787 min-width: @input-description-minwidth;
788 min-height: 1em;
788 min-height: 1em;
789 padding: 10px;
789 padding: 10px;
790 }
790 }
791 }
791 }
792
792
793 .field-sm {
793 .field-sm {
794 input,
794 input,
795 textarea {
795 textarea {
796 padding: 5px;
796 padding: 5px;
797 }
797 }
798 }
798 }
799
799
800 textarea {
800 textarea {
801 display: block;
801 display: block;
802 clear: both;
802 clear: both;
803 width: 100%;
803 width: 100%;
804 min-height: 100px;
804 min-height: 100px;
805 margin-bottom: @padding;
805 margin-bottom: @padding;
806 .box-sizing(border-box);
806 .box-sizing(border-box);
807 overflow: auto;
807 overflow: auto;
808 }
808 }
809
809
810 label {
810 label {
811 font-family: @text-light;
811 font-family: @text-light;
812 }
812 }
813
813
814 // GRAVATARS
814 // GRAVATARS
815 // centers gravatar on username to the right
815 // centers gravatar on username to the right
816
816
817 .gravatar {
817 .gravatar {
818 display: inline;
818 display: inline;
819 min-width: 16px;
819 min-width: 16px;
820 min-height: 16px;
820 min-height: 16px;
821 margin: -5px 0;
821 margin: -5px 0;
822 padding: 0;
822 padding: 0;
823 line-height: 1em;
823 line-height: 1em;
824 box-sizing: content-box;
824 box-sizing: content-box;
825 border-radius: 50%;
825 border-radius: 50%;
826
826
827 &.gravatar-large {
827 &.gravatar-large {
828 margin: -0.5em .25em -0.5em 0;
828 margin: -0.5em .25em -0.5em 0;
829 }
829 }
830
830
831 & + .user {
831 & + .user {
832 display: inline;
832 display: inline;
833 margin: 0;
833 margin: 0;
834 padding: 0 0 0 .17em;
834 padding: 0 0 0 .17em;
835 line-height: 1em;
835 line-height: 1em;
836 }
836 }
837
837
838 & + .no-margin {
838 & + .no-margin {
839 margin: 0
839 margin: 0
840 }
840 }
841
841
842 }
842 }
843
843
844 .user-inline-data {
844 .user-inline-data {
845 display: inline-block;
845 display: inline-block;
846 float: left;
846 float: left;
847 padding-left: .5em;
847 padding-left: .5em;
848 line-height: 1.3em;
848 line-height: 1.3em;
849 }
849 }
850
850
851 .rc-user { // gravatar + user wrapper
851 .rc-user { // gravatar + user wrapper
852 float: left;
852 float: left;
853 position: relative;
853 position: relative;
854 min-width: 100px;
854 min-width: 100px;
855 max-width: 200px;
855 max-width: 200px;
856 min-height: (@gravatar-size + @border-thickness * 2); // account for border
856 min-height: (@gravatar-size + @border-thickness * 2); // account for border
857 display: block;
857 display: block;
858 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
858 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
859
859
860
860
861 .gravatar {
861 .gravatar {
862 display: block;
862 display: block;
863 position: absolute;
863 position: absolute;
864 top: 0;
864 top: 0;
865 left: 0;
865 left: 0;
866 min-width: @gravatar-size;
866 min-width: @gravatar-size;
867 min-height: @gravatar-size;
867 min-height: @gravatar-size;
868 margin: 0;
868 margin: 0;
869 }
869 }
870
870
871 .user {
871 .user {
872 display: block;
872 display: block;
873 max-width: 175px;
873 max-width: 175px;
874 padding-top: 2px;
874 padding-top: 2px;
875 overflow: hidden;
875 overflow: hidden;
876 text-overflow: ellipsis;
876 text-overflow: ellipsis;
877 }
877 }
878 }
878 }
879
879
880 .gist-gravatar,
880 .gist-gravatar,
881 .journal_container {
881 .journal_container {
882 .gravatar-large {
882 .gravatar-large {
883 margin: 0 .5em -10px 0;
883 margin: 0 .5em -10px 0;
884 }
884 }
885 }
885 }
886
886
887 .gist-type-fields {
887 .gist-type-fields {
888 line-height: 30px;
888 line-height: 30px;
889 height: 30px;
889 height: 30px;
890
890
891 .gist-type-fields-wrapper {
891 .gist-type-fields-wrapper {
892 vertical-align: middle;
892 vertical-align: middle;
893 display: inline-block;
893 display: inline-block;
894 line-height: 25px;
894 line-height: 25px;
895 }
895 }
896 }
896 }
897
897
898 // ADMIN SETTINGS
898 // ADMIN SETTINGS
899
899
900 // Tag Patterns
900 // Tag Patterns
901 .tag_patterns {
901 .tag_patterns {
902 .tag_input {
902 .tag_input {
903 margin-bottom: @padding;
903 margin-bottom: @padding;
904 }
904 }
905 }
905 }
906
906
907 .locked_input {
907 .locked_input {
908 position: relative;
908 position: relative;
909
909
910 input {
910 input {
911 display: inline;
911 display: inline;
912 margin: 3px 5px 0px 0px;
912 margin: 3px 5px 0px 0px;
913 }
913 }
914
914
915 br {
915 br {
916 display: none;
916 display: none;
917 }
917 }
918
918
919 .error-message {
919 .error-message {
920 float: left;
920 float: left;
921 width: 100%;
921 width: 100%;
922 }
922 }
923
923
924 .lock_input_button {
924 .lock_input_button {
925 display: inline;
925 display: inline;
926 }
926 }
927
927
928 .help-block {
928 .help-block {
929 clear: both;
929 clear: both;
930 }
930 }
931 }
931 }
932
932
933 // Notifications
933 // Notifications
934
934
935 .notifications_buttons {
935 .notifications_buttons {
936 margin: 0 0 @space 0;
936 margin: 0 0 @space 0;
937 padding: 0;
937 padding: 0;
938
938
939 .btn {
939 .btn {
940 display: inline-block;
940 display: inline-block;
941 }
941 }
942 }
942 }
943
943
944 .notification-list {
944 .notification-list {
945
945
946 div {
946 div {
947 vertical-align: middle;
947 vertical-align: middle;
948 }
948 }
949
949
950 .container {
950 .container {
951 display: block;
951 display: block;
952 margin: 0 0 @padding 0;
952 margin: 0 0 @padding 0;
953 }
953 }
954
954
955 .delete-notifications {
955 .delete-notifications {
956 margin-left: @padding;
956 margin-left: @padding;
957 text-align: right;
957 text-align: right;
958 cursor: pointer;
958 cursor: pointer;
959 }
959 }
960
960
961 .read-notifications {
961 .read-notifications {
962 margin-left: @padding/2;
962 margin-left: @padding/2;
963 text-align: right;
963 text-align: right;
964 width: 35px;
964 width: 35px;
965 cursor: pointer;
965 cursor: pointer;
966 }
966 }
967
967
968 .icon-minus-sign {
968 .icon-minus-sign {
969 color: @alert2;
969 color: @alert2;
970 }
970 }
971
971
972 .icon-ok-sign {
972 .icon-ok-sign {
973 color: @alert1;
973 color: @alert1;
974 }
974 }
975 }
975 }
976
976
977 .user_settings {
977 .user_settings {
978 float: left;
978 float: left;
979 clear: both;
979 clear: both;
980 display: block;
980 display: block;
981 width: 100%;
981 width: 100%;
982
982
983 .gravatar_box {
983 .gravatar_box {
984 margin-bottom: @padding;
984 margin-bottom: @padding;
985
985
986 &:after {
986 &:after {
987 content: " ";
987 content: " ";
988 clear: both;
988 clear: both;
989 width: 100%;
989 width: 100%;
990 }
990 }
991 }
991 }
992
992
993 .fields .field {
993 .fields .field {
994 clear: both;
994 clear: both;
995 }
995 }
996 }
996 }
997
997
998 .advanced_settings {
998 .advanced_settings {
999 margin-bottom: @space;
999 margin-bottom: @space;
1000
1000
1001 .help-block {
1001 .help-block {
1002 margin-left: 0;
1002 margin-left: 0;
1003 }
1003 }
1004
1004
1005 button + .help-block {
1005 button + .help-block {
1006 margin-top: @padding;
1006 margin-top: @padding;
1007 }
1007 }
1008 }
1008 }
1009
1009
1010 // admin settings radio buttons and labels
1010 // admin settings radio buttons and labels
1011 .label-2 {
1011 .label-2 {
1012 float: left;
1012 float: left;
1013 width: @label2-width;
1013 width: @label2-width;
1014
1014
1015 label {
1015 label {
1016 color: @grey1;
1016 color: @grey1;
1017 }
1017 }
1018 }
1018 }
1019 .checkboxes {
1019 .checkboxes {
1020 float: left;
1020 float: left;
1021 width: @checkboxes-width;
1021 width: @checkboxes-width;
1022 margin-bottom: @padding;
1022 margin-bottom: @padding;
1023
1023
1024 .checkbox {
1024 .checkbox {
1025 width: 100%;
1025 width: 100%;
1026
1026
1027 label {
1027 label {
1028 margin: 0;
1028 margin: 0;
1029 padding: 0;
1029 padding: 0;
1030 }
1030 }
1031 }
1031 }
1032
1032
1033 .checkbox + .checkbox {
1033 .checkbox + .checkbox {
1034 display: inline-block;
1034 display: inline-block;
1035 }
1035 }
1036
1036
1037 label {
1037 label {
1038 margin-right: 1em;
1038 margin-right: 1em;
1039 }
1039 }
1040 }
1040 }
1041
1041
1042 // CHANGELOG
1042 // CHANGELOG
1043 .container_header {
1043 .container_header {
1044 float: left;
1044 float: left;
1045 display: block;
1045 display: block;
1046 width: 100%;
1046 width: 100%;
1047 margin: @padding 0 @padding;
1047 margin: @padding 0 @padding;
1048
1048
1049 #filter_changelog {
1049 #filter_changelog {
1050 float: left;
1050 float: left;
1051 margin-right: @padding;
1051 margin-right: @padding;
1052 }
1052 }
1053
1053
1054 .breadcrumbs_light {
1054 .breadcrumbs_light {
1055 display: inline-block;
1055 display: inline-block;
1056 }
1056 }
1057 }
1057 }
1058
1058
1059 .info_box {
1059 .info_box {
1060 float: right;
1060 float: right;
1061 }
1061 }
1062
1062
1063
1063
1064
1064
1065 #graph_content{
1065 #graph_content{
1066
1066
1067 // adjust for table headers so that graph renders properly
1067 // adjust for table headers so that graph renders properly
1068 // #graph_nodes padding - table cell padding
1068 // #graph_nodes padding - table cell padding
1069 padding-top: (@space - (@basefontsize * 2.4));
1069 padding-top: (@space - (@basefontsize * 2.4));
1070
1070
1071 &.graph_full_width {
1071 &.graph_full_width {
1072 width: 100%;
1072 width: 100%;
1073 max-width: 100%;
1073 max-width: 100%;
1074 }
1074 }
1075 }
1075 }
1076
1076
1077 #graph {
1077 #graph {
1078
1078
1079 .pagination-left {
1079 .pagination-left {
1080 float: left;
1080 float: left;
1081 clear: both;
1081 clear: both;
1082 }
1082 }
1083
1083
1084 .log-container {
1084 .log-container {
1085 max-width: 345px;
1085 max-width: 345px;
1086
1086
1087 .message{
1087 .message{
1088 max-width: 340px;
1088 max-width: 340px;
1089 }
1089 }
1090 }
1090 }
1091
1091
1092 .graph-col-wrapper {
1092 .graph-col-wrapper {
1093
1093
1094 #graph_nodes {
1094 #graph_nodes {
1095 width: 100px;
1095 width: 100px;
1096 position: absolute;
1096 position: absolute;
1097 left: 70px;
1097 left: 70px;
1098 z-index: -1;
1098 z-index: -1;
1099 }
1099 }
1100 }
1100 }
1101
1101
1102 .load-more-commits {
1102 .load-more-commits {
1103 text-align: center;
1103 text-align: center;
1104 }
1104 }
1105 .load-more-commits:hover {
1105 .load-more-commits:hover {
1106 background-color: @grey7;
1106 background-color: @grey7;
1107 }
1107 }
1108 .load-more-commits {
1108 .load-more-commits {
1109 a {
1109 a {
1110 display: block;
1110 display: block;
1111 }
1111 }
1112 }
1112 }
1113 }
1113 }
1114
1114
1115 .obsolete-toggle {
1115 .obsolete-toggle {
1116 line-height: 30px;
1116 line-height: 30px;
1117 margin-left: -15px;
1117 margin-left: -15px;
1118 }
1118 }
1119
1119
1120 #rev_range_container, #rev_range_clear, #rev_range_more {
1120 #rev_range_container, #rev_range_clear, #rev_range_more {
1121 margin-top: -5px;
1121 margin-top: -5px;
1122 margin-bottom: -5px;
1122 margin-bottom: -5px;
1123 }
1123 }
1124
1124
1125 #filter_changelog {
1125 #filter_changelog {
1126 float: left;
1126 float: left;
1127 }
1127 }
1128
1128
1129
1129
1130 //--- THEME ------------------//
1130 //--- THEME ------------------//
1131
1131
1132 #logo {
1132 #logo {
1133 float: left;
1133 float: left;
1134 margin: 9px 0 0 0;
1134 margin: 9px 0 0 0;
1135
1135
1136 .header {
1136 .header {
1137 background-color: transparent;
1137 background-color: transparent;
1138 }
1138 }
1139
1139
1140 a {
1140 a {
1141 display: inline-block;
1141 display: inline-block;
1142 }
1142 }
1143
1143
1144 img {
1144 img {
1145 height:30px;
1145 height:30px;
1146 }
1146 }
1147 }
1147 }
1148
1148
1149 .logo-wrapper {
1149 .logo-wrapper {
1150 float:left;
1150 float:left;
1151 }
1151 }
1152
1152
1153 .branding {
1153 .branding {
1154 float: left;
1154 float: left;
1155 padding: 9px 2px;
1155 padding: 9px 2px;
1156 line-height: 1em;
1156 line-height: 1em;
1157 font-size: @navigation-fontsize;
1157 font-size: @navigation-fontsize;
1158
1158
1159 a {
1159 a {
1160 color: @grey5
1160 color: @grey5
1161 }
1161 }
1162 @media screen and (max-width: 1200px) {
1162 @media screen and (max-width: 1200px) {
1163 display: none;
1163 display: none;
1164 }
1164 }
1165 }
1165 }
1166
1166
1167 img {
1167 img {
1168 border: none;
1168 border: none;
1169 outline: none;
1169 outline: none;
1170 }
1170 }
1171 user-profile-header
1171 user-profile-header
1172 label {
1172 label {
1173
1173
1174 input[type="checkbox"] {
1174 input[type="checkbox"] {
1175 margin-right: 1em;
1175 margin-right: 1em;
1176 }
1176 }
1177 input[type="radio"] {
1177 input[type="radio"] {
1178 margin-right: 1em;
1178 margin-right: 1em;
1179 }
1179 }
1180 }
1180 }
1181
1181
1182 .review-status {
1182 .review-status {
1183 &.under_review {
1183 &.under_review {
1184 color: @alert3;
1184 color: @alert3;
1185 }
1185 }
1186 &.approved {
1186 &.approved {
1187 color: @alert1;
1187 color: @alert1;
1188 }
1188 }
1189 &.rejected,
1189 &.rejected,
1190 &.forced_closed{
1190 &.forced_closed{
1191 color: @alert2;
1191 color: @alert2;
1192 }
1192 }
1193 &.not_reviewed {
1193 &.not_reviewed {
1194 color: @grey5;
1194 color: @grey5;
1195 }
1195 }
1196 }
1196 }
1197
1197
1198 .review-status-under_review {
1198 .review-status-under_review {
1199 color: @alert3;
1199 color: @alert3;
1200 }
1200 }
1201 .status-tag-under_review {
1201 .status-tag-under_review {
1202 border-color: @alert3;
1202 border-color: @alert3;
1203 }
1203 }
1204
1204
1205 .review-status-approved {
1205 .review-status-approved {
1206 color: @alert1;
1206 color: @alert1;
1207 }
1207 }
1208 .status-tag-approved {
1208 .status-tag-approved {
1209 border-color: @alert1;
1209 border-color: @alert1;
1210 }
1210 }
1211
1211
1212 .review-status-rejected,
1212 .review-status-rejected,
1213 .review-status-forced_closed {
1213 .review-status-forced_closed {
1214 color: @alert2;
1214 color: @alert2;
1215 }
1215 }
1216 .status-tag-rejected,
1216 .status-tag-rejected,
1217 .status-tag-forced_closed {
1217 .status-tag-forced_closed {
1218 border-color: @alert2;
1218 border-color: @alert2;
1219 }
1219 }
1220
1220
1221 .review-status-not_reviewed {
1221 .review-status-not_reviewed {
1222 color: @grey5;
1222 color: @grey5;
1223 }
1223 }
1224 .status-tag-not_reviewed {
1224 .status-tag-not_reviewed {
1225 border-color: @grey5;
1225 border-color: @grey5;
1226 }
1226 }
1227
1227
1228 .test_pattern_preview {
1228 .test_pattern_preview {
1229 margin: @space 0;
1229 margin: @space 0;
1230
1230
1231 p {
1231 p {
1232 margin-bottom: 0;
1232 margin-bottom: 0;
1233 border-bottom: @border-thickness solid @border-default-color;
1233 border-bottom: @border-thickness solid @border-default-color;
1234 color: @grey3;
1234 color: @grey3;
1235 }
1235 }
1236
1236
1237 .btn {
1237 .btn {
1238 margin-bottom: @padding;
1238 margin-bottom: @padding;
1239 }
1239 }
1240 }
1240 }
1241 #test_pattern_result {
1241 #test_pattern_result {
1242 display: none;
1242 display: none;
1243 &:extend(pre);
1243 &:extend(pre);
1244 padding: .9em;
1244 padding: .9em;
1245 color: @grey3;
1245 color: @grey3;
1246 background-color: @grey7;
1246 background-color: @grey7;
1247 border-right: @border-thickness solid @border-default-color;
1247 border-right: @border-thickness solid @border-default-color;
1248 border-bottom: @border-thickness solid @border-default-color;
1248 border-bottom: @border-thickness solid @border-default-color;
1249 border-left: @border-thickness solid @border-default-color;
1249 border-left: @border-thickness solid @border-default-color;
1250 }
1250 }
1251
1251
1252 #repo_vcs_settings {
1252 #repo_vcs_settings {
1253 #inherit_overlay_vcs_default {
1253 #inherit_overlay_vcs_default {
1254 display: none;
1254 display: none;
1255 }
1255 }
1256 #inherit_overlay_vcs_custom {
1256 #inherit_overlay_vcs_custom {
1257 display: custom;
1257 display: custom;
1258 }
1258 }
1259 &.inherited {
1259 &.inherited {
1260 #inherit_overlay_vcs_default {
1260 #inherit_overlay_vcs_default {
1261 display: block;
1261 display: block;
1262 }
1262 }
1263 #inherit_overlay_vcs_custom {
1263 #inherit_overlay_vcs_custom {
1264 display: none;
1264 display: none;
1265 }
1265 }
1266 }
1266 }
1267 }
1267 }
1268
1268
1269 .issue-tracker-link {
1269 .issue-tracker-link {
1270 color: @rcblue;
1270 color: @rcblue;
1271 }
1271 }
1272
1272
1273 // Issue Tracker Table Show/Hide
1273 // Issue Tracker Table Show/Hide
1274 #repo_issue_tracker {
1274 #repo_issue_tracker {
1275 #inherit_overlay {
1275 #inherit_overlay {
1276 display: none;
1276 display: none;
1277 }
1277 }
1278 #custom_overlay {
1278 #custom_overlay {
1279 display: custom;
1279 display: custom;
1280 }
1280 }
1281 &.inherited {
1281 &.inherited {
1282 #inherit_overlay {
1282 #inherit_overlay {
1283 display: block;
1283 display: block;
1284 }
1284 }
1285 #custom_overlay {
1285 #custom_overlay {
1286 display: none;
1286 display: none;
1287 }
1287 }
1288 }
1288 }
1289 }
1289 }
1290 table.issuetracker {
1290 table.issuetracker {
1291 &.readonly {
1291 &.readonly {
1292 tr, td {
1292 tr, td {
1293 color: @grey3;
1293 color: @grey3;
1294 }
1294 }
1295 }
1295 }
1296 .edit {
1296 .edit {
1297 display: none;
1297 display: none;
1298 }
1298 }
1299 .editopen {
1299 .editopen {
1300 .edit {
1300 .edit {
1301 display: inline;
1301 display: inline;
1302 }
1302 }
1303 .entry {
1303 .entry {
1304 display: none;
1304 display: none;
1305 }
1305 }
1306 }
1306 }
1307 tr td.td-action {
1307 tr td.td-action {
1308 min-width: 117px;
1308 min-width: 117px;
1309 }
1309 }
1310 td input {
1310 td input {
1311 max-width: none;
1311 max-width: none;
1312 min-width: 30px;
1312 min-width: 30px;
1313 width: 80%;
1313 width: 80%;
1314 }
1314 }
1315 .issuetracker_pref input {
1315 .issuetracker_pref input {
1316 width: 40%;
1316 width: 40%;
1317 }
1317 }
1318 input.edit_issuetracker_update {
1318 input.edit_issuetracker_update {
1319 margin-right: 0;
1319 margin-right: 0;
1320 width: auto;
1320 width: auto;
1321 }
1321 }
1322 }
1322 }
1323
1323
1324 table.integrations {
1324 table.integrations {
1325 .td-icon {
1325 .td-icon {
1326 width: 20px;
1326 width: 20px;
1327 .integration-icon {
1327 .integration-icon {
1328 height: 20px;
1328 height: 20px;
1329 width: 20px;
1329 width: 20px;
1330 }
1330 }
1331 }
1331 }
1332 }
1332 }
1333
1333
1334 .integrations {
1334 .integrations {
1335 a.integration-box {
1335 a.integration-box {
1336 color: @text-color;
1336 color: @text-color;
1337 &:hover {
1337 &:hover {
1338 .panel {
1338 .panel {
1339 background: #fbfbfb;
1339 background: #fbfbfb;
1340 }
1340 }
1341 }
1341 }
1342 .integration-icon {
1342 .integration-icon {
1343 width: 30px;
1343 width: 30px;
1344 height: 30px;
1344 height: 30px;
1345 margin-right: 20px;
1345 margin-right: 20px;
1346 float: left;
1346 float: left;
1347 }
1347 }
1348
1348
1349 .panel-body {
1349 .panel-body {
1350 padding: 10px;
1350 padding: 10px;
1351 }
1351 }
1352 .panel {
1352 .panel {
1353 margin-bottom: 10px;
1353 margin-bottom: 10px;
1354 }
1354 }
1355 h2 {
1355 h2 {
1356 display: inline-block;
1356 display: inline-block;
1357 margin: 0;
1357 margin: 0;
1358 min-width: 140px;
1358 min-width: 140px;
1359 }
1359 }
1360 }
1360 }
1361 a.integration-box.dummy-integration {
1361 a.integration-box.dummy-integration {
1362 color: @grey4
1362 color: @grey4
1363 }
1363 }
1364 }
1364 }
1365
1365
1366 //Permissions Settings
1366 //Permissions Settings
1367 #add_perm {
1367 #add_perm {
1368 margin: 0 0 @padding;
1368 margin: 0 0 @padding;
1369 cursor: pointer;
1369 cursor: pointer;
1370 }
1370 }
1371
1371
1372 .perm_ac {
1372 .perm_ac {
1373 input {
1373 input {
1374 width: 95%;
1374 width: 95%;
1375 }
1375 }
1376 }
1376 }
1377
1377
1378 .autocomplete-suggestions {
1378 .autocomplete-suggestions {
1379 width: auto !important; // overrides autocomplete.js
1379 width: auto !important; // overrides autocomplete.js
1380 min-width: 278px;
1380 min-width: 278px;
1381 margin: 0;
1381 margin: 0;
1382 border: @border-thickness solid @grey5;
1382 border: @border-thickness solid @grey5;
1383 border-radius: @border-radius;
1383 border-radius: @border-radius;
1384 color: @grey2;
1384 color: @grey2;
1385 background-color: white;
1385 background-color: white;
1386 }
1386 }
1387
1387
1388 .autocomplete-qfilter-suggestions {
1388 .autocomplete-qfilter-suggestions {
1389 width: auto !important; // overrides autocomplete.js
1389 width: auto !important; // overrides autocomplete.js
1390 max-height: 100% !important;
1390 max-height: 100% !important;
1391 min-width: 376px;
1391 min-width: 376px;
1392 margin: 0;
1392 margin: 0;
1393 border: @border-thickness solid @grey5;
1393 border: @border-thickness solid @grey5;
1394 color: @grey2;
1394 color: @grey2;
1395 background-color: white;
1395 background-color: white;
1396 }
1396 }
1397
1397
1398 .autocomplete-selected {
1398 .autocomplete-selected {
1399 background: #F0F0F0;
1399 background: #F0F0F0;
1400 }
1400 }
1401
1401
1402 .ac-container-wrap {
1402 .ac-container-wrap {
1403 margin: 0;
1403 margin: 0;
1404 padding: 8px;
1404 padding: 8px;
1405 border-bottom: @border-thickness solid @grey5;
1405 border-bottom: @border-thickness solid @grey5;
1406 list-style-type: none;
1406 list-style-type: none;
1407 cursor: pointer;
1407 cursor: pointer;
1408
1408
1409 &:hover {
1409 &:hover {
1410 background-color: @grey7;
1410 background-color: @grey7;
1411 }
1411 }
1412
1412
1413 img {
1413 img {
1414 height: @gravatar-size;
1414 height: @gravatar-size;
1415 width: @gravatar-size;
1415 width: @gravatar-size;
1416 margin-right: 1em;
1416 margin-right: 1em;
1417 }
1417 }
1418
1418
1419 strong {
1419 strong {
1420 font-weight: normal;
1420 font-weight: normal;
1421 }
1421 }
1422 }
1422 }
1423
1423
1424 // Settings Dropdown
1424 // Settings Dropdown
1425 .user-menu .container {
1425 .user-menu .container {
1426 padding: 0 4px;
1426 padding: 0 4px;
1427 margin: 0;
1427 margin: 0;
1428 }
1428 }
1429
1429
1430 .user-menu .gravatar {
1430 .user-menu .gravatar {
1431 cursor: pointer;
1431 cursor: pointer;
1432 }
1432 }
1433
1433
1434 .codeblock {
1434 .codeblock {
1435 margin-bottom: @padding;
1435 margin-bottom: @padding;
1436 clear: both;
1436 clear: both;
1437
1437
1438 .stats {
1438 .stats {
1439 overflow: hidden;
1439 overflow: hidden;
1440 }
1440 }
1441
1441
1442 .message{
1442 .message{
1443 textarea{
1443 textarea{
1444 margin: 0;
1444 margin: 0;
1445 }
1445 }
1446 }
1446 }
1447
1447
1448 .code-header {
1448 .code-header {
1449 .stats {
1449 .stats {
1450 line-height: 2em;
1450 line-height: 2em;
1451
1451
1452 .revision_id {
1452 .revision_id {
1453 margin-left: 0;
1453 margin-left: 0;
1454 }
1454 }
1455 .buttons {
1455 .buttons {
1456 padding-right: 0;
1456 padding-right: 0;
1457 }
1457 }
1458 }
1458 }
1459
1459
1460 .item{
1460 .item{
1461 margin-right: 0.5em;
1461 margin-right: 0.5em;
1462 }
1462 }
1463 }
1463 }
1464
1464
1465 #editor_container {
1465 #editor_container {
1466 position: relative;
1466 position: relative;
1467 margin: @padding 10px;
1467 margin: @padding 10px;
1468 }
1468 }
1469 }
1469 }
1470
1470
1471 #file_history_container {
1471 #file_history_container {
1472 display: none;
1472 display: none;
1473 }
1473 }
1474
1474
1475 .file-history-inner {
1475 .file-history-inner {
1476 margin-bottom: 10px;
1476 margin-bottom: 10px;
1477 }
1477 }
1478
1478
1479 // Pull Requests
1479 // Pull Requests
1480 .summary-details {
1480 .summary-details {
1481 width: 72%;
1481 width: 72%;
1482 }
1482 }
1483 .pr-summary {
1483 .pr-summary {
1484 border-bottom: @border-thickness solid @grey5;
1484 border-bottom: @border-thickness solid @grey5;
1485 margin-bottom: @space;
1485 margin-bottom: @space;
1486 }
1486 }
1487
1487
1488 .reviewers-title {
1488 .reviewers-title {
1489 width: 25%;
1489 width: 25%;
1490 min-width: 200px;
1490 min-width: 200px;
1491
1491
1492 &.first-panel {
1492 &.first-panel {
1493 margin-top: 34px;
1493 margin-top: 34px;
1494 }
1494 }
1495 }
1495 }
1496
1496
1497 .reviewers {
1497 .reviewers {
1498 width: 25%;
1498 width: 25%;
1499 min-width: 200px;
1499 min-width: 200px;
1500 }
1500 }
1501 .reviewers ul li {
1501 .reviewers ul li {
1502 position: relative;
1502 position: relative;
1503 width: 100%;
1503 width: 100%;
1504 padding-bottom: 8px;
1504 padding-bottom: 8px;
1505 list-style-type: none;
1505 list-style-type: none;
1506 }
1506 }
1507
1507
1508 .reviewer_entry {
1508 .reviewer_entry {
1509 min-height: 55px;
1509 min-height: 55px;
1510 }
1510 }
1511
1511
1512 .reviewers_member {
1512 .reviewers_member {
1513 width: 100%;
1513 width: 100%;
1514 overflow: auto;
1514 overflow: auto;
1515 }
1515 }
1516 .reviewer_reason {
1516 .reviewer_reason {
1517 padding-left: 20px;
1517 padding-left: 20px;
1518 line-height: 1.5em;
1518 line-height: 1.5em;
1519 }
1519 }
1520 .reviewer_status {
1520 .reviewer_status {
1521 display: inline-block;
1521 display: inline-block;
1522 width: 25px;
1522 width: 25px;
1523 min-width: 25px;
1523 min-width: 25px;
1524 height: 1.2em;
1524 height: 1.2em;
1525 line-height: 1em;
1525 line-height: 1em;
1526 }
1526 }
1527
1527
1528 .reviewer_name {
1528 .reviewer_name {
1529 display: inline-block;
1529 display: inline-block;
1530 max-width: 83%;
1530 max-width: 83%;
1531 padding-right: 20px;
1531 padding-right: 20px;
1532 vertical-align: middle;
1532 vertical-align: middle;
1533 line-height: 1;
1533 line-height: 1;
1534
1534
1535 .rc-user {
1535 .rc-user {
1536 min-width: 0;
1536 min-width: 0;
1537 margin: -2px 1em 0 0;
1537 margin: -2px 1em 0 0;
1538 }
1538 }
1539
1539
1540 .reviewer {
1540 .reviewer {
1541 float: left;
1541 float: left;
1542 }
1542 }
1543 }
1543 }
1544
1544
1545 .reviewer_member_mandatory {
1545 .reviewer_member_mandatory {
1546 position: absolute;
1546 position: absolute;
1547 left: 15px;
1547 left: 15px;
1548 top: 8px;
1548 top: 8px;
1549 width: 16px;
1549 width: 16px;
1550 font-size: 11px;
1550 font-size: 11px;
1551 margin: 0;
1551 margin: 0;
1552 padding: 0;
1552 padding: 0;
1553 color: black;
1553 color: black;
1554 }
1554 }
1555
1555
1556 .reviewer_member_mandatory_remove,
1556 .reviewer_member_mandatory_remove,
1557 .reviewer_member_remove {
1557 .reviewer_member_remove {
1558 position: absolute;
1558 position: absolute;
1559 right: 0;
1559 right: 0;
1560 top: 0;
1560 top: 0;
1561 width: 16px;
1561 width: 16px;
1562 margin-bottom: 10px;
1562 margin-bottom: 10px;
1563 padding: 0;
1563 padding: 0;
1564 color: black;
1564 color: black;
1565 }
1565 }
1566
1566
1567 .reviewer_member_mandatory_remove {
1567 .reviewer_member_mandatory_remove {
1568 color: @grey4;
1568 color: @grey4;
1569 }
1569 }
1570
1570
1571 .reviewer_member_status {
1571 .reviewer_member_status {
1572 margin-top: 5px;
1572 margin-top: 5px;
1573 }
1573 }
1574 .pr-summary #summary{
1574 .pr-summary #summary{
1575 width: 100%;
1575 width: 100%;
1576 }
1576 }
1577 .pr-summary .action_button:hover {
1577 .pr-summary .action_button:hover {
1578 border: 0;
1578 border: 0;
1579 cursor: pointer;
1579 cursor: pointer;
1580 }
1580 }
1581 .pr-details-title {
1581 .pr-details-title {
1582 padding-bottom: 8px;
1582 padding-bottom: 8px;
1583 border-bottom: @border-thickness solid @grey5;
1583 border-bottom: @border-thickness solid @grey5;
1584
1584
1585 .action_button.disabled {
1585 .action_button.disabled {
1586 color: @grey4;
1586 color: @grey4;
1587 cursor: inherit;
1587 cursor: inherit;
1588 }
1588 }
1589 .action_button {
1589 .action_button {
1590 color: @rcblue;
1590 color: @rcblue;
1591 }
1591 }
1592 }
1592 }
1593 .pr-details-content {
1593 .pr-details-content {
1594 margin-top: @textmargin - 5;
1594 margin-top: @textmargin - 5;
1595 margin-bottom: @textmargin - 5;
1595 margin-bottom: @textmargin - 5;
1596 }
1596 }
1597
1597
1598 .pr-reviewer-rules {
1598 .pr-reviewer-rules {
1599 padding: 10px 0px 20px 0px;
1599 padding: 10px 0px 20px 0px;
1600 }
1600 }
1601
1601
1602 .todo-resolved {
1602 .todo-resolved {
1603 text-decoration: line-through;
1603 text-decoration: line-through;
1604 }
1604 }
1605
1605
1606 .todo-table {
1606 .todo-table {
1607 width: 100%;
1607 width: 100%;
1608
1608
1609 td {
1609 td {
1610 padding: 5px 0px;
1610 padding: 5px 0px;
1611 }
1611 }
1612
1612
1613 .td-todo-number {
1613 .td-todo-number {
1614 text-align: left;
1614 text-align: left;
1615 white-space: nowrap;
1615 white-space: nowrap;
1616 width: 15%;
1616 width: 15%;
1617 }
1617 }
1618
1618
1619 .td-todo-gravatar {
1619 .td-todo-gravatar {
1620 width: 5%;
1620 width: 5%;
1621
1621
1622 img {
1622 img {
1623 margin: -3px 0;
1623 margin: -3px 0;
1624 }
1624 }
1625 }
1625 }
1626
1626
1627 }
1627 }
1628
1628
1629 .todo-comment-text-wrapper {
1629 .todo-comment-text-wrapper {
1630 display: inline-grid;
1630 display: inline-grid;
1631 }
1631 }
1632
1632
1633 .todo-comment-text {
1633 .todo-comment-text {
1634 margin-left: 5px;
1634 margin-left: 5px;
1635 white-space: nowrap;
1635 white-space: nowrap;
1636 overflow: hidden;
1636 overflow: hidden;
1637 text-overflow: ellipsis;
1637 text-overflow: ellipsis;
1638 }
1638 }
1639
1639
1640 .group_members {
1640 .group_members {
1641 margin-top: 0;
1641 margin-top: 0;
1642 padding: 0;
1642 padding: 0;
1643 list-style: outside none none;
1643 list-style: outside none none;
1644
1644
1645 img {
1645 img {
1646 height: @gravatar-size;
1646 height: @gravatar-size;
1647 width: @gravatar-size;
1647 width: @gravatar-size;
1648 margin-right: .5em;
1648 margin-right: .5em;
1649 margin-left: 3px;
1649 margin-left: 3px;
1650 }
1650 }
1651
1651
1652 .to-delete {
1652 .to-delete {
1653 .user {
1653 .user {
1654 text-decoration: line-through;
1654 text-decoration: line-through;
1655 }
1655 }
1656 }
1656 }
1657 }
1657 }
1658
1658
1659 .compare_view_commits_title {
1659 .compare_view_commits_title {
1660 .disabled {
1660 .disabled {
1661 cursor: inherit;
1661 cursor: inherit;
1662 &:hover{
1662 &:hover{
1663 background-color: inherit;
1663 background-color: inherit;
1664 color: inherit;
1664 color: inherit;
1665 }
1665 }
1666 }
1666 }
1667 }
1667 }
1668
1668
1669 .subtitle-compare {
1669 .subtitle-compare {
1670 margin: -15px 0px 0px 0px;
1670 margin: -15px 0px 0px 0px;
1671 }
1671 }
1672
1672
1673 // new entry in group_members
1673 // new entry in group_members
1674 .td-author-new-entry {
1674 .td-author-new-entry {
1675 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1675 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1676 }
1676 }
1677
1677
1678 .usergroup_member_remove {
1678 .usergroup_member_remove {
1679 width: 16px;
1679 width: 16px;
1680 margin-bottom: 10px;
1680 margin-bottom: 10px;
1681 padding: 0;
1681 padding: 0;
1682 color: black !important;
1682 color: black !important;
1683 cursor: pointer;
1683 cursor: pointer;
1684 }
1684 }
1685
1685
1686 .reviewer_ac .ac-input {
1686 .reviewer_ac .ac-input {
1687 width: 92%;
1687 width: 92%;
1688 margin-bottom: 1em;
1688 margin-bottom: 1em;
1689 }
1689 }
1690
1690
1691 .compare_view_commits tr{
1691 .compare_view_commits tr{
1692 height: 20px;
1692 height: 20px;
1693 }
1693 }
1694 .compare_view_commits td {
1694 .compare_view_commits td {
1695 vertical-align: top;
1695 vertical-align: top;
1696 padding-top: 10px;
1696 padding-top: 10px;
1697 }
1697 }
1698 .compare_view_commits .author {
1698 .compare_view_commits .author {
1699 margin-left: 5px;
1699 margin-left: 5px;
1700 }
1700 }
1701
1701
1702 .compare_view_commits {
1702 .compare_view_commits {
1703 .color-a {
1703 .color-a {
1704 color: @alert1;
1704 color: @alert1;
1705 }
1705 }
1706
1706
1707 .color-c {
1707 .color-c {
1708 color: @color3;
1708 color: @color3;
1709 }
1709 }
1710
1710
1711 .color-r {
1711 .color-r {
1712 color: @color5;
1712 color: @color5;
1713 }
1713 }
1714
1714
1715 .color-a-bg {
1715 .color-a-bg {
1716 background-color: @alert1;
1716 background-color: @alert1;
1717 }
1717 }
1718
1718
1719 .color-c-bg {
1719 .color-c-bg {
1720 background-color: @alert3;
1720 background-color: @alert3;
1721 }
1721 }
1722
1722
1723 .color-r-bg {
1723 .color-r-bg {
1724 background-color: @alert2;
1724 background-color: @alert2;
1725 }
1725 }
1726
1726
1727 .color-a-border {
1727 .color-a-border {
1728 border: 1px solid @alert1;
1728 border: 1px solid @alert1;
1729 }
1729 }
1730
1730
1731 .color-c-border {
1731 .color-c-border {
1732 border: 1px solid @alert3;
1732 border: 1px solid @alert3;
1733 }
1733 }
1734
1734
1735 .color-r-border {
1735 .color-r-border {
1736 border: 1px solid @alert2;
1736 border: 1px solid @alert2;
1737 }
1737 }
1738
1738
1739 .commit-change-indicator {
1739 .commit-change-indicator {
1740 width: 15px;
1740 width: 15px;
1741 height: 15px;
1741 height: 15px;
1742 position: relative;
1742 position: relative;
1743 left: 15px;
1743 left: 15px;
1744 }
1744 }
1745
1745
1746 .commit-change-content {
1746 .commit-change-content {
1747 text-align: center;
1747 text-align: center;
1748 vertical-align: middle;
1748 vertical-align: middle;
1749 line-height: 15px;
1749 line-height: 15px;
1750 }
1750 }
1751 }
1751 }
1752
1752
1753 .compare_view_filepath {
1753 .compare_view_filepath {
1754 color: @grey1;
1754 color: @grey1;
1755 }
1755 }
1756
1756
1757 .show_more {
1757 .show_more {
1758 display: inline-block;
1758 display: inline-block;
1759 width: 0;
1759 width: 0;
1760 height: 0;
1760 height: 0;
1761 vertical-align: middle;
1761 vertical-align: middle;
1762 content: "";
1762 content: "";
1763 border: 4px solid;
1763 border: 4px solid;
1764 border-right-color: transparent;
1764 border-right-color: transparent;
1765 border-bottom-color: transparent;
1765 border-bottom-color: transparent;
1766 border-left-color: transparent;
1766 border-left-color: transparent;
1767 font-size: 0;
1767 font-size: 0;
1768 }
1768 }
1769
1769
1770 .journal_more .show_more {
1770 .journal_more .show_more {
1771 display: inline;
1771 display: inline;
1772
1772
1773 &:after {
1773 &:after {
1774 content: none;
1774 content: none;
1775 }
1775 }
1776 }
1776 }
1777
1777
1778 .compare_view_commits .collapse_commit:after {
1778 .compare_view_commits .collapse_commit:after {
1779 cursor: pointer;
1779 cursor: pointer;
1780 content: "\00A0\25B4";
1780 content: "\00A0\25B4";
1781 margin-left: -3px;
1781 margin-left: -3px;
1782 font-size: 17px;
1782 font-size: 17px;
1783 color: @grey4;
1783 color: @grey4;
1784 }
1784 }
1785
1785
1786 .diff_links {
1786 .diff_links {
1787 margin-left: 8px;
1787 margin-left: 8px;
1788 }
1788 }
1789
1789
1790 #pull_request_overview {
1790 #pull_request_overview {
1791 div.ancestor {
1791 div.ancestor {
1792 margin: -33px 0;
1792 margin: -33px 0;
1793 }
1793 }
1794 }
1794 }
1795
1795
1796 div.ancestor {
1796 div.ancestor {
1797 line-height: 33px;
1797 line-height: 33px;
1798 }
1798 }
1799
1799
1800 .cs_icon_td input[type="checkbox"] {
1800 .cs_icon_td input[type="checkbox"] {
1801 display: none;
1801 display: none;
1802 }
1802 }
1803
1803
1804 .cs_icon_td .expand_file_icon:after {
1804 .cs_icon_td .expand_file_icon:after {
1805 cursor: pointer;
1805 cursor: pointer;
1806 content: "\00A0\25B6";
1806 content: "\00A0\25B6";
1807 font-size: 12px;
1807 font-size: 12px;
1808 color: @grey4;
1808 color: @grey4;
1809 }
1809 }
1810
1810
1811 .cs_icon_td .collapse_file_icon:after {
1811 .cs_icon_td .collapse_file_icon:after {
1812 cursor: pointer;
1812 cursor: pointer;
1813 content: "\00A0\25BC";
1813 content: "\00A0\25BC";
1814 font-size: 12px;
1814 font-size: 12px;
1815 color: @grey4;
1815 color: @grey4;
1816 }
1816 }
1817
1817
1818 /*new binary
1818 /*new binary
1819 NEW_FILENODE = 1
1819 NEW_FILENODE = 1
1820 DEL_FILENODE = 2
1820 DEL_FILENODE = 2
1821 MOD_FILENODE = 3
1821 MOD_FILENODE = 3
1822 RENAMED_FILENODE = 4
1822 RENAMED_FILENODE = 4
1823 COPIED_FILENODE = 5
1823 COPIED_FILENODE = 5
1824 CHMOD_FILENODE = 6
1824 CHMOD_FILENODE = 6
1825 BIN_FILENODE = 7
1825 BIN_FILENODE = 7
1826 */
1826 */
1827 .cs_files_expand {
1827 .cs_files_expand {
1828 font-size: @basefontsize + 5px;
1828 font-size: @basefontsize + 5px;
1829 line-height: 1.8em;
1829 line-height: 1.8em;
1830 float: right;
1830 float: right;
1831 }
1831 }
1832
1832
1833 .cs_files_expand span{
1833 .cs_files_expand span{
1834 color: @rcblue;
1834 color: @rcblue;
1835 cursor: pointer;
1835 cursor: pointer;
1836 }
1836 }
1837 .cs_files {
1837 .cs_files {
1838 clear: both;
1838 clear: both;
1839 padding-bottom: @padding;
1839 padding-bottom: @padding;
1840
1840
1841 .cur_cs {
1841 .cur_cs {
1842 margin: 10px 2px;
1842 margin: 10px 2px;
1843 font-weight: bold;
1843 font-weight: bold;
1844 }
1844 }
1845
1845
1846 .node {
1846 .node {
1847 float: left;
1847 float: left;
1848 }
1848 }
1849
1849
1850 .changes {
1850 .changes {
1851 float: right;
1851 float: right;
1852 color: white;
1852 color: white;
1853 font-size: @basefontsize - 4px;
1853 font-size: @basefontsize - 4px;
1854 margin-top: 4px;
1854 margin-top: 4px;
1855 opacity: 0.6;
1855 opacity: 0.6;
1856 filter: Alpha(opacity=60); /* IE8 and earlier */
1856 filter: Alpha(opacity=60); /* IE8 and earlier */
1857
1857
1858 .added {
1858 .added {
1859 background-color: @alert1;
1859 background-color: @alert1;
1860 float: left;
1860 float: left;
1861 text-align: center;
1861 text-align: center;
1862 }
1862 }
1863
1863
1864 .deleted {
1864 .deleted {
1865 background-color: @alert2;
1865 background-color: @alert2;
1866 float: left;
1866 float: left;
1867 text-align: center;
1867 text-align: center;
1868 }
1868 }
1869
1869
1870 .bin {
1870 .bin {
1871 background-color: @alert1;
1871 background-color: @alert1;
1872 text-align: center;
1872 text-align: center;
1873 }
1873 }
1874
1874
1875 /*new binary*/
1875 /*new binary*/
1876 .bin.bin1 {
1876 .bin.bin1 {
1877 background-color: @alert1;
1877 background-color: @alert1;
1878 text-align: center;
1878 text-align: center;
1879 }
1879 }
1880
1880
1881 /*deleted binary*/
1881 /*deleted binary*/
1882 .bin.bin2 {
1882 .bin.bin2 {
1883 background-color: @alert2;
1883 background-color: @alert2;
1884 text-align: center;
1884 text-align: center;
1885 }
1885 }
1886
1886
1887 /*mod binary*/
1887 /*mod binary*/
1888 .bin.bin3 {
1888 .bin.bin3 {
1889 background-color: @grey2;
1889 background-color: @grey2;
1890 text-align: center;
1890 text-align: center;
1891 }
1891 }
1892
1892
1893 /*rename file*/
1893 /*rename file*/
1894 .bin.bin4 {
1894 .bin.bin4 {
1895 background-color: @alert4;
1895 background-color: @alert4;
1896 text-align: center;
1896 text-align: center;
1897 }
1897 }
1898
1898
1899 /*copied file*/
1899 /*copied file*/
1900 .bin.bin5 {
1900 .bin.bin5 {
1901 background-color: @alert4;
1901 background-color: @alert4;
1902 text-align: center;
1902 text-align: center;
1903 }
1903 }
1904
1904
1905 /*chmod file*/
1905 /*chmod file*/
1906 .bin.bin6 {
1906 .bin.bin6 {
1907 background-color: @grey2;
1907 background-color: @grey2;
1908 text-align: center;
1908 text-align: center;
1909 }
1909 }
1910 }
1910 }
1911 }
1911 }
1912
1912
1913 .cs_files .cs_added, .cs_files .cs_A,
1913 .cs_files .cs_added, .cs_files .cs_A,
1914 .cs_files .cs_added, .cs_files .cs_M,
1914 .cs_files .cs_added, .cs_files .cs_M,
1915 .cs_files .cs_added, .cs_files .cs_D {
1915 .cs_files .cs_added, .cs_files .cs_D {
1916 height: 16px;
1916 height: 16px;
1917 padding-right: 10px;
1917 padding-right: 10px;
1918 margin-top: 7px;
1918 margin-top: 7px;
1919 text-align: left;
1919 text-align: left;
1920 }
1920 }
1921
1921
1922 .cs_icon_td {
1922 .cs_icon_td {
1923 min-width: 16px;
1923 min-width: 16px;
1924 width: 16px;
1924 width: 16px;
1925 }
1925 }
1926
1926
1927 .pull-request-merge {
1927 .pull-request-merge {
1928 border: 1px solid @grey5;
1928 border: 1px solid @grey5;
1929 padding: 10px 0px 20px;
1929 padding: 10px 0px 20px;
1930 margin-top: 10px;
1930 margin-top: 10px;
1931 margin-bottom: 20px;
1931 margin-bottom: 20px;
1932 }
1932 }
1933
1933
1934 .pull-request-merge-refresh {
1934 .pull-request-merge-refresh {
1935 margin: 2px 7px;
1935 margin: 2px 7px;
1936 a {
1936 a {
1937 color: @grey3;
1937 color: @grey3;
1938 }
1938 }
1939 }
1939 }
1940
1940
1941 .pull-request-merge ul {
1941 .pull-request-merge ul {
1942 padding: 0px 0px;
1942 padding: 0px 0px;
1943 }
1943 }
1944
1944
1945 .pull-request-merge li {
1945 .pull-request-merge li {
1946 list-style-type: none;
1946 list-style-type: none;
1947 }
1947 }
1948
1948
1949 .pull-request-merge .pull-request-wrap {
1949 .pull-request-merge .pull-request-wrap {
1950 height: auto;
1950 height: auto;
1951 padding: 0px 0px;
1951 padding: 0px 0px;
1952 text-align: right;
1952 text-align: right;
1953 }
1953 }
1954
1954
1955 .pull-request-merge span {
1955 .pull-request-merge span {
1956 margin-right: 5px;
1956 margin-right: 5px;
1957 }
1957 }
1958
1958
1959 .pull-request-merge-actions {
1959 .pull-request-merge-actions {
1960 min-height: 30px;
1960 min-height: 30px;
1961 padding: 0px 0px;
1961 padding: 0px 0px;
1962 }
1962 }
1963
1963
1964 .pull-request-merge-info {
1964 .pull-request-merge-info {
1965 padding: 0px 5px 5px 0px;
1965 padding: 0px 5px 5px 0px;
1966 }
1966 }
1967
1967
1968 .merge-status {
1968 .merge-status {
1969 margin-right: 5px;
1969 margin-right: 5px;
1970 }
1970 }
1971
1971
1972 .merge-message {
1972 .merge-message {
1973 font-size: 1.2em
1973 font-size: 1.2em
1974 }
1974 }
1975
1975
1976 .merge-message.success i,
1976 .merge-message.success i,
1977 .merge-icon.success i {
1977 .merge-icon.success i {
1978 color:@alert1;
1978 color:@alert1;
1979 }
1979 }
1980
1980
1981 .merge-message.warning i,
1981 .merge-message.warning i,
1982 .merge-icon.warning i {
1982 .merge-icon.warning i {
1983 color: @alert3;
1983 color: @alert3;
1984 }
1984 }
1985
1985
1986 .merge-message.error i,
1986 .merge-message.error i,
1987 .merge-icon.error i {
1987 .merge-icon.error i {
1988 color:@alert2;
1988 color:@alert2;
1989 }
1989 }
1990
1990
1991 .pr-versions {
1991 .pr-versions {
1992 font-size: 1.1em;
1992 font-size: 1.1em;
1993 padding: 7.5px;
1993 padding: 7.5px;
1994
1994
1995 table {
1995 table {
1996
1996
1997 }
1997 }
1998
1998
1999 td {
1999 td {
2000 line-height: 15px;
2000 line-height: 15px;
2001 }
2001 }
2002
2002
2003 .compare-radio-button {
2003 .compare-radio-button {
2004 position: relative;
2004 position: relative;
2005 top: -3px;
2005 top: -3px;
2006 }
2006 }
2007 }
2007 }
2008
2008
2009
2009
2010 #close_pull_request {
2010 #close_pull_request {
2011 margin-right: 0px;
2011 margin-right: 0px;
2012 }
2012 }
2013
2013
2014 .empty_data {
2014 .empty_data {
2015 color: @grey4;
2015 color: @grey4;
2016 }
2016 }
2017
2017
2018 #changeset_compare_view_content {
2018 #changeset_compare_view_content {
2019 clear: both;
2019 clear: both;
2020 width: 100%;
2020 width: 100%;
2021 box-sizing: border-box;
2021 box-sizing: border-box;
2022 .border-radius(@border-radius);
2022 .border-radius(@border-radius);
2023
2023
2024 .help-block {
2024 .help-block {
2025 margin: @padding 0;
2025 margin: @padding 0;
2026 color: @text-color;
2026 color: @text-color;
2027 &.pre-formatting {
2027 &.pre-formatting {
2028 white-space: pre;
2028 white-space: pre;
2029 }
2029 }
2030 }
2030 }
2031
2031
2032 .empty_data {
2032 .empty_data {
2033 margin: @padding 0;
2033 margin: @padding 0;
2034 }
2034 }
2035
2035
2036 .alert {
2036 .alert {
2037 margin-bottom: @space;
2037 margin-bottom: @space;
2038 }
2038 }
2039 }
2039 }
2040
2040
2041 .table_disp {
2041 .table_disp {
2042 .status {
2042 .status {
2043 width: auto;
2043 width: auto;
2044 }
2044 }
2045 }
2045 }
2046
2046
2047
2047
2048 .creation_in_progress {
2048 .creation_in_progress {
2049 color: @grey4
2049 color: @grey4
2050 }
2050 }
2051
2051
2052 .status_box_menu {
2052 .status_box_menu {
2053 margin: 0;
2053 margin: 0;
2054 }
2054 }
2055
2055
2056 .notification-table{
2056 .notification-table{
2057 margin-bottom: @space;
2057 margin-bottom: @space;
2058 display: table;
2058 display: table;
2059 width: 100%;
2059 width: 100%;
2060
2060
2061 .container{
2061 .container{
2062 display: table-row;
2062 display: table-row;
2063
2063
2064 .notification-header{
2064 .notification-header{
2065 border-bottom: @border-thickness solid @border-default-color;
2065 border-bottom: @border-thickness solid @border-default-color;
2066 }
2066 }
2067
2067
2068 .notification-subject{
2068 .notification-subject{
2069 display: table-cell;
2069 display: table-cell;
2070 }
2070 }
2071 }
2071 }
2072 }
2072 }
2073
2073
2074 // Notifications
2074 // Notifications
2075 .notification-header{
2075 .notification-header{
2076 display: table;
2076 display: table;
2077 width: 100%;
2077 width: 100%;
2078 padding: floor(@basefontsize/2) 0;
2078 padding: floor(@basefontsize/2) 0;
2079 line-height: 1em;
2079 line-height: 1em;
2080
2080
2081 .desc, .delete-notifications, .read-notifications{
2081 .desc, .delete-notifications, .read-notifications{
2082 display: table-cell;
2082 display: table-cell;
2083 text-align: left;
2083 text-align: left;
2084 }
2084 }
2085
2085
2086 .delete-notifications, .read-notifications{
2086 .delete-notifications, .read-notifications{
2087 width: 35px;
2087 width: 35px;
2088 min-width: 35px; //fixes when only one button is displayed
2088 min-width: 35px; //fixes when only one button is displayed
2089 }
2089 }
2090 }
2090 }
2091
2091
2092 .notification-body {
2092 .notification-body {
2093 .markdown-block,
2093 .markdown-block,
2094 .rst-block {
2094 .rst-block {
2095 padding: @padding 0;
2095 padding: @padding 0;
2096 }
2096 }
2097
2097
2098 .notification-subject {
2098 .notification-subject {
2099 padding: @textmargin 0;
2099 padding: @textmargin 0;
2100 border-bottom: @border-thickness solid @border-default-color;
2100 border-bottom: @border-thickness solid @border-default-color;
2101 }
2101 }
2102 }
2102 }
2103
2103
2104 .notice-messages {
2105 .markdown-block,
2106 .rst-block {
2107 padding: 0;
2108 }
2109 }
2104
2110
2105 .notifications_buttons{
2111 .notifications_buttons{
2106 float: right;
2112 float: right;
2107 }
2113 }
2108
2114
2109 #notification-status{
2115 #notification-status{
2110 display: inline;
2116 display: inline;
2111 }
2117 }
2112
2118
2113 // Repositories
2119 // Repositories
2114
2120
2115 #summary.fields{
2121 #summary.fields{
2116 display: table;
2122 display: table;
2117
2123
2118 .field{
2124 .field{
2119 display: table-row;
2125 display: table-row;
2120
2126
2121 .label-summary{
2127 .label-summary{
2122 display: table-cell;
2128 display: table-cell;
2123 min-width: @label-summary-minwidth;
2129 min-width: @label-summary-minwidth;
2124 padding-top: @padding/2;
2130 padding-top: @padding/2;
2125 padding-bottom: @padding/2;
2131 padding-bottom: @padding/2;
2126 padding-right: @padding/2;
2132 padding-right: @padding/2;
2127 }
2133 }
2128
2134
2129 .input{
2135 .input{
2130 display: table-cell;
2136 display: table-cell;
2131 padding: @padding/2;
2137 padding: @padding/2;
2132
2138
2133 input{
2139 input{
2134 min-width: 29em;
2140 min-width: 29em;
2135 padding: @padding/4;
2141 padding: @padding/4;
2136 }
2142 }
2137 }
2143 }
2138 .statistics, .downloads{
2144 .statistics, .downloads{
2139 .disabled{
2145 .disabled{
2140 color: @grey4;
2146 color: @grey4;
2141 }
2147 }
2142 }
2148 }
2143 }
2149 }
2144 }
2150 }
2145
2151
2146 #summary{
2152 #summary{
2147 width: 70%;
2153 width: 70%;
2148 }
2154 }
2149
2155
2150
2156
2151 // Journal
2157 // Journal
2152 .journal.title {
2158 .journal.title {
2153 h5 {
2159 h5 {
2154 float: left;
2160 float: left;
2155 margin: 0;
2161 margin: 0;
2156 width: 70%;
2162 width: 70%;
2157 }
2163 }
2158
2164
2159 ul {
2165 ul {
2160 float: right;
2166 float: right;
2161 display: inline-block;
2167 display: inline-block;
2162 margin: 0;
2168 margin: 0;
2163 width: 30%;
2169 width: 30%;
2164 text-align: right;
2170 text-align: right;
2165
2171
2166 li {
2172 li {
2167 display: inline;
2173 display: inline;
2168 font-size: @journal-fontsize;
2174 font-size: @journal-fontsize;
2169 line-height: 1em;
2175 line-height: 1em;
2170
2176
2171 list-style-type: none;
2177 list-style-type: none;
2172 }
2178 }
2173 }
2179 }
2174 }
2180 }
2175
2181
2176 .filterexample {
2182 .filterexample {
2177 position: absolute;
2183 position: absolute;
2178 top: 95px;
2184 top: 95px;
2179 left: @contentpadding;
2185 left: @contentpadding;
2180 color: @rcblue;
2186 color: @rcblue;
2181 font-size: 11px;
2187 font-size: 11px;
2182 font-family: @text-regular;
2188 font-family: @text-regular;
2183 cursor: help;
2189 cursor: help;
2184
2190
2185 &:hover {
2191 &:hover {
2186 color: @rcdarkblue;
2192 color: @rcdarkblue;
2187 }
2193 }
2188
2194
2189 @media (max-width:768px) {
2195 @media (max-width:768px) {
2190 position: relative;
2196 position: relative;
2191 top: auto;
2197 top: auto;
2192 left: auto;
2198 left: auto;
2193 display: block;
2199 display: block;
2194 }
2200 }
2195 }
2201 }
2196
2202
2197
2203
2198 #journal{
2204 #journal{
2199 margin-bottom: @space;
2205 margin-bottom: @space;
2200
2206
2201 .journal_day{
2207 .journal_day{
2202 margin-bottom: @textmargin/2;
2208 margin-bottom: @textmargin/2;
2203 padding-bottom: @textmargin/2;
2209 padding-bottom: @textmargin/2;
2204 font-size: @journal-fontsize;
2210 font-size: @journal-fontsize;
2205 border-bottom: @border-thickness solid @border-default-color;
2211 border-bottom: @border-thickness solid @border-default-color;
2206 }
2212 }
2207
2213
2208 .journal_container{
2214 .journal_container{
2209 margin-bottom: @space;
2215 margin-bottom: @space;
2210
2216
2211 .journal_user{
2217 .journal_user{
2212 display: inline-block;
2218 display: inline-block;
2213 }
2219 }
2214 .journal_action_container{
2220 .journal_action_container{
2215 display: block;
2221 display: block;
2216 margin-top: @textmargin;
2222 margin-top: @textmargin;
2217
2223
2218 div{
2224 div{
2219 display: inline;
2225 display: inline;
2220 }
2226 }
2221
2227
2222 div.journal_action_params{
2228 div.journal_action_params{
2223 display: block;
2229 display: block;
2224 }
2230 }
2225
2231
2226 div.journal_repo:after{
2232 div.journal_repo:after{
2227 content: "\A";
2233 content: "\A";
2228 white-space: pre;
2234 white-space: pre;
2229 }
2235 }
2230
2236
2231 div.date{
2237 div.date{
2232 display: block;
2238 display: block;
2233 margin-bottom: @textmargin;
2239 margin-bottom: @textmargin;
2234 }
2240 }
2235 }
2241 }
2236 }
2242 }
2237 }
2243 }
2238
2244
2239 // Files
2245 // Files
2240 .edit-file-title {
2246 .edit-file-title {
2241 font-size: 16px;
2247 font-size: 16px;
2242
2248
2243 .title-heading {
2249 .title-heading {
2244 padding: 2px;
2250 padding: 2px;
2245 }
2251 }
2246 }
2252 }
2247
2253
2248 .edit-file-fieldset {
2254 .edit-file-fieldset {
2249 margin: @sidebarpadding 0;
2255 margin: @sidebarpadding 0;
2250
2256
2251 .fieldset {
2257 .fieldset {
2252 .left-label {
2258 .left-label {
2253 width: 13%;
2259 width: 13%;
2254 }
2260 }
2255 .right-content {
2261 .right-content {
2256 width: 87%;
2262 width: 87%;
2257 max-width: 100%;
2263 max-width: 100%;
2258 }
2264 }
2259 .filename-label {
2265 .filename-label {
2260 margin-top: 13px;
2266 margin-top: 13px;
2261 }
2267 }
2262 .commit-message-label {
2268 .commit-message-label {
2263 margin-top: 4px;
2269 margin-top: 4px;
2264 }
2270 }
2265 .file-upload-input {
2271 .file-upload-input {
2266 input {
2272 input {
2267 display: none;
2273 display: none;
2268 }
2274 }
2269 margin-top: 10px;
2275 margin-top: 10px;
2270 }
2276 }
2271 .file-upload-label {
2277 .file-upload-label {
2272 margin-top: 10px;
2278 margin-top: 10px;
2273 }
2279 }
2274 p {
2280 p {
2275 margin-top: 5px;
2281 margin-top: 5px;
2276 }
2282 }
2277
2283
2278 }
2284 }
2279 .custom-path-link {
2285 .custom-path-link {
2280 margin-left: 5px;
2286 margin-left: 5px;
2281 }
2287 }
2282 #commit {
2288 #commit {
2283 resize: vertical;
2289 resize: vertical;
2284 }
2290 }
2285 }
2291 }
2286
2292
2287 .delete-file-preview {
2293 .delete-file-preview {
2288 max-height: 250px;
2294 max-height: 250px;
2289 }
2295 }
2290
2296
2291 .new-file,
2297 .new-file,
2292 #filter_activate,
2298 #filter_activate,
2293 #filter_deactivate {
2299 #filter_deactivate {
2294 float: right;
2300 float: right;
2295 margin: 0 0 0 10px;
2301 margin: 0 0 0 10px;
2296 }
2302 }
2297
2303
2298 .file-upload-transaction-wrapper {
2304 .file-upload-transaction-wrapper {
2299 margin-top: 57px;
2305 margin-top: 57px;
2300 clear: both;
2306 clear: both;
2301 }
2307 }
2302
2308
2303 .file-upload-transaction-wrapper .error {
2309 .file-upload-transaction-wrapper .error {
2304 color: @color5;
2310 color: @color5;
2305 }
2311 }
2306
2312
2307 .file-upload-transaction {
2313 .file-upload-transaction {
2308 min-height: 200px;
2314 min-height: 200px;
2309 padding: 54px;
2315 padding: 54px;
2310 border: 1px solid @grey5;
2316 border: 1px solid @grey5;
2311 text-align: center;
2317 text-align: center;
2312 clear: both;
2318 clear: both;
2313 }
2319 }
2314
2320
2315 .file-upload-transaction i {
2321 .file-upload-transaction i {
2316 font-size: 48px
2322 font-size: 48px
2317 }
2323 }
2318
2324
2319 h3.files_location{
2325 h3.files_location{
2320 line-height: 2.4em;
2326 line-height: 2.4em;
2321 }
2327 }
2322
2328
2323 .browser-nav {
2329 .browser-nav {
2324 width: 100%;
2330 width: 100%;
2325 display: table;
2331 display: table;
2326 margin-bottom: 20px;
2332 margin-bottom: 20px;
2327
2333
2328 .info_box {
2334 .info_box {
2329 float: left;
2335 float: left;
2330 display: inline-table;
2336 display: inline-table;
2331 height: 2.5em;
2337 height: 2.5em;
2332
2338
2333 .browser-cur-rev, .info_box_elem {
2339 .browser-cur-rev, .info_box_elem {
2334 display: table-cell;
2340 display: table-cell;
2335 vertical-align: middle;
2341 vertical-align: middle;
2336 }
2342 }
2337
2343
2338 .drop-menu {
2344 .drop-menu {
2339 margin: 0 10px;
2345 margin: 0 10px;
2340 }
2346 }
2341
2347
2342 .info_box_elem {
2348 .info_box_elem {
2343 border-top: @border-thickness solid @grey5;
2349 border-top: @border-thickness solid @grey5;
2344 border-bottom: @border-thickness solid @grey5;
2350 border-bottom: @border-thickness solid @grey5;
2345 box-shadow: @button-shadow;
2351 box-shadow: @button-shadow;
2346
2352
2347 #at_rev, a {
2353 #at_rev, a {
2348 padding: 0.6em 0.4em;
2354 padding: 0.6em 0.4em;
2349 margin: 0;
2355 margin: 0;
2350 .box-shadow(none);
2356 .box-shadow(none);
2351 border: 0;
2357 border: 0;
2352 height: 12px;
2358 height: 12px;
2353 color: @grey2;
2359 color: @grey2;
2354 }
2360 }
2355
2361
2356 input#at_rev {
2362 input#at_rev {
2357 max-width: 50px;
2363 max-width: 50px;
2358 text-align: center;
2364 text-align: center;
2359 }
2365 }
2360
2366
2361 &.previous {
2367 &.previous {
2362 border: @border-thickness solid @grey5;
2368 border: @border-thickness solid @grey5;
2363 border-top-left-radius: @border-radius;
2369 border-top-left-radius: @border-radius;
2364 border-bottom-left-radius: @border-radius;
2370 border-bottom-left-radius: @border-radius;
2365
2371
2366 &:hover {
2372 &:hover {
2367 border-color: @grey4;
2373 border-color: @grey4;
2368 }
2374 }
2369
2375
2370 .disabled {
2376 .disabled {
2371 color: @grey5;
2377 color: @grey5;
2372 cursor: not-allowed;
2378 cursor: not-allowed;
2373 opacity: 0.5;
2379 opacity: 0.5;
2374 }
2380 }
2375 }
2381 }
2376
2382
2377 &.next {
2383 &.next {
2378 border: @border-thickness solid @grey5;
2384 border: @border-thickness solid @grey5;
2379 border-top-right-radius: @border-radius;
2385 border-top-right-radius: @border-radius;
2380 border-bottom-right-radius: @border-radius;
2386 border-bottom-right-radius: @border-radius;
2381
2387
2382 &:hover {
2388 &:hover {
2383 border-color: @grey4;
2389 border-color: @grey4;
2384 }
2390 }
2385
2391
2386 .disabled {
2392 .disabled {
2387 color: @grey5;
2393 color: @grey5;
2388 cursor: not-allowed;
2394 cursor: not-allowed;
2389 opacity: 0.5;
2395 opacity: 0.5;
2390 }
2396 }
2391 }
2397 }
2392 }
2398 }
2393
2399
2394 .browser-cur-rev {
2400 .browser-cur-rev {
2395
2401
2396 span{
2402 span{
2397 margin: 0;
2403 margin: 0;
2398 color: @rcblue;
2404 color: @rcblue;
2399 height: 12px;
2405 height: 12px;
2400 display: inline-block;
2406 display: inline-block;
2401 padding: 0.7em 1em ;
2407 padding: 0.7em 1em ;
2402 border: @border-thickness solid @rcblue;
2408 border: @border-thickness solid @rcblue;
2403 margin-right: @padding;
2409 margin-right: @padding;
2404 }
2410 }
2405 }
2411 }
2406
2412
2407 }
2413 }
2408
2414
2409 .select-index-number {
2415 .select-index-number {
2410 margin: 0 0 0 20px;
2416 margin: 0 0 0 20px;
2411 color: @grey3;
2417 color: @grey3;
2412 }
2418 }
2413
2419
2414 .search_activate {
2420 .search_activate {
2415 display: table-cell;
2421 display: table-cell;
2416 vertical-align: middle;
2422 vertical-align: middle;
2417
2423
2418 input, label{
2424 input, label{
2419 margin: 0;
2425 margin: 0;
2420 padding: 0;
2426 padding: 0;
2421 }
2427 }
2422
2428
2423 input{
2429 input{
2424 margin-left: @textmargin;
2430 margin-left: @textmargin;
2425 }
2431 }
2426
2432
2427 }
2433 }
2428 }
2434 }
2429
2435
2430 .browser-cur-rev{
2436 .browser-cur-rev{
2431 margin-bottom: @textmargin;
2437 margin-bottom: @textmargin;
2432 }
2438 }
2433
2439
2434 #node_filter_box_loading{
2440 #node_filter_box_loading{
2435 .info_text;
2441 .info_text;
2436 }
2442 }
2437
2443
2438 .browser-search {
2444 .browser-search {
2439 margin: -25px 0px 5px 0px;
2445 margin: -25px 0px 5px 0px;
2440 }
2446 }
2441
2447
2442 .files-quick-filter {
2448 .files-quick-filter {
2443 float: right;
2449 float: right;
2444 width: 180px;
2450 width: 180px;
2445 position: relative;
2451 position: relative;
2446 }
2452 }
2447
2453
2448 .files-filter-box {
2454 .files-filter-box {
2449 display: flex;
2455 display: flex;
2450 padding: 0px;
2456 padding: 0px;
2451 border-radius: 3px;
2457 border-radius: 3px;
2452 margin-bottom: 0;
2458 margin-bottom: 0;
2453
2459
2454 a {
2460 a {
2455 border: none !important;
2461 border: none !important;
2456 }
2462 }
2457
2463
2458 li {
2464 li {
2459 list-style-type: none
2465 list-style-type: none
2460 }
2466 }
2461 }
2467 }
2462
2468
2463 .files-filter-box-path {
2469 .files-filter-box-path {
2464 line-height: 33px;
2470 line-height: 33px;
2465 padding: 0;
2471 padding: 0;
2466 width: 20px;
2472 width: 20px;
2467 position: absolute;
2473 position: absolute;
2468 z-index: 11;
2474 z-index: 11;
2469 left: 5px;
2475 left: 5px;
2470 }
2476 }
2471
2477
2472 .files-filter-box-input {
2478 .files-filter-box-input {
2473 margin-right: 0;
2479 margin-right: 0;
2474
2480
2475 input {
2481 input {
2476 border: 1px solid @white;
2482 border: 1px solid @white;
2477 padding-left: 25px;
2483 padding-left: 25px;
2478 width: 145px;
2484 width: 145px;
2479
2485
2480 &:hover {
2486 &:hover {
2481 border-color: @grey6;
2487 border-color: @grey6;
2482 }
2488 }
2483
2489
2484 &:focus {
2490 &:focus {
2485 border-color: @grey5;
2491 border-color: @grey5;
2486 }
2492 }
2487 }
2493 }
2488 }
2494 }
2489
2495
2490 .browser-result{
2496 .browser-result{
2491 td a{
2497 td a{
2492 margin-left: 0.5em;
2498 margin-left: 0.5em;
2493 display: inline-block;
2499 display: inline-block;
2494
2500
2495 em {
2501 em {
2496 font-weight: @text-bold-weight;
2502 font-weight: @text-bold-weight;
2497 font-family: @text-bold;
2503 font-family: @text-bold;
2498 }
2504 }
2499 }
2505 }
2500 }
2506 }
2501
2507
2502 .browser-highlight{
2508 .browser-highlight{
2503 background-color: @grey5-alpha;
2509 background-color: @grey5-alpha;
2504 }
2510 }
2505
2511
2506
2512
2507 .edit-file-fieldset #location,
2513 .edit-file-fieldset #location,
2508 .edit-file-fieldset #filename {
2514 .edit-file-fieldset #filename {
2509 display: flex;
2515 display: flex;
2510 width: -moz-available; /* WebKit-based browsers will ignore this. */
2516 width: -moz-available; /* WebKit-based browsers will ignore this. */
2511 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2517 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2512 width: fill-available;
2518 width: fill-available;
2513 border: 0;
2519 border: 0;
2514 }
2520 }
2515
2521
2516 .path-items {
2522 .path-items {
2517 display: flex;
2523 display: flex;
2518 padding: 0;
2524 padding: 0;
2519 border: 1px solid #eeeeee;
2525 border: 1px solid #eeeeee;
2520 width: 100%;
2526 width: 100%;
2521 float: left;
2527 float: left;
2522
2528
2523 .breadcrumb-path {
2529 .breadcrumb-path {
2524 line-height: 30px;
2530 line-height: 30px;
2525 padding: 0 4px;
2531 padding: 0 4px;
2526 white-space: nowrap;
2532 white-space: nowrap;
2527 }
2533 }
2528
2534
2529 .location-path {
2535 .location-path {
2530 width: -moz-available; /* WebKit-based browsers will ignore this. */
2536 width: -moz-available; /* WebKit-based browsers will ignore this. */
2531 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2537 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2532 width: fill-available;
2538 width: fill-available;
2533
2539
2534 .file-name-input {
2540 .file-name-input {
2535 padding: 0.5em 0;
2541 padding: 0.5em 0;
2536 }
2542 }
2537
2543
2538 }
2544 }
2539
2545
2540 ul {
2546 ul {
2541 display: flex;
2547 display: flex;
2542 margin: 0;
2548 margin: 0;
2543 padding: 0;
2549 padding: 0;
2544 width: 100%;
2550 width: 100%;
2545 }
2551 }
2546
2552
2547 li {
2553 li {
2548 list-style-type: none;
2554 list-style-type: none;
2549 }
2555 }
2550
2556
2551 }
2557 }
2552
2558
2553 .editor-items {
2559 .editor-items {
2554 height: 40px;
2560 height: 40px;
2555 margin: 10px 0 -17px 10px;
2561 margin: 10px 0 -17px 10px;
2556
2562
2557 .editor-action {
2563 .editor-action {
2558 cursor: pointer;
2564 cursor: pointer;
2559 }
2565 }
2560
2566
2561 .editor-action.active {
2567 .editor-action.active {
2562 border-bottom: 2px solid #5C5C5C;
2568 border-bottom: 2px solid #5C5C5C;
2563 }
2569 }
2564
2570
2565 li {
2571 li {
2566 list-style-type: none;
2572 list-style-type: none;
2567 }
2573 }
2568 }
2574 }
2569
2575
2570 .edit-file-fieldset .message textarea {
2576 .edit-file-fieldset .message textarea {
2571 border: 1px solid #eeeeee;
2577 border: 1px solid #eeeeee;
2572 }
2578 }
2573
2579
2574 #files_data .codeblock {
2580 #files_data .codeblock {
2575 background-color: #F5F5F5;
2581 background-color: #F5F5F5;
2576 }
2582 }
2577
2583
2578 #editor_preview {
2584 #editor_preview {
2579 background: white;
2585 background: white;
2580 }
2586 }
2581
2587
2582 .show-editor {
2588 .show-editor {
2583 padding: 10px;
2589 padding: 10px;
2584 background-color: white;
2590 background-color: white;
2585
2591
2586 }
2592 }
2587
2593
2588 .show-preview {
2594 .show-preview {
2589 padding: 10px;
2595 padding: 10px;
2590 background-color: white;
2596 background-color: white;
2591 border-left: 1px solid #eeeeee;
2597 border-left: 1px solid #eeeeee;
2592 }
2598 }
2593 // quick filter
2599 // quick filter
2594 .grid-quick-filter {
2600 .grid-quick-filter {
2595 float: right;
2601 float: right;
2596 position: relative;
2602 position: relative;
2597 }
2603 }
2598
2604
2599 .grid-filter-box {
2605 .grid-filter-box {
2600 display: flex;
2606 display: flex;
2601 padding: 0px;
2607 padding: 0px;
2602 border-radius: 3px;
2608 border-radius: 3px;
2603 margin-bottom: 0;
2609 margin-bottom: 0;
2604
2610
2605 a {
2611 a {
2606 border: none !important;
2612 border: none !important;
2607 }
2613 }
2608
2614
2609 li {
2615 li {
2610 list-style-type: none
2616 list-style-type: none
2611 }
2617 }
2612 }
2618 }
2613
2619
2614 .grid-filter-box-icon {
2620 .grid-filter-box-icon {
2615 line-height: 33px;
2621 line-height: 33px;
2616 padding: 0;
2622 padding: 0;
2617 width: 20px;
2623 width: 20px;
2618 position: absolute;
2624 position: absolute;
2619 z-index: 11;
2625 z-index: 11;
2620 left: 5px;
2626 left: 5px;
2621 }
2627 }
2622
2628
2623 .grid-filter-box-input {
2629 .grid-filter-box-input {
2624 margin-right: 0;
2630 margin-right: 0;
2625
2631
2626 input {
2632 input {
2627 border: 1px solid @white;
2633 border: 1px solid @white;
2628 padding-left: 25px;
2634 padding-left: 25px;
2629 width: 145px;
2635 width: 145px;
2630
2636
2631 &:hover {
2637 &:hover {
2632 border-color: @grey6;
2638 border-color: @grey6;
2633 }
2639 }
2634
2640
2635 &:focus {
2641 &:focus {
2636 border-color: @grey5;
2642 border-color: @grey5;
2637 }
2643 }
2638 }
2644 }
2639 }
2645 }
2640
2646
2641
2647
2642
2648
2643 // Search
2649 // Search
2644
2650
2645 .search-form{
2651 .search-form{
2646 #q {
2652 #q {
2647 width: @search-form-width;
2653 width: @search-form-width;
2648 }
2654 }
2649 .fields{
2655 .fields{
2650 margin: 0 0 @space;
2656 margin: 0 0 @space;
2651 }
2657 }
2652
2658
2653 label{
2659 label{
2654 display: inline-block;
2660 display: inline-block;
2655 margin-right: @textmargin;
2661 margin-right: @textmargin;
2656 padding-top: 0.25em;
2662 padding-top: 0.25em;
2657 }
2663 }
2658
2664
2659
2665
2660 .results{
2666 .results{
2661 clear: both;
2667 clear: both;
2662 margin: 0 0 @padding;
2668 margin: 0 0 @padding;
2663 }
2669 }
2664
2670
2665 .search-tags {
2671 .search-tags {
2666 padding: 5px 0;
2672 padding: 5px 0;
2667 }
2673 }
2668 }
2674 }
2669
2675
2670 div.search-feedback-items {
2676 div.search-feedback-items {
2671 display: inline-block;
2677 display: inline-block;
2672 }
2678 }
2673
2679
2674 div.search-code-body {
2680 div.search-code-body {
2675 background-color: #ffffff; padding: 5px 0 5px 10px;
2681 background-color: #ffffff; padding: 5px 0 5px 10px;
2676 pre {
2682 pre {
2677 .match { background-color: #faffa6;}
2683 .match { background-color: #faffa6;}
2678 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2684 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2679 }
2685 }
2680 }
2686 }
2681
2687
2682 .expand_commit.search {
2688 .expand_commit.search {
2683 .show_more.open {
2689 .show_more.open {
2684 height: auto;
2690 height: auto;
2685 max-height: none;
2691 max-height: none;
2686 }
2692 }
2687 }
2693 }
2688
2694
2689 .search-results {
2695 .search-results {
2690
2696
2691 h2 {
2697 h2 {
2692 margin-bottom: 0;
2698 margin-bottom: 0;
2693 }
2699 }
2694 .codeblock {
2700 .codeblock {
2695 border: none;
2701 border: none;
2696 background: transparent;
2702 background: transparent;
2697 }
2703 }
2698
2704
2699 .codeblock-header {
2705 .codeblock-header {
2700 border: none;
2706 border: none;
2701 background: transparent;
2707 background: transparent;
2702 }
2708 }
2703
2709
2704 .code-body {
2710 .code-body {
2705 border: @border-thickness solid @grey6;
2711 border: @border-thickness solid @grey6;
2706 .border-radius(@border-radius);
2712 .border-radius(@border-radius);
2707 }
2713 }
2708
2714
2709 .td-commit {
2715 .td-commit {
2710 &:extend(pre);
2716 &:extend(pre);
2711 border-bottom: @border-thickness solid @border-default-color;
2717 border-bottom: @border-thickness solid @border-default-color;
2712 }
2718 }
2713
2719
2714 .message {
2720 .message {
2715 height: auto;
2721 height: auto;
2716 max-width: 350px;
2722 max-width: 350px;
2717 white-space: normal;
2723 white-space: normal;
2718 text-overflow: initial;
2724 text-overflow: initial;
2719 overflow: visible;
2725 overflow: visible;
2720
2726
2721 .match { background-color: #faffa6;}
2727 .match { background-color: #faffa6;}
2722 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2728 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2723 }
2729 }
2724
2730
2725 .path {
2731 .path {
2726 border-bottom: none !important;
2732 border-bottom: none !important;
2727 border-left: 1px solid @grey6 !important;
2733 border-left: 1px solid @grey6 !important;
2728 border-right: 1px solid @grey6 !important;
2734 border-right: 1px solid @grey6 !important;
2729 }
2735 }
2730 }
2736 }
2731
2737
2732 table.rctable td.td-search-results div {
2738 table.rctable td.td-search-results div {
2733 max-width: 100%;
2739 max-width: 100%;
2734 }
2740 }
2735
2741
2736 #tip-box, .tip-box{
2742 #tip-box, .tip-box{
2737 padding: @menupadding/2;
2743 padding: @menupadding/2;
2738 display: block;
2744 display: block;
2739 border: @border-thickness solid @border-highlight-color;
2745 border: @border-thickness solid @border-highlight-color;
2740 .border-radius(@border-radius);
2746 .border-radius(@border-radius);
2741 background-color: white;
2747 background-color: white;
2742 z-index: 99;
2748 z-index: 99;
2743 white-space: pre-wrap;
2749 white-space: pre-wrap;
2744 }
2750 }
2745
2751
2746 #linktt {
2752 #linktt {
2747 width: 79px;
2753 width: 79px;
2748 }
2754 }
2749
2755
2750 #help_kb .modal-content{
2756 #help_kb .modal-content{
2751 max-width: 750px;
2757 max-width: 750px;
2752 margin: 10% auto;
2758 margin: 10% auto;
2753
2759
2754 table{
2760 table{
2755 td,th{
2761 td,th{
2756 border-bottom: none;
2762 border-bottom: none;
2757 line-height: 2.5em;
2763 line-height: 2.5em;
2758 }
2764 }
2759 th{
2765 th{
2760 padding-bottom: @textmargin/2;
2766 padding-bottom: @textmargin/2;
2761 }
2767 }
2762 td.keys{
2768 td.keys{
2763 text-align: center;
2769 text-align: center;
2764 }
2770 }
2765 }
2771 }
2766
2772
2767 .block-left{
2773 .block-left{
2768 width: 45%;
2774 width: 45%;
2769 margin-right: 5%;
2775 margin-right: 5%;
2770 }
2776 }
2771 .modal-footer{
2777 .modal-footer{
2772 clear: both;
2778 clear: both;
2773 }
2779 }
2774 .key.tag{
2780 .key.tag{
2775 padding: 0.5em;
2781 padding: 0.5em;
2776 background-color: @rcblue;
2782 background-color: @rcblue;
2777 color: white;
2783 color: white;
2778 border-color: @rcblue;
2784 border-color: @rcblue;
2779 .box-shadow(none);
2785 .box-shadow(none);
2780 }
2786 }
2781 }
2787 }
2782
2788
2783
2789
2784
2790
2785 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2791 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2786
2792
2787 @import 'statistics-graph';
2793 @import 'statistics-graph';
2788 @import 'tables';
2794 @import 'tables';
2789 @import 'forms';
2795 @import 'forms';
2790 @import 'diff';
2796 @import 'diff';
2791 @import 'summary';
2797 @import 'summary';
2792 @import 'navigation';
2798 @import 'navigation';
2793
2799
2794 //--- SHOW/HIDE SECTIONS --//
2800 //--- SHOW/HIDE SECTIONS --//
2795
2801
2796 .btn-collapse {
2802 .btn-collapse {
2797 float: right;
2803 float: right;
2798 text-align: right;
2804 text-align: right;
2799 font-family: @text-light;
2805 font-family: @text-light;
2800 font-size: @basefontsize;
2806 font-size: @basefontsize;
2801 cursor: pointer;
2807 cursor: pointer;
2802 border: none;
2808 border: none;
2803 color: @rcblue;
2809 color: @rcblue;
2804 }
2810 }
2805
2811
2806 table.rctable,
2812 table.rctable,
2807 table.dataTable {
2813 table.dataTable {
2808 .btn-collapse {
2814 .btn-collapse {
2809 float: right;
2815 float: right;
2810 text-align: right;
2816 text-align: right;
2811 }
2817 }
2812 }
2818 }
2813
2819
2814 table.rctable {
2820 table.rctable {
2815 &.permissions {
2821 &.permissions {
2816
2822
2817 th.td-owner {
2823 th.td-owner {
2818 padding: 0;
2824 padding: 0;
2819 }
2825 }
2820
2826
2821 th {
2827 th {
2822 font-weight: normal;
2828 font-weight: normal;
2823 padding: 0 5px;
2829 padding: 0 5px;
2824 }
2830 }
2825
2831
2826 }
2832 }
2827 }
2833 }
2828
2834
2829
2835
2830 // TODO: johbo: Fix for IE10, this avoids that we see a border
2836 // TODO: johbo: Fix for IE10, this avoids that we see a border
2831 // and padding around checkboxes and radio boxes. Move to the right place,
2837 // and padding around checkboxes and radio boxes. Move to the right place,
2832 // or better: Remove this once we did the form refactoring.
2838 // or better: Remove this once we did the form refactoring.
2833 input[type=checkbox],
2839 input[type=checkbox],
2834 input[type=radio] {
2840 input[type=radio] {
2835 padding: 0;
2841 padding: 0;
2836 border: none;
2842 border: none;
2837 }
2843 }
2838
2844
2839 .toggle-ajax-spinner{
2845 .toggle-ajax-spinner{
2840 height: 16px;
2846 height: 16px;
2841 width: 16px;
2847 width: 16px;
2842 }
2848 }
2843
2849
2844
2850
2845 .markup-form .clearfix {
2851 .markup-form .clearfix {
2846 .border-radius(@border-radius);
2852 .border-radius(@border-radius);
2847 margin: 0px;
2853 margin: 0px;
2848 }
2854 }
2849
2855
2850 .markup-form-area {
2856 .markup-form-area {
2851 padding: 8px 12px;
2857 padding: 8px 12px;
2852 border: 1px solid @grey4;
2858 border: 1px solid @grey4;
2853 .border-radius(@border-radius);
2859 .border-radius(@border-radius);
2854 }
2860 }
2855
2861
2856 .markup-form-area-header .nav-links {
2862 .markup-form-area-header .nav-links {
2857 display: flex;
2863 display: flex;
2858 flex-flow: row wrap;
2864 flex-flow: row wrap;
2859 -webkit-flex-flow: row wrap;
2865 -webkit-flex-flow: row wrap;
2860 width: 100%;
2866 width: 100%;
2861 }
2867 }
2862
2868
2863 .markup-form-area-footer {
2869 .markup-form-area-footer {
2864 display: flex;
2870 display: flex;
2865 }
2871 }
2866
2872
2867 .markup-form-area-footer .toolbar {
2873 .markup-form-area-footer .toolbar {
2868
2874
2869 }
2875 }
2870
2876
2871 // markup Form
2877 // markup Form
2872 div.markup-form {
2878 div.markup-form {
2873 margin-top: 20px;
2879 margin-top: 20px;
2874 }
2880 }
2875
2881
2876 .markup-form strong {
2882 .markup-form strong {
2877 display: block;
2883 display: block;
2878 margin-bottom: 15px;
2884 margin-bottom: 15px;
2879 }
2885 }
2880
2886
2881 .markup-form textarea {
2887 .markup-form textarea {
2882 width: 100%;
2888 width: 100%;
2883 height: 100px;
2889 height: 100px;
2884 font-family: @text-monospace;
2890 font-family: @text-monospace;
2885 }
2891 }
2886
2892
2887 form.markup-form {
2893 form.markup-form {
2888 margin-top: 10px;
2894 margin-top: 10px;
2889 margin-left: 10px;
2895 margin-left: 10px;
2890 }
2896 }
2891
2897
2892 .markup-form .comment-block-ta,
2898 .markup-form .comment-block-ta,
2893 .markup-form .preview-box {
2899 .markup-form .preview-box {
2894 .border-radius(@border-radius);
2900 .border-radius(@border-radius);
2895 .box-sizing(border-box);
2901 .box-sizing(border-box);
2896 background-color: white;
2902 background-color: white;
2897 }
2903 }
2898
2904
2899 .markup-form .preview-box.unloaded {
2905 .markup-form .preview-box.unloaded {
2900 height: 50px;
2906 height: 50px;
2901 text-align: center;
2907 text-align: center;
2902 padding: 20px;
2908 padding: 20px;
2903 background-color: white;
2909 background-color: white;
2904 }
2910 }
2905
2911
2906
2912
2907 .dropzone-wrapper {
2913 .dropzone-wrapper {
2908 border: 1px solid @grey5;
2914 border: 1px solid @grey5;
2909 padding: 20px;
2915 padding: 20px;
2910 }
2916 }
2911
2917
2912 .dropzone,
2918 .dropzone,
2913 .dropzone-pure {
2919 .dropzone-pure {
2914 border: 2px dashed @grey5;
2920 border: 2px dashed @grey5;
2915 border-radius: 5px;
2921 border-radius: 5px;
2916 background: white;
2922 background: white;
2917 min-height: 200px;
2923 min-height: 200px;
2918 padding: 54px;
2924 padding: 54px;
2919
2925
2920 .dz-message {
2926 .dz-message {
2921 font-weight: 700;
2927 font-weight: 700;
2922 text-align: center;
2928 text-align: center;
2923 margin: 2em 0;
2929 margin: 2em 0;
2924 }
2930 }
2925
2931
2926 }
2932 }
2927
2933
2928 .dz-preview {
2934 .dz-preview {
2929 margin: 10px 0 !important;
2935 margin: 10px 0 !important;
2930 position: relative;
2936 position: relative;
2931 vertical-align: top;
2937 vertical-align: top;
2932 padding: 10px;
2938 padding: 10px;
2933 border-bottom: 1px solid @grey5;
2939 border-bottom: 1px solid @grey5;
2934 }
2940 }
2935
2941
2936 .dz-filename {
2942 .dz-filename {
2937 font-weight: 700;
2943 font-weight: 700;
2938 float: left;
2944 float: left;
2939 }
2945 }
2940
2946
2941 .dz-sending {
2947 .dz-sending {
2942 float: right;
2948 float: right;
2943 }
2949 }
2944
2950
2945 .dz-response {
2951 .dz-response {
2946 clear: both
2952 clear: both
2947 }
2953 }
2948
2954
2949 .dz-filename-size {
2955 .dz-filename-size {
2950 float: right
2956 float: right
2951 }
2957 }
2952
2958
2953 .dz-error-message {
2959 .dz-error-message {
2954 color: @alert2;
2960 color: @alert2;
2955 padding-top: 10px;
2961 padding-top: 10px;
2956 clear: both;
2962 clear: both;
2957 }
2963 }
2958
2964
2959
2965
2960 .user-hovercard {
2966 .user-hovercard {
2961 padding: 5px;
2967 padding: 5px;
2962 }
2968 }
2963
2969
2964 .user-hovercard-icon {
2970 .user-hovercard-icon {
2965 display: inline;
2971 display: inline;
2966 padding: 0;
2972 padding: 0;
2967 box-sizing: content-box;
2973 box-sizing: content-box;
2968 border-radius: 50%;
2974 border-radius: 50%;
2969 float: left;
2975 float: left;
2970 }
2976 }
2971
2977
2972 .user-hovercard-name {
2978 .user-hovercard-name {
2973 float: right;
2979 float: right;
2974 vertical-align: top;
2980 vertical-align: top;
2975 padding-left: 10px;
2981 padding-left: 10px;
2976 min-width: 150px;
2982 min-width: 150px;
2977 }
2983 }
2978
2984
2979 .user-hovercard-bio {
2985 .user-hovercard-bio {
2980 clear: both;
2986 clear: both;
2981 padding-top: 10px;
2987 padding-top: 10px;
2982 }
2988 }
2983
2989
2984 .user-hovercard-header {
2990 .user-hovercard-header {
2985 clear: both;
2991 clear: both;
2986 min-height: 10px;
2992 min-height: 10px;
2987 }
2993 }
2988
2994
2989 .user-hovercard-footer {
2995 .user-hovercard-footer {
2990 clear: both;
2996 clear: both;
2991 min-height: 10px;
2997 min-height: 10px;
2992 }
2998 }
2993
2999
2994 .user-group-hovercard {
3000 .user-group-hovercard {
2995 padding: 5px;
3001 padding: 5px;
2996 }
3002 }
2997
3003
2998 .user-group-hovercard-icon {
3004 .user-group-hovercard-icon {
2999 display: inline;
3005 display: inline;
3000 padding: 0;
3006 padding: 0;
3001 box-sizing: content-box;
3007 box-sizing: content-box;
3002 border-radius: 50%;
3008 border-radius: 50%;
3003 float: left;
3009 float: left;
3004 }
3010 }
3005
3011
3006 .user-group-hovercard-name {
3012 .user-group-hovercard-name {
3007 float: left;
3013 float: left;
3008 vertical-align: top;
3014 vertical-align: top;
3009 padding-left: 10px;
3015 padding-left: 10px;
3010 min-width: 150px;
3016 min-width: 150px;
3011 }
3017 }
3012
3018
3013 .user-group-hovercard-icon i {
3019 .user-group-hovercard-icon i {
3014 border: 1px solid @grey4;
3020 border: 1px solid @grey4;
3015 border-radius: 4px;
3021 border-radius: 4px;
3016 }
3022 }
3017
3023
3018 .user-group-hovercard-bio {
3024 .user-group-hovercard-bio {
3019 clear: both;
3025 clear: both;
3020 padding-top: 10px;
3026 padding-top: 10px;
3021 line-height: 1.0em;
3027 line-height: 1.0em;
3022 }
3028 }
3023
3029
3024 .user-group-hovercard-header {
3030 .user-group-hovercard-header {
3025 clear: both;
3031 clear: both;
3026 min-height: 10px;
3032 min-height: 10px;
3027 }
3033 }
3028
3034
3029 .user-group-hovercard-footer {
3035 .user-group-hovercard-footer {
3030 clear: both;
3036 clear: both;
3031 min-height: 10px;
3037 min-height: 10px;
3032 }
3038 }
3033
3039
3034 .pr-hovercard-header {
3040 .pr-hovercard-header {
3035 clear: both;
3041 clear: both;
3036 display: block;
3042 display: block;
3037 line-height: 20px;
3043 line-height: 20px;
3038 }
3044 }
3039
3045
3040 .pr-hovercard-user {
3046 .pr-hovercard-user {
3041 display: flex;
3047 display: flex;
3042 align-items: center;
3048 align-items: center;
3043 padding-left: 5px;
3049 padding-left: 5px;
3044 }
3050 }
3045
3051
3046 .pr-hovercard-title {
3052 .pr-hovercard-title {
3047 padding-top: 5px;
3053 padding-top: 5px;
3048 } No newline at end of file
3054 }
@@ -1,826 +1,872 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // TOP MAIN DARK NAVIGATION
5 // TOP MAIN DARK NAVIGATION
6
6
7 .header .main_nav.horizontal-list {
7 .header .main_nav.horizontal-list {
8 float: right;
8 float: right;
9 color: @grey4;
9 color: @grey4;
10 > li {
10 > li {
11 a {
11 a {
12 color: @grey4;
12 color: @grey4;
13 }
13 }
14 }
14 }
15 }
15 }
16
16
17 // HEADER NAVIGATION
17 // HEADER NAVIGATION
18
18
19 .horizontal-list {
19 .horizontal-list {
20 display: block;
20 display: block;
21 margin: 0;
21 margin: 0;
22 padding: 0;
22 padding: 0;
23 -webkit-padding-start: 0;
23 -webkit-padding-start: 0;
24 text-align: left;
24 text-align: left;
25 font-size: @navigation-fontsize;
25 font-size: @navigation-fontsize;
26 color: @grey6;
26 color: @grey6;
27 z-index:10;
27 z-index:10;
28
28
29 li {
29 li {
30 line-height: 1em;
30 line-height: 1em;
31 list-style-type: none;
31 list-style-type: none;
32 margin: 0 20px 0 0;
32 margin: 0 20px 0 0;
33
33
34 a {
34 a {
35 padding: 0 .5em;
35 padding: 0 .5em;
36
36
37 &.menu_link_notifications {
37 &.menu_link_notifications {
38 .pill(7px,@rcblue);
38 .pill(7px,@rcblue);
39 display: inline;
39 display: inline;
40 margin: 0 7px 0 .7em;
40 margin: 0 7px 0 .7em;
41 font-size: @basefontsize;
41 font-size: @basefontsize;
42 color: white;
42 color: white;
43
43
44 &.empty {
44 &.empty {
45 background-color: @grey4;
45 background-color: @grey4;
46 }
46 }
47
47
48 &:hover {
48 &:hover {
49 background-color: @rcdarkblue;
49 background-color: @rcdarkblue;
50 }
50 }
51 }
51 }
52 }
52 }
53 .pill_container {
53 .pill_container {
54 margin: 1.25em 0px 0px 0px;
54 margin: 1.25em 0px 0px 0px;
55 float: right;
55 float: right;
56 }
56 }
57
57
58 &#quick_login_li {
58 &#quick_login_li {
59 &:hover {
59 &:hover {
60 color: @grey5;
60 color: @grey5;
61 }
61 }
62
62
63 a.menu_link_notifications {
63 a.menu_link_notifications {
64 color: white;
64 color: white;
65 }
65 }
66
66
67 .user {
67 .user {
68 padding-bottom: 10px;
68 padding-bottom: 10px;
69 }
69 }
70 }
70 }
71
71
72 &:before { content: none; }
72 &:before { content: none; }
73
73
74 &:last-child {
74 &:last-child {
75 .menulabel {
75 .menulabel {
76 padding-right: 0;
76 padding-right: 0;
77 border-right: none;
77 border-right: none;
78
78
79 .show_more {
79 .show_more {
80 padding-right: 0;
80 padding-right: 0;
81 }
81 }
82 }
82 }
83
83
84 &> a {
84 &> a {
85 border-bottom: none;
85 border-bottom: none;
86 }
86 }
87 }
87 }
88
88
89 &.open {
89 &.open {
90
90
91 a {
91 a {
92 color: white;
92 color: white;
93 }
93 }
94 }
94 }
95
95
96 &:focus {
96 &:focus {
97 outline: none;
97 outline: none;
98 }
98 }
99
99
100 ul li {
100 ul li {
101 display: block;
101 display: block;
102
102
103 &:last-child> a {
103 &:last-child> a {
104 border-bottom: none;
104 border-bottom: none;
105 }
105 }
106
106
107 ul li:last-child a {
107 ul li:last-child a {
108 /* we don't expect more then 3 levels of submenu and the third
108 /* we don't expect more then 3 levels of submenu and the third
109 level can have different html structure */
109 level can have different html structure */
110 border-bottom: none;
110 border-bottom: none;
111 }
111 }
112 }
112 }
113 }
113 }
114
114
115 > li {
115 > li {
116 float: left;
116 float: left;
117 display: block;
117 display: block;
118 padding: 0;
118 padding: 0;
119
119
120 > a,
120 > a,
121 &.has_select2 a {
121 &.has_select2 a {
122 display: block;
122 display: block;
123 padding: 10px 0;
123 padding: 10px 0;
124 }
124 }
125
125
126 .menulabel {
126 .menulabel {
127 line-height: 1em;
127 line-height: 1em;
128 // for this specifically we do not use a variable
128 // for this specifically we do not use a variable
129 }
129 }
130
130
131 .menulink-counter {
131 .menulink-counter {
132 border: 1px solid @grey2;
132 border: 1px solid @grey2;
133 border-radius: @border-radius;
133 border-radius: @border-radius;
134 background: @grey7;
134 background: @grey7;
135 display: inline-block;
135 display: inline-block;
136 padding: 0px 4px;
136 padding: 0px 4px;
137 text-align: center;
137 text-align: center;
138 font-size: 12px;
138 font-size: 12px;
139 }
139 }
140
140
141 .pr_notifications {
141 .pr_notifications {
142 padding-left: .5em;
142 padding-left: .5em;
143 }
143 }
144
144
145 .pr_notifications + .menulabel {
145 .pr_notifications + .menulabel {
146 display:inline;
146 display:inline;
147 padding-left: 0;
147 padding-left: 0;
148 }
148 }
149
149
150 &:hover,
150 &:hover,
151 &.open,
151 &.open,
152 &.active {
152 &.active {
153 a {
153 a {
154 color: @rcblue;
154 color: @rcblue;
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 pre {
159 pre {
160 margin: 0;
160 margin: 0;
161 padding: 0;
161 padding: 0;
162 }
162 }
163
163
164 .select2-container,
164 .select2-container,
165 .menulink.childs {
165 .menulink.childs {
166 position: relative;
166 position: relative;
167 }
167 }
168
168
169 .menulink {
169 .menulink {
170 &.disabled {
170 &.disabled {
171 color: @grey3;
171 color: @grey3;
172 cursor: default;
172 cursor: default;
173 opacity: 0.5;
173 opacity: 0.5;
174 }
174 }
175 }
175 }
176
176
177 #quick_login {
177 #quick_login {
178
178
179 li a {
179 li a {
180 padding: .5em 0;
180 padding: .5em 0;
181 border-bottom: none;
181 border-bottom: none;
182 color: @grey2;
182 color: @grey2;
183
183
184 &:hover { color: @rcblue; }
184 &:hover { color: @rcblue; }
185 }
185 }
186 }
186 }
187
187
188 #quick_login_link {
188 #quick_login_link {
189 display: inline-block;
189 display: inline-block;
190
190
191 .gravatar {
191 .gravatar {
192 border: 1px solid @grey5;
192 border: 1px solid @grey5;
193 }
193 }
194
194
195 .gravatar-login {
195 .gravatar-login {
196 height: 20px;
196 height: 20px;
197 width: 20px;
197 width: 20px;
198 margin: -8px 0;
198 margin: -8px 0;
199 padding: 0;
199 padding: 0;
200 }
200 }
201
201
202 &:hover .user {
202 &:hover .user {
203 color: @grey6;
203 color: @grey6;
204 }
204 }
205 }
205 }
206 }
206 }
207 .header .horizontal-list {
207 .header .horizontal-list {
208
208
209 li {
209 li {
210
210
211 &#quick_login_li {
211 &#quick_login_li {
212 padding-left: .5em;
212 padding-left: .5em;
213 margin-right: 0px;
213 margin-right: 0px;
214
214
215 &:hover #quick_login_link {
215 &:hover #quick_login_link {
216 color: inherit;
216 color: inherit;
217 }
217 }
218
218
219 .menu_link_user {
219 .menu_link_user {
220 padding: 0 2px;
220 padding: 0 2px;
221 }
221 }
222 }
222 }
223 list-style-type: none;
223 list-style-type: none;
224 }
224 }
225
225
226 > li {
226 > li {
227
227
228 a {
228 a {
229 padding: 18px 0 12px 0;
229 padding: 18px 0 12px 0;
230 color: @nav-grey;
230 color: @nav-grey;
231
231
232 &.menu_link_notifications {
232 &.menu_link_notifications {
233 padding: 1px 8px;
233 padding: 1px 8px;
234 }
234 }
235 }
235 }
236
236
237 &:hover,
237 &:hover,
238 &.open,
238 &.open,
239 &.active {
239 &.active {
240 .pill_container a {
240 .pill_container a {
241 // don't select text for the pill container, it has it' own
241 // don't select text for the pill container, it has it' own
242 // hover behaviour
242 // hover behaviour
243 color: @nav-grey;
243 color: @nav-grey;
244 }
244 }
245 }
245 }
246
246
247 &:hover,
247 &:hover,
248 &.open,
248 &.open,
249 &.active {
249 &.active {
250 a {
250 a {
251 color: @grey6;
251 color: @grey6;
252 }
252 }
253 }
253 }
254
254
255 .select2-dropdown-open a {
255 .select2-dropdown-open a {
256 color: @grey6;
256 color: @grey6;
257 }
257 }
258
258
259 .repo-switcher {
259 .repo-switcher {
260 padding-left: 0;
260 padding-left: 0;
261
261
262 .menulabel {
262 .menulabel {
263 padding-left: 0;
263 padding-left: 0;
264 }
264 }
265 }
265 }
266 }
266 }
267
267
268 li ul li {
268 li ul li {
269 background-color:@grey2;
269 background-color:@grey2;
270
270
271 a {
271 a {
272 padding: .5em 0;
272 padding: .5em 0;
273 border-bottom: @border-thickness solid @border-default-color;
273 border-bottom: @border-thickness solid @border-default-color;
274 color: @grey6;
274 color: @grey6;
275 }
275 }
276
276
277 &:last-child a, &.last a{
277 &:last-child a, &.last a{
278 border-bottom: none;
278 border-bottom: none;
279 }
279 }
280
280
281 &:hover {
281 &:hover {
282 background-color: @grey3;
282 background-color: @grey3;
283 }
283 }
284 }
284 }
285
285
286 .submenu {
286 .submenu {
287 margin-top: 5px;
287 margin-top: 5px;
288 }
288 }
289 }
289 }
290
290
291 // SUBMENUS
291 // SUBMENUS
292 .navigation .submenu {
292 .navigation .submenu {
293 display: none;
293 display: none;
294 }
294 }
295
295
296 .navigation li.open {
296 .navigation li.open {
297 .submenu {
297 .submenu {
298 display: block;
298 display: block;
299 }
299 }
300 }
300 }
301
301
302 .navigation li:last-child .submenu {
302 .navigation li:last-child .submenu {
303 right: auto;
303 right: auto;
304 left: 0;
304 left: 0;
305 border: 1px solid @grey5;
305 border: 1px solid @grey5;
306 background: @white;
306 background: @white;
307 box-shadow: @dropdown-shadow;
307 box-shadow: @dropdown-shadow;
308 }
308 }
309
309
310 .submenu {
310 .submenu {
311 position: absolute;
311 position: absolute;
312 top: 100%;
312 top: 100%;
313 left: 0;
313 left: 0;
314 min-width: 180px;
314 min-width: 180px;
315 margin: 2px 0 0;
315 margin: 2px 0 0;
316 padding: 0;
316 padding: 0;
317 text-align: left;
317 text-align: left;
318 font-family: @text-light;
318 font-family: @text-light;
319 border-radius: @border-radius;
319 border-radius: @border-radius;
320 z-index: 20;
320 z-index: 20;
321
321
322 li {
322 li {
323 display: block;
323 display: block;
324 margin: 0;
324 margin: 0;
325 padding: 0 .5em;
325 padding: 0 .5em;
326 line-height: 1em;
326 line-height: 1em;
327 color: @grey3;
327 color: @grey3;
328 background-color: @white;
328 background-color: @white;
329 list-style-type: none;
329 list-style-type: none;
330
330
331 a {
331 a {
332 display: block;
332 display: block;
333 width: 100%;
333 width: 100%;
334 padding: .5em 0;
334 padding: .5em 0;
335 border-right: none;
335 border-right: none;
336 border-bottom: @border-thickness solid white;
336 border-bottom: @border-thickness solid white;
337 color: @grey3;
337 color: @grey3;
338 }
338 }
339
339
340 ul {
340 ul {
341 display: none;
341 display: none;
342 position: absolute;
342 position: absolute;
343 top: 0;
343 top: 0;
344 right: 100%;
344 right: 100%;
345 padding: 0;
345 padding: 0;
346 z-index: 30;
346 z-index: 30;
347 }
347 }
348 &:hover {
348 &:hover {
349 background-color: @grey7;
349 background-color: @grey7;
350 -webkit-transition: background .3s;
350 -webkit-transition: background .3s;
351 -moz-transition: background .3s;
351 -moz-transition: background .3s;
352 -o-transition: background .3s;
352 -o-transition: background .3s;
353 transition: background .3s;
353 transition: background .3s;
354
354
355 ul {
355 ul {
356 display: block;
356 display: block;
357 }
357 }
358 }
358 }
359 }
359 }
360
360
361 }
361 }
362
362
363
363
364
364
365
365
366 // repo dropdown
366 // repo dropdown
367 .quick_repo_menu {
367 .quick_repo_menu {
368 width: 15px;
368 width: 15px;
369 text-align: center;
369 text-align: center;
370 position: relative;
370 position: relative;
371 cursor: pointer;
371 cursor: pointer;
372
372
373 div {
373 div {
374 overflow: visible !important;
374 overflow: visible !important;
375 }
375 }
376
376
377 &.sorting {
377 &.sorting {
378 cursor: auto;
378 cursor: auto;
379 }
379 }
380
380
381 &:hover {
381 &:hover {
382 .menu_items_container {
382 .menu_items_container {
383 position: absolute;
383 position: absolute;
384 display: block;
384 display: block;
385 }
385 }
386 .menu_items {
386 .menu_items {
387 display: block;
387 display: block;
388 }
388 }
389 }
389 }
390
390
391 i {
391 i {
392 margin: 0;
392 margin: 0;
393 color: @grey4;
393 color: @grey4;
394 }
394 }
395
395
396 .menu_items_container {
396 .menu_items_container {
397 position: absolute;
397 position: absolute;
398 top: 0;
398 top: 0;
399 left: 100%;
399 left: 100%;
400 margin: 0;
400 margin: 0;
401 padding: 0;
401 padding: 0;
402 list-style: none;
402 list-style: none;
403 background-color: @grey6;
403 background-color: @grey6;
404 z-index: 999;
404 z-index: 999;
405 text-align: left;
405 text-align: left;
406
406
407 a {
407 a {
408 color: @grey2;
408 color: @grey2;
409 }
409 }
410
410
411 ul.menu_items {
411 ul.menu_items {
412 margin: 0;
412 margin: 0;
413 padding: 0;
413 padding: 0;
414 }
414 }
415
415
416 li {
416 li {
417 margin: 0;
417 margin: 0;
418 padding: 0;
418 padding: 0;
419 line-height: 1em;
419 line-height: 1em;
420 list-style-type: none;
420 list-style-type: none;
421
421
422 a {
422 a {
423 display: block;
423 display: block;
424 height: 16px;
424 height: 16px;
425 padding: 8px; //must add up to td height (28px)
425 padding: 8px; //must add up to td height (28px)
426 width: 120px; // set width
426 width: 120px; // set width
427
427
428 &:hover {
428 &:hover {
429 background-color: @grey5;
429 background-color: @grey5;
430 -webkit-transition: background .3s;
430 -webkit-transition: background .3s;
431 -moz-transition: background .3s;
431 -moz-transition: background .3s;
432 -o-transition: background .3s;
432 -o-transition: background .3s;
433 transition: background .3s;
433 transition: background .3s;
434 }
434 }
435 }
435 }
436 }
436 }
437 }
437 }
438 }
438 }
439
439
440
440
441 // new objects main action
441 // new objects main action
442 .action-menu {
442 .action-menu {
443 left: auto;
443 left: auto;
444 right: 0;
444 right: 0;
445 padding: 12px;
445 padding: 12px;
446 z-index: 999;
446 z-index: 999;
447 overflow: hidden;
447 overflow: hidden;
448 background-color: #fff;
448 background-color: #fff;
449 border: 1px solid @grey5;
449 border: 1px solid @grey5;
450 color: @grey2;
450 color: @grey2;
451 box-shadow: @dropdown-shadow;
451 box-shadow: @dropdown-shadow;
452
452
453 .submenu-title {
453 .submenu-title {
454 font-weight: bold;
454 font-weight: bold;
455 }
455 }
456
456
457 .submenu-title:not(:first-of-type) {
457 .submenu-title:not(:first-of-type) {
458 padding-top: 10px;
458 padding-top: 10px;
459 }
459 }
460
460
461 &.submenu {
461 &.submenu {
462 min-width: 200px;
462 min-width: 200px;
463
463
464 ol {
464 ol {
465 padding:0;
465 padding:0;
466 }
466 }
467
467
468 li {
468 li {
469 display: block;
469 display: block;
470 margin: 0;
470 margin: 0;
471 padding: .2em .5em;
471 padding: .2em .5em;
472 line-height: 1em;
472 line-height: 1em;
473
473
474 background-color: #fff;
474 background-color: #fff;
475 list-style-type: none;
475 list-style-type: none;
476
476
477 a {
477 a {
478 padding: 4px;
478 padding: 4px;
479 color: @grey4 !important;
479 color: @grey4 !important;
480 border-bottom: none;
480 border-bottom: none;
481 }
481 }
482 }
482 }
483 li:not(.submenu-title) a:hover{
483 li:not(.submenu-title) a:hover{
484 color: @grey2 !important;
484 color: @grey2 !important;
485 }
485 }
486 }
486 }
487 }
487 }
488
488
489
489
490 // Header Repository Switcher
490 // Header Repository Switcher
491 // Select2 Dropdown
491 // Select2 Dropdown
492 #select2-drop.select2-drop.repo-switcher-dropdown {
492 #select2-drop.select2-drop.repo-switcher-dropdown {
493 width: auto !important;
493 width: auto !important;
494 margin-top: 5px;
494 margin-top: 5px;
495 padding: 1em 0;
495 padding: 1em 0;
496 text-align: left;
496 text-align: left;
497 .border-radius-bottom(@border-radius);
497 .border-radius-bottom(@border-radius);
498 border-color: transparent;
498 border-color: transparent;
499 color: @grey6;
499 color: @grey6;
500 background-color: @grey2;
500 background-color: @grey2;
501
501
502 input {
502 input {
503 min-width: 90%;
503 min-width: 90%;
504 }
504 }
505
505
506 ul.select2-result-sub {
506 ul.select2-result-sub {
507
507
508 li {
508 li {
509 line-height: 1em;
509 line-height: 1em;
510
510
511 &:hover,
511 &:hover,
512 &.select2-highlighted {
512 &.select2-highlighted {
513 background-color: @grey3;
513 background-color: @grey3;
514 }
514 }
515 }
515 }
516
516
517 &:before { content: none; }
517 &:before { content: none; }
518 }
518 }
519
519
520 ul.select2-results {
520 ul.select2-results {
521 min-width: 200px;
521 min-width: 200px;
522 margin: 0;
522 margin: 0;
523 padding: 0;
523 padding: 0;
524 list-style-type: none;
524 list-style-type: none;
525 overflow-x: visible;
525 overflow-x: visible;
526 overflow-y: scroll;
526 overflow-y: scroll;
527
527
528 li {
528 li {
529 padding: 0 8px;
529 padding: 0 8px;
530 line-height: 1em;
530 line-height: 1em;
531 color: @grey6;
531 color: @grey6;
532
532
533 &>.select2-result-label {
533 &>.select2-result-label {
534 padding: 8px 0;
534 padding: 8px 0;
535 border-bottom: @border-thickness solid @grey3;
535 border-bottom: @border-thickness solid @grey3;
536 white-space: nowrap;
536 white-space: nowrap;
537 color: @grey5;
537 color: @grey5;
538 cursor: pointer;
538 cursor: pointer;
539 }
539 }
540
540
541 &.select2-result-with-children {
541 &.select2-result-with-children {
542 margin: 0;
542 margin: 0;
543 padding: 0;
543 padding: 0;
544 }
544 }
545
545
546 &.select2-result-unselectable > .select2-result-label {
546 &.select2-result-unselectable > .select2-result-label {
547 margin: 0 8px;
547 margin: 0 8px;
548 }
548 }
549
549
550 }
550 }
551 }
551 }
552
552
553 ul.select2-result-sub {
553 ul.select2-result-sub {
554 margin: 0;
554 margin: 0;
555 padding: 0;
555 padding: 0;
556
556
557 li {
557 li {
558 display: block;
558 display: block;
559 margin: 0;
559 margin: 0;
560 border-right: none;
560 border-right: none;
561 line-height: 1em;
561 line-height: 1em;
562 font-family: @text-light;
562 font-family: @text-light;
563 color: @grey2;
563 color: @grey2;
564 list-style-type: none;
564 list-style-type: none;
565
565
566 &:hover {
566 &:hover {
567 background-color: @grey3;
567 background-color: @grey3;
568 }
568 }
569 }
569 }
570 }
570 }
571 }
571 }
572
572
573
573
574 #context-bar {
574 #context-bar {
575 display: block;
575 display: block;
576 margin: 0 auto 20px 0;
576 margin: 0 auto 20px 0;
577 padding: 0 @header-padding;
577 padding: 0 @header-padding;
578 background-color: @grey7;
578 background-color: @grey7;
579 border-bottom: 1px solid @grey5;
579 border-bottom: 1px solid @grey5;
580
580
581 .clear {
581 .clear {
582 clear: both;
582 clear: both;
583 }
583 }
584 }
584 }
585
585
586 ul#context-pages {
586 ul#context-pages {
587 li {
587 li {
588 list-style-type: none;
588 list-style-type: none;
589
589
590 a {
590 a {
591 color: @grey2;
591 color: @grey2;
592
592
593 &:hover {
593 &:hover {
594 color: @grey1;
594 color: @grey1;
595 }
595 }
596 }
596 }
597
597
598 &.active {
598 &.active {
599 // special case, non-variable color
599 // special case, non-variable color
600 border-bottom: 2px solid @rcblue;
600 border-bottom: 2px solid @rcblue;
601
601
602 a {
602 a {
603 color: @rcblue;
603 color: @rcblue;
604 }
604 }
605 }
605 }
606 }
606 }
607 }
607 }
608
608
609 // PAGINATION
609 // PAGINATION
610
610
611 .pagination {
611 .pagination {
612 border: @border-thickness solid @grey5;
612 border: @border-thickness solid @grey5;
613 color: @grey2;
613 color: @grey2;
614 box-shadow: @button-shadow;
614 box-shadow: @button-shadow;
615
615
616 .current {
616 .current {
617 color: @grey4;
617 color: @grey4;
618 }
618 }
619 }
619 }
620
620
621 .dataTables_processing {
621 .dataTables_processing {
622 text-align: center;
622 text-align: center;
623 font-size: 1.1em;
623 font-size: 1.1em;
624 position: relative;
624 position: relative;
625 top: 95px;
625 top: 95px;
626 height: 0;
626 height: 0;
627 }
627 }
628
628
629 .dataTables_paginate,
629 .dataTables_paginate,
630 .pagination-wh {
630 .pagination-wh {
631 text-align: center;
631 text-align: center;
632 display: inline-block;
632 display: inline-block;
633 border-left: 1px solid @grey5;
633 border-left: 1px solid @grey5;
634 float: none;
634 float: none;
635 overflow: hidden;
635 overflow: hidden;
636 box-shadow: @button-shadow;
636 box-shadow: @button-shadow;
637
637
638 .paginate_button, .pager_curpage,
638 .paginate_button, .pager_curpage,
639 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
639 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
640 display: inline-block;
640 display: inline-block;
641 padding: @menupadding/4 @menupadding;
641 padding: @menupadding/4 @menupadding;
642 border: 1px solid @grey5;
642 border: 1px solid @grey5;
643 margin-left: -1px;
643 margin-left: -1px;
644 color: @grey2;
644 color: @grey2;
645 cursor: pointer;
645 cursor: pointer;
646 float: left;
646 float: left;
647 font-weight: 600;
647 font-weight: 600;
648 white-space: nowrap;
648 white-space: nowrap;
649 vertical-align: middle;
649 vertical-align: middle;
650 user-select: none;
650 user-select: none;
651 min-width: 15px;
651 min-width: 15px;
652
652
653 &:hover {
653 &:hover {
654 color: @rcdarkblue;
654 color: @rcdarkblue;
655 }
655 }
656 }
656 }
657
657
658 .paginate_button.disabled,
658 .paginate_button.disabled,
659 .disabled {
659 .disabled {
660 color: @grey3;
660 color: @grey3;
661 cursor: default;
661 cursor: default;
662 opacity: 0.5;
662 opacity: 0.5;
663 }
663 }
664
664
665 .paginate_button.current, .pager_curpage {
665 .paginate_button.current, .pager_curpage {
666 background: @rcblue;
666 background: @rcblue;
667 border-color: @rcblue;
667 border-color: @rcblue;
668 color: @white;
668 color: @white;
669 }
669 }
670
670
671 .ellipsis {
671 .ellipsis {
672 display: inline-block;
672 display: inline-block;
673 text-align: left;
673 text-align: left;
674 padding: @menupadding/4 @menupadding;
674 padding: @menupadding/4 @menupadding;
675 border: 1px solid @grey5;
675 border: 1px solid @grey5;
676 border-left: 0;
676 border-left: 0;
677 float: left;
677 float: left;
678 }
678 }
679 }
679 }
680
680
681 // SIDEBAR
681 // SIDEBAR
682
682
683 .sidebar {
683 .sidebar {
684 .block-left;
684 .block-left;
685 clear: left;
685 clear: left;
686 max-width: @sidebar-width;
686 max-width: @sidebar-width;
687 margin-right: @sidebarpadding;
687 margin-right: @sidebarpadding;
688 padding-right: @sidebarpadding;
688 padding-right: @sidebarpadding;
689 font-family: @text-regular;
689 font-family: @text-regular;
690 color: @grey1;
690 color: @grey1;
691
691
692 .nav-pills {
692 .nav-pills {
693 margin: 0;
693 margin: 0;
694 }
694 }
695
695
696 .nav {
696 .nav {
697 list-style: none;
697 list-style: none;
698 padding: 0;
698 padding: 0;
699
699
700 li {
700 li {
701 padding-bottom: @menupadding;
701 padding-bottom: @menupadding;
702 line-height: 1em;
702 line-height: 1em;
703 color: @grey4;
703 color: @grey4;
704 list-style-type: none;
704 list-style-type: none;
705
705
706 &.active a {
706 &.active a {
707 color: @grey2;
707 color: @grey2;
708 }
708 }
709
709
710 a {
710 a {
711 color: @grey4;
711 color: @grey4;
712 }
712 }
713 }
713 }
714
714
715 }
715 }
716 }
716 }
717
717
718 .main_filter_help_box {
718 .main_filter_help_box {
719 padding: 7px 7px;
719 padding: 7px 7px;
720 display: inline-block;
720 display: inline-block;
721 vertical-align: top;
721 vertical-align: top;
722 background: inherit;
722 background: inherit;
723 position: absolute;
723 position: absolute;
724 right: 0;
724 right: 0;
725 top: 9px;
725 top: 9px;
726 }
726 }
727
727
728 .main_filter_input_box {
728 .main_filter_input_box {
729 display: inline-block;
729 display: inline-block;
730
730
731 .searchItems {
731 .searchItems {
732 display:flex;
732 display:flex;
733 background: @black;
733 background: @black;
734 padding: 0px;
734 padding: 0px;
735 border-radius: 3px;
735 border-radius: 3px;
736 border: 1px solid @black;
736 border: 1px solid @black;
737
737
738 a {
738 a {
739 border: none !important;
739 border: none !important;
740 }
740 }
741 }
741 }
742
742
743 .searchTag {
743 .searchTag {
744 line-height: 28px;
744 line-height: 28px;
745 padding: 0 5px;
745 padding: 0 5px;
746
746
747 .tag {
747 .tag {
748 color: @grey5;
748 color: @grey5;
749 border-color: @grey2;
749 border-color: @grey2;
750 background: @grey1;
750 background: @grey1;
751 }
751 }
752 }
752 }
753
753
754 .searchTagFilter {
754 .searchTagFilter {
755 background-color: @black !important;
755 background-color: @black !important;
756 margin-right: 0;
756 margin-right: 0;
757 }
757 }
758 .searchTagIcon {
758 .searchTagIcon {
759 margin: 0;
759 margin: 0;
760 background: @black !important;
760 background: @black !important;
761 }
761 }
762 .searchTagHelp {
762 .searchTagHelp {
763 background-color: @grey1 !important;
763 background-color: @grey1 !important;
764 margin: 0;
764 margin: 0;
765 }
765 }
766 .searchTagHelp:hover {
766 .searchTagHelp:hover {
767 background-color: @grey1 !important;
767 background-color: @grey1 !important;
768 }
768 }
769 .searchTagInput {
769 .searchTagInput {
770 background-color: @grey1 !important;
770 background-color: @grey1 !important;
771 margin-right: 0;
771 margin-right: 0;
772 }
772 }
773 }
773 }
774
774
775 .main_filter_box {
775 .main_filter_box {
776 margin: 9px 0 0 0;
776 margin: 9px 0 0 0;
777 }
777 }
778
778
779 #main_filter_help {
779 #main_filter_help {
780 background: @grey1;
780 background: @grey1;
781 border: 1px solid black;
781 border: 1px solid black;
782 position: absolute;
782 position: absolute;
783 white-space: pre;
783 white-space: pre;
784 z-index: 9999;
784 z-index: 9999;
785 color: @nav-grey;
785 color: @nav-grey;
786 padding: 0 10px;
786 padding: 0 10px;
787 }
787 }
788
788
789 input {
789 input {
790
790
791 &.main_filter_input {
791 &.main_filter_input {
792 padding: 5px 10px;
792 padding: 5px 10px;
793 min-width: 340px;
793 min-width: 340px;
794 color: @grey7;
794 color: @grey7;
795 background: @black;
795 background: @black;
796 min-height: 18px;
796 min-height: 18px;
797 border: 0;
797 border: 0;
798
798
799 &:active {
799 &:active {
800 color: @grey2 !important;
800 color: @grey2 !important;
801 background: white !important;
801 background: white !important;
802 }
802 }
803 &:focus {
803 &:focus {
804 color: @grey2 !important;
804 color: @grey2 !important;
805 background: white !important;
805 background: white !important;
806 }
806 }
807 }
807 }
808 }
808 }
809
809
810
810
811
811
812 .main_filter_input::placeholder {
812 .main_filter_input::placeholder {
813 color: @nav-grey;
813 color: @nav-grey;
814 opacity: 1;
814 opacity: 1;
815 }
815 }
816
816
817 .notice-box {
817 .notice-box {
818 display:block !important;
818 display:block !important;
819 padding: 9px 0 !important;
819 padding: 9px 0 !important;
820 }
820 }
821
821
822 .menulabel-notice {
822 .menulabel-notice {
823 border: 1px solid @color5;
823
824 padding:7px 10px;
824 padding:7px 10px;
825
826 &.notice-warning {
827 border: 1px solid @color3;
828 .notice-color-warning
829 }
830 &.notice-error {
831 border: 1px solid @color5;
832 .notice-color-error
833 }
834 &.notice-info {
835 border: 1px solid @color1;
836 .notice-color-info
837 }
838 }
839
840 .notice-messages-container {
841 position: absolute;
842 top: 45px;
843 }
844
845 .notice-messages {
846 display: block;
847 position: relative;
848 z-index: 300;
849 min-width: 500px;
850 max-width: 500px;
851 min-height: 100px;
852 margin-top: 4px;
853 margin-bottom: 24px;
854 font-size: 14px;
855 font-weight: 400;
856 padding: 8px 0;
857 background-color: #fff;
858 border: 1px solid @grey4;
859 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.07);
860 }
861
862 .notice-color-warning {
863 color: @color3;
864 }
865
866 .notice-color-error {
825 color: @color5;
867 color: @color5;
826 }
868 }
869
870 .notice-color-info {
871 color: @color1;
872 }
@@ -1,290 +1,291 b''
1 @font-face {
1 @font-face {
2 font-family: 'rcicons';
2 font-family: 'rcicons';
3
3
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
10
10
11 font-weight: normal;
11 font-weight: normal;
12 font-style: normal;
12 font-style: normal;
13 }
13 }
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
16 /*
16 /*
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
18 @font-face {
18 @font-face {
19 font-family: 'rcicons';
19 font-family: 'rcicons';
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
21 }
21 }
22 }
22 }
23 */
23 */
24
24
25 [class^="icon-"]:before, [class*=" icon-"]:before {
25 [class^="icon-"]:before, [class*=" icon-"]:before {
26 font-family: "rcicons";
26 font-family: "rcicons";
27 font-style: normal;
27 font-style: normal;
28 font-weight: normal;
28 font-weight: normal;
29 speak: none;
29 speak: none;
30
30
31 display: inline-block;
31 display: inline-block;
32 text-decoration: inherit;
32 text-decoration: inherit;
33 width: 1em;
33 width: 1em;
34 margin-right: .2em;
34 margin-right: .2em;
35 text-align: center;
35 text-align: center;
36 /* opacity: .8; */
36 /* opacity: .8; */
37
37
38 /* For safety - reset parent styles, that can break glyph codes*/
38 /* For safety - reset parent styles, that can break glyph codes*/
39 font-variant: normal;
39 font-variant: normal;
40 text-transform: none;
40 text-transform: none;
41
41
42 /* fix buttons height, for twitter bootstrap */
42 /* fix buttons height, for twitter bootstrap */
43 line-height: 1em;
43 line-height: 1em;
44
44
45 /* Animation center compensation - margins should be symmetric */
45 /* Animation center compensation - margins should be symmetric */
46 /* remove if not needed */
46 /* remove if not needed */
47 margin-left: .2em;
47 margin-left: .2em;
48
48
49 /* you can be more comfortable with increased icons size */
49 /* you can be more comfortable with increased icons size */
50 /* font-size: 120%; */
50 /* font-size: 120%; */
51
51
52 /* Font smoothing. That was taken from TWBS */
52 /* Font smoothing. That was taken from TWBS */
53 -webkit-font-smoothing: antialiased;
53 -webkit-font-smoothing: antialiased;
54 -moz-osx-font-smoothing: grayscale;
54 -moz-osx-font-smoothing: grayscale;
55
55
56 /* Uncomment for 3D effect */
56 /* Uncomment for 3D effect */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
58 }
58 }
59
59
60 .animate-spin {
60 .animate-spin {
61 -moz-animation: spin 2s infinite linear;
61 -moz-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
65 display: inline-block;
65 display: inline-block;
66 }
66 }
67 @-moz-keyframes spin {
67 @-moz-keyframes spin {
68 0% {
68 0% {
69 -moz-transform: rotate(0deg);
69 -moz-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
72 transform: rotate(0deg);
72 transform: rotate(0deg);
73 }
73 }
74
74
75 100% {
75 100% {
76 -moz-transform: rotate(359deg);
76 -moz-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
79 transform: rotate(359deg);
79 transform: rotate(359deg);
80 }
80 }
81 }
81 }
82 @-webkit-keyframes spin {
82 @-webkit-keyframes spin {
83 0% {
83 0% {
84 -moz-transform: rotate(0deg);
84 -moz-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
87 transform: rotate(0deg);
87 transform: rotate(0deg);
88 }
88 }
89
89
90 100% {
90 100% {
91 -moz-transform: rotate(359deg);
91 -moz-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
94 transform: rotate(359deg);
94 transform: rotate(359deg);
95 }
95 }
96 }
96 }
97 @-o-keyframes spin {
97 @-o-keyframes spin {
98 0% {
98 0% {
99 -moz-transform: rotate(0deg);
99 -moz-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
102 transform: rotate(0deg);
102 transform: rotate(0deg);
103 }
103 }
104
104
105 100% {
105 100% {
106 -moz-transform: rotate(359deg);
106 -moz-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
109 transform: rotate(359deg);
109 transform: rotate(359deg);
110 }
110 }
111 }
111 }
112 @-ms-keyframes spin {
112 @-ms-keyframes spin {
113 0% {
113 0% {
114 -moz-transform: rotate(0deg);
114 -moz-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
117 transform: rotate(0deg);
117 transform: rotate(0deg);
118 }
118 }
119
119
120 100% {
120 100% {
121 -moz-transform: rotate(359deg);
121 -moz-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
124 transform: rotate(359deg);
124 transform: rotate(359deg);
125 }
125 }
126 }
126 }
127 @keyframes spin {
127 @keyframes spin {
128 0% {
128 0% {
129 -moz-transform: rotate(0deg);
129 -moz-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
132 transform: rotate(0deg);
132 transform: rotate(0deg);
133 }
133 }
134
134
135 100% {
135 100% {
136 -moz-transform: rotate(359deg);
136 -moz-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
139 transform: rotate(359deg);
139 transform: rotate(359deg);
140 }
140 }
141 }
141 }
142
142
143
143
144
144
145 .icon-no-margin::before {
145 .icon-no-margin::before {
146 margin: 0;
146 margin: 0;
147
147
148 }
148 }
149 // -- ICON CLASSES -- //
149 // -- ICON CLASSES -- //
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
151
151
152 .icon-delete:before { content: '\e800'; } /* '' */
152 .icon-delete:before { content: '\e800'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
171 .icon-search:before { content: '\e813'; } /* '' */
171 .icon-search:before { content: '\e813'; } /* '' */
172 .icon-scissors:before { content: '\e814'; } /* '' */
172 .icon-scissors:before { content: '\e814'; } /* '' */
173 .icon-download:before { content: '\e815'; } /* '' */
173 .icon-download:before { content: '\e815'; } /* '' */
174 .icon-doc:before { content: '\e816'; } /* '' */
174 .icon-doc:before { content: '\e816'; } /* '' */
175 .icon-cog:before { content: '\e817'; } /* '' */
175 .icon-cog:before { content: '\e817'; } /* '' */
176 .icon-cog-alt:before { content: '\e818'; } /* '' */
176 .icon-cog-alt:before { content: '\e818'; } /* '' */
177 .icon-eye:before { content: '\e819'; } /* '' */
177 .icon-eye:before { content: '\e819'; } /* '' */
178 .icon-eye-off:before { content: '\e81a'; } /* '' */
178 .icon-eye-off:before { content: '\e81a'; } /* '' */
179 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
179 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
180 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
180 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
181 .icon-plus:before { content: '\e81d'; } /* '' */
181 .icon-plus:before { content: '\e81d'; } /* '' */
182 .icon-plus-circled:before { content: '\e81e'; } /* '' */
182 .icon-plus-circled:before { content: '\e81e'; } /* '' */
183 .icon-minus-circled:before { content: '\e81f'; } /* '' */
183 .icon-minus-circled:before { content: '\e81f'; } /* '' */
184 .icon-minus:before { content: '\e820'; } /* '' */
184 .icon-minus:before { content: '\e820'; } /* '' */
185 .icon-info-circled:before { content: '\e821'; } /* '' */
185 .icon-info-circled:before { content: '\e821'; } /* '' */
186 .icon-upload:before { content: '\e822'; } /* '' */
186 .icon-upload:before { content: '\e822'; } /* '' */
187 .icon-home:before { content: '\e823'; } /* '' */
187 .icon-home:before { content: '\e823'; } /* '' */
188 .icon-flag-filled:before { content: '\e824'; } /* '' */
188 .icon-flag-filled:before { content: '\e824'; } /* '' */
189 .icon-git:before { content: '\e82a'; } /* '' */
189 .icon-git:before { content: '\e82a'; } /* '' */
190 .icon-hg:before { content: '\e82d'; } /* '' */
190 .icon-hg:before { content: '\e82d'; } /* '' */
191 .icon-svn:before { content: '\e82e'; } /* '' */
191 .icon-svn:before { content: '\e82e'; } /* '' */
192 .icon-comment-add:before { content: '\e82f'; } /* '' */
192 .icon-comment-add:before { content: '\e82f'; } /* '' */
193 .icon-comment-toggle:before { content: '\e830'; } /* '' */
193 .icon-comment-toggle:before { content: '\e830'; } /* '' */
194 .icon-rhodecode:before { content: '\e831'; } /* '' */
194 .icon-rhodecode:before { content: '\e831'; } /* '' */
195 .icon-up:before { content: '\e832'; } /* '' */
195 .icon-up:before { content: '\e832'; } /* '' */
196 .icon-merge:before { content: '\e833'; } /* '' */
196 .icon-merge:before { content: '\e833'; } /* '' */
197 .icon-spin-alt:before { content: '\e834'; } /* '' */
197 .icon-spin-alt:before { content: '\e834'; } /* '' */
198 .icon-spin:before { content: '\e838'; } /* '' */
198 .icon-spin:before { content: '\e838'; } /* '' */
199 .icon-docs:before { content: '\f0c5'; } /* '' */
199 .icon-docs:before { content: '\f0c5'; } /* '' */
200 .icon-menu:before { content: '\f0c9'; } /* '' */
200 .icon-menu:before { content: '\f0c9'; } /* '' */
201 .icon-sort:before { content: '\f0dc'; } /* '' */
201 .icon-sort:before { content: '\f0dc'; } /* '' */
202 .icon-paste:before { content: '\f0ea'; } /* '' */
202 .icon-paste:before { content: '\f0ea'; } /* '' */
203 .icon-doc-text:before { content: '\f0f6'; } /* '' */
203 .icon-doc-text:before { content: '\f0f6'; } /* '' */
204 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
204 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
205 .icon-angle-left:before { content: '\f104'; } /* '' */
205 .icon-angle-left:before { content: '\f104'; } /* '' */
206 .icon-angle-right:before { content: '\f105'; } /* '' */
206 .icon-angle-right:before { content: '\f105'; } /* '' */
207 .icon-angle-up:before { content: '\f106'; } /* '' */
207 .icon-angle-up:before { content: '\f106'; } /* '' */
208 .icon-angle-down:before { content: '\f107'; } /* '' */
208 .icon-angle-down:before { content: '\f107'; } /* '' */
209 .icon-circle-empty:before { content: '\f10c'; } /* '' */
209 .icon-circle-empty:before { content: '\f10c'; } /* '' */
210 .icon-circle:before { content: '\f111'; } /* '' */
210 .icon-circle:before { content: '\f111'; } /* '' */
211 .icon-folder-empty:before { content: '\f114'; } /* '' */
211 .icon-folder-empty:before { content: '\f114'; } /* '' */
212 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
212 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
213 .icon-code:before { content: '\f121'; } /* '' */
213 .icon-code:before { content: '\f121'; } /* '' */
214 .icon-info:before { content: '\f129'; } /* '' */
214 .icon-info:before { content: '\f129'; } /* '' */
215 .icon-minus-squared:before { content: '\f146'; } /* '' */
215 .icon-minus-squared:before { content: '\f146'; } /* '' */
216 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
216 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
217 .icon-doc-inv:before { content: '\f15b'; } /* '' */
217 .icon-doc-inv:before { content: '\f15b'; } /* '' */
218 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
218 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
219 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
219 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
220 .icon-file-code:before { content: '\f1c9'; } /* '' */
220 .icon-file-code:before { content: '\f1c9'; } /* '' */
221 .icon-history:before { content: '\f1da'; } /* '' */
221 .icon-history:before { content: '\f1da'; } /* '' */
222 .icon-circle-thin:before { content: '\f1db'; } /* '' */
222 .icon-circle-thin:before { content: '\f1db'; } /* '' */
223 .icon-sliders:before { content: '\f1de'; } /* '' */
223 .icon-sliders:before { content: '\f1de'; } /* '' */
224 .icon-trash:before { content: '\f1f8'; } /* '' */
224 .icon-trash:before { content: '\f1f8'; } /* '' */
225
225
226
226
227 // MERGED ICONS BASED ON CURRENT ONES
227 // MERGED ICONS BASED ON CURRENT ONES
228 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
228 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
229 .icon-repo-private:before { &:extend(.icon-lock:before); }
229 .icon-repo-private:before { &:extend(.icon-lock:before); }
230 .icon-repo-lock:before { &:extend(.icon-lock:before); }
230 .icon-repo-lock:before { &:extend(.icon-lock:before); }
231 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
231 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
232 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
232 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
233 .icon-repo-public:before { &:extend(.icon-unlock:before); }
233 .icon-repo-public:before { &:extend(.icon-unlock:before); }
234 .icon-rss-sign:before { &:extend(.icon-feed:before); }
234 .icon-rss-sign:before { &:extend(.icon-feed:before); }
235 .icon-code-fork:before { &:extend(.icon-fork:before); }
235 .icon-code-fork:before { &:extend(.icon-fork:before); }
236 .icon-arrow_up:before { &:extend(.icon-up:before); }
236 .icon-arrow_up:before { &:extend(.icon-up:before); }
237 .icon-file:before { &:extend(.icon-file-code:before); }
237 .icon-file:before { &:extend(.icon-file-code:before); }
238 .icon-file-text:before { &:extend(.icon-file-code:before); }
238 .icon-file-text:before { &:extend(.icon-file-code:before); }
239 .icon-directory:before { &:extend(.icon-folder:before); }
239 .icon-directory:before { &:extend(.icon-folder:before); }
240 .icon-more-linked:before { &:extend(.icon-more:before); }
240 .icon-more-linked:before { &:extend(.icon-more:before); }
241 .icon-clipboard:before { &:extend(.icon-docs:before); }
241 .icon-clipboard:before { &:extend(.icon-docs:before); }
242 .icon-copy:before { &:extend(.icon-docs:before); }
242 .icon-copy:before { &:extend(.icon-docs:before); }
243 .icon-true:before { &:extend(.icon-ok:before); }
243 .icon-true:before { &:extend(.icon-ok:before); }
244 .icon-false:before { &:extend(.icon-delete:before); }
244 .icon-false:before { &:extend(.icon-delete:before); }
245 .icon-expand-linked:before { &:extend(.icon-down:before); }
245 .icon-expand-linked:before { &:extend(.icon-down:before); }
246 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
246 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
247 .icon-wide-mode:before { &:extend(.icon-sort:before); }
247 .icon-wide-mode:before { &:extend(.icon-sort:before); }
248 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
248 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
249 .icon-user-group-alt:before { &:extend(.icon-group:before); }
249 .icon-user-group-alt:before { &:extend(.icon-group:before); }
250
250
251 // TRANSFORM
251 // TRANSFORM
252 .icon-merge:before {transform: rotate(180deg);}
252 .icon-merge:before {transform: rotate(180deg);}
253 .icon-wide-mode:before {transform: rotate(90deg);}
253 .icon-wide-mode:before {transform: rotate(90deg);}
254
254
255 // -- END ICON CLASSES -- //
255 // -- END ICON CLASSES -- //
256
256
257
257
258 //--- ICONS STYLING ------------------//
258 //--- ICONS STYLING ------------------//
259
259
260 .icon-git { color: @color4 !important; }
260 .icon-git { color: @color4 !important; }
261 .icon-hg { color: @color8 !important; }
261 .icon-hg { color: @color8 !important; }
262 .icon-svn { color: @color1 !important; }
262 .icon-svn { color: @color1 !important; }
263 .icon-git-inv { color: @color4 !important; }
263 .icon-git-inv { color: @color4 !important; }
264 .icon-hg-inv { color: @color8 !important; }
264 .icon-hg-inv { color: @color8 !important; }
265 .icon-svn-inv { color: @color1 !important; }
265 .icon-svn-inv { color: @color1 !important; }
266 .icon-repo-lock { color: #FF0000; }
266 .icon-repo-lock { color: #FF0000; }
267 .icon-repo-unlock { color: #FF0000; }
267 .icon-repo-unlock { color: #FF0000; }
268 .icon-false { color: @grey5 }
268 .icon-false { color: @grey5 }
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
271 .icon-flag-filled-red { color: @color5 !important; }
271 .icon-flag-filled-red { color: @color5 !important; }
272 .icon-filled-red { color: @color5 !important; }
272
273
273 .repo-switcher-dropdown .select2-result-label {
274 .repo-switcher-dropdown .select2-result-label {
274 .icon-git:before {
275 .icon-git:before {
275 &:extend(.icon-git-transparent:before);
276 &:extend(.icon-git-transparent:before);
276 }
277 }
277 .icon-hg:before {
278 .icon-hg:before {
278 &:extend(.icon-hg-transparent:before);
279 &:extend(.icon-hg-transparent:before);
279 color: @alert4;
280 color: @alert4;
280 }
281 }
281 .icon-svn:before {
282 .icon-svn:before {
282 &:extend(.icon-svn-transparent:before);
283 &:extend(.icon-svn-transparent:before);
283 }
284 }
284 }
285 }
285
286
286 .icon-user-group:before {
287 .icon-user-group:before {
287 &:extend(.icon-group:before);
288 &:extend(.icon-group:before);
288 margin: 0;
289 margin: 0;
289 font-size: 16px;
290 font-size: 16px;
290 }
291 }
@@ -1,393 +1,394 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
43 pyroutes.register('admin_home', '/_admin', []);
43 pyroutes.register('admin_home', '/_admin', []);
44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
65 pyroutes.register('admin_settings', '/_admin/settings', []);
65 pyroutes.register('admin_settings', '/_admin/settings', []);
66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
100 pyroutes.register('users', '/_admin/users', []);
100 pyroutes.register('users', '/_admin/users', []);
101 pyroutes.register('users_data', '/_admin/users_data', []);
101 pyroutes.register('users_data', '/_admin/users_data', []);
102 pyroutes.register('users_create', '/_admin/users/create', []);
102 pyroutes.register('users_create', '/_admin/users/create', []);
103 pyroutes.register('users_new', '/_admin/users/new', []);
103 pyroutes.register('users_new', '/_admin/users/new', []);
104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
113 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
117 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
117 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
118 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
118 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
119 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
119 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
120 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
120 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
121 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
121 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
122 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
122 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
123 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
123 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
124 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
124 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
125 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
125 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
126 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
126 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
127 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
127 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
128 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
128 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
129 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
129 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
130 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
130 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
131 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
131 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
132 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
132 pyroutes.register('user_groups', '/_admin/user_groups', []);
133 pyroutes.register('user_groups', '/_admin/user_groups', []);
133 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
134 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
134 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
135 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
135 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
136 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
136 pyroutes.register('repos', '/_admin/repos', []);
137 pyroutes.register('repos', '/_admin/repos', []);
137 pyroutes.register('repos_data', '/_admin/repos_data', []);
138 pyroutes.register('repos_data', '/_admin/repos_data', []);
138 pyroutes.register('repo_new', '/_admin/repos/new', []);
139 pyroutes.register('repo_new', '/_admin/repos/new', []);
139 pyroutes.register('repo_create', '/_admin/repos/create', []);
140 pyroutes.register('repo_create', '/_admin/repos/create', []);
140 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
141 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
141 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
142 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
142 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
143 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
143 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
144 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
144 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
145 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
145 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
146 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
146 pyroutes.register('channelstream_proxy', '/_channelstream', []);
147 pyroutes.register('channelstream_proxy', '/_channelstream', []);
147 pyroutes.register('upload_file', '/_file_store/upload', []);
148 pyroutes.register('upload_file', '/_file_store/upload', []);
148 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
149 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
149 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
150 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
150 pyroutes.register('logout', '/_admin/logout', []);
151 pyroutes.register('logout', '/_admin/logout', []);
151 pyroutes.register('reset_password', '/_admin/password_reset', []);
152 pyroutes.register('reset_password', '/_admin/password_reset', []);
152 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
153 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
153 pyroutes.register('home', '/', []);
154 pyroutes.register('home', '/', []);
154 pyroutes.register('main_page_repos_data', '/_home_repos', []);
155 pyroutes.register('main_page_repos_data', '/_home_repos', []);
155 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
156 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
156 pyroutes.register('user_autocomplete_data', '/_users', []);
157 pyroutes.register('user_autocomplete_data', '/_users', []);
157 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
158 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
158 pyroutes.register('repo_list_data', '/_repos', []);
159 pyroutes.register('repo_list_data', '/_repos', []);
159 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
160 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
160 pyroutes.register('goto_switcher_data', '/_goto_data', []);
161 pyroutes.register('goto_switcher_data', '/_goto_data', []);
161 pyroutes.register('markup_preview', '/_markup_preview', []);
162 pyroutes.register('markup_preview', '/_markup_preview', []);
162 pyroutes.register('file_preview', '/_file_preview', []);
163 pyroutes.register('file_preview', '/_file_preview', []);
163 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
164 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
164 pyroutes.register('journal', '/_admin/journal', []);
165 pyroutes.register('journal', '/_admin/journal', []);
165 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
166 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
166 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
167 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
167 pyroutes.register('journal_public', '/_admin/public_journal', []);
168 pyroutes.register('journal_public', '/_admin/public_journal', []);
168 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
169 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
169 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
170 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
170 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
171 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
171 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
172 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
172 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
173 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
173 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
174 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
174 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
175 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
175 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
176 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
176 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
177 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
177 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
186 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
186 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
187 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
187 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
188 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
188 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
190 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
190 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
191 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
191 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
192 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
192 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
194 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
194 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
195 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
195 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
200 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
200 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
214 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
214 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
215 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
215 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
216 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
216 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
217 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
217 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
219 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
219 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
220 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
220 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
221 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
221 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
222 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
222 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
223 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
223 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
224 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
224 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
225 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
225 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
226 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
226 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
227 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
227 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
228 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
228 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
229 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
229 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
230 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
230 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
231 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
231 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
232 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
232 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
233 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
233 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
234 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
234 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
235 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
235 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
236 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
236 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
237 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
237 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
238 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
238 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
239 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
239 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
240 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
240 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
241 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
241 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
242 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
242 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
243 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
243 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
244 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
244 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
245 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
246 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
246 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
247 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
247 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
248 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
248 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
249 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
249 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
250 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
250 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
251 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
251 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
252 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
252 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
253 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
253 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
254 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
254 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
255 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
255 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
256 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
256 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
257 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
257 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
258 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
258 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
259 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
259 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
260 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
260 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
261 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
261 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
262 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
262 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
263 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
263 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
264 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
264 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
265 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
265 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
266 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
266 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
267 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
267 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
268 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
268 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
269 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
269 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
270 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
270 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
271 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
271 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
272 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
272 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
273 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
273 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
274 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
274 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
275 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
275 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
276 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
276 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
277 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
277 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
278 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
278 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
279 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
279 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
280 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
280 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
281 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
281 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
282 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
282 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
283 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
283 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
284 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
284 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
285 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
285 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
286 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
286 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
287 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
287 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
288 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
288 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
289 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
289 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
290 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
290 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
291 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
291 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
292 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
292 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
293 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
294 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
294 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
295 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
295 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
296 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
296 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
297 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
297 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
298 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
298 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
299 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
299 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
300 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
300 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
301 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
301 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
302 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
302 pyroutes.register('search', '/_admin/search', []);
303 pyroutes.register('search', '/_admin/search', []);
303 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
304 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
304 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
305 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
305 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
306 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
306 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
307 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
307 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
308 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
308 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
309 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
309 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
310 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
310 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
311 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
311 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
312 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
312 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
313 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
313 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
314 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
314 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
315 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
315 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
316 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
316 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
317 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
317 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
318 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
318 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
319 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
319 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
320 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
320 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
321 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
321 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
322 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
322 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
323 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
323 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
324 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
324 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
325 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
325 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
326 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
326 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
327 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
327 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
328 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
328 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
329 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
329 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
330 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
330 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
331 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
331 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
332 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
332 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
333 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
333 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
334 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
334 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
335 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
335 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
336 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
336 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
337 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
337 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
338 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
338 pyroutes.register('gists_show', '/_admin/gists', []);
339 pyroutes.register('gists_show', '/_admin/gists', []);
339 pyroutes.register('gists_new', '/_admin/gists/new', []);
340 pyroutes.register('gists_new', '/_admin/gists/new', []);
340 pyroutes.register('gists_create', '/_admin/gists/create', []);
341 pyroutes.register('gists_create', '/_admin/gists/create', []);
341 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
342 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
342 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
343 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
343 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
344 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
344 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
345 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
345 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
346 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
346 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
347 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
347 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
348 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
348 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
349 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
349 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
350 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
350 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
351 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
351 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
352 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
352 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
353 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
353 pyroutes.register('apiv2', '/_admin/api', []);
354 pyroutes.register('apiv2', '/_admin/api', []);
354 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
355 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
355 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
356 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
356 pyroutes.register('login', '/_admin/login', []);
357 pyroutes.register('login', '/_admin/login', []);
357 pyroutes.register('register', '/_admin/register', []);
358 pyroutes.register('register', '/_admin/register', []);
358 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
359 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
359 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
360 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
360 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
361 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
361 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
362 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
362 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
363 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
363 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
364 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
364 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
365 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
365 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
366 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
366 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
367 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
367 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
368 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
368 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
369 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
369 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
370 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
370 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
371 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
371 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
372 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
372 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
373 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
373 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
374 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
374 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
375 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
375 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
376 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
376 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
377 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
377 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
378 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
378 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
379 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
379 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
380 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
380 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
381 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
381 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
382 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
382 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
383 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
383 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
384 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
384 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
385 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
385 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
386 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
386 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
387 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
387 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
388 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
388 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
389 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
389 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
390 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
390 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
391 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
391 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
392 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
392 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
393 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
393 }
394 }
@@ -1,1136 +1,1189 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%!
3 <%!
4 ## base64 filter e.g ${ example | base64 }
4 ## base64 filter e.g ${ example | base64 }
5 def base64(text):
5 def base64(text):
6 import base64
6 import base64
7 from rhodecode.lib.helpers import safe_str
7 from rhodecode.lib.helpers import safe_str
8 return base64.encodestring(safe_str(text))
8 return base64.encodestring(safe_str(text))
9 %>
9 %>
10
10
11 <%inherit file="root.mako"/>
11 <%inherit file="root.mako"/>
12
12
13 <%include file="/ejs_templates/templates.html"/>
13 <%include file="/ejs_templates/templates.html"/>
14
14
15 <div class="outerwrapper">
15 <div class="outerwrapper">
16 <!-- HEADER -->
16 <!-- HEADER -->
17 <div class="header">
17 <div class="header">
18 <div id="header-inner" class="wrapper">
18 <div id="header-inner" class="wrapper">
19 <div id="logo">
19 <div id="logo">
20 <div class="logo-wrapper">
20 <div class="logo-wrapper">
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
22 </div>
22 </div>
23 % if c.rhodecode_name:
23 % if c.rhodecode_name:
24 <div class="branding">
24 <div class="branding">
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
26 </div>
26 </div>
27 % endif
27 % endif
28 </div>
28 </div>
29 <!-- MENU BAR NAV -->
29 <!-- MENU BAR NAV -->
30 ${self.menu_bar_nav()}
30 ${self.menu_bar_nav()}
31 <!-- END MENU BAR NAV -->
31 <!-- END MENU BAR NAV -->
32 </div>
32 </div>
33 </div>
33 </div>
34 ${self.menu_bar_subnav()}
34 ${self.menu_bar_subnav()}
35 <!-- END HEADER -->
35 <!-- END HEADER -->
36
36
37 <!-- CONTENT -->
37 <!-- CONTENT -->
38 <div id="content" class="wrapper">
38 <div id="content" class="wrapper">
39
39
40 <rhodecode-toast id="notifications"></rhodecode-toast>
40 <rhodecode-toast id="notifications"></rhodecode-toast>
41
41
42 <div class="main">
42 <div class="main">
43 ${next.main()}
43 ${next.main()}
44 </div>
44 </div>
45 </div>
45 </div>
46 <!-- END CONTENT -->
46 <!-- END CONTENT -->
47
47
48 </div>
48 </div>
49 <!-- FOOTER -->
49 <!-- FOOTER -->
50 <div id="footer">
50 <div id="footer">
51 <div id="footer-inner" class="title wrapper">
51 <div id="footer-inner" class="title wrapper">
52 <div>
52 <div>
53 <p class="footer-link-right">
53 <p class="footer-link-right">
54 % if c.visual.show_version:
54 % if c.visual.show_version:
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
56 % endif
56 % endif
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
58 % if c.visual.rhodecode_support_url:
58 % if c.visual.rhodecode_support_url:
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
60 % endif
60 % endif
61 </p>
61 </p>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
63 <p class="server-instance" style="display:${sid}">
63 <p class="server-instance" style="display:${sid}">
64 ## display hidden instance ID if specially defined
64 ## display hidden instance ID if specially defined
65 % if c.rhodecode_instanceid:
65 % if c.rhodecode_instanceid:
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
67 % endif
67 % endif
68 </p>
68 </p>
69 </div>
69 </div>
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <!-- END FOOTER -->
73 <!-- END FOOTER -->
74
74
75 ### MAKO DEFS ###
75 ### MAKO DEFS ###
76
76
77 <%def name="menu_bar_subnav()">
77 <%def name="menu_bar_subnav()">
78 </%def>
78 </%def>
79
79
80 <%def name="breadcrumbs(class_='breadcrumbs')">
80 <%def name="breadcrumbs(class_='breadcrumbs')">
81 <div class="${class_}">
81 <div class="${class_}">
82 ${self.breadcrumbs_links()}
82 ${self.breadcrumbs_links()}
83 </div>
83 </div>
84 </%def>
84 </%def>
85
85
86 <%def name="admin_menu(active=None)">
86 <%def name="admin_menu(active=None)">
87
87
88 <div id="context-bar">
88 <div id="context-bar">
89 <div class="wrapper">
89 <div class="wrapper">
90 <div class="title">
90 <div class="title">
91 <div class="title-content">
91 <div class="title-content">
92 <div class="title-main">
92 <div class="title-main">
93 % if c.is_super_admin:
93 % if c.is_super_admin:
94 ${_('Super-admin Panel')}
94 ${_('Super-admin Panel')}
95 % else:
95 % else:
96 ${_('Delegated Admin Panel')}
96 ${_('Delegated Admin Panel')}
97 % endif
97 % endif
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <ul id="context-pages" class="navigation horizontal-list">
102 <ul id="context-pages" class="navigation horizontal-list">
103
103
104 ## super-admin case
104 ## super-admin case
105 % if c.is_super_admin:
105 % if c.is_super_admin:
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
116
116
117 ## delegated admin
117 ## delegated admin
118 % elif c.is_delegated_admin:
118 % elif c.is_delegated_admin:
119 <%
119 <%
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
123 %>
123 %>
124
124
125 %if repositories:
125 %if repositories:
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
127 %endif
127 %endif
128 %if repository_groups:
128 %if repository_groups:
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
130 %endif
130 %endif
131 %if user_groups:
131 %if user_groups:
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
133 %endif
133 %endif
134 % endif
134 % endif
135 </ul>
135 </ul>
136
136
137 </div>
137 </div>
138 <div class="clear"></div>
138 <div class="clear"></div>
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142 <%def name="dt_info_panel(elements)">
142 <%def name="dt_info_panel(elements)">
143 <dl class="dl-horizontal">
143 <dl class="dl-horizontal">
144 %for dt, dd, title, show_items in elements:
144 %for dt, dd, title, show_items in elements:
145 <dt>${dt}:</dt>
145 <dt>${dt}:</dt>
146 <dd title="${h.tooltip(title)}">
146 <dd title="${h.tooltip(title)}">
147 %if callable(dd):
147 %if callable(dd):
148 ## allow lazy evaluation of elements
148 ## allow lazy evaluation of elements
149 ${dd()}
149 ${dd()}
150 %else:
150 %else:
151 ${dd}
151 ${dd}
152 %endif
152 %endif
153 %if show_items:
153 %if show_items:
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
155 %endif
155 %endif
156 </dd>
156 </dd>
157
157
158 %if show_items:
158 %if show_items:
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
160 %for item in show_items:
160 %for item in show_items:
161 <dt></dt>
161 <dt></dt>
162 <dd>${item}</dd>
162 <dd>${item}</dd>
163 %endfor
163 %endfor
164 </div>
164 </div>
165 %endif
165 %endif
166
166
167 %endfor
167 %endfor
168 </dl>
168 </dl>
169 </%def>
169 </%def>
170
170
171 <%def name="tr_info_entry(element)">
171 <%def name="tr_info_entry(element)">
172 <% key, val, title, show_items = element %>
172 <% key, val, title, show_items = element %>
173
173
174 <tr>
174 <tr>
175 <td style="vertical-align: top">${key}</td>
175 <td style="vertical-align: top">${key}</td>
176 <td title="${h.tooltip(title)}">
176 <td title="${h.tooltip(title)}">
177 %if callable(val):
177 %if callable(val):
178 ## allow lazy evaluation of elements
178 ## allow lazy evaluation of elements
179 ${val()}
179 ${val()}
180 %else:
180 %else:
181 ${val}
181 ${val}
182 %endif
182 %endif
183 %if show_items:
183 %if show_items:
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
185 % for item in show_items:
185 % for item in show_items:
186 <dt></dt>
186 <dt></dt>
187 <dd>${item}</dd>
187 <dd>${item}</dd>
188 % endfor
188 % endfor
189 </div>
189 </div>
190 %endif
190 %endif
191 </td>
191 </td>
192 <td style="vertical-align: top">
192 <td style="vertical-align: top">
193 %if show_items:
193 %if show_items:
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
195 %endif
195 %endif
196 </td>
196 </td>
197 </tr>
197 </tr>
198
198
199 </%def>
199 </%def>
200
200
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
202 <%
202 <%
203 if size > 16:
203 if size > 16:
204 gravatar_class = ['gravatar','gravatar-large']
204 gravatar_class = ['gravatar','gravatar-large']
205 else:
205 else:
206 gravatar_class = ['gravatar']
206 gravatar_class = ['gravatar']
207
207
208 data_hovercard_url = ''
208 data_hovercard_url = ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
210
210
211 if tooltip:
211 if tooltip:
212 gravatar_class += ['tooltip-hovercard']
212 gravatar_class += ['tooltip-hovercard']
213 if extra_class:
213 if extra_class:
214 gravatar_class += extra_class
214 gravatar_class += extra_class
215 if tooltip and user:
215 if tooltip and user:
216 if user.username == h.DEFAULT_USER:
216 if user.username == h.DEFAULT_USER:
217 gravatar_class.pop(-1)
217 gravatar_class.pop(-1)
218 else:
218 else:
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
220 gravatar_class = ' '.join(gravatar_class)
220 gravatar_class = ' '.join(gravatar_class)
221
221
222 %>
222 %>
223 <%doc>
223 <%doc>
224 TODO: johbo: For now we serve double size images to make it smooth
224 TODO: johbo: For now we serve double size images to make it smooth
225 for retina. This is how it worked until now. Should be replaced
225 for retina. This is how it worked until now. Should be replaced
226 with a better solution at some point.
226 with a better solution at some point.
227 </%doc>
227 </%doc>
228
228
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
230 </%def>
230 </%def>
231
231
232
232
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
234 <%
234 <%
235 email = h.email_or_none(contact)
235 email = h.email_or_none(contact)
236 rc_user = h.discover_user(contact)
236 rc_user = h.discover_user(contact)
237 %>
237 %>
238
238
239 <div class="${_class}">
239 <div class="${_class}">
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
242 </div>
242 </div>
243 </%def>
243 </%def>
244
244
245
245
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
247 <%
247 <%
248 if (size > 16):
248 if (size > 16):
249 gravatar_class = 'icon-user-group-alt'
249 gravatar_class = 'icon-user-group-alt'
250 else:
250 else:
251 gravatar_class = 'icon-user-group-alt'
251 gravatar_class = 'icon-user-group-alt'
252
252
253 if tooltip:
253 if tooltip:
254 gravatar_class += ' tooltip-hovercard'
254 gravatar_class += ' tooltip-hovercard'
255
255
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
257 %>
257 %>
258 <%doc>
258 <%doc>
259 TODO: johbo: For now we serve double size images to make it smooth
259 TODO: johbo: For now we serve double size images to make it smooth
260 for retina. This is how it worked until now. Should be replaced
260 for retina. This is how it worked until now. Should be replaced
261 with a better solution at some point.
261 with a better solution at some point.
262 </%doc>
262 </%doc>
263
263
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
265 </%def>
265 </%def>
266
266
267 <%def name="repo_page_title(repo_instance)">
267 <%def name="repo_page_title(repo_instance)">
268 <div class="title-content repo-title">
268 <div class="title-content repo-title">
269
269
270 <div class="title-main">
270 <div class="title-main">
271 ## SVN/HG/GIT icons
271 ## SVN/HG/GIT icons
272 %if h.is_hg(repo_instance):
272 %if h.is_hg(repo_instance):
273 <i class="icon-hg"></i>
273 <i class="icon-hg"></i>
274 %endif
274 %endif
275 %if h.is_git(repo_instance):
275 %if h.is_git(repo_instance):
276 <i class="icon-git"></i>
276 <i class="icon-git"></i>
277 %endif
277 %endif
278 %if h.is_svn(repo_instance):
278 %if h.is_svn(repo_instance):
279 <i class="icon-svn"></i>
279 <i class="icon-svn"></i>
280 %endif
280 %endif
281
281
282 ## public/private
282 ## public/private
283 %if repo_instance.private:
283 %if repo_instance.private:
284 <i class="icon-repo-private"></i>
284 <i class="icon-repo-private"></i>
285 %else:
285 %else:
286 <i class="icon-repo-public"></i>
286 <i class="icon-repo-public"></i>
287 %endif
287 %endif
288
288
289 ## repo name with group name
289 ## repo name with group name
290 ${h.breadcrumb_repo_link(repo_instance)}
290 ${h.breadcrumb_repo_link(repo_instance)}
291
291
292 ## Context Actions
292 ## Context Actions
293 <div class="pull-right">
293 <div class="pull-right">
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
296
296
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
298 % if c.repository_is_user_following:
298 % if c.repository_is_user_following:
299 <i class="icon-eye-off"></i>${_('Unwatch')}
299 <i class="icon-eye-off"></i>${_('Unwatch')}
300 % else:
300 % else:
301 <i class="icon-eye"></i>${_('Watch')}
301 <i class="icon-eye"></i>${_('Watch')}
302 % endif
302 % endif
303
303
304 </a>
304 </a>
305 %else:
305 %else:
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
307 %endif
307 %endif
308 </div>
308 </div>
309
309
310 </div>
310 </div>
311
311
312 ## FORKED
312 ## FORKED
313 %if repo_instance.fork:
313 %if repo_instance.fork:
314 <p class="discreet">
314 <p class="discreet">
315 <i class="icon-code-fork"></i> ${_('Fork of')}
315 <i class="icon-code-fork"></i> ${_('Fork of')}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
317 </p>
317 </p>
318 %endif
318 %endif
319
319
320 ## IMPORTED FROM REMOTE
320 ## IMPORTED FROM REMOTE
321 %if repo_instance.clone_uri:
321 %if repo_instance.clone_uri:
322 <p class="discreet">
322 <p class="discreet">
323 <i class="icon-code-fork"></i> ${_('Clone from')}
323 <i class="icon-code-fork"></i> ${_('Clone from')}
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
325 </p>
325 </p>
326 %endif
326 %endif
327
327
328 ## LOCKING STATUS
328 ## LOCKING STATUS
329 %if repo_instance.locked[0]:
329 %if repo_instance.locked[0]:
330 <p class="locking_locked discreet">
330 <p class="locking_locked discreet">
331 <i class="icon-repo-lock"></i>
331 <i class="icon-repo-lock"></i>
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
333 </p>
333 </p>
334 %elif repo_instance.enable_locking:
334 %elif repo_instance.enable_locking:
335 <p class="locking_unlocked discreet">
335 <p class="locking_unlocked discreet">
336 <i class="icon-repo-unlock"></i>
336 <i class="icon-repo-unlock"></i>
337 ${_('Repository not locked. Pull repository to lock it.')}
337 ${_('Repository not locked. Pull repository to lock it.')}
338 </p>
338 </p>
339 %endif
339 %endif
340
340
341 </div>
341 </div>
342 </%def>
342 </%def>
343
343
344 <%def name="repo_menu(active=None)">
344 <%def name="repo_menu(active=None)">
345 <%
345 <%
346 ## determine if we have "any" option available
346 ## determine if we have "any" option available
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
348 has_actions = can_lock
348 has_actions = can_lock
349
349
350 %>
350 %>
351 % if c.rhodecode_db_repo.archived:
351 % if c.rhodecode_db_repo.archived:
352 <div class="alert alert-warning text-center">
352 <div class="alert alert-warning text-center">
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
354 </div>
354 </div>
355 % endif
355 % endif
356
356
357 <!--- REPO CONTEXT BAR -->
357 <!--- REPO CONTEXT BAR -->
358 <div id="context-bar">
358 <div id="context-bar">
359 <div class="wrapper">
359 <div class="wrapper">
360
360
361 <div class="title">
361 <div class="title">
362 ${self.repo_page_title(c.rhodecode_db_repo)}
362 ${self.repo_page_title(c.rhodecode_db_repo)}
363 </div>
363 </div>
364
364
365 <ul id="context-pages" class="navigation horizontal-list">
365 <ul id="context-pages" class="navigation horizontal-list">
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
370
370
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
373 <li class="${h.is_active('showpullrequest', active)}">
373 <li class="${h.is_active('showpullrequest', active)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
375 <div class="menulabel">
375 <div class="menulabel">
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
377 </div>
377 </div>
378 </a>
378 </a>
379 </li>
379 </li>
380 %endif
380 %endif
381
381
382 <li class="${h.is_active('artifacts', active)}">
382 <li class="${h.is_active('artifacts', active)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
384 <div class="menulabel">
384 <div class="menulabel">
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
386 </div>
386 </div>
387 </a>
387 </a>
388 </li>
388 </li>
389
389
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
392 %endif
392 %endif
393
393
394 <li class="${h.is_active('options', active)}">
394 <li class="${h.is_active('options', active)}">
395 % if has_actions:
395 % if has_actions:
396 <a class="menulink dropdown">
396 <a class="menulink dropdown">
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
398 </a>
398 </a>
399 <ul class="submenu">
399 <ul class="submenu">
400 %if can_lock:
400 %if can_lock:
401 %if c.rhodecode_db_repo.locked[0]:
401 %if c.rhodecode_db_repo.locked[0]:
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
403 %else:
403 %else:
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
405 %endif
405 %endif
406 %endif
406 %endif
407 </ul>
407 </ul>
408 % endif
408 % endif
409 </li>
409 </li>
410
410
411 </ul>
411 </ul>
412 </div>
412 </div>
413 <div class="clear"></div>
413 <div class="clear"></div>
414 </div>
414 </div>
415
415
416 <!--- REPO END CONTEXT BAR -->
416 <!--- REPO END CONTEXT BAR -->
417
417
418 </%def>
418 </%def>
419
419
420 <%def name="repo_group_page_title(repo_group_instance)">
420 <%def name="repo_group_page_title(repo_group_instance)">
421 <div class="title-content">
421 <div class="title-content">
422 <div class="title-main">
422 <div class="title-main">
423 ## Repository Group icon
423 ## Repository Group icon
424 <i class="icon-repo-group"></i>
424 <i class="icon-repo-group"></i>
425
425
426 ## repo name with group name
426 ## repo name with group name
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
428 </div>
428 </div>
429
429
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
431 <div class="repo-group-desc discreet">
431 <div class="repo-group-desc discreet">
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
433 </div>
433 </div>
434
434
435 </div>
435 </div>
436 </%def>
436 </%def>
437
437
438
438
439 <%def name="repo_group_menu(active=None)">
439 <%def name="repo_group_menu(active=None)">
440 <%
440 <%
441 gr_name = c.repo_group.group_name if c.repo_group else None
441 gr_name = c.repo_group.group_name if c.repo_group else None
442 # create repositories with write permission on group is set to true
442 # create repositories with write permission on group is set to true
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
444
444
445 %>
445 %>
446
446
447
447
448 <!--- REPO GROUP CONTEXT BAR -->
448 <!--- REPO GROUP CONTEXT BAR -->
449 <div id="context-bar">
449 <div id="context-bar">
450 <div class="wrapper">
450 <div class="wrapper">
451 <div class="title">
451 <div class="title">
452 ${self.repo_group_page_title(c.repo_group)}
452 ${self.repo_group_page_title(c.repo_group)}
453 </div>
453 </div>
454
454
455 <ul id="context-pages" class="navigation horizontal-list">
455 <ul id="context-pages" class="navigation horizontal-list">
456 <li class="${h.is_active('home', active)}">
456 <li class="${h.is_active('home', active)}">
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
458 </li>
458 </li>
459 % if c.is_super_admin or group_admin:
459 % if c.is_super_admin or group_admin:
460 <li class="${h.is_active('settings', active)}">
460 <li class="${h.is_active('settings', active)}">
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
462 </li>
462 </li>
463 % endif
463 % endif
464
464
465 </ul>
465 </ul>
466 </div>
466 </div>
467 <div class="clear"></div>
467 <div class="clear"></div>
468 </div>
468 </div>
469
469
470 <!--- REPO GROUP CONTEXT BAR -->
470 <!--- REPO GROUP CONTEXT BAR -->
471
471
472 </%def>
472 </%def>
473
473
474
474
475 <%def name="usermenu(active=False)">
475 <%def name="usermenu(active=False)">
476 <%
476 <%
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
478
478
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
480 # create repositories with write permission on group is set to true
480 # create repositories with write permission on group is set to true
481
481
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
486
486
487 can_create_repos = c.is_super_admin or c.can_create_repo
487 can_create_repos = c.is_super_admin or c.can_create_repo
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
489
489
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
492 %>
492 %>
493
493
494 % if not_anonymous:
494 % if not_anonymous:
495 <%
495 <%
496 default_target_group = dict()
496 default_target_group = dict()
497 if c.rhodecode_user.personal_repo_group:
497 if c.rhodecode_user.personal_repo_group:
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
499 %>
499 %>
500
500
501 ## create action
501 ## create action
502 <li>
502 <li>
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
505 </a>
505 </a>
506
506
507 <div class="action-menu submenu">
507 <div class="action-menu submenu">
508
508
509 <ol>
509 <ol>
510 ## scope of within a repository
510 ## scope of within a repository
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
512 <li class="submenu-title">${_('This Repository')}</li>
512 <li class="submenu-title">${_('This Repository')}</li>
513 <li>
513 <li>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
515 </li>
515 </li>
516 % if can_fork:
516 % if can_fork:
517 <li>
517 <li>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
519 </li>
519 </li>
520 % endif
520 % endif
521 % endif
521 % endif
522
522
523 ## scope of within repository groups
523 ## scope of within repository groups
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
525 <li class="submenu-title">${_('This Repository Group')}</li>
525 <li class="submenu-title">${_('This Repository Group')}</li>
526
526
527 % if can_create_repos_in_group:
527 % if can_create_repos_in_group:
528 <li>
528 <li>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
530 </li>
530 </li>
531 % endif
531 % endif
532
532
533 % if can_create_repo_groups_in_group:
533 % if can_create_repo_groups_in_group:
534 <li>
534 <li>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
536 </li>
536 </li>
537 % endif
537 % endif
538 % endif
538 % endif
539
539
540 ## personal group
540 ## personal group
541 % if c.rhodecode_user.personal_repo_group:
541 % if c.rhodecode_user.personal_repo_group:
542 <li class="submenu-title">Personal Group</li>
542 <li class="submenu-title">Personal Group</li>
543
543
544 <li>
544 <li>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
546 </li>
546 </li>
547
547
548 <li>
548 <li>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
550 </li>
550 </li>
551 % endif
551 % endif
552
552
553 ## Global actions
553 ## Global actions
554 <li class="submenu-title">RhodeCode</li>
554 <li class="submenu-title">RhodeCode</li>
555 % if can_create_repos:
555 % if can_create_repos:
556 <li>
556 <li>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
558 </li>
558 </li>
559 % endif
559 % endif
560
560
561 % if can_create_repo_groups:
561 % if can_create_repo_groups:
562 <li>
562 <li>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
564 </li>
564 </li>
565 % endif
565 % endif
566
566
567 <li>
567 <li>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
569 </li>
569 </li>
570
570
571 </ol>
571 </ol>
572
572
573 </div>
573 </div>
574 </li>
574 </li>
575
575
576 ## notifications
576 ## notifications
577 <li>
577 <li>
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
579 ${c.unread_notifications}
579 ${c.unread_notifications}
580 </a>
580 </a>
581 </li>
581 </li>
582 % endif
582 % endif
583
583
584 ## USER MENU
584 ## USER MENU
585 <li id="quick_login_li" class="${'active' if active else ''}">
585 <li id="quick_login_li" class="${'active' if active else ''}">
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
588 ${gravatar(c.rhodecode_user.email, 20)}
588 ${gravatar(c.rhodecode_user.email, 20)}
589 <span class="user">
589 <span class="user">
590 <span>${_('Sign in')}</span>
590 <span>${_('Sign in')}</span>
591 </span>
591 </span>
592 </a>
592 </a>
593 % else:
593 % else:
594 ## logged in user
594 ## logged in user
595 <a id="quick_login_link" class="menulink childs">
595 <a id="quick_login_link" class="menulink childs">
596 ${gravatar(c.rhodecode_user.email, 20)}
596 ${gravatar(c.rhodecode_user.email, 20)}
597 <span class="user">
597 <span class="user">
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
599 <div class="show_more"></div>
599 <div class="show_more"></div>
600 </span>
600 </span>
601 </a>
601 </a>
602 ## subnav with menu for logged in user
602 ## subnav with menu for logged in user
603 <div class="user-menu submenu">
603 <div class="user-menu submenu">
604 <div id="quick_login">
604 <div id="quick_login">
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
606 <div class="">
606 <div class="">
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
610 </div>
610 </div>
611 <div class="">
611 <div class="">
612 <ol class="links">
612 <ol class="links">
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
614 % if c.rhodecode_user.personal_repo_group:
614 % if c.rhodecode_user.personal_repo_group:
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
616 % endif
616 % endif
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
618
618
619 % if c.debug_style:
619 % if c.debug_style:
620 <li>
620 <li>
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
622 <div class="menulabel">${_('[Style]')}</div>
622 <div class="menulabel">${_('[Style]')}</div>
623 </a>
623 </a>
624 </li>
624 </li>
625 % endif
625 % endif
626
626
627 ## bookmark-items
627 ## bookmark-items
628 <li class="bookmark-items">
628 <li class="bookmark-items">
629 ${_('Bookmarks')}
629 ${_('Bookmarks')}
630 <div class="pull-right">
630 <div class="pull-right">
631 <a href="${h.route_path('my_account_bookmarks')}">
631 <a href="${h.route_path('my_account_bookmarks')}">
632
632
633 <i class="icon-cog"></i>
633 <i class="icon-cog"></i>
634 </a>
634 </a>
635 </div>
635 </div>
636 </li>
636 </li>
637 % if not c.bookmark_items:
637 % if not c.bookmark_items:
638 <li>
638 <li>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
640 </li>
640 </li>
641 % endif
641 % endif
642 % for item in c.bookmark_items:
642 % for item in c.bookmark_items:
643 <li>
643 <li>
644 % if item.repository:
644 % if item.repository:
645 <div>
645 <div>
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
647 <code>${item.position}</code>
647 <code>${item.position}</code>
648 % if item.repository.repo_type == 'hg':
648 % if item.repository.repo_type == 'hg':
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
650 % elif item.repository.repo_type == 'git':
650 % elif item.repository.repo_type == 'git':
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
652 % elif item.repository.repo_type == 'svn':
652 % elif item.repository.repo_type == 'svn':
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
654 % endif
654 % endif
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
656 </a>
656 </a>
657 </div>
657 </div>
658 % elif item.repository_group:
658 % elif item.repository_group:
659 <div>
659 <div>
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
661 <code>${item.position}</code>
661 <code>${item.position}</code>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
664 </a>
664 </a>
665 </div>
665 </div>
666 % else:
666 % else:
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
668 <code>${item.position}</code>
668 <code>${item.position}</code>
669 ${item.title}
669 ${item.title}
670 </a>
670 </a>
671 % endif
671 % endif
672 </li>
672 </li>
673 % endfor
673 % endfor
674
674
675 <li class="logout">
675 <li class="logout">
676 ${h.secure_form(h.route_path('logout'), request=request)}
676 ${h.secure_form(h.route_path('logout'), request=request)}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
678 ${h.end_form()}
678 ${h.end_form()}
679 </li>
679 </li>
680 </ol>
680 </ol>
681 </div>
681 </div>
682 %endif
682 %endif
683 </div>
683 </div>
684 </div>
684 </div>
685
685
686 % endif
686 % endif
687 </li>
687 </li>
688 </%def>
688 </%def>
689
689
690 <%def name="menu_items(active=None)">
690 <%def name="menu_items(active=None)">
691 <%
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 %>
695 <style>
696
697 </style>
691
698
692 <ul id="quick" class="main_nav navigation horizontal-list">
699 <ul id="quick" class="main_nav navigation horizontal-list">
693 ## notice box for important system messages
700 ## notice box for important system messages
694 <li style="display: none">
701 <li style="display: ${notice_display}">
695 <a class="notice-box" href="#openNotice" onclick="return false">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
696 <div class="menulabel-notice" >
703 <div class="menulabel-notice ${notice_level}" >
697 0
704 ${len(notice_messages)}
698 </div>
705 </div>
699 </a>
706 </a>
700 </li>
707 </li>
708 <div class="notice-messages-container" style="display: none">
709 <div class="notice-messages">
710 <table class="rctable">
711 % for notice in notice_messages:
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 <td style="vertical-align: text-top; width: 20px">
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 </td>
716 <td>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 ${notice['subject']}
701
719
720 <div id="notice-${notice['msg_id']}" style="display: none">
721 ${h.render(notice['body'], renderer='markdown')}
722 </div>
723 </td>
724 <td style="vertical-align: text-top; width: 35px;">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 <i class="icon-remove icon-filled-red"></i>
727 </a>
728 </td>
729 </tr>
730
731 % endfor
732 </table>
733 </div>
734 </div>
702 ## Main filter
735 ## Main filter
703 <li>
736 <li>
704 <div class="menulabel main_filter_box">
737 <div class="menulabel main_filter_box">
705 <div class="main_filter_input_box">
738 <div class="main_filter_input_box">
706 <ul class="searchItems">
739 <ul class="searchItems">
707
740
708 <li class="searchTag searchTagIcon">
741 <li class="searchTag searchTagIcon">
709 <i class="icon-search"></i>
742 <i class="icon-search"></i>
710 </li>
743 </li>
711
744
712 % if c.template_context['search_context']['repo_id']:
745 % if c.template_context['search_context']['repo_id']:
713 <li class="searchTag searchTagFilter searchTagHidable" >
746 <li class="searchTag searchTagFilter searchTagHidable" >
714 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
715 <span class="tag">
748 <span class="tag">
716 This repo
749 This repo
717 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
718 </span>
751 </span>
719 ##</a>
752 ##</a>
720 </li>
753 </li>
721 % elif c.template_context['search_context']['repo_group_id']:
754 % elif c.template_context['search_context']['repo_group_id']:
722 <li class="searchTag searchTagFilter searchTagHidable">
755 <li class="searchTag searchTagFilter searchTagHidable">
723 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
724 <span class="tag">
757 <span class="tag">
725 This group
758 This group
726 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
727 </span>
760 </span>
728 ##</a>
761 ##</a>
729 </li>
762 </li>
730 % endif
763 % endif
731
764
732 <li class="searchTagInput">
765 <li class="searchTagInput">
733 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
734 </li>
767 </li>
735 <li class="searchTag searchTagHelp">
768 <li class="searchTag searchTagHelp">
736 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
737 </li>
770 </li>
738 </ul>
771 </ul>
739 </div>
772 </div>
740 </div>
773 </div>
741
774
742 <div id="main_filter_help" style="display: none">
775 <div id="main_filter_help" style="display: none">
743 - Use '/' key to quickly access this field.
776 - Use '/' key to quickly access this field.
744
777
745 - Enter a name of repository, or repository group for quick search.
778 - Enter a name of repository, or repository group for quick search.
746
779
747 - Prefix query to allow special search:
780 - Prefix query to allow special search:
748
781
749 user:admin, to search for usernames, always global
782 user:admin, to search for usernames, always global
750
783
751 user_group:devops, to search for user groups, always global
784 user_group:devops, to search for user groups, always global
752
785
753 commit:efced4, to search for commits, scoped to repositories or groups
786 commit:efced4, to search for commits, scoped to repositories or groups
754
787
755 file:models.py, to search for file paths, scoped to repositories or groups
788 file:models.py, to search for file paths, scoped to repositories or groups
756
789
757 % if c.template_context['search_context']['repo_id']:
790 % if c.template_context['search_context']['repo_id']:
758 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
791 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
759 % elif c.template_context['search_context']['repo_group_id']:
792 % elif c.template_context['search_context']['repo_group_id']:
760 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
793 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
761 % else:
794 % else:
762 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
795 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
763 % endif
796 % endif
764 </div>
797 </div>
765 </li>
798 </li>
766
799
767 ## ROOT MENU
800 ## ROOT MENU
768 <li class="${h.is_active('home', active)}">
801 <li class="${h.is_active('home', active)}">
769 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
802 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
770 <div class="menulabel">${_('Home')}</div>
803 <div class="menulabel">${_('Home')}</div>
771 </a>
804 </a>
772 </li>
805 </li>
773
806
774 %if c.rhodecode_user.username != h.DEFAULT_USER:
807 %if c.rhodecode_user.username != h.DEFAULT_USER:
775 <li class="${h.is_active('journal', active)}">
808 <li class="${h.is_active('journal', active)}">
776 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
809 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
777 <div class="menulabel">${_('Journal')}</div>
810 <div class="menulabel">${_('Journal')}</div>
778 </a>
811 </a>
779 </li>
812 </li>
780 %else:
813 %else:
781 <li class="${h.is_active('journal', active)}">
814 <li class="${h.is_active('journal', active)}">
782 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
815 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
783 <div class="menulabel">${_('Public journal')}</div>
816 <div class="menulabel">${_('Public journal')}</div>
784 </a>
817 </a>
785 </li>
818 </li>
786 %endif
819 %endif
787
820
788 <li class="${h.is_active('gists', active)}">
821 <li class="${h.is_active('gists', active)}">
789 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
822 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
790 <div class="menulabel">${_('Gists')}</div>
823 <div class="menulabel">${_('Gists')}</div>
791 </a>
824 </a>
792 </li>
825 </li>
793
826
794 % if c.is_super_admin or c.is_delegated_admin:
827 % if c.is_super_admin or c.is_delegated_admin:
795 <li class="${h.is_active('admin', active)}">
828 <li class="${h.is_active('admin', active)}">
796 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
829 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
797 <div class="menulabel">${_('Admin')} </div>
830 <div class="menulabel">${_('Admin')} </div>
798 </a>
831 </a>
799 </li>
832 </li>
800 % endif
833 % endif
801
834
802 ## render extra user menu
835 ## render extra user menu
803 ${usermenu(active=(active=='my_account'))}
836 ${usermenu(active=(active=='my_account'))}
804
837
805 </ul>
838 </ul>
806
839
807 <script type="text/javascript">
840 <script type="text/javascript">
808 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
841 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
809
842
810 var formatRepoResult = function(result, container, query, escapeMarkup) {
843 var formatRepoResult = function(result, container, query, escapeMarkup) {
811 return function(data, escapeMarkup) {
844 return function(data, escapeMarkup) {
812 if (!data.repo_id){
845 if (!data.repo_id){
813 return data.text; // optgroup text Repositories
846 return data.text; // optgroup text Repositories
814 }
847 }
815
848
816 var tmpl = '';
849 var tmpl = '';
817 var repoType = data['repo_type'];
850 var repoType = data['repo_type'];
818 var repoName = data['text'];
851 var repoName = data['text'];
819
852
820 if(data && data.type == 'repo'){
853 if(data && data.type == 'repo'){
821 if(repoType === 'hg'){
854 if(repoType === 'hg'){
822 tmpl += '<i class="icon-hg"></i> ';
855 tmpl += '<i class="icon-hg"></i> ';
823 }
856 }
824 else if(repoType === 'git'){
857 else if(repoType === 'git'){
825 tmpl += '<i class="icon-git"></i> ';
858 tmpl += '<i class="icon-git"></i> ';
826 }
859 }
827 else if(repoType === 'svn'){
860 else if(repoType === 'svn'){
828 tmpl += '<i class="icon-svn"></i> ';
861 tmpl += '<i class="icon-svn"></i> ';
829 }
862 }
830 if(data['private']){
863 if(data['private']){
831 tmpl += '<i class="icon-lock" ></i> ';
864 tmpl += '<i class="icon-lock" ></i> ';
832 }
865 }
833 else if(visualShowPublicIcon){
866 else if(visualShowPublicIcon){
834 tmpl += '<i class="icon-unlock-alt"></i> ';
867 tmpl += '<i class="icon-unlock-alt"></i> ';
835 }
868 }
836 }
869 }
837 tmpl += escapeMarkup(repoName);
870 tmpl += escapeMarkup(repoName);
838 return tmpl;
871 return tmpl;
839
872
840 }(result, escapeMarkup);
873 }(result, escapeMarkup);
841 };
874 };
842
875
843 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
876 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
844 return function(data, escapeMarkup) {
877 return function(data, escapeMarkup) {
845 if (!data.repo_group_id){
878 if (!data.repo_group_id){
846 return data.text; // optgroup text Repositories
879 return data.text; // optgroup text Repositories
847 }
880 }
848
881
849 var tmpl = '';
882 var tmpl = '';
850 var repoGroupName = data['text'];
883 var repoGroupName = data['text'];
851
884
852 if(data){
885 if(data){
853
886
854 tmpl += '<i class="icon-repo-group"></i> ';
887 tmpl += '<i class="icon-repo-group"></i> ';
855
888
856 }
889 }
857 tmpl += escapeMarkup(repoGroupName);
890 tmpl += escapeMarkup(repoGroupName);
858 return tmpl;
891 return tmpl;
859
892
860 }(result, escapeMarkup);
893 }(result, escapeMarkup);
861 };
894 };
862
895
863 var escapeRegExChars = function (value) {
896 var escapeRegExChars = function (value) {
864 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
897 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
865 };
898 };
866
899
867 var getRepoIcon = function(repo_type) {
900 var getRepoIcon = function(repo_type) {
868 if (repo_type === 'hg') {
901 if (repo_type === 'hg') {
869 return '<i class="icon-hg"></i> ';
902 return '<i class="icon-hg"></i> ';
870 }
903 }
871 else if (repo_type === 'git') {
904 else if (repo_type === 'git') {
872 return '<i class="icon-git"></i> ';
905 return '<i class="icon-git"></i> ';
873 }
906 }
874 else if (repo_type === 'svn') {
907 else if (repo_type === 'svn') {
875 return '<i class="icon-svn"></i> ';
908 return '<i class="icon-svn"></i> ';
876 }
909 }
877 return ''
910 return ''
878 };
911 };
879
912
880 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
913 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
881
914
882 if (value.split(':').length === 2) {
915 if (value.split(':').length === 2) {
883 value = value.split(':')[1]
916 value = value.split(':')[1]
884 }
917 }
885
918
886 var searchType = data['type'];
919 var searchType = data['type'];
887 var searchSubType = data['subtype'];
920 var searchSubType = data['subtype'];
888 var valueDisplay = data['value_display'];
921 var valueDisplay = data['value_display'];
889 var valueIcon = data['value_icon'];
922 var valueIcon = data['value_icon'];
890
923
891 var pattern = '(' + escapeRegExChars(value) + ')';
924 var pattern = '(' + escapeRegExChars(value) + ')';
892
925
893 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
926 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
894
927
895 // highlight match
928 // highlight match
896 if (searchType != 'text') {
929 if (searchType != 'text') {
897 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
930 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
898 }
931 }
899
932
900 var icon = '';
933 var icon = '';
901
934
902 if (searchType === 'hint') {
935 if (searchType === 'hint') {
903 icon += '<i class="icon-repo-group"></i> ';
936 icon += '<i class="icon-repo-group"></i> ';
904 }
937 }
905 // full text search/hints
938 // full text search/hints
906 else if (searchType === 'search') {
939 else if (searchType === 'search') {
907 if (valueIcon === undefined) {
940 if (valueIcon === undefined) {
908 icon += '<i class="icon-more"></i> ';
941 icon += '<i class="icon-more"></i> ';
909 } else {
942 } else {
910 icon += valueIcon + ' ';
943 icon += valueIcon + ' ';
911 }
944 }
912
945
913 if (searchSubType !== undefined && searchSubType == 'repo') {
946 if (searchSubType !== undefined && searchSubType == 'repo') {
914 valueDisplay += '<div class="pull-right tag">repository</div>';
947 valueDisplay += '<div class="pull-right tag">repository</div>';
915 }
948 }
916 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
949 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
917 valueDisplay += '<div class="pull-right tag">repo group</div>';
950 valueDisplay += '<div class="pull-right tag">repo group</div>';
918 }
951 }
919 }
952 }
920 // repository
953 // repository
921 else if (searchType === 'repo') {
954 else if (searchType === 'repo') {
922
955
923 var repoIcon = getRepoIcon(data['repo_type']);
956 var repoIcon = getRepoIcon(data['repo_type']);
924 icon += repoIcon;
957 icon += repoIcon;
925
958
926 if (data['private']) {
959 if (data['private']) {
927 icon += '<i class="icon-lock" ></i> ';
960 icon += '<i class="icon-lock" ></i> ';
928 }
961 }
929 else if (visualShowPublicIcon) {
962 else if (visualShowPublicIcon) {
930 icon += '<i class="icon-unlock-alt"></i> ';
963 icon += '<i class="icon-unlock-alt"></i> ';
931 }
964 }
932 }
965 }
933 // repository groups
966 // repository groups
934 else if (searchType === 'repo_group') {
967 else if (searchType === 'repo_group') {
935 icon += '<i class="icon-repo-group"></i> ';
968 icon += '<i class="icon-repo-group"></i> ';
936 }
969 }
937 // user group
970 // user group
938 else if (searchType === 'user_group') {
971 else if (searchType === 'user_group') {
939 icon += '<i class="icon-group"></i> ';
972 icon += '<i class="icon-group"></i> ';
940 }
973 }
941 // user
974 // user
942 else if (searchType === 'user') {
975 else if (searchType === 'user') {
943 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
976 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
944 }
977 }
945 // commit
978 // commit
946 else if (searchType === 'commit') {
979 else if (searchType === 'commit') {
947 var repo_data = data['repo_data'];
980 var repo_data = data['repo_data'];
948 var repoIcon = getRepoIcon(repo_data['repository_type']);
981 var repoIcon = getRepoIcon(repo_data['repository_type']);
949 if (repoIcon) {
982 if (repoIcon) {
950 icon += repoIcon;
983 icon += repoIcon;
951 } else {
984 } else {
952 icon += '<i class="icon-tag"></i>';
985 icon += '<i class="icon-tag"></i>';
953 }
986 }
954 }
987 }
955 // file
988 // file
956 else if (searchType === 'file') {
989 else if (searchType === 'file') {
957 var repo_data = data['repo_data'];
990 var repo_data = data['repo_data'];
958 var repoIcon = getRepoIcon(repo_data['repository_type']);
991 var repoIcon = getRepoIcon(repo_data['repository_type']);
959 if (repoIcon) {
992 if (repoIcon) {
960 icon += repoIcon;
993 icon += repoIcon;
961 } else {
994 } else {
962 icon += '<i class="icon-tag"></i>';
995 icon += '<i class="icon-tag"></i>';
963 }
996 }
964 }
997 }
965 // generic text
998 // generic text
966 else if (searchType === 'text') {
999 else if (searchType === 'text') {
967 icon = '';
1000 icon = '';
968 }
1001 }
969
1002
970 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1003 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
971 return tmpl.format(icon, valueDisplay);
1004 return tmpl.format(icon, valueDisplay);
972 };
1005 };
973
1006
974 var handleSelect = function(element, suggestion) {
1007 var handleSelect = function(element, suggestion) {
975 if (suggestion.type === "hint") {
1008 if (suggestion.type === "hint") {
976 // we skip action
1009 // we skip action
977 $('#main_filter').focus();
1010 $('#main_filter').focus();
978 }
1011 }
979 else if (suggestion.type === "text") {
1012 else if (suggestion.type === "text") {
980 // we skip action
1013 // we skip action
981 $('#main_filter').focus();
1014 $('#main_filter').focus();
982
1015
983 } else {
1016 } else {
984 window.location = suggestion['url'];
1017 window.location = suggestion['url'];
985 }
1018 }
986 };
1019 };
987
1020
988 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1021 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
989 if (queryLowerCase.split(':').length === 2) {
1022 if (queryLowerCase.split(':').length === 2) {
990 queryLowerCase = queryLowerCase.split(':')[1]
1023 queryLowerCase = queryLowerCase.split(':')[1]
991 }
1024 }
992 if (suggestion.type === "text") {
1025 if (suggestion.type === "text") {
993 // special case we don't want to "skip" display for
1026 // special case we don't want to "skip" display for
994 return true
1027 return true
995 }
1028 }
996 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1029 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
997 };
1030 };
998
1031
999 var cleanContext = {
1032 var cleanContext = {
1000 repo_view_type: null,
1033 repo_view_type: null,
1001
1034
1002 repo_id: null,
1035 repo_id: null,
1003 repo_name: "",
1036 repo_name: "",
1004
1037
1005 repo_group_id: null,
1038 repo_group_id: null,
1006 repo_group_name: null
1039 repo_group_name: null
1007 };
1040 };
1008 var removeGoToFilter = function () {
1041 var removeGoToFilter = function () {
1009 $('.searchTagHidable').hide();
1042 $('.searchTagHidable').hide();
1010 $('#main_filter').autocomplete(
1043 $('#main_filter').autocomplete(
1011 'setOptions', {params:{search_context: cleanContext}});
1044 'setOptions', {params:{search_context: cleanContext}});
1012 };
1045 };
1013
1046
1014 $('#main_filter').autocomplete({
1047 $('#main_filter').autocomplete({
1015 serviceUrl: pyroutes.url('goto_switcher_data'),
1048 serviceUrl: pyroutes.url('goto_switcher_data'),
1016 params: {
1049 params: {
1017 "search_context": templateContext.search_context
1050 "search_context": templateContext.search_context
1018 },
1051 },
1019 minChars:2,
1052 minChars:2,
1020 maxHeight:400,
1053 maxHeight:400,
1021 deferRequestBy: 300, //miliseconds
1054 deferRequestBy: 300, //miliseconds
1022 tabDisabled: true,
1055 tabDisabled: true,
1023 autoSelectFirst: false,
1056 autoSelectFirst: false,
1024 containerClass: 'autocomplete-qfilter-suggestions',
1057 containerClass: 'autocomplete-qfilter-suggestions',
1025 formatResult: autocompleteMainFilterFormatResult,
1058 formatResult: autocompleteMainFilterFormatResult,
1026 lookupFilter: autocompleteMainFilterResult,
1059 lookupFilter: autocompleteMainFilterResult,
1027 onSelect: function (element, suggestion) {
1060 onSelect: function (element, suggestion) {
1028 handleSelect(element, suggestion);
1061 handleSelect(element, suggestion);
1029 return false;
1062 return false;
1030 },
1063 },
1031 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1064 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1032 if (jqXHR !== 'abort') {
1065 if (jqXHR !== 'abort') {
1033 alert("Error during search.\nError code: {0}".format(textStatus));
1066 alert("Error during search.\nError code: {0}".format(textStatus));
1034 window.location = '';
1067 window.location = '';
1035 }
1068 }
1036 },
1069 },
1037 onSearchStart: function (params) {
1070 onSearchStart: function (params) {
1038 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1071 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1039 },
1072 },
1040 onSearchComplete: function (query, suggestions) {
1073 onSearchComplete: function (query, suggestions) {
1041 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1074 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1042 },
1075 },
1043 });
1076 });
1044
1077
1045 showMainFilterBox = function () {
1078 showMainFilterBox = function () {
1046 $('#main_filter_help').toggle();
1079 $('#main_filter_help').toggle();
1047 };
1080 };
1048
1081
1049 $('#main_filter').on('keydown.autocomplete', function (e) {
1082 $('#main_filter').on('keydown.autocomplete', function (e) {
1050
1083
1051 var BACKSPACE = 8;
1084 var BACKSPACE = 8;
1052 var el = $(e.currentTarget);
1085 var el = $(e.currentTarget);
1053 if(e.which === BACKSPACE){
1086 if(e.which === BACKSPACE){
1054 var inputVal = el.val();
1087 var inputVal = el.val();
1055 if (inputVal === ""){
1088 if (inputVal === ""){
1056 removeGoToFilter()
1089 removeGoToFilter()
1057 }
1090 }
1058 }
1091 }
1059 });
1092 });
1060
1093
1094 var dismissNotice = function(noticeId) {
1095
1096 var url = pyroutes.url('user_notice_dismiss',
1097 {"user_id": templateContext.rhodecode_user.user_id});
1098
1099 var postData = {
1100 'csrf_token': CSRF_TOKEN,
1101 'notice_id': noticeId,
1102 };
1103
1104 var success = function(response) {
1105 $('#notice-message-' + noticeId).remove();
1106 return false;
1107 };
1108 var failure = function(data, textStatus, xhr) {
1109 alert("error processing request: " + textStatus);
1110 return false;
1111 };
1112 ajaxPOST(url, postData, success, failure);
1113 }
1061 </script>
1114 </script>
1062 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1115 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1063 </%def>
1116 </%def>
1064
1117
1065 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1118 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1066 <div class="modal-dialog">
1119 <div class="modal-dialog">
1067 <div class="modal-content">
1120 <div class="modal-content">
1068 <div class="modal-header">
1121 <div class="modal-header">
1069 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1122 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1070 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1123 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1071 </div>
1124 </div>
1072 <div class="modal-body">
1125 <div class="modal-body">
1073 <div class="block-left">
1126 <div class="block-left">
1074 <table class="keyboard-mappings">
1127 <table class="keyboard-mappings">
1075 <tbody>
1128 <tbody>
1076 <tr>
1129 <tr>
1077 <th></th>
1130 <th></th>
1078 <th>${_('Site-wide shortcuts')}</th>
1131 <th>${_('Site-wide shortcuts')}</th>
1079 </tr>
1132 </tr>
1080 <%
1133 <%
1081 elems = [
1134 elems = [
1082 ('/', 'Use quick search box'),
1135 ('/', 'Use quick search box'),
1083 ('g h', 'Goto home page'),
1136 ('g h', 'Goto home page'),
1084 ('g g', 'Goto my private gists page'),
1137 ('g g', 'Goto my private gists page'),
1085 ('g G', 'Goto my public gists page'),
1138 ('g G', 'Goto my public gists page'),
1086 ('g 0-9', 'Goto bookmarked items from 0-9'),
1139 ('g 0-9', 'Goto bookmarked items from 0-9'),
1087 ('n r', 'New repository page'),
1140 ('n r', 'New repository page'),
1088 ('n g', 'New gist page'),
1141 ('n g', 'New gist page'),
1089 ]
1142 ]
1090 %>
1143 %>
1091 %for key, desc in elems:
1144 %for key, desc in elems:
1092 <tr>
1145 <tr>
1093 <td class="keys">
1146 <td class="keys">
1094 <span class="key tag">${key}</span>
1147 <span class="key tag">${key}</span>
1095 </td>
1148 </td>
1096 <td>${desc}</td>
1149 <td>${desc}</td>
1097 </tr>
1150 </tr>
1098 %endfor
1151 %endfor
1099 </tbody>
1152 </tbody>
1100 </table>
1153 </table>
1101 </div>
1154 </div>
1102 <div class="block-left">
1155 <div class="block-left">
1103 <table class="keyboard-mappings">
1156 <table class="keyboard-mappings">
1104 <tbody>
1157 <tbody>
1105 <tr>
1158 <tr>
1106 <th></th>
1159 <th></th>
1107 <th>${_('Repositories')}</th>
1160 <th>${_('Repositories')}</th>
1108 </tr>
1161 </tr>
1109 <%
1162 <%
1110 elems = [
1163 elems = [
1111 ('g s', 'Goto summary page'),
1164 ('g s', 'Goto summary page'),
1112 ('g c', 'Goto changelog page'),
1165 ('g c', 'Goto changelog page'),
1113 ('g f', 'Goto files page'),
1166 ('g f', 'Goto files page'),
1114 ('g F', 'Goto files page with file search activated'),
1167 ('g F', 'Goto files page with file search activated'),
1115 ('g p', 'Goto pull requests page'),
1168 ('g p', 'Goto pull requests page'),
1116 ('g o', 'Goto repository settings'),
1169 ('g o', 'Goto repository settings'),
1117 ('g O', 'Goto repository access permissions settings'),
1170 ('g O', 'Goto repository access permissions settings'),
1118 ]
1171 ]
1119 %>
1172 %>
1120 %for key, desc in elems:
1173 %for key, desc in elems:
1121 <tr>
1174 <tr>
1122 <td class="keys">
1175 <td class="keys">
1123 <span class="key tag">${key}</span>
1176 <span class="key tag">${key}</span>
1124 </td>
1177 </td>
1125 <td>${desc}</td>
1178 <td>${desc}</td>
1126 </tr>
1179 </tr>
1127 %endfor
1180 %endfor
1128 </tbody>
1181 </tbody>
1129 </table>
1182 </table>
1130 </div>
1183 </div>
1131 </div>
1184 </div>
1132 <div class="modal-footer">
1185 <div class="modal-footer">
1133 </div>
1186 </div>
1134 </div><!-- /.modal-content -->
1187 </div><!-- /.modal-content -->
1135 </div><!-- /.modal-dialog -->
1188 </div><!-- /.modal-dialog -->
1136 </div><!-- /.modal -->
1189 </div><!-- /.modal -->
@@ -1,165 +1,166 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 go_import_header = ''
6 go_import_header = ''
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
12
12
13 if getattr(c, 'repo_group', None):
13 if getattr(c, 'repo_group', None):
14 c.template_context['repo_group_id'] = c.repo_group.group_id
14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 c.template_context['repo_group_name'] = c.repo_group.group_name
15 c.template_context['repo_group_name'] = c.repo_group.group_name
16
16
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 c.template_context['rhodecode_user']['user_id'] = c.rhodecode_user.user_id
18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
21 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
22 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
23 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
23
24
24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
25 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
25 c.template_context['default_user'] = {
26 c.template_context['default_user'] = {
26 'username': h.DEFAULT_USER,
27 'username': h.DEFAULT_USER,
27 'user_id': 1
28 'user_id': 1
28 }
29 }
29 c.template_context['search_context'] = {
30 c.template_context['search_context'] = {
30 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_id': c.template_context.get('repo_id'),
33 'repo_id': c.template_context.get('repo_id'),
33 'repo_name': c.template_context.get('repo_name'),
34 'repo_name': c.template_context.get('repo_name'),
34 'repo_view_type': c.template_context.get('repo_view_type'),
35 'repo_view_type': c.template_context.get('repo_view_type'),
35 }
36 }
36
37
37 c.template_context['attachment_store'] = {
38 c.template_context['attachment_store'] = {
38 'max_file_size_mb': 10,
39 'max_file_size_mb': 10,
39 'image_ext': ["png", "jpg", "gif", "jpeg"]
40 'image_ext': ["png", "jpg", "gif", "jpeg"]
40 }
41 }
41
42
42 %>
43 %>
43 <html xmlns="http://www.w3.org/1999/xhtml">
44 <html xmlns="http://www.w3.org/1999/xhtml">
44 <head>
45 <head>
45 <title>${self.title()}</title>
46 <title>${self.title()}</title>
46 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
47 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
47
48
48 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
49 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
49
50
50 % if 'safari' in (request.user_agent or '').lower():
51 % if 'safari' in (request.user_agent or '').lower():
51 <meta name="referrer" content="origin">
52 <meta name="referrer" content="origin">
52 % else:
53 % else:
53 <meta name="referrer" content="origin-when-cross-origin">
54 <meta name="referrer" content="origin-when-cross-origin">
54 % endif
55 % endif
55
56
56 <%def name="robots()">
57 <%def name="robots()">
57 <meta name="robots" content="index, nofollow"/>
58 <meta name="robots" content="index, nofollow"/>
58 </%def>
59 </%def>
59 ${self.robots()}
60 ${self.robots()}
60 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
61 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
61 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
62 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
62 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
63 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
63
64
64 ## CSS definitions
65 ## CSS definitions
65 <%def name="css()">
66 <%def name="css()">
66 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
67 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
67 ## EXTRA FOR CSS
68 ## EXTRA FOR CSS
68 ${self.css_extra()}
69 ${self.css_extra()}
69 </%def>
70 </%def>
70 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
71 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
71 <%def name="css_extra()">
72 <%def name="css_extra()">
72 </%def>
73 </%def>
73
74
74 ${self.css()}
75 ${self.css()}
75
76
76 ## JAVASCRIPT
77 ## JAVASCRIPT
77 <%def name="js()">
78 <%def name="js()">
78
79
79 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
80 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
80 <script type="text/javascript">
81 <script type="text/javascript">
81 // register templateContext to pass template variables to JS
82 // register templateContext to pass template variables to JS
82 var templateContext = ${h.json.dumps(c.template_context)|n};
83 var templateContext = ${h.json.dumps(c.template_context)|n};
83
84
84 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
85 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
85 var APPLICATION_PLUGINS = [];
86 var APPLICATION_PLUGINS = [];
86 var ASSET_URL = "${h.asset('')}";
87 var ASSET_URL = "${h.asset('')}";
87 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
88 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
88 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
89 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
89
90
90 var APPENLIGHT = {
91 var APPENLIGHT = {
91 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
92 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
92 key: '${getattr(c, "appenlight_api_public_key", "")}',
93 key: '${getattr(c, "appenlight_api_public_key", "")}',
93 % if getattr(c, 'appenlight_server_url', None):
94 % if getattr(c, 'appenlight_server_url', None):
94 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
95 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
95 % endif
96 % endif
96 requestInfo: {
97 requestInfo: {
97 % if getattr(c, 'rhodecode_user', None):
98 % if getattr(c, 'rhodecode_user', None):
98 ip: '${c.rhodecode_user.ip_addr}',
99 ip: '${c.rhodecode_user.ip_addr}',
99 username: '${c.rhodecode_user.username}'
100 username: '${c.rhodecode_user.username}'
100 % endif
101 % endif
101 },
102 },
102 tags: {
103 tags: {
103 rhodecode_version: '${c.rhodecode_version}',
104 rhodecode_version: '${c.rhodecode_version}',
104 rhodecode_edition: '${c.rhodecode_edition}'
105 rhodecode_edition: '${c.rhodecode_edition}'
105 }
106 }
106 };
107 };
107
108
108 </script>
109 </script>
109 <%include file="/base/plugins_base.mako"/>
110 <%include file="/base/plugins_base.mako"/>
110 <!--[if lt IE 9]>
111 <!--[if lt IE 9]>
111 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
112 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
112 <![endif]-->
113 <![endif]-->
113 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
114 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
114 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
115 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
115 ## avoide escaping the %N
116 ## avoide escaping the %N
116 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
117 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
117 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
118 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
118
119
119
120
120 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
121 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
121 ${self.js_extra()}
122 ${self.js_extra()}
122
123
123 <script type="text/javascript">
124 <script type="text/javascript">
124 Rhodecode = (function() {
125 Rhodecode = (function() {
125 function _Rhodecode() {
126 function _Rhodecode() {
126 this.comments = new CommentsController();
127 this.comments = new CommentsController();
127 }
128 }
128 return new _Rhodecode();
129 return new _Rhodecode();
129 })();
130 })();
130
131
131 $(document).ready(function(){
132 $(document).ready(function(){
132 show_more_event();
133 show_more_event();
133 timeagoActivate();
134 timeagoActivate();
134 tooltipActivate();
135 tooltipActivate();
135 clipboardActivate();
136 clipboardActivate();
136 })
137 })
137 </script>
138 </script>
138
139
139 </%def>
140 </%def>
140
141
141 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
142 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
142 <%def name="js_extra()"></%def>
143 <%def name="js_extra()"></%def>
143 ${self.js()}
144 ${self.js()}
144
145
145 <%def name="head_extra()"></%def>
146 <%def name="head_extra()"></%def>
146 ${self.head_extra()}
147 ${self.head_extra()}
147 ## extra stuff
148 ## extra stuff
148 %if c.pre_code:
149 %if c.pre_code:
149 ${c.pre_code|n}
150 ${c.pre_code|n}
150 %endif
151 %endif
151 </head>
152 </head>
152 <body id="body">
153 <body id="body">
153 <noscript>
154 <noscript>
154 <div class="noscript-error">
155 <div class="noscript-error">
155 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
156 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
156 </div>
157 </div>
157 </noscript>
158 </noscript>
158
159
159 ${next.body()}
160 ${next.body()}
160 %if c.post_code:
161 %if c.post_code:
161 ${c.post_code|n}
162 ${c.post_code|n}
162 %endif
163 %endif
163 <rhodecode-app></rhodecode-app>
164 <rhodecode-app></rhodecode-app>
164 </body>
165 </body>
165 </html>
166 </html>
General Comments 0
You need to be logged in to leave comments. Login now