##// END OF EJS Templates
pull-requests: added observers, and fix few problems with versioned comments
marcink -
r4481:d52ab7ab default
parent child
Show More
This diff has been collapsed as it changes many lines, (5689 lines changed) Show them Hide them
@@ -0,0 +1,5689
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2020 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 extra_sort_num = '1' # default
107
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
112
113
114 def display_user_group_sort(obj):
115 """
116 Sort function used to sort permissions in .permissions() function of
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 of all other resources
119 """
120
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 return prefix + obj.users_group_name
123
124
125 def _hash_key(k):
126 return sha1_safe(k)
127
128
129 def in_filter_generator(qry, items, limit=500):
130 """
131 Splits IN() into multiple with OR
132 e.g.::
133 cnt = Repository.query().filter(
134 or_(
135 *in_filter_generator(Repository.repo_id, range(100000))
136 )).count()
137 """
138 if not items:
139 # empty list will cause empty query which might cause security issues
140 # this can lead to hidden unpleasant results
141 items = [-1]
142
143 parts = []
144 for chunk in xrange(0, len(items), limit):
145 parts.append(
146 qry.in_(items[chunk: chunk + limit])
147 )
148
149 return parts
150
151
152 base_table_args = {
153 'extend_existing': True,
154 'mysql_engine': 'InnoDB',
155 'mysql_charset': 'utf8',
156 'sqlite_autoincrement': True
157 }
158
159
160 class EncryptedTextValue(TypeDecorator):
161 """
162 Special column for encrypted long text data, use like::
163
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165
166 This column is intelligent so if value is in unencrypted form it return
167 unencrypted form, but on save it always encrypts
168 """
169 impl = Text
170
171 def process_bind_param(self, value, dialect):
172 """
173 Setter for storing value
174 """
175 import rhodecode
176 if not value:
177 return value
178
179 # protect against double encrypting if values is already encrypted
180 if value.startswith('enc$aes$') \
181 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc2$'):
183 raise ValueError('value needs to be in unencrypted format, '
184 'ie. not starting with enc$ or enc2$')
185
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 if algo == 'aes':
188 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
189 elif algo == 'fernet':
190 return Encryptor(ENCRYPTION_KEY).encrypt(value)
191 else:
192 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
193
194 def process_result_value(self, value, dialect):
195 """
196 Getter for retrieving value
197 """
198
199 import rhodecode
200 if not value:
201 return value
202
203 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
204 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
205 if algo == 'aes':
206 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
207 elif algo == 'fernet':
208 return Encryptor(ENCRYPTION_KEY).decrypt(value)
209 else:
210 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
211 return decrypted_data
212
213
214 class BaseModel(object):
215 """
216 Base Model for all classes
217 """
218
219 @classmethod
220 def _get_keys(cls):
221 """return column names for this model """
222 return class_mapper(cls).c.keys()
223
224 def get_dict(self):
225 """
226 return dict with keys and values corresponding
227 to this model data """
228
229 d = {}
230 for k in self._get_keys():
231 d[k] = getattr(self, k)
232
233 # also use __json__() if present to get additional fields
234 _json_attr = getattr(self, '__json__', None)
235 if _json_attr:
236 # update with attributes from __json__
237 if callable(_json_attr):
238 _json_attr = _json_attr()
239 for k, val in _json_attr.iteritems():
240 d[k] = val
241 return d
242
243 def get_appstruct(self):
244 """return list with keys and values tuples corresponding
245 to this model data """
246
247 lst = []
248 for k in self._get_keys():
249 lst.append((k, getattr(self, k),))
250 return lst
251
252 def populate_obj(self, populate_dict):
253 """populate model with data from given populate_dict"""
254
255 for k in self._get_keys():
256 if k in populate_dict:
257 setattr(self, k, populate_dict[k])
258
259 @classmethod
260 def query(cls):
261 return Session().query(cls)
262
263 @classmethod
264 def get(cls, id_):
265 if id_:
266 return cls.query().get(id_)
267
268 @classmethod
269 def get_or_404(cls, id_):
270 from pyramid.httpexceptions import HTTPNotFound
271
272 try:
273 id_ = int(id_)
274 except (TypeError, ValueError):
275 raise HTTPNotFound()
276
277 res = cls.query().get(id_)
278 if not res:
279 raise HTTPNotFound()
280 return res
281
282 @classmethod
283 def getAll(cls):
284 # deprecated and left for backward compatibility
285 return cls.get_all()
286
287 @classmethod
288 def get_all(cls):
289 return cls.query().all()
290
291 @classmethod
292 def delete(cls, id_):
293 obj = cls.query().get(id_)
294 Session().delete(obj)
295
296 @classmethod
297 def identity_cache(cls, session, attr_name, value):
298 exist_in_session = []
299 for (item_cls, pkey), instance in session.identity_map.items():
300 if cls == item_cls and getattr(instance, attr_name) == value:
301 exist_in_session.append(instance)
302 if exist_in_session:
303 if len(exist_in_session) == 1:
304 return exist_in_session[0]
305 log.exception(
306 'multiple objects with attr %s and '
307 'value %s found with same name: %r',
308 attr_name, value, exist_in_session)
309
310 def __repr__(self):
311 if hasattr(self, '__unicode__'):
312 # python repr needs to return str
313 try:
314 return safe_str(self.__unicode__())
315 except UnicodeDecodeError:
316 pass
317 return '<DB:%s>' % (self.__class__.__name__)
318
319
320 class RhodeCodeSetting(Base, BaseModel):
321 __tablename__ = 'rhodecode_settings'
322 __table_args__ = (
323 UniqueConstraint('app_settings_name'),
324 base_table_args
325 )
326
327 SETTINGS_TYPES = {
328 'str': safe_str,
329 'int': safe_int,
330 'unicode': safe_unicode,
331 'bool': str2bool,
332 'list': functools.partial(aslist, sep=',')
333 }
334 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
335 GLOBAL_CONF_KEY = 'app_settings'
336
337 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
338 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
339 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
340 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
341
342 def __init__(self, key='', val='', type='unicode'):
343 self.app_settings_name = key
344 self.app_settings_type = type
345 self.app_settings_value = val
346
347 @validates('_app_settings_value')
348 def validate_settings_value(self, key, val):
349 assert type(val) == unicode
350 return val
351
352 @hybrid_property
353 def app_settings_value(self):
354 v = self._app_settings_value
355 _type = self.app_settings_type
356 if _type:
357 _type = self.app_settings_type.split('.')[0]
358 # decode the encrypted value
359 if 'encrypted' in self.app_settings_type:
360 cipher = EncryptedTextValue()
361 v = safe_unicode(cipher.process_result_value(v, None))
362
363 converter = self.SETTINGS_TYPES.get(_type) or \
364 self.SETTINGS_TYPES['unicode']
365 return converter(v)
366
367 @app_settings_value.setter
368 def app_settings_value(self, val):
369 """
370 Setter that will always make sure we use unicode in app_settings_value
371
372 :param val:
373 """
374 val = safe_unicode(val)
375 # encode the encrypted value
376 if 'encrypted' in self.app_settings_type:
377 cipher = EncryptedTextValue()
378 val = safe_unicode(cipher.process_bind_param(val, None))
379 self._app_settings_value = val
380
381 @hybrid_property
382 def app_settings_type(self):
383 return self._app_settings_type
384
385 @app_settings_type.setter
386 def app_settings_type(self, val):
387 if val.split('.')[0] not in self.SETTINGS_TYPES:
388 raise Exception('type must be one of %s got %s'
389 % (self.SETTINGS_TYPES.keys(), val))
390 self._app_settings_type = val
391
392 @classmethod
393 def get_by_prefix(cls, prefix):
394 return RhodeCodeSetting.query()\
395 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
396 .all()
397
398 def __unicode__(self):
399 return u"<%s('%s:%s[%s]')>" % (
400 self.__class__.__name__,
401 self.app_settings_name, self.app_settings_value,
402 self.app_settings_type
403 )
404
405
406 class RhodeCodeUi(Base, BaseModel):
407 __tablename__ = 'rhodecode_ui'
408 __table_args__ = (
409 UniqueConstraint('ui_key'),
410 base_table_args
411 )
412
413 HOOK_REPO_SIZE = 'changegroup.repo_size'
414 # HG
415 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
416 HOOK_PULL = 'outgoing.pull_logger'
417 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
418 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
419 HOOK_PUSH = 'changegroup.push_logger'
420 HOOK_PUSH_KEY = 'pushkey.key_push'
421
422 HOOKS_BUILTIN = [
423 HOOK_PRE_PULL,
424 HOOK_PULL,
425 HOOK_PRE_PUSH,
426 HOOK_PRETX_PUSH,
427 HOOK_PUSH,
428 HOOK_PUSH_KEY,
429 ]
430
431 # TODO: johbo: Unify way how hooks are configured for git and hg,
432 # git part is currently hardcoded.
433
434 # SVN PATTERNS
435 SVN_BRANCH_ID = 'vcs_svn_branch'
436 SVN_TAG_ID = 'vcs_svn_tag'
437
438 ui_id = Column(
439 "ui_id", Integer(), nullable=False, unique=True, default=None,
440 primary_key=True)
441 ui_section = Column(
442 "ui_section", String(255), nullable=True, unique=None, default=None)
443 ui_key = Column(
444 "ui_key", String(255), nullable=True, unique=None, default=None)
445 ui_value = Column(
446 "ui_value", String(255), nullable=True, unique=None, default=None)
447 ui_active = Column(
448 "ui_active", Boolean(), nullable=True, unique=None, default=True)
449
450 def __repr__(self):
451 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
452 self.ui_key, self.ui_value)
453
454
455 class RepoRhodeCodeSetting(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_settings'
457 __table_args__ = (
458 UniqueConstraint(
459 'app_settings_name', 'repository_id',
460 name='uq_repo_rhodecode_setting_name_repo_id'),
461 base_table_args
462 )
463
464 repository_id = Column(
465 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 nullable=False)
467 app_settings_id = Column(
468 "app_settings_id", Integer(), nullable=False, unique=True,
469 default=None, primary_key=True)
470 app_settings_name = Column(
471 "app_settings_name", String(255), nullable=True, unique=None,
472 default=None)
473 _app_settings_value = Column(
474 "app_settings_value", String(4096), nullable=True, unique=None,
475 default=None)
476 _app_settings_type = Column(
477 "app_settings_type", String(255), nullable=True, unique=None,
478 default=None)
479
480 repository = relationship('Repository')
481
482 def __init__(self, repository_id, key='', val='', type='unicode'):
483 self.repository_id = repository_id
484 self.app_settings_name = key
485 self.app_settings_type = type
486 self.app_settings_value = val
487
488 @validates('_app_settings_value')
489 def validate_settings_value(self, key, val):
490 assert type(val) == unicode
491 return val
492
493 @hybrid_property
494 def app_settings_value(self):
495 v = self._app_settings_value
496 type_ = self.app_settings_type
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
499 return converter(v)
500
501 @app_settings_value.setter
502 def app_settings_value(self, val):
503 """
504 Setter that will always make sure we use unicode in app_settings_value
505
506 :param val:
507 """
508 self._app_settings_value = safe_unicode(val)
509
510 @hybrid_property
511 def app_settings_type(self):
512 return self._app_settings_type
513
514 @app_settings_type.setter
515 def app_settings_type(self, val):
516 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
517 if val not in SETTINGS_TYPES:
518 raise Exception('type must be one of %s got %s'
519 % (SETTINGS_TYPES.keys(), val))
520 self._app_settings_type = val
521
522 def __unicode__(self):
523 return u"<%s('%s:%s:%s[%s]')>" % (
524 self.__class__.__name__, self.repository.repo_name,
525 self.app_settings_name, self.app_settings_value,
526 self.app_settings_type
527 )
528
529
530 class RepoRhodeCodeUi(Base, BaseModel):
531 __tablename__ = 'repo_rhodecode_ui'
532 __table_args__ = (
533 UniqueConstraint(
534 'repository_id', 'ui_section', 'ui_key',
535 name='uq_repo_rhodecode_ui_repository_id_section_key'),
536 base_table_args
537 )
538
539 repository_id = Column(
540 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
541 nullable=False)
542 ui_id = Column(
543 "ui_id", Integer(), nullable=False, unique=True, default=None,
544 primary_key=True)
545 ui_section = Column(
546 "ui_section", String(255), nullable=True, unique=None, default=None)
547 ui_key = Column(
548 "ui_key", String(255), nullable=True, unique=None, default=None)
549 ui_value = Column(
550 "ui_value", String(255), nullable=True, unique=None, default=None)
551 ui_active = Column(
552 "ui_active", Boolean(), nullable=True, unique=None, default=True)
553
554 repository = relationship('Repository')
555
556 def __repr__(self):
557 return '<%s[%s:%s]%s=>%s]>' % (
558 self.__class__.__name__, self.repository.repo_name,
559 self.ui_section, self.ui_key, self.ui_value)
560
561
562 class User(Base, BaseModel):
563 __tablename__ = 'users'
564 __table_args__ = (
565 UniqueConstraint('username'), UniqueConstraint('email'),
566 Index('u_username_idx', 'username'),
567 Index('u_email_idx', 'email'),
568 base_table_args
569 )
570
571 DEFAULT_USER = 'default'
572 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
573 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
574
575 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
576 username = Column("username", String(255), nullable=True, unique=None, default=None)
577 password = Column("password", String(255), nullable=True, unique=None, default=None)
578 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
579 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
580 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
581 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
582 _email = Column("email", String(255), nullable=True, unique=None, default=None)
583 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
584 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
585 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
586
587 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
588 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
589 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
590 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
591 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
592 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
593
594 user_log = relationship('UserLog')
595 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
596
597 repositories = relationship('Repository')
598 repository_groups = relationship('RepoGroup')
599 user_groups = relationship('UserGroup')
600
601 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
602 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
603
604 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
605 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
607
608 group_member = relationship('UserGroupMember', cascade='all')
609
610 notifications = relationship('UserNotification', cascade='all')
611 # notifications assigned to this user
612 user_created_notifications = relationship('Notification', cascade='all')
613 # comments created by this user
614 user_comments = relationship('ChangesetComment', cascade='all')
615 # user profile extra info
616 user_emails = relationship('UserEmailMap', cascade='all')
617 user_ip_map = relationship('UserIpMap', cascade='all')
618 user_auth_tokens = relationship('UserApiKeys', cascade='all')
619 user_ssh_keys = relationship('UserSshKeys', cascade='all')
620
621 # gists
622 user_gists = relationship('Gist', cascade='all')
623 # user pull requests
624 user_pull_requests = relationship('PullRequest', cascade='all')
625
626 # external identities
627 external_identities = relationship(
628 'ExternalIdentity',
629 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
630 cascade='all')
631 # review rules
632 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
633
634 # artifacts owned
635 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
636
637 # no cascade, set NULL
638 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
639
640 def __unicode__(self):
641 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
642 self.user_id, self.username)
643
644 @hybrid_property
645 def email(self):
646 return self._email
647
648 @email.setter
649 def email(self, val):
650 self._email = val.lower() if val else None
651
652 @hybrid_property
653 def first_name(self):
654 from rhodecode.lib import helpers as h
655 if self.name:
656 return h.escape(self.name)
657 return self.name
658
659 @hybrid_property
660 def last_name(self):
661 from rhodecode.lib import helpers as h
662 if self.lastname:
663 return h.escape(self.lastname)
664 return self.lastname
665
666 @hybrid_property
667 def api_key(self):
668 """
669 Fetch if exist an auth-token with role ALL connected to this user
670 """
671 user_auth_token = UserApiKeys.query()\
672 .filter(UserApiKeys.user_id == self.user_id)\
673 .filter(or_(UserApiKeys.expires == -1,
674 UserApiKeys.expires >= time.time()))\
675 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
676 if user_auth_token:
677 user_auth_token = user_auth_token.api_key
678
679 return user_auth_token
680
681 @api_key.setter
682 def api_key(self, val):
683 # don't allow to set API key this is deprecated for now
684 self._api_key = None
685
686 @property
687 def reviewer_pull_requests(self):
688 return PullRequestReviewers.query() \
689 .options(joinedload(PullRequestReviewers.pull_request)) \
690 .filter(PullRequestReviewers.user_id == self.user_id) \
691 .all()
692
693 @property
694 def firstname(self):
695 # alias for future
696 return self.name
697
698 @property
699 def emails(self):
700 other = UserEmailMap.query()\
701 .filter(UserEmailMap.user == self) \
702 .order_by(UserEmailMap.email_id.asc()) \
703 .all()
704 return [self.email] + [x.email for x in other]
705
706 def emails_cached(self):
707 emails = UserEmailMap.query()\
708 .filter(UserEmailMap.user == self) \
709 .order_by(UserEmailMap.email_id.asc())
710
711 emails = emails.options(
712 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
713 )
714
715 return [self.email] + [x.email for x in emails]
716
717 @property
718 def auth_tokens(self):
719 auth_tokens = self.get_auth_tokens()
720 return [x.api_key for x in auth_tokens]
721
722 def get_auth_tokens(self):
723 return UserApiKeys.query()\
724 .filter(UserApiKeys.user == self)\
725 .order_by(UserApiKeys.user_api_key_id.asc())\
726 .all()
727
728 @LazyProperty
729 def feed_token(self):
730 return self.get_feed_token()
731
732 def get_feed_token(self, cache=True):
733 feed_tokens = UserApiKeys.query()\
734 .filter(UserApiKeys.user == self)\
735 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
736 if cache:
737 feed_tokens = feed_tokens.options(
738 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
739
740 feed_tokens = feed_tokens.all()
741 if feed_tokens:
742 return feed_tokens[0].api_key
743 return 'NO_FEED_TOKEN_AVAILABLE'
744
745 @LazyProperty
746 def artifact_token(self):
747 return self.get_artifact_token()
748
749 def get_artifact_token(self, cache=True):
750 artifacts_tokens = UserApiKeys.query()\
751 .filter(UserApiKeys.user == self)\
752 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
753 if cache:
754 artifacts_tokens = artifacts_tokens.options(
755 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
756
757 artifacts_tokens = artifacts_tokens.all()
758 if artifacts_tokens:
759 return artifacts_tokens[0].api_key
760 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
761
762 @classmethod