##// END OF EJS Templates
fix(cache-invalidation): fixed case when empty repos once cached wouldn't invalidate caches because of empty case condition
super-admin -
r5300:eaec72ab default
parent child Browse files
Show More
@@ -1,5887 +1,5887 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Database Models for RhodeCode Enterprise
20 Database Models for RhodeCode Enterprise
21 """
21 """
22
22
23 import re
23 import re
24 import os
24 import os
25 import time
25 import time
26 import string
26 import string
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import uuid
29 import uuid
30 import warnings
30 import warnings
31 import ipaddress
31 import ipaddress
32 import functools
32 import functools
33 import traceback
33 import traceback
34 import collections
34 import collections
35
35
36 from sqlalchemy import (
36 from sqlalchemy import (
37 or_, and_, not_, func, cast, TypeDecorator, event, select,
37 or_, and_, not_, func, cast, TypeDecorator, event, select,
38 true, false, null,
38 true, false, null,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType, BigInteger)
41 Text, Float, PickleType, BigInteger)
42 from sqlalchemy.sql.expression import case
42 from sqlalchemy.sql.expression import case
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, lazyload, joinedload, class_mapper, validates, aliased, load_only)
45 relationship, lazyload, joinedload, class_mapper, validates, aliased, load_only)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52 from webhelpers2.text import remove_formatting
52 from webhelpers2.text import remove_formatting
53
53
54 from rhodecode.lib.str_utils import safe_bytes
54 from rhodecode.lib.str_utils import safe_bytes
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance, VCSError
56 from rhodecode.lib.vcs import get_vcs_instance, VCSError
57 from rhodecode.lib.vcs.backends.base import (
57 from rhodecode.lib.vcs.backends.base import (
58 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
58 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
59 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
60 str2bool, safe_str, get_commit_safe, sha1_safe,
60 str2bool, safe_str, get_commit_safe, sha1_safe,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
63 from rhodecode.lib.jsonalchemy import (
63 from rhodecode.lib.jsonalchemy import (
64 MutationObj, MutationList, JsonType, JsonRaw)
64 MutationObj, MutationList, JsonType, JsonRaw)
65 from rhodecode.lib.hash_utils import sha1
65 from rhodecode.lib.hash_utils import sha1
66 from rhodecode.lib import ext_json
66 from rhodecode.lib import ext_json
67 from rhodecode.lib import enc_utils
67 from rhodecode.lib import enc_utils
68 from rhodecode.lib.ext_json import json, str_json
68 from rhodecode.lib.ext_json import json, str_json
69 from rhodecode.lib.caching_query import FromCache
69 from rhodecode.lib.caching_query import FromCache
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: bytes = b''
84 ENCRYPTION_KEY: bytes = b''
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 extra_sort_num = '1' # default
106 extra_sort_num = '1' # default
107
107
108 # NOTE(dan): inactive duplicates goes last
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
111 return prefix + extra_sort_num + obj.username
112
112
113
113
114 def display_user_group_sort(obj):
114 def display_user_group_sort(obj):
115 """
115 """
116 Sort function used to sort permissions in .permissions() function of
116 Sort function used to sort permissions in .permissions() function of
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 of all other resources
118 of all other resources
119 """
119 """
120
120
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 return prefix + obj.users_group_name
122 return prefix + obj.users_group_name
123
123
124
124
125 def _hash_key(k):
125 def _hash_key(k):
126 return sha1_safe(k)
126 return sha1_safe(k)
127
127
128
128
129 def in_filter_generator(qry, items, limit=500):
129 def in_filter_generator(qry, items, limit=500):
130 """
130 """
131 Splits IN() into multiple with OR
131 Splits IN() into multiple with OR
132 e.g.::
132 e.g.::
133 cnt = Repository.query().filter(
133 cnt = Repository.query().filter(
134 or_(
134 or_(
135 *in_filter_generator(Repository.repo_id, range(100000))
135 *in_filter_generator(Repository.repo_id, range(100000))
136 )).count()
136 )).count()
137 """
137 """
138 if not items:
138 if not items:
139 # empty list will cause empty query which might cause security issues
139 # empty list will cause empty query which might cause security issues
140 # this can lead to hidden unpleasant results
140 # this can lead to hidden unpleasant results
141 items = [-1]
141 items = [-1]
142
142
143 parts = []
143 parts = []
144 for chunk in range(0, len(items), limit):
144 for chunk in range(0, len(items), limit):
145 parts.append(
145 parts.append(
146 qry.in_(items[chunk: chunk + limit])
146 qry.in_(items[chunk: chunk + limit])
147 )
147 )
148
148
149 return parts
149 return parts
150
150
151
151
152 base_table_args = {
152 base_table_args = {
153 'extend_existing': True,
153 'extend_existing': True,
154 'mysql_engine': 'InnoDB',
154 'mysql_engine': 'InnoDB',
155 'mysql_charset': 'utf8',
155 'mysql_charset': 'utf8',
156 'sqlite_autoincrement': True
156 'sqlite_autoincrement': True
157 }
157 }
158
158
159
159
160 class EncryptedTextValue(TypeDecorator):
160 class EncryptedTextValue(TypeDecorator):
161 """
161 """
162 Special column for encrypted long text data, use like::
162 Special column for encrypted long text data, use like::
163
163
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165
165
166 This column is intelligent so if value is in unencrypted form it return
166 This column is intelligent so if value is in unencrypted form it return
167 unencrypted form, but on save it always encrypts
167 unencrypted form, but on save it always encrypts
168 """
168 """
169 cache_ok = True
169 cache_ok = True
170 impl = Text
170 impl = Text
171
171
172 def process_bind_param(self, value, dialect):
172 def process_bind_param(self, value, dialect):
173 """
173 """
174 Setter for storing value
174 Setter for storing value
175 """
175 """
176 import rhodecode
176 import rhodecode
177 if not value:
177 if not value:
178 return value
178 return value
179
179
180 # protect against double encrypting if values is already encrypted
180 # protect against double encrypting if values is already encrypted
181 if value.startswith('enc$aes$') \
181 if value.startswith('enc$aes$') \
182 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc$aes_hmac$') \
183 or value.startswith('enc2$'):
183 or value.startswith('enc2$'):
184 raise ValueError('value needs to be in unencrypted format, '
184 raise ValueError('value needs to be in unencrypted format, '
185 'ie. not starting with enc$ or enc2$')
185 'ie. not starting with enc$ or enc2$')
186
186
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
188 bytes_val = enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
188 bytes_val = enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
189 return safe_str(bytes_val)
189 return safe_str(bytes_val)
190
190
191 def process_result_value(self, value, dialect):
191 def process_result_value(self, value, dialect):
192 """
192 """
193 Getter for retrieving value
193 Getter for retrieving value
194 """
194 """
195
195
196 import rhodecode
196 import rhodecode
197 if not value:
197 if not value:
198 return value
198 return value
199
199
200 enc_strict_mode = rhodecode.ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
200 enc_strict_mode = rhodecode.ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
201
201
202 bytes_val = enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
202 bytes_val = enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
203
203
204 return safe_str(bytes_val)
204 return safe_str(bytes_val)
205
205
206
206
207 class BaseModel(object):
207 class BaseModel(object):
208 """
208 """
209 Base Model for all classes
209 Base Model for all classes
210 """
210 """
211
211
212 @classmethod
212 @classmethod
213 def _get_keys(cls):
213 def _get_keys(cls):
214 """return column names for this model """
214 """return column names for this model """
215 return class_mapper(cls).c.keys()
215 return class_mapper(cls).c.keys()
216
216
217 def get_dict(self):
217 def get_dict(self):
218 """
218 """
219 return dict with keys and values corresponding
219 return dict with keys and values corresponding
220 to this model data """
220 to this model data """
221
221
222 d = {}
222 d = {}
223 for k in self._get_keys():
223 for k in self._get_keys():
224 d[k] = getattr(self, k)
224 d[k] = getattr(self, k)
225
225
226 # also use __json__() if present to get additional fields
226 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
227 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
228 if _json_attr:
229 # update with attributes from __json__
229 # update with attributes from __json__
230 if callable(_json_attr):
230 if callable(_json_attr):
231 _json_attr = _json_attr()
231 _json_attr = _json_attr()
232 for k, val in _json_attr.items():
232 for k, val in _json_attr.items():
233 d[k] = val
233 d[k] = val
234 return d
234 return d
235
235
236 def get_appstruct(self):
236 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
237 """return list with keys and values tuples corresponding
238 to this model data """
238 to this model data """
239
239
240 lst = []
240 lst = []
241 for k in self._get_keys():
241 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
242 lst.append((k, getattr(self, k),))
243 return lst
243 return lst
244
244
245 def populate_obj(self, populate_dict):
245 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
246 """populate model with data from given populate_dict"""
247
247
248 for k in self._get_keys():
248 for k in self._get_keys():
249 if k in populate_dict:
249 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
250 setattr(self, k, populate_dict[k])
251
251
252 @classmethod
252 @classmethod
253 def query(cls):
253 def query(cls):
254 return Session().query(cls)
254 return Session().query(cls)
255
255
256 @classmethod
256 @classmethod
257 def select(cls, custom_cls=None):
257 def select(cls, custom_cls=None):
258 """
258 """
259 stmt = cls.select().where(cls.user_id==1)
259 stmt = cls.select().where(cls.user_id==1)
260 # optionally
260 # optionally
261 stmt = cls.select(User.user_id).where(cls.user_id==1)
261 stmt = cls.select(User.user_id).where(cls.user_id==1)
262 result = cls.execute(stmt) | cls.scalars(stmt)
262 result = cls.execute(stmt) | cls.scalars(stmt)
263 """
263 """
264
264
265 if custom_cls:
265 if custom_cls:
266 stmt = select(custom_cls)
266 stmt = select(custom_cls)
267 else:
267 else:
268 stmt = select(cls)
268 stmt = select(cls)
269 return stmt
269 return stmt
270
270
271 @classmethod
271 @classmethod
272 def execute(cls, stmt):
272 def execute(cls, stmt):
273 return Session().execute(stmt)
273 return Session().execute(stmt)
274
274
275 @classmethod
275 @classmethod
276 def scalars(cls, stmt):
276 def scalars(cls, stmt):
277 return Session().scalars(stmt)
277 return Session().scalars(stmt)
278
278
279 @classmethod
279 @classmethod
280 def get(cls, id_):
280 def get(cls, id_):
281 if id_:
281 if id_:
282 return cls.query().get(id_)
282 return cls.query().get(id_)
283
283
284 @classmethod
284 @classmethod
285 def get_or_404(cls, id_):
285 def get_or_404(cls, id_):
286 from pyramid.httpexceptions import HTTPNotFound
286 from pyramid.httpexceptions import HTTPNotFound
287
287
288 try:
288 try:
289 id_ = int(id_)
289 id_ = int(id_)
290 except (TypeError, ValueError):
290 except (TypeError, ValueError):
291 raise HTTPNotFound()
291 raise HTTPNotFound()
292
292
293 res = cls.query().get(id_)
293 res = cls.query().get(id_)
294 if not res:
294 if not res:
295 raise HTTPNotFound()
295 raise HTTPNotFound()
296 return res
296 return res
297
297
298 @classmethod
298 @classmethod
299 def getAll(cls):
299 def getAll(cls):
300 # deprecated and left for backward compatibility
300 # deprecated and left for backward compatibility
301 return cls.get_all()
301 return cls.get_all()
302
302
303 @classmethod
303 @classmethod
304 def get_all(cls):
304 def get_all(cls):
305 return cls.query().all()
305 return cls.query().all()
306
306
307 @classmethod
307 @classmethod
308 def delete(cls, id_):
308 def delete(cls, id_):
309 obj = cls.query().get(id_)
309 obj = cls.query().get(id_)
310 Session().delete(obj)
310 Session().delete(obj)
311
311
312 @classmethod
312 @classmethod
313 def identity_cache(cls, session, attr_name, value):
313 def identity_cache(cls, session, attr_name, value):
314 exist_in_session = []
314 exist_in_session = []
315 for (item_cls, pkey), instance in session.identity_map.items():
315 for (item_cls, pkey), instance in session.identity_map.items():
316 if cls == item_cls and getattr(instance, attr_name) == value:
316 if cls == item_cls and getattr(instance, attr_name) == value:
317 exist_in_session.append(instance)
317 exist_in_session.append(instance)
318 if exist_in_session:
318 if exist_in_session:
319 if len(exist_in_session) == 1:
319 if len(exist_in_session) == 1:
320 return exist_in_session[0]
320 return exist_in_session[0]
321 log.exception(
321 log.exception(
322 'multiple objects with attr %s and '
322 'multiple objects with attr %s and '
323 'value %s found with same name: %r',
323 'value %s found with same name: %r',
324 attr_name, value, exist_in_session)
324 attr_name, value, exist_in_session)
325
325
326 @property
326 @property
327 def cls_name(self):
327 def cls_name(self):
328 return self.__class__.__name__
328 return self.__class__.__name__
329
329
330 def __repr__(self):
330 def __repr__(self):
331 return f'<DB:{self.cls_name}>'
331 return f'<DB:{self.cls_name}>'
332
332
333
333
334 class RhodeCodeSetting(Base, BaseModel):
334 class RhodeCodeSetting(Base, BaseModel):
335 __tablename__ = 'rhodecode_settings'
335 __tablename__ = 'rhodecode_settings'
336 __table_args__ = (
336 __table_args__ = (
337 UniqueConstraint('app_settings_name'),
337 UniqueConstraint('app_settings_name'),
338 base_table_args
338 base_table_args
339 )
339 )
340
340
341 SETTINGS_TYPES = {
341 SETTINGS_TYPES = {
342 'str': safe_str,
342 'str': safe_str,
343 'int': safe_int,
343 'int': safe_int,
344 'unicode': safe_str,
344 'unicode': safe_str,
345 'bool': str2bool,
345 'bool': str2bool,
346 'list': functools.partial(aslist, sep=',')
346 'list': functools.partial(aslist, sep=',')
347 }
347 }
348 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
348 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
349 GLOBAL_CONF_KEY = 'app_settings'
349 GLOBAL_CONF_KEY = 'app_settings'
350
350
351 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
351 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
352 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
352 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
353 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
353 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
354 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
354 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
355
355
356 def __init__(self, key='', val='', type='unicode'):
356 def __init__(self, key='', val='', type='unicode'):
357 self.app_settings_name = key
357 self.app_settings_name = key
358 self.app_settings_type = type
358 self.app_settings_type = type
359 self.app_settings_value = val
359 self.app_settings_value = val
360
360
361 @validates('_app_settings_value')
361 @validates('_app_settings_value')
362 def validate_settings_value(self, key, val):
362 def validate_settings_value(self, key, val):
363 assert type(val) == str
363 assert type(val) == str
364 return val
364 return val
365
365
366 @hybrid_property
366 @hybrid_property
367 def app_settings_value(self):
367 def app_settings_value(self):
368 v = self._app_settings_value
368 v = self._app_settings_value
369 _type = self.app_settings_type
369 _type = self.app_settings_type
370 if _type:
370 if _type:
371 _type = self.app_settings_type.split('.')[0]
371 _type = self.app_settings_type.split('.')[0]
372 # decode the encrypted value
372 # decode the encrypted value
373 if 'encrypted' in self.app_settings_type:
373 if 'encrypted' in self.app_settings_type:
374 cipher = EncryptedTextValue()
374 cipher = EncryptedTextValue()
375 v = safe_str(cipher.process_result_value(v, None))
375 v = safe_str(cipher.process_result_value(v, None))
376
376
377 converter = self.SETTINGS_TYPES.get(_type) or \
377 converter = self.SETTINGS_TYPES.get(_type) or \
378 self.SETTINGS_TYPES['unicode']
378 self.SETTINGS_TYPES['unicode']
379 return converter(v)
379 return converter(v)
380
380
381 @app_settings_value.setter
381 @app_settings_value.setter
382 def app_settings_value(self, val):
382 def app_settings_value(self, val):
383 """
383 """
384 Setter that will always make sure we use unicode in app_settings_value
384 Setter that will always make sure we use unicode in app_settings_value
385
385
386 :param val:
386 :param val:
387 """
387 """
388 val = safe_str(val)
388 val = safe_str(val)
389 # encode the encrypted value
389 # encode the encrypted value
390 if 'encrypted' in self.app_settings_type:
390 if 'encrypted' in self.app_settings_type:
391 cipher = EncryptedTextValue()
391 cipher = EncryptedTextValue()
392 val = safe_str(cipher.process_bind_param(val, None))
392 val = safe_str(cipher.process_bind_param(val, None))
393 self._app_settings_value = val
393 self._app_settings_value = val
394
394
395 @hybrid_property
395 @hybrid_property
396 def app_settings_type(self):
396 def app_settings_type(self):
397 return self._app_settings_type
397 return self._app_settings_type
398
398
399 @app_settings_type.setter
399 @app_settings_type.setter
400 def app_settings_type(self, val):
400 def app_settings_type(self, val):
401 if val.split('.')[0] not in self.SETTINGS_TYPES:
401 if val.split('.')[0] not in self.SETTINGS_TYPES:
402 raise Exception('type must be one of %s got %s'
402 raise Exception('type must be one of %s got %s'
403 % (self.SETTINGS_TYPES.keys(), val))
403 % (self.SETTINGS_TYPES.keys(), val))
404 self._app_settings_type = val
404 self._app_settings_type = val
405
405
406 @classmethod
406 @classmethod
407 def get_by_prefix(cls, prefix):
407 def get_by_prefix(cls, prefix):
408 return RhodeCodeSetting.query()\
408 return RhodeCodeSetting.query()\
409 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
409 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
410 .all()
410 .all()
411
411
412 def __repr__(self):
412 def __repr__(self):
413 return "<%s('%s:%s[%s]')>" % (
413 return "<%s('%s:%s[%s]')>" % (
414 self.cls_name,
414 self.cls_name,
415 self.app_settings_name, self.app_settings_value,
415 self.app_settings_name, self.app_settings_value,
416 self.app_settings_type
416 self.app_settings_type
417 )
417 )
418
418
419
419
420 class RhodeCodeUi(Base, BaseModel):
420 class RhodeCodeUi(Base, BaseModel):
421 __tablename__ = 'rhodecode_ui'
421 __tablename__ = 'rhodecode_ui'
422 __table_args__ = (
422 __table_args__ = (
423 UniqueConstraint('ui_key'),
423 UniqueConstraint('ui_key'),
424 base_table_args
424 base_table_args
425 )
425 )
426 # Sync those values with vcsserver.config.hooks
426 # Sync those values with vcsserver.config.hooks
427
427
428 HOOK_REPO_SIZE = 'changegroup.repo_size'
428 HOOK_REPO_SIZE = 'changegroup.repo_size'
429 # HG
429 # HG
430 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
430 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
431 HOOK_PULL = 'outgoing.pull_logger'
431 HOOK_PULL = 'outgoing.pull_logger'
432 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
432 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
433 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
433 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
434 HOOK_PUSH = 'changegroup.push_logger'
434 HOOK_PUSH = 'changegroup.push_logger'
435 HOOK_PUSH_KEY = 'pushkey.key_push'
435 HOOK_PUSH_KEY = 'pushkey.key_push'
436
436
437 HOOKS_BUILTIN = [
437 HOOKS_BUILTIN = [
438 HOOK_PRE_PULL,
438 HOOK_PRE_PULL,
439 HOOK_PULL,
439 HOOK_PULL,
440 HOOK_PRE_PUSH,
440 HOOK_PRE_PUSH,
441 HOOK_PRETX_PUSH,
441 HOOK_PRETX_PUSH,
442 HOOK_PUSH,
442 HOOK_PUSH,
443 HOOK_PUSH_KEY,
443 HOOK_PUSH_KEY,
444 ]
444 ]
445
445
446 # TODO: johbo: Unify way how hooks are configured for git and hg,
446 # TODO: johbo: Unify way how hooks are configured for git and hg,
447 # git part is currently hardcoded.
447 # git part is currently hardcoded.
448
448
449 # SVN PATTERNS
449 # SVN PATTERNS
450 SVN_BRANCH_ID = 'vcs_svn_branch'
450 SVN_BRANCH_ID = 'vcs_svn_branch'
451 SVN_TAG_ID = 'vcs_svn_tag'
451 SVN_TAG_ID = 'vcs_svn_tag'
452
452
453 ui_id = Column(
453 ui_id = Column(
454 "ui_id", Integer(), nullable=False, unique=True, default=None,
454 "ui_id", Integer(), nullable=False, unique=True, default=None,
455 primary_key=True)
455 primary_key=True)
456 ui_section = Column(
456 ui_section = Column(
457 "ui_section", String(255), nullable=True, unique=None, default=None)
457 "ui_section", String(255), nullable=True, unique=None, default=None)
458 ui_key = Column(
458 ui_key = Column(
459 "ui_key", String(255), nullable=True, unique=None, default=None)
459 "ui_key", String(255), nullable=True, unique=None, default=None)
460 ui_value = Column(
460 ui_value = Column(
461 "ui_value", String(255), nullable=True, unique=None, default=None)
461 "ui_value", String(255), nullable=True, unique=None, default=None)
462 ui_active = Column(
462 ui_active = Column(
463 "ui_active", Boolean(), nullable=True, unique=None, default=True)
463 "ui_active", Boolean(), nullable=True, unique=None, default=True)
464
464
465 def __repr__(self):
465 def __repr__(self):
466 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.ui_section,
466 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.ui_section,
467 self.ui_key, self.ui_value)
467 self.ui_key, self.ui_value)
468
468
469
469
470 class RepoRhodeCodeSetting(Base, BaseModel):
470 class RepoRhodeCodeSetting(Base, BaseModel):
471 __tablename__ = 'repo_rhodecode_settings'
471 __tablename__ = 'repo_rhodecode_settings'
472 __table_args__ = (
472 __table_args__ = (
473 UniqueConstraint(
473 UniqueConstraint(
474 'app_settings_name', 'repository_id',
474 'app_settings_name', 'repository_id',
475 name='uq_repo_rhodecode_setting_name_repo_id'),
475 name='uq_repo_rhodecode_setting_name_repo_id'),
476 base_table_args
476 base_table_args
477 )
477 )
478
478
479 repository_id = Column(
479 repository_id = Column(
480 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
480 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
481 nullable=False)
481 nullable=False)
482 app_settings_id = Column(
482 app_settings_id = Column(
483 "app_settings_id", Integer(), nullable=False, unique=True,
483 "app_settings_id", Integer(), nullable=False, unique=True,
484 default=None, primary_key=True)
484 default=None, primary_key=True)
485 app_settings_name = Column(
485 app_settings_name = Column(
486 "app_settings_name", String(255), nullable=True, unique=None,
486 "app_settings_name", String(255), nullable=True, unique=None,
487 default=None)
487 default=None)
488 _app_settings_value = Column(
488 _app_settings_value = Column(
489 "app_settings_value", String(4096), nullable=True, unique=None,
489 "app_settings_value", String(4096), nullable=True, unique=None,
490 default=None)
490 default=None)
491 _app_settings_type = Column(
491 _app_settings_type = Column(
492 "app_settings_type", String(255), nullable=True, unique=None,
492 "app_settings_type", String(255), nullable=True, unique=None,
493 default=None)
493 default=None)
494
494
495 repository = relationship('Repository', viewonly=True)
495 repository = relationship('Repository', viewonly=True)
496
496
497 def __init__(self, repository_id, key='', val='', type='unicode'):
497 def __init__(self, repository_id, key='', val='', type='unicode'):
498 self.repository_id = repository_id
498 self.repository_id = repository_id
499 self.app_settings_name = key
499 self.app_settings_name = key
500 self.app_settings_type = type
500 self.app_settings_type = type
501 self.app_settings_value = val
501 self.app_settings_value = val
502
502
503 @validates('_app_settings_value')
503 @validates('_app_settings_value')
504 def validate_settings_value(self, key, val):
504 def validate_settings_value(self, key, val):
505 assert type(val) == str
505 assert type(val) == str
506 return val
506 return val
507
507
508 @hybrid_property
508 @hybrid_property
509 def app_settings_value(self):
509 def app_settings_value(self):
510 v = self._app_settings_value
510 v = self._app_settings_value
511 type_ = self.app_settings_type
511 type_ = self.app_settings_type
512 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
513 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
513 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
514 return converter(v)
514 return converter(v)
515
515
516 @app_settings_value.setter
516 @app_settings_value.setter
517 def app_settings_value(self, val):
517 def app_settings_value(self, val):
518 """
518 """
519 Setter that will always make sure we use unicode in app_settings_value
519 Setter that will always make sure we use unicode in app_settings_value
520
520
521 :param val:
521 :param val:
522 """
522 """
523 self._app_settings_value = safe_str(val)
523 self._app_settings_value = safe_str(val)
524
524
525 @hybrid_property
525 @hybrid_property
526 def app_settings_type(self):
526 def app_settings_type(self):
527 return self._app_settings_type
527 return self._app_settings_type
528
528
529 @app_settings_type.setter
529 @app_settings_type.setter
530 def app_settings_type(self, val):
530 def app_settings_type(self, val):
531 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
531 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
532 if val not in SETTINGS_TYPES:
532 if val not in SETTINGS_TYPES:
533 raise Exception('type must be one of %s got %s'
533 raise Exception('type must be one of %s got %s'
534 % (SETTINGS_TYPES.keys(), val))
534 % (SETTINGS_TYPES.keys(), val))
535 self._app_settings_type = val
535 self._app_settings_type = val
536
536
537 def __repr__(self):
537 def __repr__(self):
538 return "<%s('%s:%s:%s[%s]')>" % (
538 return "<%s('%s:%s:%s[%s]')>" % (
539 self.cls_name, self.repository.repo_name,
539 self.cls_name, self.repository.repo_name,
540 self.app_settings_name, self.app_settings_value,
540 self.app_settings_name, self.app_settings_value,
541 self.app_settings_type
541 self.app_settings_type
542 )
542 )
543
543
544
544
545 class RepoRhodeCodeUi(Base, BaseModel):
545 class RepoRhodeCodeUi(Base, BaseModel):
546 __tablename__ = 'repo_rhodecode_ui'
546 __tablename__ = 'repo_rhodecode_ui'
547 __table_args__ = (
547 __table_args__ = (
548 UniqueConstraint(
548 UniqueConstraint(
549 'repository_id', 'ui_section', 'ui_key',
549 'repository_id', 'ui_section', 'ui_key',
550 name='uq_repo_rhodecode_ui_repository_id_section_key'),
550 name='uq_repo_rhodecode_ui_repository_id_section_key'),
551 base_table_args
551 base_table_args
552 )
552 )
553
553
554 repository_id = Column(
554 repository_id = Column(
555 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
555 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
556 nullable=False)
556 nullable=False)
557 ui_id = Column(
557 ui_id = Column(
558 "ui_id", Integer(), nullable=False, unique=True, default=None,
558 "ui_id", Integer(), nullable=False, unique=True, default=None,
559 primary_key=True)
559 primary_key=True)
560 ui_section = Column(
560 ui_section = Column(
561 "ui_section", String(255), nullable=True, unique=None, default=None)
561 "ui_section", String(255), nullable=True, unique=None, default=None)
562 ui_key = Column(
562 ui_key = Column(
563 "ui_key", String(255), nullable=True, unique=None, default=None)
563 "ui_key", String(255), nullable=True, unique=None, default=None)
564 ui_value = Column(
564 ui_value = Column(
565 "ui_value", String(255), nullable=True, unique=None, default=None)
565 "ui_value", String(255), nullable=True, unique=None, default=None)
566 ui_active = Column(
566 ui_active = Column(
567 "ui_active", Boolean(), nullable=True, unique=None, default=True)
567 "ui_active", Boolean(), nullable=True, unique=None, default=True)
568
568
569 repository = relationship('Repository', viewonly=True)
569 repository = relationship('Repository', viewonly=True)
570
570
571 def __repr__(self):
571 def __repr__(self):
572 return '<%s[%s:%s]%s=>%s]>' % (
572 return '<%s[%s:%s]%s=>%s]>' % (
573 self.cls_name, self.repository.repo_name,
573 self.cls_name, self.repository.repo_name,
574 self.ui_section, self.ui_key, self.ui_value)
574 self.ui_section, self.ui_key, self.ui_value)
575
575
576
576
577 class User(Base, BaseModel):
577 class User(Base, BaseModel):
578 __tablename__ = 'users'
578 __tablename__ = 'users'
579 __table_args__ = (
579 __table_args__ = (
580 UniqueConstraint('username'), UniqueConstraint('email'),
580 UniqueConstraint('username'), UniqueConstraint('email'),
581 Index('u_username_idx', 'username'),
581 Index('u_username_idx', 'username'),
582 Index('u_email_idx', 'email'),
582 Index('u_email_idx', 'email'),
583 base_table_args
583 base_table_args
584 )
584 )
585
585
586 DEFAULT_USER = 'default'
586 DEFAULT_USER = 'default'
587 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
587 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
588 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
588 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
589
589
590 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
590 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 username = Column("username", String(255), nullable=True, unique=None, default=None)
591 username = Column("username", String(255), nullable=True, unique=None, default=None)
592 password = Column("password", String(255), nullable=True, unique=None, default=None)
592 password = Column("password", String(255), nullable=True, unique=None, default=None)
593 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
593 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
594 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
594 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
595 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
595 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
596 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
596 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
597 _email = Column("email", String(255), nullable=True, unique=None, default=None)
597 _email = Column("email", String(255), nullable=True, unique=None, default=None)
598 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
599 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
600 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
600 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
601
601
602 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
602 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
603 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
603 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
604 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
604 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
605 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
605 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
607 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
607 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
608
608
609 user_log = relationship('UserLog', back_populates='user')
609 user_log = relationship('UserLog', back_populates='user')
610 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
610 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
611
611
612 repositories = relationship('Repository', back_populates='user')
612 repositories = relationship('Repository', back_populates='user')
613 repository_groups = relationship('RepoGroup', back_populates='user')
613 repository_groups = relationship('RepoGroup', back_populates='user')
614 user_groups = relationship('UserGroup', back_populates='user')
614 user_groups = relationship('UserGroup', back_populates='user')
615
615
616 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all', back_populates='follows_user')
616 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all', back_populates='follows_user')
617 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all', back_populates='user')
617 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all', back_populates='user')
618
618
619 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
619 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
620 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
620 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
621 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
621 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan', back_populates='user')
622
622
623 group_member = relationship('UserGroupMember', cascade='all', back_populates='user')
623 group_member = relationship('UserGroupMember', cascade='all', back_populates='user')
624
624
625 notifications = relationship('UserNotification', cascade='all', back_populates='user')
625 notifications = relationship('UserNotification', cascade='all', back_populates='user')
626 # notifications assigned to this user
626 # notifications assigned to this user
627 user_created_notifications = relationship('Notification', cascade='all', back_populates='created_by_user')
627 user_created_notifications = relationship('Notification', cascade='all', back_populates='created_by_user')
628 # comments created by this user
628 # comments created by this user
629 user_comments = relationship('ChangesetComment', cascade='all', back_populates='author')
629 user_comments = relationship('ChangesetComment', cascade='all', back_populates='author')
630 # user profile extra info
630 # user profile extra info
631 user_emails = relationship('UserEmailMap', cascade='all', back_populates='user')
631 user_emails = relationship('UserEmailMap', cascade='all', back_populates='user')
632 user_ip_map = relationship('UserIpMap', cascade='all', back_populates='user')
632 user_ip_map = relationship('UserIpMap', cascade='all', back_populates='user')
633 user_auth_tokens = relationship('UserApiKeys', cascade='all', back_populates='user')
633 user_auth_tokens = relationship('UserApiKeys', cascade='all', back_populates='user')
634 user_ssh_keys = relationship('UserSshKeys', cascade='all', back_populates='user')
634 user_ssh_keys = relationship('UserSshKeys', cascade='all', back_populates='user')
635
635
636 # gists
636 # gists
637 user_gists = relationship('Gist', cascade='all', back_populates='owner')
637 user_gists = relationship('Gist', cascade='all', back_populates='owner')
638 # user pull requests
638 # user pull requests
639 user_pull_requests = relationship('PullRequest', cascade='all', back_populates='author')
639 user_pull_requests = relationship('PullRequest', cascade='all', back_populates='author')
640
640
641 # external identities
641 # external identities
642 external_identities = relationship('ExternalIdentity', primaryjoin="User.user_id==ExternalIdentity.local_user_id", cascade='all')
642 external_identities = relationship('ExternalIdentity', primaryjoin="User.user_id==ExternalIdentity.local_user_id", cascade='all')
643 # review rules
643 # review rules
644 user_review_rules = relationship('RepoReviewRuleUser', cascade='all', back_populates='user')
644 user_review_rules = relationship('RepoReviewRuleUser', cascade='all', back_populates='user')
645
645
646 # artifacts owned
646 # artifacts owned
647 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id', back_populates='upload_user')
647 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id', back_populates='upload_user')
648
648
649 # no cascade, set NULL
649 # no cascade, set NULL
650 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id', cascade='', back_populates='user')
650 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id', cascade='', back_populates='user')
651
651
652 def __repr__(self):
652 def __repr__(self):
653 return f"<{self.cls_name}('id={self.user_id}, username={self.username}')>"
653 return f"<{self.cls_name}('id={self.user_id}, username={self.username}')>"
654
654
655 @hybrid_property
655 @hybrid_property
656 def email(self):
656 def email(self):
657 return self._email
657 return self._email
658
658
659 @email.setter
659 @email.setter
660 def email(self, val):
660 def email(self, val):
661 self._email = val.lower() if val else None
661 self._email = val.lower() if val else None
662
662
663 @hybrid_property
663 @hybrid_property
664 def first_name(self):
664 def first_name(self):
665 from rhodecode.lib import helpers as h
665 from rhodecode.lib import helpers as h
666 if self.name:
666 if self.name:
667 return h.escape(self.name)
667 return h.escape(self.name)
668 return self.name
668 return self.name
669
669
670 @hybrid_property
670 @hybrid_property
671 def last_name(self):
671 def last_name(self):
672 from rhodecode.lib import helpers as h
672 from rhodecode.lib import helpers as h
673 if self.lastname:
673 if self.lastname:
674 return h.escape(self.lastname)
674 return h.escape(self.lastname)
675 return self.lastname
675 return self.lastname
676
676
677 @hybrid_property
677 @hybrid_property
678 def api_key(self):
678 def api_key(self):
679 """
679 """
680 Fetch if exist an auth-token with role ALL connected to this user
680 Fetch if exist an auth-token with role ALL connected to this user
681 """
681 """
682 user_auth_token = UserApiKeys.query()\
682 user_auth_token = UserApiKeys.query()\
683 .filter(UserApiKeys.user_id == self.user_id)\
683 .filter(UserApiKeys.user_id == self.user_id)\
684 .filter(or_(UserApiKeys.expires == -1,
684 .filter(or_(UserApiKeys.expires == -1,
685 UserApiKeys.expires >= time.time()))\
685 UserApiKeys.expires >= time.time()))\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
687 if user_auth_token:
687 if user_auth_token:
688 user_auth_token = user_auth_token.api_key
688 user_auth_token = user_auth_token.api_key
689
689
690 return user_auth_token
690 return user_auth_token
691
691
692 @api_key.setter
692 @api_key.setter
693 def api_key(self, val):
693 def api_key(self, val):
694 # don't allow to set API key this is deprecated for now
694 # don't allow to set API key this is deprecated for now
695 self._api_key = None
695 self._api_key = None
696
696
697 @property
697 @property
698 def reviewer_pull_requests(self):
698 def reviewer_pull_requests(self):
699 return PullRequestReviewers.query() \
699 return PullRequestReviewers.query() \
700 .options(joinedload(PullRequestReviewers.pull_request)) \
700 .options(joinedload(PullRequestReviewers.pull_request)) \
701 .filter(PullRequestReviewers.user_id == self.user_id) \
701 .filter(PullRequestReviewers.user_id == self.user_id) \
702 .all()
702 .all()
703
703
704 @property
704 @property
705 def firstname(self):
705 def firstname(self):
706 # alias for future
706 # alias for future
707 return self.name
707 return self.name
708
708
709 @property
709 @property
710 def emails(self):
710 def emails(self):
711 other = UserEmailMap.query()\
711 other = UserEmailMap.query()\
712 .filter(UserEmailMap.user == self) \
712 .filter(UserEmailMap.user == self) \
713 .order_by(UserEmailMap.email_id.asc()) \
713 .order_by(UserEmailMap.email_id.asc()) \
714 .all()
714 .all()
715 return [self.email] + [x.email for x in other]
715 return [self.email] + [x.email for x in other]
716
716
717 def emails_cached(self):
717 def emails_cached(self):
718 emails = []
718 emails = []
719 if self.user_id != self.get_default_user_id():
719 if self.user_id != self.get_default_user_id():
720 emails = UserEmailMap.query()\
720 emails = UserEmailMap.query()\
721 .filter(UserEmailMap.user == self) \
721 .filter(UserEmailMap.user == self) \
722 .order_by(UserEmailMap.email_id.asc())
722 .order_by(UserEmailMap.email_id.asc())
723
723
724 emails = emails.options(
724 emails = emails.options(
725 FromCache("sql_cache_short", f"get_user_{self.user_id}_emails")
725 FromCache("sql_cache_short", f"get_user_{self.user_id}_emails")
726 )
726 )
727
727
728 return [self.email] + [x.email for x in emails]
728 return [self.email] + [x.email for x in emails]
729
729
730 @property
730 @property
731 def auth_tokens(self):
731 def auth_tokens(self):
732 auth_tokens = self.get_auth_tokens()
732 auth_tokens = self.get_auth_tokens()
733 return [x.api_key for x in auth_tokens]
733 return [x.api_key for x in auth_tokens]
734
734
735 def get_auth_tokens(self):
735 def get_auth_tokens(self):
736 return UserApiKeys.query()\
736 return UserApiKeys.query()\
737 .filter(UserApiKeys.user == self)\
737 .filter(UserApiKeys.user == self)\
738 .order_by(UserApiKeys.user_api_key_id.asc())\
738 .order_by(UserApiKeys.user_api_key_id.asc())\
739 .all()
739 .all()
740
740
741 @LazyProperty
741 @LazyProperty
742 def feed_token(self):
742 def feed_token(self):
743 return self.get_feed_token()
743 return self.get_feed_token()
744
744
745 def get_feed_token(self, cache=True):
745 def get_feed_token(self, cache=True):
746 feed_tokens = UserApiKeys.query()\
746 feed_tokens = UserApiKeys.query()\
747 .filter(UserApiKeys.user == self)\
747 .filter(UserApiKeys.user == self)\
748 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
748 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
749 if cache:
749 if cache:
750 feed_tokens = feed_tokens.options(
750 feed_tokens = feed_tokens.options(
751 FromCache("sql_cache_short", f"get_user_feed_token_{self.user_id}"))
751 FromCache("sql_cache_short", f"get_user_feed_token_{self.user_id}"))
752
752
753 feed_tokens = feed_tokens.all()
753 feed_tokens = feed_tokens.all()
754 if feed_tokens:
754 if feed_tokens:
755 return feed_tokens[0].api_key
755 return feed_tokens[0].api_key
756 return 'NO_FEED_TOKEN_AVAILABLE'
756 return 'NO_FEED_TOKEN_AVAILABLE'
757
757
758 @LazyProperty
758 @LazyProperty
759 def artifact_token(self):
759 def artifact_token(self):
760 return self.get_artifact_token()
760 return self.get_artifact_token()
761
761
762 def get_artifact_token(self, cache=True):
762 def get_artifact_token(self, cache=True):
763 artifacts_tokens = UserApiKeys.query()\
763 artifacts_tokens = UserApiKeys.query()\
764 .filter(UserApiKeys.user == self) \
764 .filter(UserApiKeys.user == self) \
765 .filter(or_(UserApiKeys.expires == -1,
765 .filter(or_(UserApiKeys.expires == -1,
766 UserApiKeys.expires >= time.time())) \
766 UserApiKeys.expires >= time.time())) \
767 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
767 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
768
768
769 if cache:
769 if cache:
770 artifacts_tokens = artifacts_tokens.options(
770 artifacts_tokens = artifacts_tokens.options(
771 FromCache("sql_cache_short", f"get_user_artifact_token_{self.user_id}"))
771 FromCache("sql_cache_short", f"get_user_artifact_token_{self.user_id}"))
772
772
773 artifacts_tokens = artifacts_tokens.all()
773 artifacts_tokens = artifacts_tokens.all()
774 if artifacts_tokens:
774 if artifacts_tokens:
775 return artifacts_tokens[0].api_key
775 return artifacts_tokens[0].api_key
776 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
776 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
777
777
778 def get_or_create_artifact_token(self):
778 def get_or_create_artifact_token(self):
779 artifacts_tokens = UserApiKeys.query()\
779 artifacts_tokens = UserApiKeys.query()\
780 .filter(UserApiKeys.user == self) \
780 .filter(UserApiKeys.user == self) \
781 .filter(or_(UserApiKeys.expires == -1,
781 .filter(or_(UserApiKeys.expires == -1,
782 UserApiKeys.expires >= time.time())) \
782 UserApiKeys.expires >= time.time())) \
783 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
783 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
784
784
785 artifacts_tokens = artifacts_tokens.all()
785 artifacts_tokens = artifacts_tokens.all()
786 if artifacts_tokens:
786 if artifacts_tokens:
787 return artifacts_tokens[0].api_key
787 return artifacts_tokens[0].api_key
788 else:
788 else:
789 from rhodecode.model.auth_token import AuthTokenModel
789 from rhodecode.model.auth_token import AuthTokenModel
790 artifact_token = AuthTokenModel().create(
790 artifact_token = AuthTokenModel().create(
791 self, 'auto-generated-artifact-token',
791 self, 'auto-generated-artifact-token',
792 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
792 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
793 Session.commit()
793 Session.commit()
794 return artifact_token.api_key
794 return artifact_token.api_key
795
795
796 @classmethod
796 @classmethod
797 def get(cls, user_id, cache=False):
797 def get(cls, user_id, cache=False):
798 if not user_id:
798 if not user_id:
799 return
799 return
800
800
801 user = cls.query()
801 user = cls.query()
802 if cache:
802 if cache:
803 user = user.options(
803 user = user.options(
804 FromCache("sql_cache_short", f"get_users_{user_id}"))
804 FromCache("sql_cache_short", f"get_users_{user_id}"))
805 return user.get(user_id)
805 return user.get(user_id)
806
806
807 @classmethod
807 @classmethod
808 def extra_valid_auth_tokens(cls, user, role=None):
808 def extra_valid_auth_tokens(cls, user, role=None):
809 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
809 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
810 .filter(or_(UserApiKeys.expires == -1,
810 .filter(or_(UserApiKeys.expires == -1,
811 UserApiKeys.expires >= time.time()))
811 UserApiKeys.expires >= time.time()))
812 if role:
812 if role:
813 tokens = tokens.filter(or_(UserApiKeys.role == role,
813 tokens = tokens.filter(or_(UserApiKeys.role == role,
814 UserApiKeys.role == UserApiKeys.ROLE_ALL))
814 UserApiKeys.role == UserApiKeys.ROLE_ALL))
815 return tokens.all()
815 return tokens.all()
816
816
817 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
817 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
818 from rhodecode.lib import auth
818 from rhodecode.lib import auth
819
819
820 log.debug('Trying to authenticate user: %s via auth-token, '
820 log.debug('Trying to authenticate user: %s via auth-token, '
821 'and roles: %s', self, roles)
821 'and roles: %s', self, roles)
822
822
823 if not auth_token:
823 if not auth_token:
824 return False
824 return False
825
825
826 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
826 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
827 tokens_q = UserApiKeys.query()\
827 tokens_q = UserApiKeys.query()\
828 .filter(UserApiKeys.user_id == self.user_id)\
828 .filter(UserApiKeys.user_id == self.user_id)\
829 .filter(or_(UserApiKeys.expires == -1,
829 .filter(or_(UserApiKeys.expires == -1,
830 UserApiKeys.expires >= time.time()))
830 UserApiKeys.expires >= time.time()))
831
831
832 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
832 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
833
833
834 crypto_backend = auth.crypto_backend()
834 crypto_backend = auth.crypto_backend()
835 enc_token_map = {}
835 enc_token_map = {}
836 plain_token_map = {}
836 plain_token_map = {}
837 for token in tokens_q:
837 for token in tokens_q:
838 if token.api_key.startswith(crypto_backend.ENC_PREF):
838 if token.api_key.startswith(crypto_backend.ENC_PREF):
839 enc_token_map[token.api_key] = token
839 enc_token_map[token.api_key] = token
840 else:
840 else:
841 plain_token_map[token.api_key] = token
841 plain_token_map[token.api_key] = token
842 log.debug(
842 log.debug(
843 'Found %s plain and %s encrypted tokens to check for authentication for this user',
843 'Found %s plain and %s encrypted tokens to check for authentication for this user',
844 len(plain_token_map), len(enc_token_map))
844 len(plain_token_map), len(enc_token_map))
845
845
846 # plain token match comes first
846 # plain token match comes first
847 match = plain_token_map.get(auth_token)
847 match = plain_token_map.get(auth_token)
848
848
849 # check encrypted tokens now
849 # check encrypted tokens now
850 if not match:
850 if not match:
851 for token_hash, token in enc_token_map.items():
851 for token_hash, token in enc_token_map.items():
852 # NOTE(marcink): this is expensive to calculate, but most secure
852 # NOTE(marcink): this is expensive to calculate, but most secure
853 if crypto_backend.hash_check(auth_token, token_hash):
853 if crypto_backend.hash_check(auth_token, token_hash):
854 match = token
854 match = token
855 break
855 break
856
856
857 if match:
857 if match:
858 log.debug('Found matching token %s', match)
858 log.debug('Found matching token %s', match)
859 if match.repo_id:
859 if match.repo_id:
860 log.debug('Found scope, checking for scope match of token %s', match)
860 log.debug('Found scope, checking for scope match of token %s', match)
861 if match.repo_id == scope_repo_id:
861 if match.repo_id == scope_repo_id:
862 return True
862 return True
863 else:
863 else:
864 log.debug(
864 log.debug(
865 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
865 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
866 'and calling scope is:%s, skipping further checks',
866 'and calling scope is:%s, skipping further checks',
867 match.repo, scope_repo_id)
867 match.repo, scope_repo_id)
868 return False
868 return False
869 else:
869 else:
870 return True
870 return True
871
871
872 return False
872 return False
873
873
874 @property
874 @property
875 def ip_addresses(self):
875 def ip_addresses(self):
876 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
876 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
877 return [x.ip_addr for x in ret]
877 return [x.ip_addr for x in ret]
878
878
879 @property
879 @property
880 def username_and_name(self):
880 def username_and_name(self):
881 return f'{self.username} ({self.first_name} {self.last_name})'
881 return f'{self.username} ({self.first_name} {self.last_name})'
882
882
883 @property
883 @property
884 def username_or_name_or_email(self):
884 def username_or_name_or_email(self):
885 full_name = self.full_name if self.full_name != ' ' else None
885 full_name = self.full_name if self.full_name != ' ' else None
886 return self.username or full_name or self.email
886 return self.username or full_name or self.email
887
887
888 @property
888 @property
889 def full_name(self):
889 def full_name(self):
890 return f'{self.first_name} {self.last_name}'
890 return f'{self.first_name} {self.last_name}'
891
891
892 @property
892 @property
893 def full_name_or_username(self):
893 def full_name_or_username(self):
894 return (f'{self.first_name} {self.last_name}'
894 return (f'{self.first_name} {self.last_name}'
895 if (self.first_name and self.last_name) else self.username)
895 if (self.first_name and self.last_name) else self.username)
896
896
897 @property
897 @property
898 def full_contact(self):
898 def full_contact(self):
899 return f'{self.first_name} {self.last_name} <{self.email}>'
899 return f'{self.first_name} {self.last_name} <{self.email}>'
900
900
901 @property
901 @property
902 def short_contact(self):
902 def short_contact(self):
903 return f'{self.first_name} {self.last_name}'
903 return f'{self.first_name} {self.last_name}'
904
904
905 @property
905 @property
906 def is_admin(self):
906 def is_admin(self):
907 return self.admin
907 return self.admin
908
908
909 @property
909 @property
910 def language(self):
910 def language(self):
911 return self.user_data.get('language')
911 return self.user_data.get('language')
912
912
913 def AuthUser(self, **kwargs):
913 def AuthUser(self, **kwargs):
914 """
914 """
915 Returns instance of AuthUser for this user
915 Returns instance of AuthUser for this user
916 """
916 """
917 from rhodecode.lib.auth import AuthUser
917 from rhodecode.lib.auth import AuthUser
918 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
918 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
919
919
920 @hybrid_property
920 @hybrid_property
921 def user_data(self):
921 def user_data(self):
922 if not self._user_data:
922 if not self._user_data:
923 return {}
923 return {}
924
924
925 try:
925 try:
926 return json.loads(self._user_data) or {}
926 return json.loads(self._user_data) or {}
927 except TypeError:
927 except TypeError:
928 return {}
928 return {}
929
929
930 @user_data.setter
930 @user_data.setter
931 def user_data(self, val):
931 def user_data(self, val):
932 if not isinstance(val, dict):
932 if not isinstance(val, dict):
933 raise Exception('user_data must be dict, got %s' % type(val))
933 raise Exception('user_data must be dict, got %s' % type(val))
934 try:
934 try:
935 self._user_data = safe_bytes(json.dumps(val))
935 self._user_data = safe_bytes(json.dumps(val))
936 except Exception:
936 except Exception:
937 log.error(traceback.format_exc())
937 log.error(traceback.format_exc())
938
938
939 @classmethod
939 @classmethod
940 def get_by_username(cls, username, case_insensitive=False,
940 def get_by_username(cls, username, case_insensitive=False,
941 cache=False):
941 cache=False):
942
942
943 if case_insensitive:
943 if case_insensitive:
944 q = cls.select().where(
944 q = cls.select().where(
945 func.lower(cls.username) == func.lower(username))
945 func.lower(cls.username) == func.lower(username))
946 else:
946 else:
947 q = cls.select().where(cls.username == username)
947 q = cls.select().where(cls.username == username)
948
948
949 if cache:
949 if cache:
950 hash_key = _hash_key(username)
950 hash_key = _hash_key(username)
951 q = q.options(
951 q = q.options(
952 FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
952 FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
953
953
954 return cls.execute(q).scalar_one_or_none()
954 return cls.execute(q).scalar_one_or_none()
955
955
956 @classmethod
956 @classmethod
957 def get_by_auth_token(cls, auth_token, cache=False):
957 def get_by_auth_token(cls, auth_token, cache=False):
958
958
959 q = cls.select(User)\
959 q = cls.select(User)\
960 .join(UserApiKeys)\
960 .join(UserApiKeys)\
961 .where(UserApiKeys.api_key == auth_token)\
961 .where(UserApiKeys.api_key == auth_token)\
962 .where(or_(UserApiKeys.expires == -1,
962 .where(or_(UserApiKeys.expires == -1,
963 UserApiKeys.expires >= time.time()))
963 UserApiKeys.expires >= time.time()))
964
964
965 if cache:
965 if cache:
966 q = q.options(
966 q = q.options(
967 FromCache("sql_cache_short", f"get_auth_token_{auth_token}"))
967 FromCache("sql_cache_short", f"get_auth_token_{auth_token}"))
968
968
969 matched_user = cls.execute(q).scalar_one_or_none()
969 matched_user = cls.execute(q).scalar_one_or_none()
970
970
971 return matched_user
971 return matched_user
972
972
973 @classmethod
973 @classmethod
974 def get_by_email(cls, email, case_insensitive=False, cache=False):
974 def get_by_email(cls, email, case_insensitive=False, cache=False):
975
975
976 if case_insensitive:
976 if case_insensitive:
977 q = cls.select().where(func.lower(cls.email) == func.lower(email))
977 q = cls.select().where(func.lower(cls.email) == func.lower(email))
978 else:
978 else:
979 q = cls.select().where(cls.email == email)
979 q = cls.select().where(cls.email == email)
980
980
981 if cache:
981 if cache:
982 email_key = _hash_key(email)
982 email_key = _hash_key(email)
983 q = q.options(
983 q = q.options(
984 FromCache("sql_cache_short", f"get_email_key_{email_key}"))
984 FromCache("sql_cache_short", f"get_email_key_{email_key}"))
985
985
986 ret = cls.execute(q).scalar_one_or_none()
986 ret = cls.execute(q).scalar_one_or_none()
987
987
988 if ret is None:
988 if ret is None:
989 q = cls.select(UserEmailMap)
989 q = cls.select(UserEmailMap)
990 # try fetching in alternate email map
990 # try fetching in alternate email map
991 if case_insensitive:
991 if case_insensitive:
992 q = q.where(func.lower(UserEmailMap.email) == func.lower(email))
992 q = q.where(func.lower(UserEmailMap.email) == func.lower(email))
993 else:
993 else:
994 q = q.where(UserEmailMap.email == email)
994 q = q.where(UserEmailMap.email == email)
995 q = q.options(joinedload(UserEmailMap.user))
995 q = q.options(joinedload(UserEmailMap.user))
996 if cache:
996 if cache:
997 q = q.options(
997 q = q.options(
998 FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
998 FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
999
999
1000 result = cls.execute(q).scalar_one_or_none()
1000 result = cls.execute(q).scalar_one_or_none()
1001 ret = getattr(result, 'user', None)
1001 ret = getattr(result, 'user', None)
1002
1002
1003 return ret
1003 return ret
1004
1004
1005 @classmethod
1005 @classmethod
1006 def get_from_cs_author(cls, author):
1006 def get_from_cs_author(cls, author):
1007 """
1007 """
1008 Tries to get User objects out of commit author string
1008 Tries to get User objects out of commit author string
1009
1009
1010 :param author:
1010 :param author:
1011 """
1011 """
1012 from rhodecode.lib.helpers import email, author_name
1012 from rhodecode.lib.helpers import email, author_name
1013 # Valid email in the attribute passed, see if they're in the system
1013 # Valid email in the attribute passed, see if they're in the system
1014 _email = email(author)
1014 _email = email(author)
1015 if _email:
1015 if _email:
1016 user = cls.get_by_email(_email, case_insensitive=True)
1016 user = cls.get_by_email(_email, case_insensitive=True)
1017 if user:
1017 if user:
1018 return user
1018 return user
1019 # Maybe we can match by username?
1019 # Maybe we can match by username?
1020 _author = author_name(author)
1020 _author = author_name(author)
1021 user = cls.get_by_username(_author, case_insensitive=True)
1021 user = cls.get_by_username(_author, case_insensitive=True)
1022 if user:
1022 if user:
1023 return user
1023 return user
1024
1024
1025 def update_userdata(self, **kwargs):
1025 def update_userdata(self, **kwargs):
1026 usr = self
1026 usr = self
1027 old = usr.user_data
1027 old = usr.user_data
1028 old.update(**kwargs)
1028 old.update(**kwargs)
1029 usr.user_data = old
1029 usr.user_data = old
1030 Session().add(usr)
1030 Session().add(usr)
1031 log.debug('updated userdata with %s', kwargs)
1031 log.debug('updated userdata with %s', kwargs)
1032
1032
1033 def update_lastlogin(self):
1033 def update_lastlogin(self):
1034 """Update user lastlogin"""
1034 """Update user lastlogin"""
1035 self.last_login = datetime.datetime.now()
1035 self.last_login = datetime.datetime.now()
1036 Session().add(self)
1036 Session().add(self)
1037 log.debug('updated user %s lastlogin', self.username)
1037 log.debug('updated user %s lastlogin', self.username)
1038
1038
1039 def update_password(self, new_password):
1039 def update_password(self, new_password):
1040 from rhodecode.lib.auth import get_crypt_password
1040 from rhodecode.lib.auth import get_crypt_password
1041
1041
1042 self.password = get_crypt_password(new_password)
1042 self.password = get_crypt_password(new_password)
1043 Session().add(self)
1043 Session().add(self)
1044
1044
1045 @classmethod
1045 @classmethod
1046 def get_first_super_admin(cls):
1046 def get_first_super_admin(cls):
1047 stmt = cls.select().where(User.admin == true()).order_by(User.user_id.asc())
1047 stmt = cls.select().where(User.admin == true()).order_by(User.user_id.asc())
1048 user = cls.scalars(stmt).first()
1048 user = cls.scalars(stmt).first()
1049
1049
1050 if user is None:
1050 if user is None:
1051 raise Exception('FATAL: Missing administrative account!')
1051 raise Exception('FATAL: Missing administrative account!')
1052 return user
1052 return user
1053
1053
1054 @classmethod
1054 @classmethod
1055 def get_all_super_admins(cls, only_active=False):
1055 def get_all_super_admins(cls, only_active=False):
1056 """
1056 """
1057 Returns all admin accounts sorted by username
1057 Returns all admin accounts sorted by username
1058 """
1058 """
1059 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1059 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1060 if only_active:
1060 if only_active:
1061 qry = qry.filter(User.active == true())
1061 qry = qry.filter(User.active == true())
1062 return qry.all()
1062 return qry.all()
1063
1063
1064 @classmethod
1064 @classmethod
1065 def get_all_user_ids(cls, only_active=True):
1065 def get_all_user_ids(cls, only_active=True):
1066 """
1066 """
1067 Returns all users IDs
1067 Returns all users IDs
1068 """
1068 """
1069 qry = Session().query(User.user_id)
1069 qry = Session().query(User.user_id)
1070
1070
1071 if only_active:
1071 if only_active:
1072 qry = qry.filter(User.active == true())
1072 qry = qry.filter(User.active == true())
1073 return [x.user_id for x in qry]
1073 return [x.user_id for x in qry]
1074
1074
1075 @classmethod
1075 @classmethod
1076 def get_default_user(cls, cache=False, refresh=False):
1076 def get_default_user(cls, cache=False, refresh=False):
1077 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1077 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1078 if user is None:
1078 if user is None:
1079 raise Exception('FATAL: Missing default account!')
1079 raise Exception('FATAL: Missing default account!')
1080 if refresh:
1080 if refresh:
1081 # The default user might be based on outdated state which
1081 # The default user might be based on outdated state which
1082 # has been loaded from the cache.
1082 # has been loaded from the cache.
1083 # A call to refresh() ensures that the
1083 # A call to refresh() ensures that the
1084 # latest state from the database is used.
1084 # latest state from the database is used.
1085 Session().refresh(user)
1085 Session().refresh(user)
1086
1086
1087 return user
1087 return user
1088
1088
1089 @classmethod
1089 @classmethod
1090 def get_default_user_id(cls):
1090 def get_default_user_id(cls):
1091 import rhodecode
1091 import rhodecode
1092 return rhodecode.CONFIG['default_user_id']
1092 return rhodecode.CONFIG['default_user_id']
1093
1093
1094 def _get_default_perms(self, user, suffix=''):
1094 def _get_default_perms(self, user, suffix=''):
1095 from rhodecode.model.permission import PermissionModel
1095 from rhodecode.model.permission import PermissionModel
1096 return PermissionModel().get_default_perms(user.user_perms, suffix)
1096 return PermissionModel().get_default_perms(user.user_perms, suffix)
1097
1097
1098 def get_default_perms(self, suffix=''):
1098 def get_default_perms(self, suffix=''):
1099 return self._get_default_perms(self, suffix)
1099 return self._get_default_perms(self, suffix)
1100
1100
1101 def get_api_data(self, include_secrets=False, details='full'):
1101 def get_api_data(self, include_secrets=False, details='full'):
1102 """
1102 """
1103 Common function for generating user related data for API
1103 Common function for generating user related data for API
1104
1104
1105 :param include_secrets: By default secrets in the API data will be replaced
1105 :param include_secrets: By default secrets in the API data will be replaced
1106 by a placeholder value to prevent exposing this data by accident. In case
1106 by a placeholder value to prevent exposing this data by accident. In case
1107 this data shall be exposed, set this flag to ``True``.
1107 this data shall be exposed, set this flag to ``True``.
1108
1108
1109 :param details: details can be 'basic|full' basic gives only a subset of
1109 :param details: details can be 'basic|full' basic gives only a subset of
1110 the available user information that includes user_id, name and emails.
1110 the available user information that includes user_id, name and emails.
1111 """
1111 """
1112 user = self
1112 user = self
1113 user_data = self.user_data
1113 user_data = self.user_data
1114 data = {
1114 data = {
1115 'user_id': user.user_id,
1115 'user_id': user.user_id,
1116 'username': user.username,
1116 'username': user.username,
1117 'firstname': user.name,
1117 'firstname': user.name,
1118 'lastname': user.lastname,
1118 'lastname': user.lastname,
1119 'description': user.description,
1119 'description': user.description,
1120 'email': user.email,
1120 'email': user.email,
1121 'emails': user.emails,
1121 'emails': user.emails,
1122 }
1122 }
1123 if details == 'basic':
1123 if details == 'basic':
1124 return data
1124 return data
1125
1125
1126 auth_token_length = 40
1126 auth_token_length = 40
1127 auth_token_replacement = '*' * auth_token_length
1127 auth_token_replacement = '*' * auth_token_length
1128
1128
1129 extras = {
1129 extras = {
1130 'auth_tokens': [auth_token_replacement],
1130 'auth_tokens': [auth_token_replacement],
1131 'active': user.active,
1131 'active': user.active,
1132 'admin': user.admin,
1132 'admin': user.admin,
1133 'extern_type': user.extern_type,
1133 'extern_type': user.extern_type,
1134 'extern_name': user.extern_name,
1134 'extern_name': user.extern_name,
1135 'last_login': user.last_login,
1135 'last_login': user.last_login,
1136 'last_activity': user.last_activity,
1136 'last_activity': user.last_activity,
1137 'ip_addresses': user.ip_addresses,
1137 'ip_addresses': user.ip_addresses,
1138 'language': user_data.get('language')
1138 'language': user_data.get('language')
1139 }
1139 }
1140 data.update(extras)
1140 data.update(extras)
1141
1141
1142 if include_secrets:
1142 if include_secrets:
1143 data['auth_tokens'] = user.auth_tokens
1143 data['auth_tokens'] = user.auth_tokens
1144 return data
1144 return data
1145
1145
1146 def __json__(self):
1146 def __json__(self):
1147 data = {
1147 data = {
1148 'full_name': self.full_name,
1148 'full_name': self.full_name,
1149 'full_name_or_username': self.full_name_or_username,
1149 'full_name_or_username': self.full_name_or_username,
1150 'short_contact': self.short_contact,
1150 'short_contact': self.short_contact,
1151 'full_contact': self.full_contact,
1151 'full_contact': self.full_contact,
1152 }
1152 }
1153 data.update(self.get_api_data())
1153 data.update(self.get_api_data())
1154 return data
1154 return data
1155
1155
1156
1156
1157 class UserApiKeys(Base, BaseModel):
1157 class UserApiKeys(Base, BaseModel):
1158 __tablename__ = 'user_api_keys'
1158 __tablename__ = 'user_api_keys'
1159 __table_args__ = (
1159 __table_args__ = (
1160 Index('uak_api_key_idx', 'api_key'),
1160 Index('uak_api_key_idx', 'api_key'),
1161 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1161 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1162 base_table_args
1162 base_table_args
1163 )
1163 )
1164
1164
1165 # ApiKey role
1165 # ApiKey role
1166 ROLE_ALL = 'token_role_all'
1166 ROLE_ALL = 'token_role_all'
1167 ROLE_VCS = 'token_role_vcs'
1167 ROLE_VCS = 'token_role_vcs'
1168 ROLE_API = 'token_role_api'
1168 ROLE_API = 'token_role_api'
1169 ROLE_HTTP = 'token_role_http'
1169 ROLE_HTTP = 'token_role_http'
1170 ROLE_FEED = 'token_role_feed'
1170 ROLE_FEED = 'token_role_feed'
1171 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1171 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1172 # The last one is ignored in the list as we only
1172 # The last one is ignored in the list as we only
1173 # use it for one action, and cannot be created by users
1173 # use it for one action, and cannot be created by users
1174 ROLE_PASSWORD_RESET = 'token_password_reset'
1174 ROLE_PASSWORD_RESET = 'token_password_reset'
1175
1175
1176 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1176 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1177
1177
1178 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1178 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1179 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1179 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1180 api_key = Column("api_key", String(255), nullable=False, unique=True)
1180 api_key = Column("api_key", String(255), nullable=False, unique=True)
1181 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1181 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1182 expires = Column('expires', Float(53), nullable=False)
1182 expires = Column('expires', Float(53), nullable=False)
1183 role = Column('role', String(255), nullable=True)
1183 role = Column('role', String(255), nullable=True)
1184 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1184 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1185
1185
1186 # scope columns
1186 # scope columns
1187 repo_id = Column(
1187 repo_id = Column(
1188 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1188 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1189 nullable=True, unique=None, default=None)
1189 nullable=True, unique=None, default=None)
1190 repo = relationship('Repository', lazy='joined', back_populates='scoped_tokens')
1190 repo = relationship('Repository', lazy='joined', back_populates='scoped_tokens')
1191
1191
1192 repo_group_id = Column(
1192 repo_group_id = Column(
1193 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1193 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1194 nullable=True, unique=None, default=None)
1194 nullable=True, unique=None, default=None)
1195 repo_group = relationship('RepoGroup', lazy='joined')
1195 repo_group = relationship('RepoGroup', lazy='joined')
1196
1196
1197 user = relationship('User', lazy='joined', back_populates='user_auth_tokens')
1197 user = relationship('User', lazy='joined', back_populates='user_auth_tokens')
1198
1198
1199 def __repr__(self):
1199 def __repr__(self):
1200 return f"<{self.cls_name}('{self.role}')>"
1200 return f"<{self.cls_name}('{self.role}')>"
1201
1201
1202 def __json__(self):
1202 def __json__(self):
1203 data = {
1203 data = {
1204 'auth_token': self.api_key,
1204 'auth_token': self.api_key,
1205 'role': self.role,
1205 'role': self.role,
1206 'scope': self.scope_humanized,
1206 'scope': self.scope_humanized,
1207 'expired': self.expired
1207 'expired': self.expired
1208 }
1208 }
1209 return data
1209 return data
1210
1210
1211 def get_api_data(self, include_secrets=False):
1211 def get_api_data(self, include_secrets=False):
1212 data = self.__json__()
1212 data = self.__json__()
1213 if include_secrets:
1213 if include_secrets:
1214 return data
1214 return data
1215 else:
1215 else:
1216 data['auth_token'] = self.token_obfuscated
1216 data['auth_token'] = self.token_obfuscated
1217 return data
1217 return data
1218
1218
1219 @hybrid_property
1219 @hybrid_property
1220 def description_safe(self):
1220 def description_safe(self):
1221 from rhodecode.lib import helpers as h
1221 from rhodecode.lib import helpers as h
1222 return h.escape(self.description)
1222 return h.escape(self.description)
1223
1223
1224 @property
1224 @property
1225 def expired(self):
1225 def expired(self):
1226 if self.expires == -1:
1226 if self.expires == -1:
1227 return False
1227 return False
1228 return time.time() > self.expires
1228 return time.time() > self.expires
1229
1229
1230 @classmethod
1230 @classmethod
1231 def _get_role_name(cls, role):
1231 def _get_role_name(cls, role):
1232 return {
1232 return {
1233 cls.ROLE_ALL: _('all'),
1233 cls.ROLE_ALL: _('all'),
1234 cls.ROLE_HTTP: _('http/web interface'),
1234 cls.ROLE_HTTP: _('http/web interface'),
1235 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1235 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1236 cls.ROLE_API: _('api calls'),
1236 cls.ROLE_API: _('api calls'),
1237 cls.ROLE_FEED: _('feed access'),
1237 cls.ROLE_FEED: _('feed access'),
1238 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1238 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1239 }.get(role, role)
1239 }.get(role, role)
1240
1240
1241 @classmethod
1241 @classmethod
1242 def _get_role_description(cls, role):
1242 def _get_role_description(cls, role):
1243 return {
1243 return {
1244 cls.ROLE_ALL: _('Token for all actions.'),
1244 cls.ROLE_ALL: _('Token for all actions.'),
1245 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1245 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1246 'login using `api_access_controllers_whitelist` functionality.'),
1246 'login using `api_access_controllers_whitelist` functionality.'),
1247 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1247 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1248 'Requires auth_token authentication plugin to be active. <br/>'
1248 'Requires auth_token authentication plugin to be active. <br/>'
1249 'Such Token should be used then instead of a password to '
1249 'Such Token should be used then instead of a password to '
1250 'interact with a repository, and additionally can be '
1250 'interact with a repository, and additionally can be '
1251 'limited to single repository using repo scope.'),
1251 'limited to single repository using repo scope.'),
1252 cls.ROLE_API: _('Token limited to api calls.'),
1252 cls.ROLE_API: _('Token limited to api calls.'),
1253 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1253 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1254 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1254 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1255 }.get(role, role)
1255 }.get(role, role)
1256
1256
1257 @property
1257 @property
1258 def role_humanized(self):
1258 def role_humanized(self):
1259 return self._get_role_name(self.role)
1259 return self._get_role_name(self.role)
1260
1260
1261 def _get_scope(self):
1261 def _get_scope(self):
1262 if self.repo:
1262 if self.repo:
1263 return 'Repository: {}'.format(self.repo.repo_name)
1263 return 'Repository: {}'.format(self.repo.repo_name)
1264 if self.repo_group:
1264 if self.repo_group:
1265 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1265 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1266 return 'Global'
1266 return 'Global'
1267
1267
1268 @property
1268 @property
1269 def scope_humanized(self):
1269 def scope_humanized(self):
1270 return self._get_scope()
1270 return self._get_scope()
1271
1271
1272 @property
1272 @property
1273 def token_obfuscated(self):
1273 def token_obfuscated(self):
1274 if self.api_key:
1274 if self.api_key:
1275 return self.api_key[:4] + "****"
1275 return self.api_key[:4] + "****"
1276
1276
1277
1277
1278 class UserEmailMap(Base, BaseModel):
1278 class UserEmailMap(Base, BaseModel):
1279 __tablename__ = 'user_email_map'
1279 __tablename__ = 'user_email_map'
1280 __table_args__ = (
1280 __table_args__ = (
1281 Index('uem_email_idx', 'email'),
1281 Index('uem_email_idx', 'email'),
1282 Index('uem_user_id_idx', 'user_id'),
1282 Index('uem_user_id_idx', 'user_id'),
1283 UniqueConstraint('email'),
1283 UniqueConstraint('email'),
1284 base_table_args
1284 base_table_args
1285 )
1285 )
1286
1286
1287 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1287 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1288 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1288 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1289 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1289 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1290 user = relationship('User', lazy='joined', back_populates='user_emails')
1290 user = relationship('User', lazy='joined', back_populates='user_emails')
1291
1291
1292 @validates('_email')
1292 @validates('_email')
1293 def validate_email(self, key, email):
1293 def validate_email(self, key, email):
1294 # check if this email is not main one
1294 # check if this email is not main one
1295 main_email = Session().query(User).filter(User.email == email).scalar()
1295 main_email = Session().query(User).filter(User.email == email).scalar()
1296 if main_email is not None:
1296 if main_email is not None:
1297 raise AttributeError('email %s is present is user table' % email)
1297 raise AttributeError('email %s is present is user table' % email)
1298 return email
1298 return email
1299
1299
1300 @hybrid_property
1300 @hybrid_property
1301 def email(self):
1301 def email(self):
1302 return self._email
1302 return self._email
1303
1303
1304 @email.setter
1304 @email.setter
1305 def email(self, val):
1305 def email(self, val):
1306 self._email = val.lower() if val else None
1306 self._email = val.lower() if val else None
1307
1307
1308
1308
1309 class UserIpMap(Base, BaseModel):
1309 class UserIpMap(Base, BaseModel):
1310 __tablename__ = 'user_ip_map'
1310 __tablename__ = 'user_ip_map'
1311 __table_args__ = (
1311 __table_args__ = (
1312 UniqueConstraint('user_id', 'ip_addr'),
1312 UniqueConstraint('user_id', 'ip_addr'),
1313 base_table_args
1313 base_table_args
1314 )
1314 )
1315
1315
1316 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1316 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1317 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1317 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1318 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1318 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1319 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1319 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1320 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1320 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1321 user = relationship('User', lazy='joined', back_populates='user_ip_map')
1321 user = relationship('User', lazy='joined', back_populates='user_ip_map')
1322
1322
1323 @hybrid_property
1323 @hybrid_property
1324 def description_safe(self):
1324 def description_safe(self):
1325 from rhodecode.lib import helpers as h
1325 from rhodecode.lib import helpers as h
1326 return h.escape(self.description)
1326 return h.escape(self.description)
1327
1327
1328 @classmethod
1328 @classmethod
1329 def _get_ip_range(cls, ip_addr):
1329 def _get_ip_range(cls, ip_addr):
1330 net = ipaddress.ip_network(safe_str(ip_addr), strict=False)
1330 net = ipaddress.ip_network(safe_str(ip_addr), strict=False)
1331 return [str(net.network_address), str(net.broadcast_address)]
1331 return [str(net.network_address), str(net.broadcast_address)]
1332
1332
1333 def __json__(self):
1333 def __json__(self):
1334 return {
1334 return {
1335 'ip_addr': self.ip_addr,
1335 'ip_addr': self.ip_addr,
1336 'ip_range': self._get_ip_range(self.ip_addr),
1336 'ip_range': self._get_ip_range(self.ip_addr),
1337 }
1337 }
1338
1338
1339 def __repr__(self):
1339 def __repr__(self):
1340 return f"<{self.cls_name}('user_id={self.user_id} => ip={self.ip_addr}')>"
1340 return f"<{self.cls_name}('user_id={self.user_id} => ip={self.ip_addr}')>"
1341
1341
1342
1342
1343 class UserSshKeys(Base, BaseModel):
1343 class UserSshKeys(Base, BaseModel):
1344 __tablename__ = 'user_ssh_keys'
1344 __tablename__ = 'user_ssh_keys'
1345 __table_args__ = (
1345 __table_args__ = (
1346 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1346 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1347
1347
1348 UniqueConstraint('ssh_key_fingerprint'),
1348 UniqueConstraint('ssh_key_fingerprint'),
1349
1349
1350 base_table_args
1350 base_table_args
1351 )
1351 )
1352
1352
1353 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1353 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1354 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1354 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1355 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1355 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1356
1356
1357 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1357 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1358
1358
1359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1360 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1360 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1361 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1361 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1362
1362
1363 user = relationship('User', lazy='joined', back_populates='user_ssh_keys')
1363 user = relationship('User', lazy='joined', back_populates='user_ssh_keys')
1364
1364
1365 def __json__(self):
1365 def __json__(self):
1366 data = {
1366 data = {
1367 'ssh_fingerprint': self.ssh_key_fingerprint,
1367 'ssh_fingerprint': self.ssh_key_fingerprint,
1368 'description': self.description,
1368 'description': self.description,
1369 'created_on': self.created_on
1369 'created_on': self.created_on
1370 }
1370 }
1371 return data
1371 return data
1372
1372
1373 def get_api_data(self):
1373 def get_api_data(self):
1374 data = self.__json__()
1374 data = self.__json__()
1375 return data
1375 return data
1376
1376
1377
1377
1378 class UserLog(Base, BaseModel):
1378 class UserLog(Base, BaseModel):
1379 __tablename__ = 'user_logs'
1379 __tablename__ = 'user_logs'
1380 __table_args__ = (
1380 __table_args__ = (
1381 base_table_args,
1381 base_table_args,
1382 )
1382 )
1383
1383
1384 VERSION_1 = 'v1'
1384 VERSION_1 = 'v1'
1385 VERSION_2 = 'v2'
1385 VERSION_2 = 'v2'
1386 VERSIONS = [VERSION_1, VERSION_2]
1386 VERSIONS = [VERSION_1, VERSION_2]
1387
1387
1388 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1388 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1389 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1389 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1390 username = Column("username", String(255), nullable=True, unique=None, default=None)
1390 username = Column("username", String(255), nullable=True, unique=None, default=None)
1391 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1391 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1392 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1392 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1393 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1393 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1394 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1394 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1395 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1395 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1396
1396
1397 version = Column("version", String(255), nullable=True, default=VERSION_1)
1397 version = Column("version", String(255), nullable=True, default=VERSION_1)
1398 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1398 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1399 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1399 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1400 user = relationship('User', cascade='', back_populates='user_log')
1400 user = relationship('User', cascade='', back_populates='user_log')
1401 repository = relationship('Repository', cascade='', back_populates='logs')
1401 repository = relationship('Repository', cascade='', back_populates='logs')
1402
1402
1403 def __repr__(self):
1403 def __repr__(self):
1404 return f"<{self.cls_name}('id:{self.repository_name}:{self.action}')>"
1404 return f"<{self.cls_name}('id:{self.repository_name}:{self.action}')>"
1405
1405
1406 def __json__(self):
1406 def __json__(self):
1407 return {
1407 return {
1408 'user_id': self.user_id,
1408 'user_id': self.user_id,
1409 'username': self.username,
1409 'username': self.username,
1410 'repository_id': self.repository_id,
1410 'repository_id': self.repository_id,
1411 'repository_name': self.repository_name,
1411 'repository_name': self.repository_name,
1412 'user_ip': self.user_ip,
1412 'user_ip': self.user_ip,
1413 'action_date': self.action_date,
1413 'action_date': self.action_date,
1414 'action': self.action,
1414 'action': self.action,
1415 }
1415 }
1416
1416
1417 @hybrid_property
1417 @hybrid_property
1418 def entry_id(self):
1418 def entry_id(self):
1419 return self.user_log_id
1419 return self.user_log_id
1420
1420
1421 @property
1421 @property
1422 def action_as_day(self):
1422 def action_as_day(self):
1423 return datetime.date(*self.action_date.timetuple()[:3])
1423 return datetime.date(*self.action_date.timetuple()[:3])
1424
1424
1425
1425
1426 class UserGroup(Base, BaseModel):
1426 class UserGroup(Base, BaseModel):
1427 __tablename__ = 'users_groups'
1427 __tablename__ = 'users_groups'
1428 __table_args__ = (
1428 __table_args__ = (
1429 base_table_args,
1429 base_table_args,
1430 )
1430 )
1431
1431
1432 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1432 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1433 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1433 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1434 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1434 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1436 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1436 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1439 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1439 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1440
1440
1441 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined", back_populates='users_group')
1441 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined", back_populates='users_group')
1442 users_group_to_perm = relationship('UserGroupToPerm', cascade='all', back_populates='users_group')
1442 users_group_to_perm = relationship('UserGroupToPerm', cascade='all', back_populates='users_group')
1443 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='users_group')
1443 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='users_group')
1444 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='users_group')
1444 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='users_group')
1445 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all', back_populates='user_group')
1445 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all', back_populates='user_group')
1446
1446
1447 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all', back_populates='target_user_group')
1447 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all', back_populates='target_user_group')
1448
1448
1449 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all', back_populates='users_group')
1449 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all', back_populates='users_group')
1450 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id", back_populates='user_groups')
1450 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id", back_populates='user_groups')
1451
1451
1452 @classmethod
1452 @classmethod
1453 def _load_group_data(cls, column):
1453 def _load_group_data(cls, column):
1454 if not column:
1454 if not column:
1455 return {}
1455 return {}
1456
1456
1457 try:
1457 try:
1458 return json.loads(column) or {}
1458 return json.loads(column) or {}
1459 except TypeError:
1459 except TypeError:
1460 return {}
1460 return {}
1461
1461
1462 @hybrid_property
1462 @hybrid_property
1463 def description_safe(self):
1463 def description_safe(self):
1464 from rhodecode.lib import helpers as h
1464 from rhodecode.lib import helpers as h
1465 return h.escape(self.user_group_description)
1465 return h.escape(self.user_group_description)
1466
1466
1467 @hybrid_property
1467 @hybrid_property
1468 def group_data(self):
1468 def group_data(self):
1469 return self._load_group_data(self._group_data)
1469 return self._load_group_data(self._group_data)
1470
1470
1471 @group_data.expression
1471 @group_data.expression
1472 def group_data(self, **kwargs):
1472 def group_data(self, **kwargs):
1473 return self._group_data
1473 return self._group_data
1474
1474
1475 @group_data.setter
1475 @group_data.setter
1476 def group_data(self, val):
1476 def group_data(self, val):
1477 try:
1477 try:
1478 self._group_data = json.dumps(val)
1478 self._group_data = json.dumps(val)
1479 except Exception:
1479 except Exception:
1480 log.error(traceback.format_exc())
1480 log.error(traceback.format_exc())
1481
1481
1482 @classmethod
1482 @classmethod
1483 def _load_sync(cls, group_data):
1483 def _load_sync(cls, group_data):
1484 if group_data:
1484 if group_data:
1485 return group_data.get('extern_type')
1485 return group_data.get('extern_type')
1486
1486
1487 @property
1487 @property
1488 def sync(self):
1488 def sync(self):
1489 return self._load_sync(self.group_data)
1489 return self._load_sync(self.group_data)
1490
1490
1491 def __repr__(self):
1491 def __repr__(self):
1492 return f"<{self.cls_name}('id:{self.users_group_id}:{self.users_group_name}')>"
1492 return f"<{self.cls_name}('id:{self.users_group_id}:{self.users_group_name}')>"
1493
1493
1494 @classmethod
1494 @classmethod
1495 def get_by_group_name(cls, group_name, cache=False,
1495 def get_by_group_name(cls, group_name, cache=False,
1496 case_insensitive=False):
1496 case_insensitive=False):
1497 if case_insensitive:
1497 if case_insensitive:
1498 q = cls.query().filter(func.lower(cls.users_group_name) ==
1498 q = cls.query().filter(func.lower(cls.users_group_name) ==
1499 func.lower(group_name))
1499 func.lower(group_name))
1500
1500
1501 else:
1501 else:
1502 q = cls.query().filter(cls.users_group_name == group_name)
1502 q = cls.query().filter(cls.users_group_name == group_name)
1503 if cache:
1503 if cache:
1504 name_key = _hash_key(group_name)
1504 name_key = _hash_key(group_name)
1505 q = q.options(
1505 q = q.options(
1506 FromCache("sql_cache_short", f"get_group_{name_key}"))
1506 FromCache("sql_cache_short", f"get_group_{name_key}"))
1507 return q.scalar()
1507 return q.scalar()
1508
1508
1509 @classmethod
1509 @classmethod
1510 def get(cls, user_group_id, cache=False):
1510 def get(cls, user_group_id, cache=False):
1511 if not user_group_id:
1511 if not user_group_id:
1512 return
1512 return
1513
1513
1514 user_group = cls.query()
1514 user_group = cls.query()
1515 if cache:
1515 if cache:
1516 user_group = user_group.options(
1516 user_group = user_group.options(
1517 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1517 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1518 return user_group.get(user_group_id)
1518 return user_group.get(user_group_id)
1519
1519
1520 def permissions(self, with_admins=True, with_owner=True,
1520 def permissions(self, with_admins=True, with_owner=True,
1521 expand_from_user_groups=False):
1521 expand_from_user_groups=False):
1522 """
1522 """
1523 Permissions for user groups
1523 Permissions for user groups
1524 """
1524 """
1525 _admin_perm = 'usergroup.admin'
1525 _admin_perm = 'usergroup.admin'
1526
1526
1527 owner_row = []
1527 owner_row = []
1528 if with_owner:
1528 if with_owner:
1529 usr = AttributeDict(self.user.get_dict())
1529 usr = AttributeDict(self.user.get_dict())
1530 usr.owner_row = True
1530 usr.owner_row = True
1531 usr.permission = _admin_perm
1531 usr.permission = _admin_perm
1532 owner_row.append(usr)
1532 owner_row.append(usr)
1533
1533
1534 super_admin_ids = []
1534 super_admin_ids = []
1535 super_admin_rows = []
1535 super_admin_rows = []
1536 if with_admins:
1536 if with_admins:
1537 for usr in User.get_all_super_admins():
1537 for usr in User.get_all_super_admins():
1538 super_admin_ids.append(usr.user_id)
1538 super_admin_ids.append(usr.user_id)
1539 # if this admin is also owner, don't double the record
1539 # if this admin is also owner, don't double the record
1540 if usr.user_id == owner_row[0].user_id:
1540 if usr.user_id == owner_row[0].user_id:
1541 owner_row[0].admin_row = True
1541 owner_row[0].admin_row = True
1542 else:
1542 else:
1543 usr = AttributeDict(usr.get_dict())
1543 usr = AttributeDict(usr.get_dict())
1544 usr.admin_row = True
1544 usr.admin_row = True
1545 usr.permission = _admin_perm
1545 usr.permission = _admin_perm
1546 super_admin_rows.append(usr)
1546 super_admin_rows.append(usr)
1547
1547
1548 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1548 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1549 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1549 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1550 joinedload(UserUserGroupToPerm.user),
1550 joinedload(UserUserGroupToPerm.user),
1551 joinedload(UserUserGroupToPerm.permission),)
1551 joinedload(UserUserGroupToPerm.permission),)
1552
1552
1553 # get owners and admins and permissions. We do a trick of re-writing
1553 # get owners and admins and permissions. We do a trick of re-writing
1554 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1554 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1555 # has a global reference and changing one object propagates to all
1555 # has a global reference and changing one object propagates to all
1556 # others. This means if admin is also an owner admin_row that change
1556 # others. This means if admin is also an owner admin_row that change
1557 # would propagate to both objects
1557 # would propagate to both objects
1558 perm_rows = []
1558 perm_rows = []
1559 for _usr in q.all():
1559 for _usr in q.all():
1560 usr = AttributeDict(_usr.user.get_dict())
1560 usr = AttributeDict(_usr.user.get_dict())
1561 # if this user is also owner/admin, mark as duplicate record
1561 # if this user is also owner/admin, mark as duplicate record
1562 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1562 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1563 usr.duplicate_perm = True
1563 usr.duplicate_perm = True
1564 usr.permission = _usr.permission.permission_name
1564 usr.permission = _usr.permission.permission_name
1565 perm_rows.append(usr)
1565 perm_rows.append(usr)
1566
1566
1567 # filter the perm rows by 'default' first and then sort them by
1567 # filter the perm rows by 'default' first and then sort them by
1568 # admin,write,read,none permissions sorted again alphabetically in
1568 # admin,write,read,none permissions sorted again alphabetically in
1569 # each group
1569 # each group
1570 perm_rows = sorted(perm_rows, key=display_user_sort)
1570 perm_rows = sorted(perm_rows, key=display_user_sort)
1571
1571
1572 user_groups_rows = []
1572 user_groups_rows = []
1573 if expand_from_user_groups:
1573 if expand_from_user_groups:
1574 for ug in self.permission_user_groups(with_members=True):
1574 for ug in self.permission_user_groups(with_members=True):
1575 for user_data in ug.members:
1575 for user_data in ug.members:
1576 user_groups_rows.append(user_data)
1576 user_groups_rows.append(user_data)
1577
1577
1578 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1578 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1579
1579
1580 def permission_user_groups(self, with_members=False):
1580 def permission_user_groups(self, with_members=False):
1581 q = UserGroupUserGroupToPerm.query()\
1581 q = UserGroupUserGroupToPerm.query()\
1582 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1582 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1583 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1583 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1584 joinedload(UserGroupUserGroupToPerm.target_user_group),
1584 joinedload(UserGroupUserGroupToPerm.target_user_group),
1585 joinedload(UserGroupUserGroupToPerm.permission),)
1585 joinedload(UserGroupUserGroupToPerm.permission),)
1586
1586
1587 perm_rows = []
1587 perm_rows = []
1588 for _user_group in q.all():
1588 for _user_group in q.all():
1589 entry = AttributeDict(_user_group.user_group.get_dict())
1589 entry = AttributeDict(_user_group.user_group.get_dict())
1590 entry.permission = _user_group.permission.permission_name
1590 entry.permission = _user_group.permission.permission_name
1591 if with_members:
1591 if with_members:
1592 entry.members = [x.user.get_dict()
1592 entry.members = [x.user.get_dict()
1593 for x in _user_group.user_group.members]
1593 for x in _user_group.user_group.members]
1594 perm_rows.append(entry)
1594 perm_rows.append(entry)
1595
1595
1596 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1596 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1597 return perm_rows
1597 return perm_rows
1598
1598
1599 def _get_default_perms(self, user_group, suffix=''):
1599 def _get_default_perms(self, user_group, suffix=''):
1600 from rhodecode.model.permission import PermissionModel
1600 from rhodecode.model.permission import PermissionModel
1601 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1601 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1602
1602
1603 def get_default_perms(self, suffix=''):
1603 def get_default_perms(self, suffix=''):
1604 return self._get_default_perms(self, suffix)
1604 return self._get_default_perms(self, suffix)
1605
1605
1606 def get_api_data(self, with_group_members=True, include_secrets=False):
1606 def get_api_data(self, with_group_members=True, include_secrets=False):
1607 """
1607 """
1608 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1608 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1609 basically forwarded.
1609 basically forwarded.
1610
1610
1611 """
1611 """
1612 user_group = self
1612 user_group = self
1613 data = {
1613 data = {
1614 'users_group_id': user_group.users_group_id,
1614 'users_group_id': user_group.users_group_id,
1615 'group_name': user_group.users_group_name,
1615 'group_name': user_group.users_group_name,
1616 'group_description': user_group.user_group_description,
1616 'group_description': user_group.user_group_description,
1617 'active': user_group.users_group_active,
1617 'active': user_group.users_group_active,
1618 'owner': user_group.user.username,
1618 'owner': user_group.user.username,
1619 'sync': user_group.sync,
1619 'sync': user_group.sync,
1620 'owner_email': user_group.user.email,
1620 'owner_email': user_group.user.email,
1621 }
1621 }
1622
1622
1623 if with_group_members:
1623 if with_group_members:
1624 users = []
1624 users = []
1625 for user in user_group.members:
1625 for user in user_group.members:
1626 user = user.user
1626 user = user.user
1627 users.append(user.get_api_data(include_secrets=include_secrets))
1627 users.append(user.get_api_data(include_secrets=include_secrets))
1628 data['users'] = users
1628 data['users'] = users
1629
1629
1630 return data
1630 return data
1631
1631
1632
1632
1633 class UserGroupMember(Base, BaseModel):
1633 class UserGroupMember(Base, BaseModel):
1634 __tablename__ = 'users_groups_members'
1634 __tablename__ = 'users_groups_members'
1635 __table_args__ = (
1635 __table_args__ = (
1636 base_table_args,
1636 base_table_args,
1637 )
1637 )
1638
1638
1639 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1639 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1641 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1641 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1642
1642
1643 user = relationship('User', lazy='joined', back_populates='group_member')
1643 user = relationship('User', lazy='joined', back_populates='group_member')
1644 users_group = relationship('UserGroup', back_populates='members')
1644 users_group = relationship('UserGroup', back_populates='members')
1645
1645
1646 def __init__(self, gr_id='', u_id=''):
1646 def __init__(self, gr_id='', u_id=''):
1647 self.users_group_id = gr_id
1647 self.users_group_id = gr_id
1648 self.user_id = u_id
1648 self.user_id = u_id
1649
1649
1650
1650
1651 class RepositoryField(Base, BaseModel):
1651 class RepositoryField(Base, BaseModel):
1652 __tablename__ = 'repositories_fields'
1652 __tablename__ = 'repositories_fields'
1653 __table_args__ = (
1653 __table_args__ = (
1654 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1654 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1655 base_table_args,
1655 base_table_args,
1656 )
1656 )
1657
1657
1658 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1658 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1659
1659
1660 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1660 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1661 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1661 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1662 field_key = Column("field_key", String(250))
1662 field_key = Column("field_key", String(250))
1663 field_label = Column("field_label", String(1024), nullable=False)
1663 field_label = Column("field_label", String(1024), nullable=False)
1664 field_value = Column("field_value", String(10000), nullable=False)
1664 field_value = Column("field_value", String(10000), nullable=False)
1665 field_desc = Column("field_desc", String(1024), nullable=False)
1665 field_desc = Column("field_desc", String(1024), nullable=False)
1666 field_type = Column("field_type", String(255), nullable=False, unique=None)
1666 field_type = Column("field_type", String(255), nullable=False, unique=None)
1667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1668
1668
1669 repository = relationship('Repository', back_populates='extra_fields')
1669 repository = relationship('Repository', back_populates='extra_fields')
1670
1670
1671 @property
1671 @property
1672 def field_key_prefixed(self):
1672 def field_key_prefixed(self):
1673 return 'ex_%s' % self.field_key
1673 return 'ex_%s' % self.field_key
1674
1674
1675 @classmethod
1675 @classmethod
1676 def un_prefix_key(cls, key):
1676 def un_prefix_key(cls, key):
1677 if key.startswith(cls.PREFIX):
1677 if key.startswith(cls.PREFIX):
1678 return key[len(cls.PREFIX):]
1678 return key[len(cls.PREFIX):]
1679 return key
1679 return key
1680
1680
1681 @classmethod
1681 @classmethod
1682 def get_by_key_name(cls, key, repo):
1682 def get_by_key_name(cls, key, repo):
1683 row = cls.query()\
1683 row = cls.query()\
1684 .filter(cls.repository == repo)\
1684 .filter(cls.repository == repo)\
1685 .filter(cls.field_key == key).scalar()
1685 .filter(cls.field_key == key).scalar()
1686 return row
1686 return row
1687
1687
1688
1688
1689 class Repository(Base, BaseModel):
1689 class Repository(Base, BaseModel):
1690 __tablename__ = 'repositories'
1690 __tablename__ = 'repositories'
1691 __table_args__ = (
1691 __table_args__ = (
1692 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1692 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1693 base_table_args,
1693 base_table_args,
1694 )
1694 )
1695 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1695 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1696 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1696 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1697 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1697 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1698
1698
1699 STATE_CREATED = 'repo_state_created'
1699 STATE_CREATED = 'repo_state_created'
1700 STATE_PENDING = 'repo_state_pending'
1700 STATE_PENDING = 'repo_state_pending'
1701 STATE_ERROR = 'repo_state_error'
1701 STATE_ERROR = 'repo_state_error'
1702
1702
1703 LOCK_AUTOMATIC = 'lock_auto'
1703 LOCK_AUTOMATIC = 'lock_auto'
1704 LOCK_API = 'lock_api'
1704 LOCK_API = 'lock_api'
1705 LOCK_WEB = 'lock_web'
1705 LOCK_WEB = 'lock_web'
1706 LOCK_PULL = 'lock_pull'
1706 LOCK_PULL = 'lock_pull'
1707
1707
1708 NAME_SEP = URL_SEP
1708 NAME_SEP = URL_SEP
1709
1709
1710 repo_id = Column(
1710 repo_id = Column(
1711 "repo_id", Integer(), nullable=False, unique=True, default=None,
1711 "repo_id", Integer(), nullable=False, unique=True, default=None,
1712 primary_key=True)
1712 primary_key=True)
1713 _repo_name = Column(
1713 _repo_name = Column(
1714 "repo_name", Text(), nullable=False, default=None)
1714 "repo_name", Text(), nullable=False, default=None)
1715 repo_name_hash = Column(
1715 repo_name_hash = Column(
1716 "repo_name_hash", String(255), nullable=False, unique=True)
1716 "repo_name_hash", String(255), nullable=False, unique=True)
1717 repo_state = Column("repo_state", String(255), nullable=True)
1717 repo_state = Column("repo_state", String(255), nullable=True)
1718
1718
1719 clone_uri = Column(
1719 clone_uri = Column(
1720 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1720 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1721 default=None)
1721 default=None)
1722 push_uri = Column(
1722 push_uri = Column(
1723 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1723 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1724 default=None)
1724 default=None)
1725 repo_type = Column(
1725 repo_type = Column(
1726 "repo_type", String(255), nullable=False, unique=False, default=None)
1726 "repo_type", String(255), nullable=False, unique=False, default=None)
1727 user_id = Column(
1727 user_id = Column(
1728 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1728 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1729 unique=False, default=None)
1729 unique=False, default=None)
1730 private = Column(
1730 private = Column(
1731 "private", Boolean(), nullable=True, unique=None, default=None)
1731 "private", Boolean(), nullable=True, unique=None, default=None)
1732 archived = Column(
1732 archived = Column(
1733 "archived", Boolean(), nullable=True, unique=None, default=None)
1733 "archived", Boolean(), nullable=True, unique=None, default=None)
1734 enable_statistics = Column(
1734 enable_statistics = Column(
1735 "statistics", Boolean(), nullable=True, unique=None, default=True)
1735 "statistics", Boolean(), nullable=True, unique=None, default=True)
1736 enable_downloads = Column(
1736 enable_downloads = Column(
1737 "downloads", Boolean(), nullable=True, unique=None, default=True)
1737 "downloads", Boolean(), nullable=True, unique=None, default=True)
1738 description = Column(
1738 description = Column(
1739 "description", String(10000), nullable=True, unique=None, default=None)
1739 "description", String(10000), nullable=True, unique=None, default=None)
1740 created_on = Column(
1740 created_on = Column(
1741 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1741 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1742 default=datetime.datetime.now)
1742 default=datetime.datetime.now)
1743 updated_on = Column(
1743 updated_on = Column(
1744 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1744 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1745 default=datetime.datetime.now)
1745 default=datetime.datetime.now)
1746 _landing_revision = Column(
1746 _landing_revision = Column(
1747 "landing_revision", String(255), nullable=False, unique=False,
1747 "landing_revision", String(255), nullable=False, unique=False,
1748 default=None)
1748 default=None)
1749 enable_locking = Column(
1749 enable_locking = Column(
1750 "enable_locking", Boolean(), nullable=False, unique=None,
1750 "enable_locking", Boolean(), nullable=False, unique=None,
1751 default=False)
1751 default=False)
1752 _locked = Column(
1752 _locked = Column(
1753 "locked", String(255), nullable=True, unique=False, default=None)
1753 "locked", String(255), nullable=True, unique=False, default=None)
1754 _changeset_cache = Column(
1754 _changeset_cache = Column(
1755 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1755 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1756
1756
1757 fork_id = Column(
1757 fork_id = Column(
1758 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1758 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1759 nullable=True, unique=False, default=None)
1759 nullable=True, unique=False, default=None)
1760 group_id = Column(
1760 group_id = Column(
1761 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1761 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1762 unique=False, default=None)
1762 unique=False, default=None)
1763
1763
1764 user = relationship('User', lazy='joined', back_populates='repositories')
1764 user = relationship('User', lazy='joined', back_populates='repositories')
1765 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1765 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1766 group = relationship('RepoGroup', lazy='joined')
1766 group = relationship('RepoGroup', lazy='joined')
1767 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1767 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
1768 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='repository')
1768 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all', back_populates='repository')
1769 stats = relationship('Statistics', cascade='all', uselist=False)
1769 stats = relationship('Statistics', cascade='all', uselist=False)
1770
1770
1771 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all', back_populates='follows_repository')
1771 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all', back_populates='follows_repository')
1772 extra_fields = relationship('RepositoryField', cascade="all, delete-orphan", back_populates='repository')
1772 extra_fields = relationship('RepositoryField', cascade="all, delete-orphan", back_populates='repository')
1773
1773
1774 logs = relationship('UserLog', back_populates='repository')
1774 logs = relationship('UserLog', back_populates='repository')
1775
1775
1776 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='repo')
1776 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='repo')
1777
1777
1778 pull_requests_source = relationship(
1778 pull_requests_source = relationship(
1779 'PullRequest',
1779 'PullRequest',
1780 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1780 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1781 cascade="all, delete-orphan",
1781 cascade="all, delete-orphan",
1782 overlaps="source_repo"
1782 overlaps="source_repo"
1783 )
1783 )
1784 pull_requests_target = relationship(
1784 pull_requests_target = relationship(
1785 'PullRequest',
1785 'PullRequest',
1786 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1786 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1787 cascade="all, delete-orphan",
1787 cascade="all, delete-orphan",
1788 overlaps="target_repo"
1788 overlaps="target_repo"
1789 )
1789 )
1790
1790
1791 ui = relationship('RepoRhodeCodeUi', cascade="all")
1791 ui = relationship('RepoRhodeCodeUi', cascade="all")
1792 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1792 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1793 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo')
1793 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo')
1794
1794
1795 scoped_tokens = relationship('UserApiKeys', cascade="all", back_populates='repo')
1795 scoped_tokens = relationship('UserApiKeys', cascade="all", back_populates='repo')
1796
1796
1797 # no cascade, set NULL
1797 # no cascade, set NULL
1798 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id', viewonly=True)
1798 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id', viewonly=True)
1799
1799
1800 review_rules = relationship('RepoReviewRule')
1800 review_rules = relationship('RepoReviewRule')
1801 user_branch_perms = relationship('UserToRepoBranchPermission')
1801 user_branch_perms = relationship('UserToRepoBranchPermission')
1802 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission')
1802 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission')
1803
1803
1804 def __repr__(self):
1804 def __repr__(self):
1805 return "<%s('%s:%s')>" % (self.cls_name, self.repo_id, self.repo_name)
1805 return "<%s('%s:%s')>" % (self.cls_name, self.repo_id, self.repo_name)
1806
1806
1807 @hybrid_property
1807 @hybrid_property
1808 def description_safe(self):
1808 def description_safe(self):
1809 from rhodecode.lib import helpers as h
1809 from rhodecode.lib import helpers as h
1810 return h.escape(self.description)
1810 return h.escape(self.description)
1811
1811
1812 @hybrid_property
1812 @hybrid_property
1813 def landing_rev(self):
1813 def landing_rev(self):
1814 # always should return [rev_type, rev], e.g ['branch', 'master']
1814 # always should return [rev_type, rev], e.g ['branch', 'master']
1815 if self._landing_revision:
1815 if self._landing_revision:
1816 _rev_info = self._landing_revision.split(':')
1816 _rev_info = self._landing_revision.split(':')
1817 if len(_rev_info) < 2:
1817 if len(_rev_info) < 2:
1818 _rev_info.insert(0, 'rev')
1818 _rev_info.insert(0, 'rev')
1819 return [_rev_info[0], _rev_info[1]]
1819 return [_rev_info[0], _rev_info[1]]
1820 return [None, None]
1820 return [None, None]
1821
1821
1822 @property
1822 @property
1823 def landing_ref_type(self):
1823 def landing_ref_type(self):
1824 return self.landing_rev[0]
1824 return self.landing_rev[0]
1825
1825
1826 @property
1826 @property
1827 def landing_ref_name(self):
1827 def landing_ref_name(self):
1828 return self.landing_rev[1]
1828 return self.landing_rev[1]
1829
1829
1830 @landing_rev.setter
1830 @landing_rev.setter
1831 def landing_rev(self, val):
1831 def landing_rev(self, val):
1832 if ':' not in val:
1832 if ':' not in val:
1833 raise ValueError('value must be delimited with `:` and consist '
1833 raise ValueError('value must be delimited with `:` and consist '
1834 'of <rev_type>:<rev>, got %s instead' % val)
1834 'of <rev_type>:<rev>, got %s instead' % val)
1835 self._landing_revision = val
1835 self._landing_revision = val
1836
1836
1837 @hybrid_property
1837 @hybrid_property
1838 def locked(self):
1838 def locked(self):
1839 if self._locked:
1839 if self._locked:
1840 user_id, timelocked, reason = self._locked.split(':')
1840 user_id, timelocked, reason = self._locked.split(':')
1841 lock_values = int(user_id), timelocked, reason
1841 lock_values = int(user_id), timelocked, reason
1842 else:
1842 else:
1843 lock_values = [None, None, None]
1843 lock_values = [None, None, None]
1844 return lock_values
1844 return lock_values
1845
1845
1846 @locked.setter
1846 @locked.setter
1847 def locked(self, val):
1847 def locked(self, val):
1848 if val and isinstance(val, (list, tuple)):
1848 if val and isinstance(val, (list, tuple)):
1849 self._locked = ':'.join(map(str, val))
1849 self._locked = ':'.join(map(str, val))
1850 else:
1850 else:
1851 self._locked = None
1851 self._locked = None
1852
1852
1853 @classmethod
1853 @classmethod
1854 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1854 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1855 from rhodecode.lib.vcs.backends.base import EmptyCommit
1855 from rhodecode.lib.vcs.backends.base import EmptyCommit
1856 dummy = EmptyCommit().__json__()
1856 dummy = EmptyCommit().__json__()
1857 if not changeset_cache_raw:
1857 if not changeset_cache_raw:
1858 dummy['source_repo_id'] = repo_id
1858 dummy['source_repo_id'] = repo_id
1859 return json.loads(json.dumps(dummy))
1859 return json.loads(json.dumps(dummy))
1860
1860
1861 try:
1861 try:
1862 return json.loads(changeset_cache_raw)
1862 return json.loads(changeset_cache_raw)
1863 except TypeError:
1863 except TypeError:
1864 return dummy
1864 return dummy
1865 except Exception:
1865 except Exception:
1866 log.error(traceback.format_exc())
1866 log.error(traceback.format_exc())
1867 return dummy
1867 return dummy
1868
1868
1869 @hybrid_property
1869 @hybrid_property
1870 def changeset_cache(self):
1870 def changeset_cache(self):
1871 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1871 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1872
1872
1873 @changeset_cache.setter
1873 @changeset_cache.setter
1874 def changeset_cache(self, val):
1874 def changeset_cache(self, val):
1875 try:
1875 try:
1876 self._changeset_cache = json.dumps(val)
1876 self._changeset_cache = json.dumps(val)
1877 except Exception:
1877 except Exception:
1878 log.error(traceback.format_exc())
1878 log.error(traceback.format_exc())
1879
1879
1880 @hybrid_property
1880 @hybrid_property
1881 def repo_name(self):
1881 def repo_name(self):
1882 return self._repo_name
1882 return self._repo_name
1883
1883
1884 @repo_name.setter
1884 @repo_name.setter
1885 def repo_name(self, value):
1885 def repo_name(self, value):
1886 self._repo_name = value
1886 self._repo_name = value
1887 self.repo_name_hash = sha1(safe_bytes(value))
1887 self.repo_name_hash = sha1(safe_bytes(value))
1888
1888
1889 @classmethod
1889 @classmethod
1890 def normalize_repo_name(cls, repo_name):
1890 def normalize_repo_name(cls, repo_name):
1891 """
1891 """
1892 Normalizes os specific repo_name to the format internally stored inside
1892 Normalizes os specific repo_name to the format internally stored inside
1893 database using URL_SEP
1893 database using URL_SEP
1894
1894
1895 :param cls:
1895 :param cls:
1896 :param repo_name:
1896 :param repo_name:
1897 """
1897 """
1898 return cls.NAME_SEP.join(repo_name.split(os.sep))
1898 return cls.NAME_SEP.join(repo_name.split(os.sep))
1899
1899
1900 @classmethod
1900 @classmethod
1901 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1901 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1902 session = Session()
1902 session = Session()
1903 q = session.query(cls).filter(cls.repo_name == repo_name)
1903 q = session.query(cls).filter(cls.repo_name == repo_name)
1904
1904
1905 if cache:
1905 if cache:
1906 if identity_cache:
1906 if identity_cache:
1907 val = cls.identity_cache(session, 'repo_name', repo_name)
1907 val = cls.identity_cache(session, 'repo_name', repo_name)
1908 if val:
1908 if val:
1909 return val
1909 return val
1910 else:
1910 else:
1911 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1911 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1912 q = q.options(
1912 q = q.options(
1913 FromCache("sql_cache_short", cache_key))
1913 FromCache("sql_cache_short", cache_key))
1914
1914
1915 return q.scalar()
1915 return q.scalar()
1916
1916
1917 @classmethod
1917 @classmethod
1918 def get_by_id_or_repo_name(cls, repoid):
1918 def get_by_id_or_repo_name(cls, repoid):
1919 if isinstance(repoid, int):
1919 if isinstance(repoid, int):
1920 try:
1920 try:
1921 repo = cls.get(repoid)
1921 repo = cls.get(repoid)
1922 except ValueError:
1922 except ValueError:
1923 repo = None
1923 repo = None
1924 else:
1924 else:
1925 repo = cls.get_by_repo_name(repoid)
1925 repo = cls.get_by_repo_name(repoid)
1926 return repo
1926 return repo
1927
1927
1928 @classmethod
1928 @classmethod
1929 def get_by_full_path(cls, repo_full_path):
1929 def get_by_full_path(cls, repo_full_path):
1930 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1930 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1931 repo_name = cls.normalize_repo_name(repo_name)
1931 repo_name = cls.normalize_repo_name(repo_name)
1932 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1932 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1933
1933
1934 @classmethod
1934 @classmethod
1935 def get_repo_forks(cls, repo_id):
1935 def get_repo_forks(cls, repo_id):
1936 return cls.query().filter(Repository.fork_id == repo_id)
1936 return cls.query().filter(Repository.fork_id == repo_id)
1937
1937
1938 @classmethod
1938 @classmethod
1939 def base_path(cls):
1939 def base_path(cls):
1940 """
1940 """
1941 Returns base path when all repos are stored
1941 Returns base path when all repos are stored
1942
1942
1943 :param cls:
1943 :param cls:
1944 """
1944 """
1945 from rhodecode.lib.utils import get_rhodecode_base_path
1945 from rhodecode.lib.utils import get_rhodecode_base_path
1946 return get_rhodecode_base_path()
1946 return get_rhodecode_base_path()
1947
1947
1948 @classmethod
1948 @classmethod
1949 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1949 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1950 case_insensitive=True, archived=False):
1950 case_insensitive=True, archived=False):
1951 q = Repository.query()
1951 q = Repository.query()
1952
1952
1953 if not archived:
1953 if not archived:
1954 q = q.filter(Repository.archived.isnot(true()))
1954 q = q.filter(Repository.archived.isnot(true()))
1955
1955
1956 if not isinstance(user_id, Optional):
1956 if not isinstance(user_id, Optional):
1957 q = q.filter(Repository.user_id == user_id)
1957 q = q.filter(Repository.user_id == user_id)
1958
1958
1959 if not isinstance(group_id, Optional):
1959 if not isinstance(group_id, Optional):
1960 q = q.filter(Repository.group_id == group_id)
1960 q = q.filter(Repository.group_id == group_id)
1961
1961
1962 if case_insensitive:
1962 if case_insensitive:
1963 q = q.order_by(func.lower(Repository.repo_name))
1963 q = q.order_by(func.lower(Repository.repo_name))
1964 else:
1964 else:
1965 q = q.order_by(Repository.repo_name)
1965 q = q.order_by(Repository.repo_name)
1966
1966
1967 return q.all()
1967 return q.all()
1968
1968
1969 @property
1969 @property
1970 def repo_uid(self):
1970 def repo_uid(self):
1971 return '_{}'.format(self.repo_id)
1971 return '_{}'.format(self.repo_id)
1972
1972
1973 @property
1973 @property
1974 def forks(self):
1974 def forks(self):
1975 """
1975 """
1976 Return forks of this repo
1976 Return forks of this repo
1977 """
1977 """
1978 return Repository.get_repo_forks(self.repo_id)
1978 return Repository.get_repo_forks(self.repo_id)
1979
1979
1980 @property
1980 @property
1981 def parent(self):
1981 def parent(self):
1982 """
1982 """
1983 Returns fork parent
1983 Returns fork parent
1984 """
1984 """
1985 return self.fork
1985 return self.fork
1986
1986
1987 @property
1987 @property
1988 def just_name(self):
1988 def just_name(self):
1989 return self.repo_name.split(self.NAME_SEP)[-1]
1989 return self.repo_name.split(self.NAME_SEP)[-1]
1990
1990
1991 @property
1991 @property
1992 def groups_with_parents(self):
1992 def groups_with_parents(self):
1993 groups = []
1993 groups = []
1994 if self.group is None:
1994 if self.group is None:
1995 return groups
1995 return groups
1996
1996
1997 cur_gr = self.group
1997 cur_gr = self.group
1998 groups.insert(0, cur_gr)
1998 groups.insert(0, cur_gr)
1999 while 1:
1999 while 1:
2000 gr = getattr(cur_gr, 'parent_group', None)
2000 gr = getattr(cur_gr, 'parent_group', None)
2001 cur_gr = cur_gr.parent_group
2001 cur_gr = cur_gr.parent_group
2002 if gr is None:
2002 if gr is None:
2003 break
2003 break
2004 groups.insert(0, gr)
2004 groups.insert(0, gr)
2005
2005
2006 return groups
2006 return groups
2007
2007
2008 @property
2008 @property
2009 def groups_and_repo(self):
2009 def groups_and_repo(self):
2010 return self.groups_with_parents, self
2010 return self.groups_with_parents, self
2011
2011
2012 @LazyProperty
2012 @LazyProperty
2013 def repo_path(self):
2013 def repo_path(self):
2014 """
2014 """
2015 Returns base full path for that repository means where it actually
2015 Returns base full path for that repository means where it actually
2016 exists on a filesystem
2016 exists on a filesystem
2017 """
2017 """
2018 q = Session().query(RhodeCodeUi).filter(
2018 q = Session().query(RhodeCodeUi).filter(
2019 RhodeCodeUi.ui_key == self.NAME_SEP)
2019 RhodeCodeUi.ui_key == self.NAME_SEP)
2020 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2020 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2021 return q.one().ui_value
2021 return q.one().ui_value
2022
2022
2023 @property
2023 @property
2024 def repo_full_path(self):
2024 def repo_full_path(self):
2025 p = [self.repo_path]
2025 p = [self.repo_path]
2026 # we need to split the name by / since this is how we store the
2026 # we need to split the name by / since this is how we store the
2027 # names in the database, but that eventually needs to be converted
2027 # names in the database, but that eventually needs to be converted
2028 # into a valid system path
2028 # into a valid system path
2029 p += self.repo_name.split(self.NAME_SEP)
2029 p += self.repo_name.split(self.NAME_SEP)
2030 return os.path.join(*map(safe_str, p))
2030 return os.path.join(*map(safe_str, p))
2031
2031
2032 @property
2032 @property
2033 def cache_keys(self):
2033 def cache_keys(self):
2034 """
2034 """
2035 Returns associated cache keys for that repo
2035 Returns associated cache keys for that repo
2036 """
2036 """
2037 repo_namespace_key = CacheKey.REPO_INVALIDATION_NAMESPACE.format(repo_id=self.repo_id)
2037 repo_namespace_key = CacheKey.REPO_INVALIDATION_NAMESPACE.format(repo_id=self.repo_id)
2038 return CacheKey.query()\
2038 return CacheKey.query()\
2039 .filter(CacheKey.cache_key == repo_namespace_key)\
2039 .filter(CacheKey.cache_key == repo_namespace_key)\
2040 .order_by(CacheKey.cache_key)\
2040 .order_by(CacheKey.cache_key)\
2041 .all()
2041 .all()
2042
2042
2043 @property
2043 @property
2044 def cached_diffs_relative_dir(self):
2044 def cached_diffs_relative_dir(self):
2045 """
2045 """
2046 Return a relative to the repository store path of cached diffs
2046 Return a relative to the repository store path of cached diffs
2047 used for safe display for users, who shouldn't know the absolute store
2047 used for safe display for users, who shouldn't know the absolute store
2048 path
2048 path
2049 """
2049 """
2050 return os.path.join(
2050 return os.path.join(
2051 os.path.dirname(self.repo_name),
2051 os.path.dirname(self.repo_name),
2052 self.cached_diffs_dir.split(os.path.sep)[-1])
2052 self.cached_diffs_dir.split(os.path.sep)[-1])
2053
2053
2054 @property
2054 @property
2055 def cached_diffs_dir(self):
2055 def cached_diffs_dir(self):
2056 path = self.repo_full_path
2056 path = self.repo_full_path
2057 return os.path.join(
2057 return os.path.join(
2058 os.path.dirname(path),
2058 os.path.dirname(path),
2059 f'.__shadow_diff_cache_repo_{self.repo_id}')
2059 f'.__shadow_diff_cache_repo_{self.repo_id}')
2060
2060
2061 def cached_diffs(self):
2061 def cached_diffs(self):
2062 diff_cache_dir = self.cached_diffs_dir
2062 diff_cache_dir = self.cached_diffs_dir
2063 if os.path.isdir(diff_cache_dir):
2063 if os.path.isdir(diff_cache_dir):
2064 return os.listdir(diff_cache_dir)
2064 return os.listdir(diff_cache_dir)
2065 return []
2065 return []
2066
2066
2067 def shadow_repos(self):
2067 def shadow_repos(self):
2068 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2068 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2069 return [
2069 return [
2070 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2070 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2071 if x.startswith(shadow_repos_pattern)
2071 if x.startswith(shadow_repos_pattern)
2072 ]
2072 ]
2073
2073
2074 def get_new_name(self, repo_name):
2074 def get_new_name(self, repo_name):
2075 """
2075 """
2076 returns new full repository name based on assigned group and new new
2076 returns new full repository name based on assigned group and new new
2077
2077
2078 :param repo_name:
2078 :param repo_name:
2079 """
2079 """
2080 path_prefix = self.group.full_path_splitted if self.group else []
2080 path_prefix = self.group.full_path_splitted if self.group else []
2081 return self.NAME_SEP.join(path_prefix + [repo_name])
2081 return self.NAME_SEP.join(path_prefix + [repo_name])
2082
2082
2083 @property
2083 @property
2084 def _config(self):
2084 def _config(self):
2085 """
2085 """
2086 Returns db based config object.
2086 Returns db based config object.
2087 """
2087 """
2088 from rhodecode.lib.utils import make_db_config
2088 from rhodecode.lib.utils import make_db_config
2089 return make_db_config(clear_session=False, repo=self)
2089 return make_db_config(clear_session=False, repo=self)
2090
2090
2091 def permissions(self, with_admins=True, with_owner=True,
2091 def permissions(self, with_admins=True, with_owner=True,
2092 expand_from_user_groups=False):
2092 expand_from_user_groups=False):
2093 """
2093 """
2094 Permissions for repositories
2094 Permissions for repositories
2095 """
2095 """
2096 _admin_perm = 'repository.admin'
2096 _admin_perm = 'repository.admin'
2097
2097
2098 owner_row = []
2098 owner_row = []
2099 if with_owner:
2099 if with_owner:
2100 usr = AttributeDict(self.user.get_dict())
2100 usr = AttributeDict(self.user.get_dict())
2101 usr.owner_row = True
2101 usr.owner_row = True
2102 usr.permission = _admin_perm
2102 usr.permission = _admin_perm
2103 usr.permission_id = None
2103 usr.permission_id = None
2104 owner_row.append(usr)
2104 owner_row.append(usr)
2105
2105
2106 super_admin_ids = []
2106 super_admin_ids = []
2107 super_admin_rows = []
2107 super_admin_rows = []
2108 if with_admins:
2108 if with_admins:
2109 for usr in User.get_all_super_admins():
2109 for usr in User.get_all_super_admins():
2110 super_admin_ids.append(usr.user_id)
2110 super_admin_ids.append(usr.user_id)
2111 # if this admin is also owner, don't double the record
2111 # if this admin is also owner, don't double the record
2112 if usr.user_id == owner_row[0].user_id:
2112 if usr.user_id == owner_row[0].user_id:
2113 owner_row[0].admin_row = True
2113 owner_row[0].admin_row = True
2114 else:
2114 else:
2115 usr = AttributeDict(usr.get_dict())
2115 usr = AttributeDict(usr.get_dict())
2116 usr.admin_row = True
2116 usr.admin_row = True
2117 usr.permission = _admin_perm
2117 usr.permission = _admin_perm
2118 usr.permission_id = None
2118 usr.permission_id = None
2119 super_admin_rows.append(usr)
2119 super_admin_rows.append(usr)
2120
2120
2121 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2121 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2122 q = q.options(joinedload(UserRepoToPerm.repository),
2122 q = q.options(joinedload(UserRepoToPerm.repository),
2123 joinedload(UserRepoToPerm.user),
2123 joinedload(UserRepoToPerm.user),
2124 joinedload(UserRepoToPerm.permission),)
2124 joinedload(UserRepoToPerm.permission),)
2125
2125
2126 # get owners and admins and permissions. We do a trick of re-writing
2126 # get owners and admins and permissions. We do a trick of re-writing
2127 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2127 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2128 # has a global reference and changing one object propagates to all
2128 # has a global reference and changing one object propagates to all
2129 # others. This means if admin is also an owner admin_row that change
2129 # others. This means if admin is also an owner admin_row that change
2130 # would propagate to both objects
2130 # would propagate to both objects
2131 perm_rows = []
2131 perm_rows = []
2132 for _usr in q.all():
2132 for _usr in q.all():
2133 usr = AttributeDict(_usr.user.get_dict())
2133 usr = AttributeDict(_usr.user.get_dict())
2134 # if this user is also owner/admin, mark as duplicate record
2134 # if this user is also owner/admin, mark as duplicate record
2135 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2135 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2136 usr.duplicate_perm = True
2136 usr.duplicate_perm = True
2137 # also check if this permission is maybe used by branch_permissions
2137 # also check if this permission is maybe used by branch_permissions
2138 if _usr.branch_perm_entry:
2138 if _usr.branch_perm_entry:
2139 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2139 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2140
2140
2141 usr.permission = _usr.permission.permission_name
2141 usr.permission = _usr.permission.permission_name
2142 usr.permission_id = _usr.repo_to_perm_id
2142 usr.permission_id = _usr.repo_to_perm_id
2143 perm_rows.append(usr)
2143 perm_rows.append(usr)
2144
2144
2145 # filter the perm rows by 'default' first and then sort them by
2145 # filter the perm rows by 'default' first and then sort them by
2146 # admin,write,read,none permissions sorted again alphabetically in
2146 # admin,write,read,none permissions sorted again alphabetically in
2147 # each group
2147 # each group
2148 perm_rows = sorted(perm_rows, key=display_user_sort)
2148 perm_rows = sorted(perm_rows, key=display_user_sort)
2149
2149
2150 user_groups_rows = []
2150 user_groups_rows = []
2151 if expand_from_user_groups:
2151 if expand_from_user_groups:
2152 for ug in self.permission_user_groups(with_members=True):
2152 for ug in self.permission_user_groups(with_members=True):
2153 for user_data in ug.members:
2153 for user_data in ug.members:
2154 user_groups_rows.append(user_data)
2154 user_groups_rows.append(user_data)
2155
2155
2156 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2156 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2157
2157
2158 def permission_user_groups(self, with_members=True):
2158 def permission_user_groups(self, with_members=True):
2159 q = UserGroupRepoToPerm.query()\
2159 q = UserGroupRepoToPerm.query()\
2160 .filter(UserGroupRepoToPerm.repository == self)
2160 .filter(UserGroupRepoToPerm.repository == self)
2161 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2161 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2162 joinedload(UserGroupRepoToPerm.users_group),
2162 joinedload(UserGroupRepoToPerm.users_group),
2163 joinedload(UserGroupRepoToPerm.permission),)
2163 joinedload(UserGroupRepoToPerm.permission),)
2164
2164
2165 perm_rows = []
2165 perm_rows = []
2166 for _user_group in q.all():
2166 for _user_group in q.all():
2167 entry = AttributeDict(_user_group.users_group.get_dict())
2167 entry = AttributeDict(_user_group.users_group.get_dict())
2168 entry.permission = _user_group.permission.permission_name
2168 entry.permission = _user_group.permission.permission_name
2169 if with_members:
2169 if with_members:
2170 entry.members = [x.user.get_dict()
2170 entry.members = [x.user.get_dict()
2171 for x in _user_group.users_group.members]
2171 for x in _user_group.users_group.members]
2172 perm_rows.append(entry)
2172 perm_rows.append(entry)
2173
2173
2174 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2174 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2175 return perm_rows
2175 return perm_rows
2176
2176
2177 def get_api_data(self, include_secrets=False):
2177 def get_api_data(self, include_secrets=False):
2178 """
2178 """
2179 Common function for generating repo api data
2179 Common function for generating repo api data
2180
2180
2181 :param include_secrets: See :meth:`User.get_api_data`.
2181 :param include_secrets: See :meth:`User.get_api_data`.
2182
2182
2183 """
2183 """
2184 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2184 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2185 # move this methods on models level.
2185 # move this methods on models level.
2186 from rhodecode.model.settings import SettingsModel
2186 from rhodecode.model.settings import SettingsModel
2187 from rhodecode.model.repo import RepoModel
2187 from rhodecode.model.repo import RepoModel
2188
2188
2189 repo = self
2189 repo = self
2190 _user_id, _time, _reason = self.locked
2190 _user_id, _time, _reason = self.locked
2191
2191
2192 data = {
2192 data = {
2193 'repo_id': repo.repo_id,
2193 'repo_id': repo.repo_id,
2194 'repo_name': repo.repo_name,
2194 'repo_name': repo.repo_name,
2195 'repo_type': repo.repo_type,
2195 'repo_type': repo.repo_type,
2196 'clone_uri': repo.clone_uri or '',
2196 'clone_uri': repo.clone_uri or '',
2197 'push_uri': repo.push_uri or '',
2197 'push_uri': repo.push_uri or '',
2198 'url': RepoModel().get_url(self),
2198 'url': RepoModel().get_url(self),
2199 'private': repo.private,
2199 'private': repo.private,
2200 'created_on': repo.created_on,
2200 'created_on': repo.created_on,
2201 'description': repo.description_safe,
2201 'description': repo.description_safe,
2202 'landing_rev': repo.landing_rev,
2202 'landing_rev': repo.landing_rev,
2203 'owner': repo.user.username,
2203 'owner': repo.user.username,
2204 'fork_of': repo.fork.repo_name if repo.fork else None,
2204 'fork_of': repo.fork.repo_name if repo.fork else None,
2205 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2205 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2206 'enable_statistics': repo.enable_statistics,
2206 'enable_statistics': repo.enable_statistics,
2207 'enable_locking': repo.enable_locking,
2207 'enable_locking': repo.enable_locking,
2208 'enable_downloads': repo.enable_downloads,
2208 'enable_downloads': repo.enable_downloads,
2209 'last_changeset': repo.changeset_cache,
2209 'last_changeset': repo.changeset_cache,
2210 'locked_by': User.get(_user_id).get_api_data(
2210 'locked_by': User.get(_user_id).get_api_data(
2211 include_secrets=include_secrets) if _user_id else None,
2211 include_secrets=include_secrets) if _user_id else None,
2212 'locked_date': time_to_datetime(_time) if _time else None,
2212 'locked_date': time_to_datetime(_time) if _time else None,
2213 'lock_reason': _reason if _reason else None,
2213 'lock_reason': _reason if _reason else None,
2214 }
2214 }
2215
2215
2216 # TODO: mikhail: should be per-repo settings here
2216 # TODO: mikhail: should be per-repo settings here
2217 rc_config = SettingsModel().get_all_settings()
2217 rc_config = SettingsModel().get_all_settings()
2218 repository_fields = str2bool(
2218 repository_fields = str2bool(
2219 rc_config.get('rhodecode_repository_fields'))
2219 rc_config.get('rhodecode_repository_fields'))
2220 if repository_fields:
2220 if repository_fields:
2221 for f in self.extra_fields:
2221 for f in self.extra_fields:
2222 data[f.field_key_prefixed] = f.field_value
2222 data[f.field_key_prefixed] = f.field_value
2223
2223
2224 return data
2224 return data
2225
2225
2226 @classmethod
2226 @classmethod
2227 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2227 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2228 if not lock_time:
2228 if not lock_time:
2229 lock_time = time.time()
2229 lock_time = time.time()
2230 if not lock_reason:
2230 if not lock_reason:
2231 lock_reason = cls.LOCK_AUTOMATIC
2231 lock_reason = cls.LOCK_AUTOMATIC
2232 repo.locked = [user_id, lock_time, lock_reason]
2232 repo.locked = [user_id, lock_time, lock_reason]
2233 Session().add(repo)
2233 Session().add(repo)
2234 Session().commit()
2234 Session().commit()
2235
2235
2236 @classmethod
2236 @classmethod
2237 def unlock(cls, repo):
2237 def unlock(cls, repo):
2238 repo.locked = None
2238 repo.locked = None
2239 Session().add(repo)
2239 Session().add(repo)
2240 Session().commit()
2240 Session().commit()
2241
2241
2242 @classmethod
2242 @classmethod
2243 def getlock(cls, repo):
2243 def getlock(cls, repo):
2244 return repo.locked
2244 return repo.locked
2245
2245
2246 def get_locking_state(self, action, user_id, only_when_enabled=True):
2246 def get_locking_state(self, action, user_id, only_when_enabled=True):
2247 """
2247 """
2248 Checks locking on this repository, if locking is enabled and lock is
2248 Checks locking on this repository, if locking is enabled and lock is
2249 present returns a tuple of make_lock, locked, locked_by.
2249 present returns a tuple of make_lock, locked, locked_by.
2250 make_lock can have 3 states None (do nothing) True, make lock
2250 make_lock can have 3 states None (do nothing) True, make lock
2251 False release lock, This value is later propagated to hooks, which
2251 False release lock, This value is later propagated to hooks, which
2252 do the locking. Think about this as signals passed to hooks what to do.
2252 do the locking. Think about this as signals passed to hooks what to do.
2253
2253
2254 """
2254 """
2255 # TODO: johbo: This is part of the business logic and should be moved
2255 # TODO: johbo: This is part of the business logic and should be moved
2256 # into the RepositoryModel.
2256 # into the RepositoryModel.
2257
2257
2258 if action not in ('push', 'pull'):
2258 if action not in ('push', 'pull'):
2259 raise ValueError("Invalid action value: %s" % repr(action))
2259 raise ValueError("Invalid action value: %s" % repr(action))
2260
2260
2261 # defines if locked error should be thrown to user
2261 # defines if locked error should be thrown to user
2262 currently_locked = False
2262 currently_locked = False
2263 # defines if new lock should be made, tri-state
2263 # defines if new lock should be made, tri-state
2264 make_lock = None
2264 make_lock = None
2265 repo = self
2265 repo = self
2266 user = User.get(user_id)
2266 user = User.get(user_id)
2267
2267
2268 lock_info = repo.locked
2268 lock_info = repo.locked
2269
2269
2270 if repo and (repo.enable_locking or not only_when_enabled):
2270 if repo and (repo.enable_locking or not only_when_enabled):
2271 if action == 'push':
2271 if action == 'push':
2272 # check if it's already locked !, if it is compare users
2272 # check if it's already locked !, if it is compare users
2273 locked_by_user_id = lock_info[0]
2273 locked_by_user_id = lock_info[0]
2274 if user.user_id == locked_by_user_id:
2274 if user.user_id == locked_by_user_id:
2275 log.debug(
2275 log.debug(
2276 'Got `push` action from user %s, now unlocking', user)
2276 'Got `push` action from user %s, now unlocking', user)
2277 # unlock if we have push from user who locked
2277 # unlock if we have push from user who locked
2278 make_lock = False
2278 make_lock = False
2279 else:
2279 else:
2280 # we're not the same user who locked, ban with
2280 # we're not the same user who locked, ban with
2281 # code defined in settings (default is 423 HTTP Locked) !
2281 # code defined in settings (default is 423 HTTP Locked) !
2282 log.debug('Repo %s is currently locked by %s', repo, user)
2282 log.debug('Repo %s is currently locked by %s', repo, user)
2283 currently_locked = True
2283 currently_locked = True
2284 elif action == 'pull':
2284 elif action == 'pull':
2285 # [0] user [1] date
2285 # [0] user [1] date
2286 if lock_info[0] and lock_info[1]:
2286 if lock_info[0] and lock_info[1]:
2287 log.debug('Repo %s is currently locked by %s', repo, user)
2287 log.debug('Repo %s is currently locked by %s', repo, user)
2288 currently_locked = True
2288 currently_locked = True
2289 else:
2289 else:
2290 log.debug('Setting lock on repo %s by %s', repo, user)
2290 log.debug('Setting lock on repo %s by %s', repo, user)
2291 make_lock = True
2291 make_lock = True
2292
2292
2293 else:
2293 else:
2294 log.debug('Repository %s do not have locking enabled', repo)
2294 log.debug('Repository %s do not have locking enabled', repo)
2295
2295
2296 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2296 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2297 make_lock, currently_locked, lock_info)
2297 make_lock, currently_locked, lock_info)
2298
2298
2299 from rhodecode.lib.auth import HasRepoPermissionAny
2299 from rhodecode.lib.auth import HasRepoPermissionAny
2300 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2300 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2301 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2301 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2302 # if we don't have at least write permission we cannot make a lock
2302 # if we don't have at least write permission we cannot make a lock
2303 log.debug('lock state reset back to FALSE due to lack '
2303 log.debug('lock state reset back to FALSE due to lack '
2304 'of at least read permission')
2304 'of at least read permission')
2305 make_lock = False
2305 make_lock = False
2306
2306
2307 return make_lock, currently_locked, lock_info
2307 return make_lock, currently_locked, lock_info
2308
2308
2309 @property
2309 @property
2310 def last_commit_cache_update_diff(self):
2310 def last_commit_cache_update_diff(self):
2311 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2311 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2312
2312
2313 @classmethod
2313 @classmethod
2314 def _load_commit_change(cls, last_commit_cache):
2314 def _load_commit_change(cls, last_commit_cache):
2315 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2315 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2316 empty_date = datetime.datetime.fromtimestamp(0)
2316 empty_date = datetime.datetime.fromtimestamp(0)
2317 date_latest = last_commit_cache.get('date', empty_date)
2317 date_latest = last_commit_cache.get('date', empty_date)
2318 try:
2318 try:
2319 return parse_datetime(date_latest)
2319 return parse_datetime(date_latest)
2320 except Exception:
2320 except Exception:
2321 return empty_date
2321 return empty_date
2322
2322
2323 @property
2323 @property
2324 def last_commit_change(self):
2324 def last_commit_change(self):
2325 return self._load_commit_change(self.changeset_cache)
2325 return self._load_commit_change(self.changeset_cache)
2326
2326
2327 @property
2327 @property
2328 def last_db_change(self):
2328 def last_db_change(self):
2329 return self.updated_on
2329 return self.updated_on
2330
2330
2331 @property
2331 @property
2332 def clone_uri_hidden(self):
2332 def clone_uri_hidden(self):
2333 clone_uri = self.clone_uri
2333 clone_uri = self.clone_uri
2334 if clone_uri:
2334 if clone_uri:
2335 import urlobject
2335 import urlobject
2336 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2336 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2337 if url_obj.password:
2337 if url_obj.password:
2338 clone_uri = url_obj.with_password('*****')
2338 clone_uri = url_obj.with_password('*****')
2339 return clone_uri
2339 return clone_uri
2340
2340
2341 @property
2341 @property
2342 def push_uri_hidden(self):
2342 def push_uri_hidden(self):
2343 push_uri = self.push_uri
2343 push_uri = self.push_uri
2344 if push_uri:
2344 if push_uri:
2345 import urlobject
2345 import urlobject
2346 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2346 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2347 if url_obj.password:
2347 if url_obj.password:
2348 push_uri = url_obj.with_password('*****')
2348 push_uri = url_obj.with_password('*****')
2349 return push_uri
2349 return push_uri
2350
2350
2351 def clone_url(self, **override):
2351 def clone_url(self, **override):
2352 from rhodecode.model.settings import SettingsModel
2352 from rhodecode.model.settings import SettingsModel
2353
2353
2354 uri_tmpl = None
2354 uri_tmpl = None
2355 if 'with_id' in override:
2355 if 'with_id' in override:
2356 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2356 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2357 del override['with_id']
2357 del override['with_id']
2358
2358
2359 if 'uri_tmpl' in override:
2359 if 'uri_tmpl' in override:
2360 uri_tmpl = override['uri_tmpl']
2360 uri_tmpl = override['uri_tmpl']
2361 del override['uri_tmpl']
2361 del override['uri_tmpl']
2362
2362
2363 ssh = False
2363 ssh = False
2364 if 'ssh' in override:
2364 if 'ssh' in override:
2365 ssh = True
2365 ssh = True
2366 del override['ssh']
2366 del override['ssh']
2367
2367
2368 # we didn't override our tmpl from **overrides
2368 # we didn't override our tmpl from **overrides
2369 request = get_current_request()
2369 request = get_current_request()
2370 if not uri_tmpl:
2370 if not uri_tmpl:
2371 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2371 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2372 rc_config = request.call_context.rc_config
2372 rc_config = request.call_context.rc_config
2373 else:
2373 else:
2374 rc_config = SettingsModel().get_all_settings(cache=True)
2374 rc_config = SettingsModel().get_all_settings(cache=True)
2375
2375
2376 if ssh:
2376 if ssh:
2377 uri_tmpl = rc_config.get(
2377 uri_tmpl = rc_config.get(
2378 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2378 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2379
2379
2380 else:
2380 else:
2381 uri_tmpl = rc_config.get(
2381 uri_tmpl = rc_config.get(
2382 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2382 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2383
2383
2384 return get_clone_url(request=request,
2384 return get_clone_url(request=request,
2385 uri_tmpl=uri_tmpl,
2385 uri_tmpl=uri_tmpl,
2386 repo_name=self.repo_name,
2386 repo_name=self.repo_name,
2387 repo_id=self.repo_id,
2387 repo_id=self.repo_id,
2388 repo_type=self.repo_type,
2388 repo_type=self.repo_type,
2389 **override)
2389 **override)
2390
2390
2391 def set_state(self, state):
2391 def set_state(self, state):
2392 self.repo_state = state
2392 self.repo_state = state
2393 Session().add(self)
2393 Session().add(self)
2394 #==========================================================================
2394 #==========================================================================
2395 # SCM PROPERTIES
2395 # SCM PROPERTIES
2396 #==========================================================================
2396 #==========================================================================
2397
2397
2398 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2398 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2399 return get_commit_safe(
2399 return get_commit_safe(
2400 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2400 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2401 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2401 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2402
2402
2403 def get_changeset(self, rev=None, pre_load=None):
2403 def get_changeset(self, rev=None, pre_load=None):
2404 warnings.warn("Use get_commit", DeprecationWarning)
2404 warnings.warn("Use get_commit", DeprecationWarning)
2405 commit_id = None
2405 commit_id = None
2406 commit_idx = None
2406 commit_idx = None
2407 if isinstance(rev, str):
2407 if isinstance(rev, str):
2408 commit_id = rev
2408 commit_id = rev
2409 else:
2409 else:
2410 commit_idx = rev
2410 commit_idx = rev
2411 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2411 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2412 pre_load=pre_load)
2412 pre_load=pre_load)
2413
2413
2414 def get_landing_commit(self):
2414 def get_landing_commit(self):
2415 """
2415 """
2416 Returns landing commit, or if that doesn't exist returns the tip
2416 Returns landing commit, or if that doesn't exist returns the tip
2417 """
2417 """
2418 _rev_type, _rev = self.landing_rev
2418 _rev_type, _rev = self.landing_rev
2419 commit = self.get_commit(_rev)
2419 commit = self.get_commit(_rev)
2420 if isinstance(commit, EmptyCommit):
2420 if isinstance(commit, EmptyCommit):
2421 return self.get_commit()
2421 return self.get_commit()
2422 return commit
2422 return commit
2423
2423
2424 def flush_commit_cache(self):
2424 def flush_commit_cache(self):
2425 self.update_commit_cache(cs_cache={'raw_id':'0'})
2425 self.update_commit_cache(cs_cache={'raw_id':'0'})
2426 self.update_commit_cache()
2426 self.update_commit_cache()
2427
2427
2428 def update_commit_cache(self, cs_cache=None, config=None):
2428 def update_commit_cache(self, cs_cache=None, config=None):
2429 """
2429 """
2430 Update cache of last commit for repository
2430 Update cache of last commit for repository
2431 cache_keys should be::
2431 cache_keys should be::
2432
2432
2433 source_repo_id
2433 source_repo_id
2434 short_id
2434 short_id
2435 raw_id
2435 raw_id
2436 revision
2436 revision
2437 parents
2437 parents
2438 message
2438 message
2439 date
2439 date
2440 author
2440 author
2441 updated_on
2441 updated_on
2442
2442
2443 """
2443 """
2444 from rhodecode.lib.vcs.backends.base import BaseCommit
2444 from rhodecode.lib.vcs.backends.base import BaseCommit
2445 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2445 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2446 empty_date = datetime.datetime.fromtimestamp(0)
2446 empty_date = datetime.datetime.fromtimestamp(0)
2447 repo_commit_count = 0
2447 repo_commit_count = 0
2448
2448
2449 if cs_cache is None:
2449 if cs_cache is None:
2450 # use no-cache version here
2450 # use no-cache version here
2451 try:
2451 try:
2452 scm_repo = self.scm_instance(cache=False, config=config)
2452 scm_repo = self.scm_instance(cache=False, config=config)
2453 except VCSError:
2453 except VCSError:
2454 scm_repo = None
2454 scm_repo = None
2455 empty = scm_repo is None or scm_repo.is_empty()
2455 empty = scm_repo is None or scm_repo.is_empty()
2456
2456
2457 if not empty:
2457 if not empty:
2458 cs_cache = scm_repo.get_commit(
2458 cs_cache = scm_repo.get_commit(
2459 pre_load=["author", "date", "message", "parents", "branch"])
2459 pre_load=["author", "date", "message", "parents", "branch"])
2460 repo_commit_count = scm_repo.count()
2460 repo_commit_count = scm_repo.count()
2461 else:
2461 else:
2462 cs_cache = EmptyCommit()
2462 cs_cache = EmptyCommit()
2463
2463
2464 if isinstance(cs_cache, BaseCommit):
2464 if isinstance(cs_cache, BaseCommit):
2465 cs_cache = cs_cache.__json__()
2465 cs_cache = cs_cache.__json__()
2466
2466
2467 def is_outdated(new_cs_cache):
2467 def is_outdated(new_cs_cache):
2468 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2468 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2469 new_cs_cache['revision'] != self.changeset_cache['revision']):
2469 new_cs_cache['revision'] != self.changeset_cache['revision']):
2470 return True
2470 return True
2471 return False
2471 return False
2472
2472
2473 # check if we have maybe already latest cached revision
2473 # check if we have maybe already latest cached revision
2474 if is_outdated(cs_cache) or not self.changeset_cache:
2474 if is_outdated(cs_cache) or not self.changeset_cache:
2475 _current_datetime = datetime.datetime.utcnow()
2475 _current_datetime = datetime.datetime.utcnow()
2476 last_change = cs_cache.get('date') or _current_datetime
2476 last_change = cs_cache.get('date') or _current_datetime
2477 # we check if last update is newer than the new value
2477 # we check if last update is newer than the new value
2478 # if yes, we use the current timestamp instead. Imagine you get
2478 # if yes, we use the current timestamp instead. Imagine you get
2479 # old commit pushed 1y ago, we'd set last update 1y to ago.
2479 # old commit pushed 1y ago, we'd set last update 1y to ago.
2480 last_change_timestamp = datetime_to_time(last_change)
2480 last_change_timestamp = datetime_to_time(last_change)
2481 current_timestamp = datetime_to_time(last_change)
2481 current_timestamp = datetime_to_time(last_change)
2482 if last_change_timestamp > current_timestamp and not empty:
2482 if last_change_timestamp > current_timestamp and not empty:
2483 cs_cache['date'] = _current_datetime
2483 cs_cache['date'] = _current_datetime
2484
2484
2485 # also store size of repo
2485 # also store size of repo
2486 cs_cache['repo_commit_count'] = repo_commit_count
2486 cs_cache['repo_commit_count'] = repo_commit_count
2487
2487
2488 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2488 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2489 cs_cache['updated_on'] = time.time()
2489 cs_cache['updated_on'] = time.time()
2490 self.changeset_cache = cs_cache
2490 self.changeset_cache = cs_cache
2491 self.updated_on = last_change
2491 self.updated_on = last_change
2492 Session().add(self)
2492 Session().add(self)
2493 Session().commit()
2493 Session().commit()
2494
2494
2495 else:
2495 else:
2496 if empty:
2496 if empty:
2497 cs_cache = EmptyCommit().__json__()
2497 cs_cache = EmptyCommit().__json__()
2498 else:
2498 else:
2499 cs_cache = self.changeset_cache
2499 cs_cache = self.changeset_cache
2500
2500
2501 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2501 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2502
2502
2503 cs_cache['updated_on'] = time.time()
2503 cs_cache['updated_on'] = time.time()
2504 self.changeset_cache = cs_cache
2504 self.changeset_cache = cs_cache
2505 self.updated_on = _date_latest
2505 self.updated_on = _date_latest
2506 Session().add(self)
2506 Session().add(self)
2507 Session().commit()
2507 Session().commit()
2508
2508
2509 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2509 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2510 self.repo_name, cs_cache, _date_latest)
2510 self.repo_name, cs_cache, _date_latest)
2511
2511
2512 @property
2512 @property
2513 def tip(self):
2513 def tip(self):
2514 return self.get_commit('tip')
2514 return self.get_commit('tip')
2515
2515
2516 @property
2516 @property
2517 def author(self):
2517 def author(self):
2518 return self.tip.author
2518 return self.tip.author
2519
2519
2520 @property
2520 @property
2521 def last_change(self):
2521 def last_change(self):
2522 return self.scm_instance().last_change
2522 return self.scm_instance().last_change
2523
2523
2524 def get_comments(self, revisions=None):
2524 def get_comments(self, revisions=None):
2525 """
2525 """
2526 Returns comments for this repository grouped by revisions
2526 Returns comments for this repository grouped by revisions
2527
2527
2528 :param revisions: filter query by revisions only
2528 :param revisions: filter query by revisions only
2529 """
2529 """
2530 cmts = ChangesetComment.query()\
2530 cmts = ChangesetComment.query()\
2531 .filter(ChangesetComment.repo == self)
2531 .filter(ChangesetComment.repo == self)
2532 if revisions:
2532 if revisions:
2533 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2533 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2534 grouped = collections.defaultdict(list)
2534 grouped = collections.defaultdict(list)
2535 for cmt in cmts.all():
2535 for cmt in cmts.all():
2536 grouped[cmt.revision].append(cmt)
2536 grouped[cmt.revision].append(cmt)
2537 return grouped
2537 return grouped
2538
2538
2539 def statuses(self, revisions=None):
2539 def statuses(self, revisions=None):
2540 """
2540 """
2541 Returns statuses for this repository
2541 Returns statuses for this repository
2542
2542
2543 :param revisions: list of revisions to get statuses for
2543 :param revisions: list of revisions to get statuses for
2544 """
2544 """
2545 statuses = ChangesetStatus.query()\
2545 statuses = ChangesetStatus.query()\
2546 .filter(ChangesetStatus.repo == self)\
2546 .filter(ChangesetStatus.repo == self)\
2547 .filter(ChangesetStatus.version == 0)
2547 .filter(ChangesetStatus.version == 0)
2548
2548
2549 if revisions:
2549 if revisions:
2550 # Try doing the filtering in chunks to avoid hitting limits
2550 # Try doing the filtering in chunks to avoid hitting limits
2551 size = 500
2551 size = 500
2552 status_results = []
2552 status_results = []
2553 for chunk in range(0, len(revisions), size):
2553 for chunk in range(0, len(revisions), size):
2554 status_results += statuses.filter(
2554 status_results += statuses.filter(
2555 ChangesetStatus.revision.in_(
2555 ChangesetStatus.revision.in_(
2556 revisions[chunk: chunk+size])
2556 revisions[chunk: chunk+size])
2557 ).all()
2557 ).all()
2558 else:
2558 else:
2559 status_results = statuses.all()
2559 status_results = statuses.all()
2560
2560
2561 grouped = {}
2561 grouped = {}
2562
2562
2563 # maybe we have open new pullrequest without a status?
2563 # maybe we have open new pullrequest without a status?
2564 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2564 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2565 status_lbl = ChangesetStatus.get_status_lbl(stat)
2565 status_lbl = ChangesetStatus.get_status_lbl(stat)
2566 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2566 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2567 for rev in pr.revisions:
2567 for rev in pr.revisions:
2568 pr_id = pr.pull_request_id
2568 pr_id = pr.pull_request_id
2569 pr_repo = pr.target_repo.repo_name
2569 pr_repo = pr.target_repo.repo_name
2570 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2570 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2571
2571
2572 for stat in status_results:
2572 for stat in status_results:
2573 pr_id = pr_repo = None
2573 pr_id = pr_repo = None
2574 if stat.pull_request:
2574 if stat.pull_request:
2575 pr_id = stat.pull_request.pull_request_id
2575 pr_id = stat.pull_request.pull_request_id
2576 pr_repo = stat.pull_request.target_repo.repo_name
2576 pr_repo = stat.pull_request.target_repo.repo_name
2577 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2577 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2578 pr_id, pr_repo]
2578 pr_id, pr_repo]
2579 return grouped
2579 return grouped
2580
2580
2581 # ==========================================================================
2581 # ==========================================================================
2582 # SCM CACHE INSTANCE
2582 # SCM CACHE INSTANCE
2583 # ==========================================================================
2583 # ==========================================================================
2584
2584
2585 def scm_instance(self, **kwargs):
2585 def scm_instance(self, **kwargs):
2586 import rhodecode
2586 import rhodecode
2587
2587
2588 # Passing a config will not hit the cache currently only used
2588 # Passing a config will not hit the cache currently only used
2589 # for repo2dbmapper
2589 # for repo2dbmapper
2590 config = kwargs.pop('config', None)
2590 config = kwargs.pop('config', None)
2591 cache = kwargs.pop('cache', None)
2591 cache = kwargs.pop('cache', None)
2592 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2592 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2593 if vcs_full_cache is not None:
2593 if vcs_full_cache is not None:
2594 # allows override global config
2594 # allows override global config
2595 full_cache = vcs_full_cache
2595 full_cache = vcs_full_cache
2596 else:
2596 else:
2597 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2597 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2598 # if cache is NOT defined use default global, else we have a full
2598 # if cache is NOT defined use default global, else we have a full
2599 # control over cache behaviour
2599 # control over cache behaviour
2600 if cache is None and full_cache and not config:
2600 if cache is None and full_cache and not config:
2601 log.debug('Initializing pure cached instance for %s', self.repo_path)
2601 log.debug('Initializing pure cached instance for %s', self.repo_path)
2602 return self._get_instance_cached()
2602 return self._get_instance_cached()
2603
2603
2604 # cache here is sent to the "vcs server"
2604 # cache here is sent to the "vcs server"
2605 return self._get_instance(cache=bool(cache), config=config)
2605 return self._get_instance(cache=bool(cache), config=config)
2606
2606
2607 def _get_instance_cached(self):
2607 def _get_instance_cached(self):
2608 from rhodecode.lib import rc_cache
2608 from rhodecode.lib import rc_cache
2609
2609
2610 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2610 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2611 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2611 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2612
2612
2613 # we must use thread scoped cache here,
2613 # we must use thread scoped cache here,
2614 # because each thread of gevent needs it's own not shared connection and cache
2614 # because each thread of gevent needs it's own not shared connection and cache
2615 # we also alter `args` so the cache key is individual for every green thread.
2615 # we also alter `args` so the cache key is individual for every green thread.
2616 repo_namespace_key = CacheKey.REPO_INVALIDATION_NAMESPACE.format(repo_id=self.repo_id)
2616 repo_namespace_key = CacheKey.REPO_INVALIDATION_NAMESPACE.format(repo_id=self.repo_id)
2617 inv_context_manager = rc_cache.InvalidationContext(key=repo_namespace_key, thread_scoped=True)
2617 inv_context_manager = rc_cache.InvalidationContext(key=repo_namespace_key, thread_scoped=True)
2618
2618
2619 # our wrapped caching function that takes state_uid to save the previous state in
2619 # our wrapped caching function that takes state_uid to save the previous state in
2620 def cache_generator(_state_uid):
2620 def cache_generator(_state_uid):
2621
2621
2622 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2622 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2623 def get_instance_cached(_repo_id, _process_context_id):
2623 def get_instance_cached(_repo_id, _process_context_id):
2624 # we save in cached func the generation state so we can detect a change and invalidate caches
2624 # we save in cached func the generation state so we can detect a change and invalidate caches
2625 return _state_uid, self._get_instance(repo_state_uid=_state_uid)
2625 return _state_uid, self._get_instance(repo_state_uid=_state_uid)
2626
2626
2627 return get_instance_cached
2627 return get_instance_cached
2628
2628
2629 with inv_context_manager as invalidation_context:
2629 with inv_context_manager as invalidation_context:
2630 cache_state_uid = invalidation_context.state_uid
2630 cache_state_uid = invalidation_context.state_uid
2631 cache_func = cache_generator(cache_state_uid)
2631 cache_func = cache_generator(cache_state_uid)
2632
2632
2633 args = self.repo_id, inv_context_manager.proc_key
2633 args = self.repo_id, inv_context_manager.proc_key
2634
2634
2635 previous_state_uid, instance = cache_func(*args)
2635 previous_state_uid, instance = cache_func(*args)
2636
2636
2637 if instance:
2637 # now compare keys, the "cache" state vs expected state.
2638 # now compare keys, the "cache" state vs expected state.
2638 if previous_state_uid != cache_state_uid:
2639 if previous_state_uid != cache_state_uid:
2639 log.warning('Cached state uid %s is different than current state uid %s',
2640 log.warning('Cached state uid %s is different than current state uid %s',
2640 previous_state_uid, cache_state_uid)
2641 previous_state_uid, cache_state_uid)
2641 _, instance = cache_func.refresh(*args)
2642 _, instance = cache_func.refresh(*args)
2643
2642
2644 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2643 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2645 return instance
2644 return instance
2646
2645
2647 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2646 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2648 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2647 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2649 self.repo_type, self.repo_path, cache)
2648 self.repo_type, self.repo_path, cache)
2650 config = config or self._config
2649 config = config or self._config
2651 custom_wire = {
2650 custom_wire = {
2652 'cache': cache, # controls the vcs.remote cache
2651 'cache': cache, # controls the vcs.remote cache
2653 'repo_state_uid': repo_state_uid
2652 'repo_state_uid': repo_state_uid
2654 }
2653 }
2654
2655 repo = get_vcs_instance(
2655 repo = get_vcs_instance(
2656 repo_path=safe_str(self.repo_full_path),
2656 repo_path=safe_str(self.repo_full_path),
2657 config=config,
2657 config=config,
2658 with_wire=custom_wire,
2658 with_wire=custom_wire,
2659 create=False,
2659 create=False,
2660 _vcs_alias=self.repo_type)
2660 _vcs_alias=self.repo_type)
2661 if repo is not None:
2661 if repo is not None:
2662 repo.count() # cache rebuild
2662 repo.count() # cache rebuild
2663
2663
2664 return repo
2664 return repo
2665
2665
2666 def get_shadow_repository_path(self, workspace_id):
2666 def get_shadow_repository_path(self, workspace_id):
2667 from rhodecode.lib.vcs.backends.base import BaseRepository
2667 from rhodecode.lib.vcs.backends.base import BaseRepository
2668 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2668 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2669 self.repo_full_path, self.repo_id, workspace_id)
2669 self.repo_full_path, self.repo_id, workspace_id)
2670 return shadow_repo_path
2670 return shadow_repo_path
2671
2671
2672 def __json__(self):
2672 def __json__(self):
2673 return {'landing_rev': self.landing_rev}
2673 return {'landing_rev': self.landing_rev}
2674
2674
2675 def get_dict(self):
2675 def get_dict(self):
2676
2676
2677 # Since we transformed `repo_name` to a hybrid property, we need to
2677 # Since we transformed `repo_name` to a hybrid property, we need to
2678 # keep compatibility with the code which uses `repo_name` field.
2678 # keep compatibility with the code which uses `repo_name` field.
2679
2679
2680 result = super(Repository, self).get_dict()
2680 result = super(Repository, self).get_dict()
2681 result['repo_name'] = result.pop('_repo_name', None)
2681 result['repo_name'] = result.pop('_repo_name', None)
2682 result.pop('_changeset_cache', '')
2682 result.pop('_changeset_cache', '')
2683 return result
2683 return result
2684
2684
2685
2685
2686 class RepoGroup(Base, BaseModel):
2686 class RepoGroup(Base, BaseModel):
2687 __tablename__ = 'groups'
2687 __tablename__ = 'groups'
2688 __table_args__ = (
2688 __table_args__ = (
2689 UniqueConstraint('group_name', 'group_parent_id'),
2689 UniqueConstraint('group_name', 'group_parent_id'),
2690 base_table_args,
2690 base_table_args,
2691 )
2691 )
2692
2692
2693 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2693 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2694
2694
2695 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2695 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2696 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2696 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2697 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2697 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2698 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2698 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2699 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2699 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2700 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2700 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2701 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2701 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2702 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2702 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2703 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2703 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2704 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2704 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2705 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2705 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2706
2706
2707 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2707 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2708 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2708 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2709 parent_group = relationship('RepoGroup', remote_side=group_id)
2709 parent_group = relationship('RepoGroup', remote_side=group_id)
2710 user = relationship('User', back_populates='repository_groups')
2710 user = relationship('User', back_populates='repository_groups')
2711 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2711 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2712
2712
2713 # no cascade, set NULL
2713 # no cascade, set NULL
2714 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2714 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2715
2715
2716 def __init__(self, group_name='', parent_group=None):
2716 def __init__(self, group_name='', parent_group=None):
2717 self.group_name = group_name
2717 self.group_name = group_name
2718 self.parent_group = parent_group
2718 self.parent_group = parent_group
2719
2719
2720 def __repr__(self):
2720 def __repr__(self):
2721 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2721 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2722
2722
2723 @hybrid_property
2723 @hybrid_property
2724 def group_name(self):
2724 def group_name(self):
2725 return self._group_name
2725 return self._group_name
2726
2726
2727 @group_name.setter
2727 @group_name.setter
2728 def group_name(self, value):
2728 def group_name(self, value):
2729 self._group_name = value
2729 self._group_name = value
2730 self.group_name_hash = self.hash_repo_group_name(value)
2730 self.group_name_hash = self.hash_repo_group_name(value)
2731
2731
2732 @classmethod
2732 @classmethod
2733 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2733 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2734 from rhodecode.lib.vcs.backends.base import EmptyCommit
2734 from rhodecode.lib.vcs.backends.base import EmptyCommit
2735 dummy = EmptyCommit().__json__()
2735 dummy = EmptyCommit().__json__()
2736 if not changeset_cache_raw:
2736 if not changeset_cache_raw:
2737 dummy['source_repo_id'] = repo_id
2737 dummy['source_repo_id'] = repo_id
2738 return json.loads(json.dumps(dummy))
2738 return json.loads(json.dumps(dummy))
2739
2739
2740 try:
2740 try:
2741 return json.loads(changeset_cache_raw)
2741 return json.loads(changeset_cache_raw)
2742 except TypeError:
2742 except TypeError:
2743 return dummy
2743 return dummy
2744 except Exception:
2744 except Exception:
2745 log.error(traceback.format_exc())
2745 log.error(traceback.format_exc())
2746 return dummy
2746 return dummy
2747
2747
2748 @hybrid_property
2748 @hybrid_property
2749 def changeset_cache(self):
2749 def changeset_cache(self):
2750 return self._load_changeset_cache('', self._changeset_cache)
2750 return self._load_changeset_cache('', self._changeset_cache)
2751
2751
2752 @changeset_cache.setter
2752 @changeset_cache.setter
2753 def changeset_cache(self, val):
2753 def changeset_cache(self, val):
2754 try:
2754 try:
2755 self._changeset_cache = json.dumps(val)
2755 self._changeset_cache = json.dumps(val)
2756 except Exception:
2756 except Exception:
2757 log.error(traceback.format_exc())
2757 log.error(traceback.format_exc())
2758
2758
2759 @validates('group_parent_id')
2759 @validates('group_parent_id')
2760 def validate_group_parent_id(self, key, val):
2760 def validate_group_parent_id(self, key, val):
2761 """
2761 """
2762 Check cycle references for a parent group to self
2762 Check cycle references for a parent group to self
2763 """
2763 """
2764 if self.group_id and val:
2764 if self.group_id and val:
2765 assert val != self.group_id
2765 assert val != self.group_id
2766
2766
2767 return val
2767 return val
2768
2768
2769 @hybrid_property
2769 @hybrid_property
2770 def description_safe(self):
2770 def description_safe(self):
2771 from rhodecode.lib import helpers as h
2771 from rhodecode.lib import helpers as h
2772 return h.escape(self.group_description)
2772 return h.escape(self.group_description)
2773
2773
2774 @classmethod
2774 @classmethod
2775 def hash_repo_group_name(cls, repo_group_name):
2775 def hash_repo_group_name(cls, repo_group_name):
2776 val = remove_formatting(repo_group_name)
2776 val = remove_formatting(repo_group_name)
2777 val = safe_str(val).lower()
2777 val = safe_str(val).lower()
2778 chars = []
2778 chars = []
2779 for c in val:
2779 for c in val:
2780 if c not in string.ascii_letters:
2780 if c not in string.ascii_letters:
2781 c = str(ord(c))
2781 c = str(ord(c))
2782 chars.append(c)
2782 chars.append(c)
2783
2783
2784 return ''.join(chars)
2784 return ''.join(chars)
2785
2785
2786 @classmethod
2786 @classmethod
2787 def _generate_choice(cls, repo_group):
2787 def _generate_choice(cls, repo_group):
2788 from webhelpers2.html import literal as _literal
2788 from webhelpers2.html import literal as _literal
2789
2789
2790 def _name(k):
2790 def _name(k):
2791 return _literal(cls.CHOICES_SEPARATOR.join(k))
2791 return _literal(cls.CHOICES_SEPARATOR.join(k))
2792
2792
2793 return repo_group.group_id, _name(repo_group.full_path_splitted)
2793 return repo_group.group_id, _name(repo_group.full_path_splitted)
2794
2794
2795 @classmethod
2795 @classmethod
2796 def groups_choices(cls, groups=None, show_empty_group=True):
2796 def groups_choices(cls, groups=None, show_empty_group=True):
2797 if not groups:
2797 if not groups:
2798 groups = cls.query().all()
2798 groups = cls.query().all()
2799
2799
2800 repo_groups = []
2800 repo_groups = []
2801 if show_empty_group:
2801 if show_empty_group:
2802 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2802 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2803
2803
2804 repo_groups.extend([cls._generate_choice(x) for x in groups])
2804 repo_groups.extend([cls._generate_choice(x) for x in groups])
2805
2805
2806 repo_groups = sorted(
2806 repo_groups = sorted(
2807 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2807 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2808 return repo_groups
2808 return repo_groups
2809
2809
2810 @classmethod
2810 @classmethod
2811 def url_sep(cls):
2811 def url_sep(cls):
2812 return URL_SEP
2812 return URL_SEP
2813
2813
2814 @classmethod
2814 @classmethod
2815 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2815 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2816 if case_insensitive:
2816 if case_insensitive:
2817 gr = cls.query().filter(func.lower(cls.group_name)
2817 gr = cls.query().filter(func.lower(cls.group_name)
2818 == func.lower(group_name))
2818 == func.lower(group_name))
2819 else:
2819 else:
2820 gr = cls.query().filter(cls.group_name == group_name)
2820 gr = cls.query().filter(cls.group_name == group_name)
2821 if cache:
2821 if cache:
2822 name_key = _hash_key(group_name)
2822 name_key = _hash_key(group_name)
2823 gr = gr.options(
2823 gr = gr.options(
2824 FromCache("sql_cache_short", f"get_group_{name_key}"))
2824 FromCache("sql_cache_short", f"get_group_{name_key}"))
2825 return gr.scalar()
2825 return gr.scalar()
2826
2826
2827 @classmethod
2827 @classmethod
2828 def get_user_personal_repo_group(cls, user_id):
2828 def get_user_personal_repo_group(cls, user_id):
2829 user = User.get(user_id)
2829 user = User.get(user_id)
2830 if user.username == User.DEFAULT_USER:
2830 if user.username == User.DEFAULT_USER:
2831 return None
2831 return None
2832
2832
2833 return cls.query()\
2833 return cls.query()\
2834 .filter(cls.personal == true()) \
2834 .filter(cls.personal == true()) \
2835 .filter(cls.user == user) \
2835 .filter(cls.user == user) \
2836 .order_by(cls.group_id.asc()) \
2836 .order_by(cls.group_id.asc()) \
2837 .first()
2837 .first()
2838
2838
2839 @classmethod
2839 @classmethod
2840 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2840 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2841 case_insensitive=True):
2841 case_insensitive=True):
2842 q = RepoGroup.query()
2842 q = RepoGroup.query()
2843
2843
2844 if not isinstance(user_id, Optional):
2844 if not isinstance(user_id, Optional):
2845 q = q.filter(RepoGroup.user_id == user_id)
2845 q = q.filter(RepoGroup.user_id == user_id)
2846
2846
2847 if not isinstance(group_id, Optional):
2847 if not isinstance(group_id, Optional):
2848 q = q.filter(RepoGroup.group_parent_id == group_id)
2848 q = q.filter(RepoGroup.group_parent_id == group_id)
2849
2849
2850 if case_insensitive:
2850 if case_insensitive:
2851 q = q.order_by(func.lower(RepoGroup.group_name))
2851 q = q.order_by(func.lower(RepoGroup.group_name))
2852 else:
2852 else:
2853 q = q.order_by(RepoGroup.group_name)
2853 q = q.order_by(RepoGroup.group_name)
2854 return q.all()
2854 return q.all()
2855
2855
2856 @property
2856 @property
2857 def parents(self, parents_recursion_limit=10):
2857 def parents(self, parents_recursion_limit=10):
2858 groups = []
2858 groups = []
2859 if self.parent_group is None:
2859 if self.parent_group is None:
2860 return groups
2860 return groups
2861 cur_gr = self.parent_group
2861 cur_gr = self.parent_group
2862 groups.insert(0, cur_gr)
2862 groups.insert(0, cur_gr)
2863 cnt = 0
2863 cnt = 0
2864 while 1:
2864 while 1:
2865 cnt += 1
2865 cnt += 1
2866 gr = getattr(cur_gr, 'parent_group', None)
2866 gr = getattr(cur_gr, 'parent_group', None)
2867 cur_gr = cur_gr.parent_group
2867 cur_gr = cur_gr.parent_group
2868 if gr is None:
2868 if gr is None:
2869 break
2869 break
2870 if cnt == parents_recursion_limit:
2870 if cnt == parents_recursion_limit:
2871 # this will prevent accidental infinit loops
2871 # this will prevent accidental infinit loops
2872 log.error('more than %s parents found for group %s, stopping '
2872 log.error('more than %s parents found for group %s, stopping '
2873 'recursive parent fetching', parents_recursion_limit, self)
2873 'recursive parent fetching', parents_recursion_limit, self)
2874 break
2874 break
2875
2875
2876 groups.insert(0, gr)
2876 groups.insert(0, gr)
2877 return groups
2877 return groups
2878
2878
2879 @property
2879 @property
2880 def last_commit_cache_update_diff(self):
2880 def last_commit_cache_update_diff(self):
2881 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2881 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2882
2882
2883 @classmethod
2883 @classmethod
2884 def _load_commit_change(cls, last_commit_cache):
2884 def _load_commit_change(cls, last_commit_cache):
2885 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2885 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2886 empty_date = datetime.datetime.fromtimestamp(0)
2886 empty_date = datetime.datetime.fromtimestamp(0)
2887 date_latest = last_commit_cache.get('date', empty_date)
2887 date_latest = last_commit_cache.get('date', empty_date)
2888 try:
2888 try:
2889 return parse_datetime(date_latest)
2889 return parse_datetime(date_latest)
2890 except Exception:
2890 except Exception:
2891 return empty_date
2891 return empty_date
2892
2892
2893 @property
2893 @property
2894 def last_commit_change(self):
2894 def last_commit_change(self):
2895 return self._load_commit_change(self.changeset_cache)
2895 return self._load_commit_change(self.changeset_cache)
2896
2896
2897 @property
2897 @property
2898 def last_db_change(self):
2898 def last_db_change(self):
2899 return self.updated_on
2899 return self.updated_on
2900
2900
2901 @property
2901 @property
2902 def children(self):
2902 def children(self):
2903 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2903 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2904
2904
2905 @property
2905 @property
2906 def name(self):
2906 def name(self):
2907 return self.group_name.split(RepoGroup.url_sep())[-1]
2907 return self.group_name.split(RepoGroup.url_sep())[-1]
2908
2908
2909 @property
2909 @property
2910 def full_path(self):
2910 def full_path(self):
2911 return self.group_name
2911 return self.group_name
2912
2912
2913 @property
2913 @property
2914 def full_path_splitted(self):
2914 def full_path_splitted(self):
2915 return self.group_name.split(RepoGroup.url_sep())
2915 return self.group_name.split(RepoGroup.url_sep())
2916
2916
2917 @property
2917 @property
2918 def repositories(self):
2918 def repositories(self):
2919 return Repository.query()\
2919 return Repository.query()\
2920 .filter(Repository.group == self)\
2920 .filter(Repository.group == self)\
2921 .order_by(Repository.repo_name)
2921 .order_by(Repository.repo_name)
2922
2922
2923 @property
2923 @property
2924 def repositories_recursive_count(self):
2924 def repositories_recursive_count(self):
2925 cnt = self.repositories.count()
2925 cnt = self.repositories.count()
2926
2926
2927 def children_count(group):
2927 def children_count(group):
2928 cnt = 0
2928 cnt = 0
2929 for child in group.children:
2929 for child in group.children:
2930 cnt += child.repositories.count()
2930 cnt += child.repositories.count()
2931 cnt += children_count(child)
2931 cnt += children_count(child)
2932 return cnt
2932 return cnt
2933
2933
2934 return cnt + children_count(self)
2934 return cnt + children_count(self)
2935
2935
2936 def _recursive_objects(self, include_repos=True, include_groups=True):
2936 def _recursive_objects(self, include_repos=True, include_groups=True):
2937 all_ = []
2937 all_ = []
2938
2938
2939 def _get_members(root_gr):
2939 def _get_members(root_gr):
2940 if include_repos:
2940 if include_repos:
2941 for r in root_gr.repositories:
2941 for r in root_gr.repositories:
2942 all_.append(r)
2942 all_.append(r)
2943 childs = root_gr.children.all()
2943 childs = root_gr.children.all()
2944 if childs:
2944 if childs:
2945 for gr in childs:
2945 for gr in childs:
2946 if include_groups:
2946 if include_groups:
2947 all_.append(gr)
2947 all_.append(gr)
2948 _get_members(gr)
2948 _get_members(gr)
2949
2949
2950 root_group = []
2950 root_group = []
2951 if include_groups:
2951 if include_groups:
2952 root_group = [self]
2952 root_group = [self]
2953
2953
2954 _get_members(self)
2954 _get_members(self)
2955 return root_group + all_
2955 return root_group + all_
2956
2956
2957 def recursive_groups_and_repos(self):
2957 def recursive_groups_and_repos(self):
2958 """
2958 """
2959 Recursive return all groups, with repositories in those groups
2959 Recursive return all groups, with repositories in those groups
2960 """
2960 """
2961 return self._recursive_objects()
2961 return self._recursive_objects()
2962
2962
2963 def recursive_groups(self):
2963 def recursive_groups(self):
2964 """
2964 """
2965 Returns all children groups for this group including children of children
2965 Returns all children groups for this group including children of children
2966 """
2966 """
2967 return self._recursive_objects(include_repos=False)
2967 return self._recursive_objects(include_repos=False)
2968
2968
2969 def recursive_repos(self):
2969 def recursive_repos(self):
2970 """
2970 """
2971 Returns all children repositories for this group
2971 Returns all children repositories for this group
2972 """
2972 """
2973 return self._recursive_objects(include_groups=False)
2973 return self._recursive_objects(include_groups=False)
2974
2974
2975 def get_new_name(self, group_name):
2975 def get_new_name(self, group_name):
2976 """
2976 """
2977 returns new full group name based on parent and new name
2977 returns new full group name based on parent and new name
2978
2978
2979 :param group_name:
2979 :param group_name:
2980 """
2980 """
2981 path_prefix = (self.parent_group.full_path_splitted if
2981 path_prefix = (self.parent_group.full_path_splitted if
2982 self.parent_group else [])
2982 self.parent_group else [])
2983 return RepoGroup.url_sep().join(path_prefix + [group_name])
2983 return RepoGroup.url_sep().join(path_prefix + [group_name])
2984
2984
2985 def update_commit_cache(self, config=None):
2985 def update_commit_cache(self, config=None):
2986 """
2986 """
2987 Update cache of last commit for newest repository inside this repository group.
2987 Update cache of last commit for newest repository inside this repository group.
2988 cache_keys should be::
2988 cache_keys should be::
2989
2989
2990 source_repo_id
2990 source_repo_id
2991 short_id
2991 short_id
2992 raw_id
2992 raw_id
2993 revision
2993 revision
2994 parents
2994 parents
2995 message
2995 message
2996 date
2996 date
2997 author
2997 author
2998
2998
2999 """
2999 """
3000 from rhodecode.lib.vcs.utils.helpers import parse_datetime
3000 from rhodecode.lib.vcs.utils.helpers import parse_datetime
3001 empty_date = datetime.datetime.fromtimestamp(0)
3001 empty_date = datetime.datetime.fromtimestamp(0)
3002
3002
3003 def repo_groups_and_repos(root_gr):
3003 def repo_groups_and_repos(root_gr):
3004 for _repo in root_gr.repositories:
3004 for _repo in root_gr.repositories:
3005 yield _repo
3005 yield _repo
3006 for child_group in root_gr.children.all():
3006 for child_group in root_gr.children.all():
3007 yield child_group
3007 yield child_group
3008
3008
3009 latest_repo_cs_cache = {}
3009 latest_repo_cs_cache = {}
3010 for obj in repo_groups_and_repos(self):
3010 for obj in repo_groups_and_repos(self):
3011 repo_cs_cache = obj.changeset_cache
3011 repo_cs_cache = obj.changeset_cache
3012 date_latest = latest_repo_cs_cache.get('date', empty_date)
3012 date_latest = latest_repo_cs_cache.get('date', empty_date)
3013 date_current = repo_cs_cache.get('date', empty_date)
3013 date_current = repo_cs_cache.get('date', empty_date)
3014 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3014 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3015 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3015 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3016 latest_repo_cs_cache = repo_cs_cache
3016 latest_repo_cs_cache = repo_cs_cache
3017 if hasattr(obj, 'repo_id'):
3017 if hasattr(obj, 'repo_id'):
3018 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3018 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3019 else:
3019 else:
3020 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3020 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3021
3021
3022 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3022 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3023
3023
3024 latest_repo_cs_cache['updated_on'] = time.time()
3024 latest_repo_cs_cache['updated_on'] = time.time()
3025 self.changeset_cache = latest_repo_cs_cache
3025 self.changeset_cache = latest_repo_cs_cache
3026 self.updated_on = _date_latest
3026 self.updated_on = _date_latest
3027 Session().add(self)
3027 Session().add(self)
3028 Session().commit()
3028 Session().commit()
3029
3029
3030 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3030 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3031 self.group_name, latest_repo_cs_cache, _date_latest)
3031 self.group_name, latest_repo_cs_cache, _date_latest)
3032
3032
3033 def permissions(self, with_admins=True, with_owner=True,
3033 def permissions(self, with_admins=True, with_owner=True,
3034 expand_from_user_groups=False):
3034 expand_from_user_groups=False):
3035 """
3035 """
3036 Permissions for repository groups
3036 Permissions for repository groups
3037 """
3037 """
3038 _admin_perm = 'group.admin'
3038 _admin_perm = 'group.admin'
3039
3039
3040 owner_row = []
3040 owner_row = []
3041 if with_owner:
3041 if with_owner:
3042 usr = AttributeDict(self.user.get_dict())
3042 usr = AttributeDict(self.user.get_dict())
3043 usr.owner_row = True
3043 usr.owner_row = True
3044 usr.permission = _admin_perm
3044 usr.permission = _admin_perm
3045 owner_row.append(usr)
3045 owner_row.append(usr)
3046
3046
3047 super_admin_ids = []
3047 super_admin_ids = []
3048 super_admin_rows = []
3048 super_admin_rows = []
3049 if with_admins:
3049 if with_admins:
3050 for usr in User.get_all_super_admins():
3050 for usr in User.get_all_super_admins():
3051 super_admin_ids.append(usr.user_id)
3051 super_admin_ids.append(usr.user_id)
3052 # if this admin is also owner, don't double the record
3052 # if this admin is also owner, don't double the record
3053 if usr.user_id == owner_row[0].user_id:
3053 if usr.user_id == owner_row[0].user_id:
3054 owner_row[0].admin_row = True
3054 owner_row[0].admin_row = True
3055 else:
3055 else:
3056 usr = AttributeDict(usr.get_dict())
3056 usr = AttributeDict(usr.get_dict())
3057 usr.admin_row = True
3057 usr.admin_row = True
3058 usr.permission = _admin_perm
3058 usr.permission = _admin_perm
3059 super_admin_rows.append(usr)
3059 super_admin_rows.append(usr)
3060
3060
3061 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3061 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3062 q = q.options(joinedload(UserRepoGroupToPerm.group),
3062 q = q.options(joinedload(UserRepoGroupToPerm.group),
3063 joinedload(UserRepoGroupToPerm.user),
3063 joinedload(UserRepoGroupToPerm.user),
3064 joinedload(UserRepoGroupToPerm.permission),)
3064 joinedload(UserRepoGroupToPerm.permission),)
3065
3065
3066 # get owners and admins and permissions. We do a trick of re-writing
3066 # get owners and admins and permissions. We do a trick of re-writing
3067 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3067 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3068 # has a global reference and changing one object propagates to all
3068 # has a global reference and changing one object propagates to all
3069 # others. This means if admin is also an owner admin_row that change
3069 # others. This means if admin is also an owner admin_row that change
3070 # would propagate to both objects
3070 # would propagate to both objects
3071 perm_rows = []
3071 perm_rows = []
3072 for _usr in q.all():
3072 for _usr in q.all():
3073 usr = AttributeDict(_usr.user.get_dict())
3073 usr = AttributeDict(_usr.user.get_dict())
3074 # if this user is also owner/admin, mark as duplicate record
3074 # if this user is also owner/admin, mark as duplicate record
3075 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3075 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3076 usr.duplicate_perm = True
3076 usr.duplicate_perm = True
3077 usr.permission = _usr.permission.permission_name
3077 usr.permission = _usr.permission.permission_name
3078 perm_rows.append(usr)
3078 perm_rows.append(usr)
3079
3079
3080 # filter the perm rows by 'default' first and then sort them by
3080 # filter the perm rows by 'default' first and then sort them by
3081 # admin,write,read,none permissions sorted again alphabetically in
3081 # admin,write,read,none permissions sorted again alphabetically in
3082 # each group
3082 # each group
3083 perm_rows = sorted(perm_rows, key=display_user_sort)
3083 perm_rows = sorted(perm_rows, key=display_user_sort)
3084
3084
3085 user_groups_rows = []
3085 user_groups_rows = []
3086 if expand_from_user_groups:
3086 if expand_from_user_groups:
3087 for ug in self.permission_user_groups(with_members=True):
3087 for ug in self.permission_user_groups(with_members=True):
3088 for user_data in ug.members:
3088 for user_data in ug.members:
3089 user_groups_rows.append(user_data)
3089 user_groups_rows.append(user_data)
3090
3090
3091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3092
3092
3093 def permission_user_groups(self, with_members=False):
3093 def permission_user_groups(self, with_members=False):
3094 q = UserGroupRepoGroupToPerm.query()\
3094 q = UserGroupRepoGroupToPerm.query()\
3095 .filter(UserGroupRepoGroupToPerm.group == self)
3095 .filter(UserGroupRepoGroupToPerm.group == self)
3096 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3096 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3097 joinedload(UserGroupRepoGroupToPerm.users_group),
3097 joinedload(UserGroupRepoGroupToPerm.users_group),
3098 joinedload(UserGroupRepoGroupToPerm.permission),)
3098 joinedload(UserGroupRepoGroupToPerm.permission),)
3099
3099
3100 perm_rows = []
3100 perm_rows = []
3101 for _user_group in q.all():
3101 for _user_group in q.all():
3102 entry = AttributeDict(_user_group.users_group.get_dict())
3102 entry = AttributeDict(_user_group.users_group.get_dict())
3103 entry.permission = _user_group.permission.permission_name
3103 entry.permission = _user_group.permission.permission_name
3104 if with_members:
3104 if with_members:
3105 entry.members = [x.user.get_dict()
3105 entry.members = [x.user.get_dict()
3106 for x in _user_group.users_group.members]
3106 for x in _user_group.users_group.members]
3107 perm_rows.append(entry)
3107 perm_rows.append(entry)
3108
3108
3109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3110 return perm_rows
3110 return perm_rows
3111
3111
3112 def get_api_data(self):
3112 def get_api_data(self):
3113 """
3113 """
3114 Common function for generating api data
3114 Common function for generating api data
3115
3115
3116 """
3116 """
3117 group = self
3117 group = self
3118 data = {
3118 data = {
3119 'group_id': group.group_id,
3119 'group_id': group.group_id,
3120 'group_name': group.group_name,
3120 'group_name': group.group_name,
3121 'group_description': group.description_safe,
3121 'group_description': group.description_safe,
3122 'parent_group': group.parent_group.group_name if group.parent_group else None,
3122 'parent_group': group.parent_group.group_name if group.parent_group else None,
3123 'repositories': [x.repo_name for x in group.repositories],
3123 'repositories': [x.repo_name for x in group.repositories],
3124 'owner': group.user.username,
3124 'owner': group.user.username,
3125 }
3125 }
3126 return data
3126 return data
3127
3127
3128 def get_dict(self):
3128 def get_dict(self):
3129 # Since we transformed `group_name` to a hybrid property, we need to
3129 # Since we transformed `group_name` to a hybrid property, we need to
3130 # keep compatibility with the code which uses `group_name` field.
3130 # keep compatibility with the code which uses `group_name` field.
3131 result = super(RepoGroup, self).get_dict()
3131 result = super(RepoGroup, self).get_dict()
3132 result['group_name'] = result.pop('_group_name', None)
3132 result['group_name'] = result.pop('_group_name', None)
3133 result.pop('_changeset_cache', '')
3133 result.pop('_changeset_cache', '')
3134 return result
3134 return result
3135
3135
3136
3136
3137 class Permission(Base, BaseModel):
3137 class Permission(Base, BaseModel):
3138 __tablename__ = 'permissions'
3138 __tablename__ = 'permissions'
3139 __table_args__ = (
3139 __table_args__ = (
3140 Index('p_perm_name_idx', 'permission_name'),
3140 Index('p_perm_name_idx', 'permission_name'),
3141 base_table_args,
3141 base_table_args,
3142 )
3142 )
3143
3143
3144 PERMS = [
3144 PERMS = [
3145 ('hg.admin', _('RhodeCode Super Administrator')),
3145 ('hg.admin', _('RhodeCode Super Administrator')),
3146
3146
3147 ('repository.none', _('Repository no access')),
3147 ('repository.none', _('Repository no access')),
3148 ('repository.read', _('Repository read access')),
3148 ('repository.read', _('Repository read access')),
3149 ('repository.write', _('Repository write access')),
3149 ('repository.write', _('Repository write access')),
3150 ('repository.admin', _('Repository admin access')),
3150 ('repository.admin', _('Repository admin access')),
3151
3151
3152 ('group.none', _('Repository group no access')),
3152 ('group.none', _('Repository group no access')),
3153 ('group.read', _('Repository group read access')),
3153 ('group.read', _('Repository group read access')),
3154 ('group.write', _('Repository group write access')),
3154 ('group.write', _('Repository group write access')),
3155 ('group.admin', _('Repository group admin access')),
3155 ('group.admin', _('Repository group admin access')),
3156
3156
3157 ('usergroup.none', _('User group no access')),
3157 ('usergroup.none', _('User group no access')),
3158 ('usergroup.read', _('User group read access')),
3158 ('usergroup.read', _('User group read access')),
3159 ('usergroup.write', _('User group write access')),
3159 ('usergroup.write', _('User group write access')),
3160 ('usergroup.admin', _('User group admin access')),
3160 ('usergroup.admin', _('User group admin access')),
3161
3161
3162 ('branch.none', _('Branch no permissions')),
3162 ('branch.none', _('Branch no permissions')),
3163 ('branch.merge', _('Branch access by web merge')),
3163 ('branch.merge', _('Branch access by web merge')),
3164 ('branch.push', _('Branch access by push')),
3164 ('branch.push', _('Branch access by push')),
3165 ('branch.push_force', _('Branch access by push with force')),
3165 ('branch.push_force', _('Branch access by push with force')),
3166
3166
3167 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3167 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3168 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3168 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3169
3169
3170 ('hg.usergroup.create.false', _('User Group creation disabled')),
3170 ('hg.usergroup.create.false', _('User Group creation disabled')),
3171 ('hg.usergroup.create.true', _('User Group creation enabled')),
3171 ('hg.usergroup.create.true', _('User Group creation enabled')),
3172
3172
3173 ('hg.create.none', _('Repository creation disabled')),
3173 ('hg.create.none', _('Repository creation disabled')),
3174 ('hg.create.repository', _('Repository creation enabled')),
3174 ('hg.create.repository', _('Repository creation enabled')),
3175 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3175 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3176 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3176 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3177
3177
3178 ('hg.fork.none', _('Repository forking disabled')),
3178 ('hg.fork.none', _('Repository forking disabled')),
3179 ('hg.fork.repository', _('Repository forking enabled')),
3179 ('hg.fork.repository', _('Repository forking enabled')),
3180
3180
3181 ('hg.register.none', _('Registration disabled')),
3181 ('hg.register.none', _('Registration disabled')),
3182 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3182 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3183 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3183 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3184
3184
3185 ('hg.password_reset.enabled', _('Password reset enabled')),
3185 ('hg.password_reset.enabled', _('Password reset enabled')),
3186 ('hg.password_reset.hidden', _('Password reset hidden')),
3186 ('hg.password_reset.hidden', _('Password reset hidden')),
3187 ('hg.password_reset.disabled', _('Password reset disabled')),
3187 ('hg.password_reset.disabled', _('Password reset disabled')),
3188
3188
3189 ('hg.extern_activate.manual', _('Manual activation of external account')),
3189 ('hg.extern_activate.manual', _('Manual activation of external account')),
3190 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3190 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3191
3191
3192 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3192 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3193 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3193 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3194 ]
3194 ]
3195
3195
3196 # definition of system default permissions for DEFAULT user, created on
3196 # definition of system default permissions for DEFAULT user, created on
3197 # system setup
3197 # system setup
3198 DEFAULT_USER_PERMISSIONS = [
3198 DEFAULT_USER_PERMISSIONS = [
3199 # object perms
3199 # object perms
3200 'repository.read',
3200 'repository.read',
3201 'group.read',
3201 'group.read',
3202 'usergroup.read',
3202 'usergroup.read',
3203 # branch, for backward compat we need same value as before so forced pushed
3203 # branch, for backward compat we need same value as before so forced pushed
3204 'branch.push_force',
3204 'branch.push_force',
3205 # global
3205 # global
3206 'hg.create.repository',
3206 'hg.create.repository',
3207 'hg.repogroup.create.false',
3207 'hg.repogroup.create.false',
3208 'hg.usergroup.create.false',
3208 'hg.usergroup.create.false',
3209 'hg.create.write_on_repogroup.true',
3209 'hg.create.write_on_repogroup.true',
3210 'hg.fork.repository',
3210 'hg.fork.repository',
3211 'hg.register.manual_activate',
3211 'hg.register.manual_activate',
3212 'hg.password_reset.enabled',
3212 'hg.password_reset.enabled',
3213 'hg.extern_activate.auto',
3213 'hg.extern_activate.auto',
3214 'hg.inherit_default_perms.true',
3214 'hg.inherit_default_perms.true',
3215 ]
3215 ]
3216
3216
3217 # defines which permissions are more important higher the more important
3217 # defines which permissions are more important higher the more important
3218 # Weight defines which permissions are more important.
3218 # Weight defines which permissions are more important.
3219 # The higher number the more important.
3219 # The higher number the more important.
3220 PERM_WEIGHTS = {
3220 PERM_WEIGHTS = {
3221 'repository.none': 0,
3221 'repository.none': 0,
3222 'repository.read': 1,
3222 'repository.read': 1,
3223 'repository.write': 3,
3223 'repository.write': 3,
3224 'repository.admin': 4,
3224 'repository.admin': 4,
3225
3225
3226 'group.none': 0,
3226 'group.none': 0,
3227 'group.read': 1,
3227 'group.read': 1,
3228 'group.write': 3,
3228 'group.write': 3,
3229 'group.admin': 4,
3229 'group.admin': 4,
3230
3230
3231 'usergroup.none': 0,
3231 'usergroup.none': 0,
3232 'usergroup.read': 1,
3232 'usergroup.read': 1,
3233 'usergroup.write': 3,
3233 'usergroup.write': 3,
3234 'usergroup.admin': 4,
3234 'usergroup.admin': 4,
3235
3235
3236 'branch.none': 0,
3236 'branch.none': 0,
3237 'branch.merge': 1,
3237 'branch.merge': 1,
3238 'branch.push': 3,
3238 'branch.push': 3,
3239 'branch.push_force': 4,
3239 'branch.push_force': 4,
3240
3240
3241 'hg.repogroup.create.false': 0,
3241 'hg.repogroup.create.false': 0,
3242 'hg.repogroup.create.true': 1,
3242 'hg.repogroup.create.true': 1,
3243
3243
3244 'hg.usergroup.create.false': 0,
3244 'hg.usergroup.create.false': 0,
3245 'hg.usergroup.create.true': 1,
3245 'hg.usergroup.create.true': 1,
3246
3246
3247 'hg.fork.none': 0,
3247 'hg.fork.none': 0,
3248 'hg.fork.repository': 1,
3248 'hg.fork.repository': 1,
3249 'hg.create.none': 0,
3249 'hg.create.none': 0,
3250 'hg.create.repository': 1
3250 'hg.create.repository': 1
3251 }
3251 }
3252
3252
3253 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3253 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3254 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3254 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3255 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3255 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3256
3256
3257 def __repr__(self):
3257 def __repr__(self):
3258 return "<%s('%s:%s')>" % (
3258 return "<%s('%s:%s')>" % (
3259 self.cls_name, self.permission_id, self.permission_name
3259 self.cls_name, self.permission_id, self.permission_name
3260 )
3260 )
3261
3261
3262 @classmethod
3262 @classmethod
3263 def get_by_key(cls, key):
3263 def get_by_key(cls, key):
3264 return cls.query().filter(cls.permission_name == key).scalar()
3264 return cls.query().filter(cls.permission_name == key).scalar()
3265
3265
3266 @classmethod
3266 @classmethod
3267 def get_default_repo_perms(cls, user_id, repo_id=None):
3267 def get_default_repo_perms(cls, user_id, repo_id=None):
3268 q = Session().query(UserRepoToPerm, Repository, Permission)\
3268 q = Session().query(UserRepoToPerm, Repository, Permission)\
3269 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3269 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3270 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3270 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3271 .filter(UserRepoToPerm.user_id == user_id)
3271 .filter(UserRepoToPerm.user_id == user_id)
3272 if repo_id:
3272 if repo_id:
3273 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3273 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3274 return q.all()
3274 return q.all()
3275
3275
3276 @classmethod
3276 @classmethod
3277 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3277 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3278 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3278 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3279 .join(
3279 .join(
3280 Permission,
3280 Permission,
3281 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3281 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3282 .join(
3282 .join(
3283 UserRepoToPerm,
3283 UserRepoToPerm,
3284 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3284 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3285 .filter(UserRepoToPerm.user_id == user_id)
3285 .filter(UserRepoToPerm.user_id == user_id)
3286
3286
3287 if repo_id:
3287 if repo_id:
3288 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3288 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3289 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3289 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3290
3290
3291 @classmethod
3291 @classmethod
3292 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3292 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3293 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3293 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3294 .join(
3294 .join(
3295 Permission,
3295 Permission,
3296 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3296 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3297 .join(
3297 .join(
3298 Repository,
3298 Repository,
3299 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3299 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3300 .join(
3300 .join(
3301 UserGroup,
3301 UserGroup,
3302 UserGroupRepoToPerm.users_group_id ==
3302 UserGroupRepoToPerm.users_group_id ==
3303 UserGroup.users_group_id)\
3303 UserGroup.users_group_id)\
3304 .join(
3304 .join(
3305 UserGroupMember,
3305 UserGroupMember,
3306 UserGroupRepoToPerm.users_group_id ==
3306 UserGroupRepoToPerm.users_group_id ==
3307 UserGroupMember.users_group_id)\
3307 UserGroupMember.users_group_id)\
3308 .filter(
3308 .filter(
3309 UserGroupMember.user_id == user_id,
3309 UserGroupMember.user_id == user_id,
3310 UserGroup.users_group_active == true())
3310 UserGroup.users_group_active == true())
3311 if repo_id:
3311 if repo_id:
3312 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3312 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3313 return q.all()
3313 return q.all()
3314
3314
3315 @classmethod
3315 @classmethod
3316 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3316 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3317 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3317 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3318 .join(
3318 .join(
3319 Permission,
3319 Permission,
3320 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3320 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3321 .join(
3321 .join(
3322 UserGroupRepoToPerm,
3322 UserGroupRepoToPerm,
3323 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3323 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3324 .join(
3324 .join(
3325 UserGroup,
3325 UserGroup,
3326 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3326 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3327 .join(
3327 .join(
3328 UserGroupMember,
3328 UserGroupMember,
3329 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3329 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3330 .filter(
3330 .filter(
3331 UserGroupMember.user_id == user_id,
3331 UserGroupMember.user_id == user_id,
3332 UserGroup.users_group_active == true())
3332 UserGroup.users_group_active == true())
3333
3333
3334 if repo_id:
3334 if repo_id:
3335 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3335 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3336 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3336 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3337
3337
3338 @classmethod
3338 @classmethod
3339 def get_default_group_perms(cls, user_id, repo_group_id=None):
3339 def get_default_group_perms(cls, user_id, repo_group_id=None):
3340 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3340 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3341 .join(
3341 .join(
3342 Permission,
3342 Permission,
3343 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3343 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3344 .join(
3344 .join(
3345 RepoGroup,
3345 RepoGroup,
3346 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3346 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3347 .filter(UserRepoGroupToPerm.user_id == user_id)
3347 .filter(UserRepoGroupToPerm.user_id == user_id)
3348 if repo_group_id:
3348 if repo_group_id:
3349 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3349 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3350 return q.all()
3350 return q.all()
3351
3351
3352 @classmethod
3352 @classmethod
3353 def get_default_group_perms_from_user_group(
3353 def get_default_group_perms_from_user_group(
3354 cls, user_id, repo_group_id=None):
3354 cls, user_id, repo_group_id=None):
3355 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3355 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3356 .join(
3356 .join(
3357 Permission,
3357 Permission,
3358 UserGroupRepoGroupToPerm.permission_id ==
3358 UserGroupRepoGroupToPerm.permission_id ==
3359 Permission.permission_id)\
3359 Permission.permission_id)\
3360 .join(
3360 .join(
3361 RepoGroup,
3361 RepoGroup,
3362 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3362 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3363 .join(
3363 .join(
3364 UserGroup,
3364 UserGroup,
3365 UserGroupRepoGroupToPerm.users_group_id ==
3365 UserGroupRepoGroupToPerm.users_group_id ==
3366 UserGroup.users_group_id)\
3366 UserGroup.users_group_id)\
3367 .join(
3367 .join(
3368 UserGroupMember,
3368 UserGroupMember,
3369 UserGroupRepoGroupToPerm.users_group_id ==
3369 UserGroupRepoGroupToPerm.users_group_id ==
3370 UserGroupMember.users_group_id)\
3370 UserGroupMember.users_group_id)\
3371 .filter(
3371 .filter(
3372 UserGroupMember.user_id == user_id,
3372 UserGroupMember.user_id == user_id,
3373 UserGroup.users_group_active == true())
3373 UserGroup.users_group_active == true())
3374 if repo_group_id:
3374 if repo_group_id:
3375 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3375 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3376 return q.all()
3376 return q.all()
3377
3377
3378 @classmethod
3378 @classmethod
3379 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3379 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3380 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3380 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3381 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3381 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3382 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3382 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3383 .filter(UserUserGroupToPerm.user_id == user_id)
3383 .filter(UserUserGroupToPerm.user_id == user_id)
3384 if user_group_id:
3384 if user_group_id:
3385 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3385 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3386 return q.all()
3386 return q.all()
3387
3387
3388 @classmethod
3388 @classmethod
3389 def get_default_user_group_perms_from_user_group(
3389 def get_default_user_group_perms_from_user_group(
3390 cls, user_id, user_group_id=None):
3390 cls, user_id, user_group_id=None):
3391 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3391 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3392 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3392 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3393 .join(
3393 .join(
3394 Permission,
3394 Permission,
3395 UserGroupUserGroupToPerm.permission_id ==
3395 UserGroupUserGroupToPerm.permission_id ==
3396 Permission.permission_id)\
3396 Permission.permission_id)\
3397 .join(
3397 .join(
3398 TargetUserGroup,
3398 TargetUserGroup,
3399 UserGroupUserGroupToPerm.target_user_group_id ==
3399 UserGroupUserGroupToPerm.target_user_group_id ==
3400 TargetUserGroup.users_group_id)\
3400 TargetUserGroup.users_group_id)\
3401 .join(
3401 .join(
3402 UserGroup,
3402 UserGroup,
3403 UserGroupUserGroupToPerm.user_group_id ==
3403 UserGroupUserGroupToPerm.user_group_id ==
3404 UserGroup.users_group_id)\
3404 UserGroup.users_group_id)\
3405 .join(
3405 .join(
3406 UserGroupMember,
3406 UserGroupMember,
3407 UserGroupUserGroupToPerm.user_group_id ==
3407 UserGroupUserGroupToPerm.user_group_id ==
3408 UserGroupMember.users_group_id)\
3408 UserGroupMember.users_group_id)\
3409 .filter(
3409 .filter(
3410 UserGroupMember.user_id == user_id,
3410 UserGroupMember.user_id == user_id,
3411 UserGroup.users_group_active == true())
3411 UserGroup.users_group_active == true())
3412 if user_group_id:
3412 if user_group_id:
3413 q = q.filter(
3413 q = q.filter(
3414 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3414 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3415
3415
3416 return q.all()
3416 return q.all()
3417
3417
3418
3418
3419 class UserRepoToPerm(Base, BaseModel):
3419 class UserRepoToPerm(Base, BaseModel):
3420 __tablename__ = 'repo_to_perm'
3420 __tablename__ = 'repo_to_perm'
3421 __table_args__ = (
3421 __table_args__ = (
3422 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3422 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3423 base_table_args
3423 base_table_args
3424 )
3424 )
3425
3425
3426 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3426 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3429 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3429 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3430
3430
3431 user = relationship('User', back_populates="repo_to_perm")
3431 user = relationship('User', back_populates="repo_to_perm")
3432 repository = relationship('Repository', back_populates="repo_to_perm")
3432 repository = relationship('Repository', back_populates="repo_to_perm")
3433 permission = relationship('Permission')
3433 permission = relationship('Permission')
3434
3434
3435 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3435 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3436
3436
3437 @classmethod
3437 @classmethod
3438 def create(cls, user, repository, permission):
3438 def create(cls, user, repository, permission):
3439 n = cls()
3439 n = cls()
3440 n.user = user
3440 n.user = user
3441 n.repository = repository
3441 n.repository = repository
3442 n.permission = permission
3442 n.permission = permission
3443 Session().add(n)
3443 Session().add(n)
3444 return n
3444 return n
3445
3445
3446 def __repr__(self):
3446 def __repr__(self):
3447 return f'<{self.user} => {self.repository} >'
3447 return f'<{self.user} => {self.repository} >'
3448
3448
3449
3449
3450 class UserUserGroupToPerm(Base, BaseModel):
3450 class UserUserGroupToPerm(Base, BaseModel):
3451 __tablename__ = 'user_user_group_to_perm'
3451 __tablename__ = 'user_user_group_to_perm'
3452 __table_args__ = (
3452 __table_args__ = (
3453 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3453 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3454 base_table_args
3454 base_table_args
3455 )
3455 )
3456
3456
3457 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3457 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3458 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3458 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3459 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3459 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3460 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3460 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3461
3461
3462 user = relationship('User', back_populates='user_group_to_perm')
3462 user = relationship('User', back_populates='user_group_to_perm')
3463 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3463 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3464 permission = relationship('Permission')
3464 permission = relationship('Permission')
3465
3465
3466 @classmethod
3466 @classmethod
3467 def create(cls, user, user_group, permission):
3467 def create(cls, user, user_group, permission):
3468 n = cls()
3468 n = cls()
3469 n.user = user
3469 n.user = user
3470 n.user_group = user_group
3470 n.user_group = user_group
3471 n.permission = permission
3471 n.permission = permission
3472 Session().add(n)
3472 Session().add(n)
3473 return n
3473 return n
3474
3474
3475 def __repr__(self):
3475 def __repr__(self):
3476 return f'<{self.user} => {self.user_group} >'
3476 return f'<{self.user} => {self.user_group} >'
3477
3477
3478
3478
3479 class UserToPerm(Base, BaseModel):
3479 class UserToPerm(Base, BaseModel):
3480 __tablename__ = 'user_to_perm'
3480 __tablename__ = 'user_to_perm'
3481 __table_args__ = (
3481 __table_args__ = (
3482 UniqueConstraint('user_id', 'permission_id'),
3482 UniqueConstraint('user_id', 'permission_id'),
3483 base_table_args
3483 base_table_args
3484 )
3484 )
3485
3485
3486 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3486 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3487 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3487 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3488 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3488 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3489
3489
3490 user = relationship('User', back_populates='user_perms')
3490 user = relationship('User', back_populates='user_perms')
3491 permission = relationship('Permission', lazy='joined')
3491 permission = relationship('Permission', lazy='joined')
3492
3492
3493 def __repr__(self):
3493 def __repr__(self):
3494 return f'<{self.user} => {self.permission} >'
3494 return f'<{self.user} => {self.permission} >'
3495
3495
3496
3496
3497 class UserGroupRepoToPerm(Base, BaseModel):
3497 class UserGroupRepoToPerm(Base, BaseModel):
3498 __tablename__ = 'users_group_repo_to_perm'
3498 __tablename__ = 'users_group_repo_to_perm'
3499 __table_args__ = (
3499 __table_args__ = (
3500 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3500 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3501 base_table_args
3501 base_table_args
3502 )
3502 )
3503
3503
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3507 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3508
3508
3509 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3509 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3510 permission = relationship('Permission')
3510 permission = relationship('Permission')
3511 repository = relationship('Repository', back_populates='users_group_to_perm')
3511 repository = relationship('Repository', back_populates='users_group_to_perm')
3512 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3512 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3513
3513
3514 @classmethod
3514 @classmethod
3515 def create(cls, users_group, repository, permission):
3515 def create(cls, users_group, repository, permission):
3516 n = cls()
3516 n = cls()
3517 n.users_group = users_group
3517 n.users_group = users_group
3518 n.repository = repository
3518 n.repository = repository
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 def __repr__(self):
3523 def __repr__(self):
3524 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3524 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3525
3525
3526
3526
3527 class UserGroupUserGroupToPerm(Base, BaseModel):
3527 class UserGroupUserGroupToPerm(Base, BaseModel):
3528 __tablename__ = 'user_group_user_group_to_perm'
3528 __tablename__ = 'user_group_user_group_to_perm'
3529 __table_args__ = (
3529 __table_args__ = (
3530 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3530 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3531 CheckConstraint('target_user_group_id != user_group_id'),
3531 CheckConstraint('target_user_group_id != user_group_id'),
3532 base_table_args
3532 base_table_args
3533 )
3533 )
3534
3534
3535 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)
3535 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)
3536 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3536 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3539
3539
3540 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3540 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3541 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3541 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3542 permission = relationship('Permission')
3542 permission = relationship('Permission')
3543
3543
3544 @classmethod
3544 @classmethod
3545 def create(cls, target_user_group, user_group, permission):
3545 def create(cls, target_user_group, user_group, permission):
3546 n = cls()
3546 n = cls()
3547 n.target_user_group = target_user_group
3547 n.target_user_group = target_user_group
3548 n.user_group = user_group
3548 n.user_group = user_group
3549 n.permission = permission
3549 n.permission = permission
3550 Session().add(n)
3550 Session().add(n)
3551 return n
3551 return n
3552
3552
3553 def __repr__(self):
3553 def __repr__(self):
3554 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3554 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3555
3555
3556
3556
3557 class UserGroupToPerm(Base, BaseModel):
3557 class UserGroupToPerm(Base, BaseModel):
3558 __tablename__ = 'users_group_to_perm'
3558 __tablename__ = 'users_group_to_perm'
3559 __table_args__ = (
3559 __table_args__ = (
3560 UniqueConstraint('users_group_id', 'permission_id',),
3560 UniqueConstraint('users_group_id', 'permission_id',),
3561 base_table_args
3561 base_table_args
3562 )
3562 )
3563
3563
3564 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3564 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3565 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3565 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3567
3567
3568 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3568 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3569 permission = relationship('Permission')
3569 permission = relationship('Permission')
3570
3570
3571
3571
3572 class UserRepoGroupToPerm(Base, BaseModel):
3572 class UserRepoGroupToPerm(Base, BaseModel):
3573 __tablename__ = 'user_repo_group_to_perm'
3573 __tablename__ = 'user_repo_group_to_perm'
3574 __table_args__ = (
3574 __table_args__ = (
3575 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3575 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3576 base_table_args
3576 base_table_args
3577 )
3577 )
3578
3578
3579 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3579 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3580 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3580 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3581 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3581 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3582 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3582 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3583
3583
3584 user = relationship('User', back_populates='repo_group_to_perm')
3584 user = relationship('User', back_populates='repo_group_to_perm')
3585 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3585 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3586 permission = relationship('Permission')
3586 permission = relationship('Permission')
3587
3587
3588 @classmethod
3588 @classmethod
3589 def create(cls, user, repository_group, permission):
3589 def create(cls, user, repository_group, permission):
3590 n = cls()
3590 n = cls()
3591 n.user = user
3591 n.user = user
3592 n.group = repository_group
3592 n.group = repository_group
3593 n.permission = permission
3593 n.permission = permission
3594 Session().add(n)
3594 Session().add(n)
3595 return n
3595 return n
3596
3596
3597
3597
3598 class UserGroupRepoGroupToPerm(Base, BaseModel):
3598 class UserGroupRepoGroupToPerm(Base, BaseModel):
3599 __tablename__ = 'users_group_repo_group_to_perm'
3599 __tablename__ = 'users_group_repo_group_to_perm'
3600 __table_args__ = (
3600 __table_args__ = (
3601 UniqueConstraint('users_group_id', 'group_id'),
3601 UniqueConstraint('users_group_id', 'group_id'),
3602 base_table_args
3602 base_table_args
3603 )
3603 )
3604
3604
3605 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)
3605 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)
3606 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3606 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3607 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3607 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3608 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3608 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3609
3609
3610 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3610 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3611 permission = relationship('Permission')
3611 permission = relationship('Permission')
3612 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3612 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3613
3613
3614 @classmethod
3614 @classmethod
3615 def create(cls, user_group, repository_group, permission):
3615 def create(cls, user_group, repository_group, permission):
3616 n = cls()
3616 n = cls()
3617 n.users_group = user_group
3617 n.users_group = user_group
3618 n.group = repository_group
3618 n.group = repository_group
3619 n.permission = permission
3619 n.permission = permission
3620 Session().add(n)
3620 Session().add(n)
3621 return n
3621 return n
3622
3622
3623 def __repr__(self):
3623 def __repr__(self):
3624 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3624 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3625
3625
3626
3626
3627 class Statistics(Base, BaseModel):
3627 class Statistics(Base, BaseModel):
3628 __tablename__ = 'statistics'
3628 __tablename__ = 'statistics'
3629 __table_args__ = (
3629 __table_args__ = (
3630 base_table_args
3630 base_table_args
3631 )
3631 )
3632
3632
3633 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3633 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3634 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3634 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3635 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3635 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3636 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3636 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3637 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3637 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3638 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3638 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3639
3639
3640 repository = relationship('Repository', single_parent=True, viewonly=True)
3640 repository = relationship('Repository', single_parent=True, viewonly=True)
3641
3641
3642
3642
3643 class UserFollowing(Base, BaseModel):
3643 class UserFollowing(Base, BaseModel):
3644 __tablename__ = 'user_followings'
3644 __tablename__ = 'user_followings'
3645 __table_args__ = (
3645 __table_args__ = (
3646 UniqueConstraint('user_id', 'follows_repository_id'),
3646 UniqueConstraint('user_id', 'follows_repository_id'),
3647 UniqueConstraint('user_id', 'follows_user_id'),
3647 UniqueConstraint('user_id', 'follows_user_id'),
3648 base_table_args
3648 base_table_args
3649 )
3649 )
3650
3650
3651 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3651 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3653 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3653 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3654 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3654 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3655 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3655 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3656
3656
3657 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3657 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3658
3658
3659 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3659 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3660 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3660 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3661
3661
3662 @classmethod
3662 @classmethod
3663 def get_repo_followers(cls, repo_id):
3663 def get_repo_followers(cls, repo_id):
3664 return cls.query().filter(cls.follows_repo_id == repo_id)
3664 return cls.query().filter(cls.follows_repo_id == repo_id)
3665
3665
3666
3666
3667 class CacheKey(Base, BaseModel):
3667 class CacheKey(Base, BaseModel):
3668 __tablename__ = 'cache_invalidation'
3668 __tablename__ = 'cache_invalidation'
3669 __table_args__ = (
3669 __table_args__ = (
3670 UniqueConstraint('cache_key'),
3670 UniqueConstraint('cache_key'),
3671 Index('key_idx', 'cache_key'),
3671 Index('key_idx', 'cache_key'),
3672 Index('cache_args_idx', 'cache_args'),
3672 Index('cache_args_idx', 'cache_args'),
3673 base_table_args,
3673 base_table_args,
3674 )
3674 )
3675
3675
3676 CACHE_TYPE_FEED = 'FEED'
3676 CACHE_TYPE_FEED = 'FEED'
3677
3677
3678 # namespaces used to register process/thread aware caches
3678 # namespaces used to register process/thread aware caches
3679 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3679 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3680
3680
3681 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3681 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3682 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3682 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3683 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3683 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3684 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3684 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3685 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3685 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3686
3686
3687 def __init__(self, cache_key, cache_args='', cache_state_uid=None, cache_active=False):
3687 def __init__(self, cache_key, cache_args='', cache_state_uid=None, cache_active=False):
3688 self.cache_key = cache_key
3688 self.cache_key = cache_key
3689 self.cache_args = cache_args
3689 self.cache_args = cache_args
3690 self.cache_active = cache_active
3690 self.cache_active = cache_active
3691 # first key should be same for all entries, since all workers should share it
3691 # first key should be same for all entries, since all workers should share it
3692 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3692 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3693
3693
3694 def __repr__(self):
3694 def __repr__(self):
3695 return "<%s('%s:%s[%s]')>" % (
3695 return "<%s('%s:%s[%s]')>" % (
3696 self.cls_name,
3696 self.cls_name,
3697 self.cache_id, self.cache_key, self.cache_active)
3697 self.cache_id, self.cache_key, self.cache_active)
3698
3698
3699 def _cache_key_partition(self):
3699 def _cache_key_partition(self):
3700 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3700 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3701 return prefix, repo_name, suffix
3701 return prefix, repo_name, suffix
3702
3702
3703 def get_prefix(self):
3703 def get_prefix(self):
3704 """
3704 """
3705 Try to extract prefix from existing cache key. The key could consist
3705 Try to extract prefix from existing cache key. The key could consist
3706 of prefix, repo_name, suffix
3706 of prefix, repo_name, suffix
3707 """
3707 """
3708 # this returns prefix, repo_name, suffix
3708 # this returns prefix, repo_name, suffix
3709 return self._cache_key_partition()[0]
3709 return self._cache_key_partition()[0]
3710
3710
3711 def get_suffix(self):
3711 def get_suffix(self):
3712 """
3712 """
3713 get suffix that might have been used in _get_cache_key to
3713 get suffix that might have been used in _get_cache_key to
3714 generate self.cache_key. Only used for informational purposes
3714 generate self.cache_key. Only used for informational purposes
3715 in repo_edit.mako.
3715 in repo_edit.mako.
3716 """
3716 """
3717 # prefix, repo_name, suffix
3717 # prefix, repo_name, suffix
3718 return self._cache_key_partition()[2]
3718 return self._cache_key_partition()[2]
3719
3719
3720 @classmethod
3720 @classmethod
3721 def generate_new_state_uid(cls, based_on=None):
3721 def generate_new_state_uid(cls, based_on=None):
3722 if based_on:
3722 if based_on:
3723 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3723 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3724 else:
3724 else:
3725 return str(uuid.uuid4())
3725 return str(uuid.uuid4())
3726
3726
3727 @classmethod
3727 @classmethod
3728 def delete_all_cache(cls):
3728 def delete_all_cache(cls):
3729 """
3729 """
3730 Delete all cache keys from database.
3730 Delete all cache keys from database.
3731 Should only be run when all instances are down and all entries
3731 Should only be run when all instances are down and all entries
3732 thus stale.
3732 thus stale.
3733 """
3733 """
3734 cls.query().delete()
3734 cls.query().delete()
3735 Session().commit()
3735 Session().commit()
3736
3736
3737 @classmethod
3737 @classmethod
3738 def set_invalidate(cls, cache_uid, delete=False):
3738 def set_invalidate(cls, cache_uid, delete=False):
3739 """
3739 """
3740 Mark all caches of a repo as invalid in the database.
3740 Mark all caches of a repo as invalid in the database.
3741 """
3741 """
3742 try:
3742 try:
3743 qry = Session().query(cls).filter(cls.cache_key == cache_uid)
3743 qry = Session().query(cls).filter(cls.cache_key == cache_uid)
3744 if delete:
3744 if delete:
3745 qry.delete()
3745 qry.delete()
3746 log.debug('cache objects deleted for cache args %s',
3746 log.debug('cache objects deleted for cache args %s',
3747 safe_str(cache_uid))
3747 safe_str(cache_uid))
3748 else:
3748 else:
3749 new_uid = cls.generate_new_state_uid()
3749 new_uid = cls.generate_new_state_uid()
3750 qry.update({"cache_state_uid": new_uid,
3750 qry.update({"cache_state_uid": new_uid,
3751 "cache_args": f"repo_state:{time.time()}"})
3751 "cache_args": f"repo_state:{time.time()}"})
3752 log.debug('cache object %s set new UID %s',
3752 log.debug('cache object %s set new UID %s',
3753 safe_str(cache_uid), new_uid)
3753 safe_str(cache_uid), new_uid)
3754
3754
3755 Session().commit()
3755 Session().commit()
3756 except Exception:
3756 except Exception:
3757 log.exception(
3757 log.exception(
3758 'Cache key invalidation failed for cache args %s',
3758 'Cache key invalidation failed for cache args %s',
3759 safe_str(cache_uid))
3759 safe_str(cache_uid))
3760 Session().rollback()
3760 Session().rollback()
3761
3761
3762 @classmethod
3762 @classmethod
3763 def get_active_cache(cls, cache_key):
3763 def get_active_cache(cls, cache_key):
3764 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3764 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3765 if inv_obj:
3765 if inv_obj:
3766 return inv_obj
3766 return inv_obj
3767 return None
3767 return None
3768
3768
3769 @classmethod
3769 @classmethod
3770 def get_namespace_map(cls, namespace):
3770 def get_namespace_map(cls, namespace):
3771 return {
3771 return {
3772 x.cache_key: x
3772 x.cache_key: x
3773 for x in cls.query().filter(cls.cache_args == namespace)}
3773 for x in cls.query().filter(cls.cache_args == namespace)}
3774
3774
3775
3775
3776 class ChangesetComment(Base, BaseModel):
3776 class ChangesetComment(Base, BaseModel):
3777 __tablename__ = 'changeset_comments'
3777 __tablename__ = 'changeset_comments'
3778 __table_args__ = (
3778 __table_args__ = (
3779 Index('cc_revision_idx', 'revision'),
3779 Index('cc_revision_idx', 'revision'),
3780 base_table_args,
3780 base_table_args,
3781 )
3781 )
3782
3782
3783 COMMENT_OUTDATED = 'comment_outdated'
3783 COMMENT_OUTDATED = 'comment_outdated'
3784 COMMENT_TYPE_NOTE = 'note'
3784 COMMENT_TYPE_NOTE = 'note'
3785 COMMENT_TYPE_TODO = 'todo'
3785 COMMENT_TYPE_TODO = 'todo'
3786 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3786 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3787
3787
3788 OP_IMMUTABLE = 'immutable'
3788 OP_IMMUTABLE = 'immutable'
3789 OP_CHANGEABLE = 'changeable'
3789 OP_CHANGEABLE = 'changeable'
3790
3790
3791 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3791 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3792 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3792 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3793 revision = Column('revision', String(40), nullable=True)
3793 revision = Column('revision', String(40), nullable=True)
3794 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3794 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3795 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3795 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3796 line_no = Column('line_no', Unicode(10), nullable=True)
3796 line_no = Column('line_no', Unicode(10), nullable=True)
3797 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3797 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3798 f_path = Column('f_path', Unicode(1000), nullable=True)
3798 f_path = Column('f_path', Unicode(1000), nullable=True)
3799 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3799 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3800 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3800 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3801 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3801 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3802 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3802 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3803 renderer = Column('renderer', Unicode(64), nullable=True)
3803 renderer = Column('renderer', Unicode(64), nullable=True)
3804 display_state = Column('display_state', Unicode(128), nullable=True)
3804 display_state = Column('display_state', Unicode(128), nullable=True)
3805 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3805 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3806 draft = Column('draft', Boolean(), nullable=True, default=False)
3806 draft = Column('draft', Boolean(), nullable=True, default=False)
3807
3807
3808 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3808 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3809 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3809 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3810
3810
3811 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3811 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3812 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3812 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3813
3813
3814 author = relationship('User', lazy='select', back_populates='user_comments')
3814 author = relationship('User', lazy='select', back_populates='user_comments')
3815 repo = relationship('Repository', back_populates='comments')
3815 repo = relationship('Repository', back_populates='comments')
3816 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3816 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3817 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3817 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3818 pull_request_version = relationship('PullRequestVersion', lazy='select')
3818 pull_request_version = relationship('PullRequestVersion', lazy='select')
3819 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3819 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3820
3820
3821 @classmethod
3821 @classmethod
3822 def get_users(cls, revision=None, pull_request_id=None):
3822 def get_users(cls, revision=None, pull_request_id=None):
3823 """
3823 """
3824 Returns user associated with this ChangesetComment. ie those
3824 Returns user associated with this ChangesetComment. ie those
3825 who actually commented
3825 who actually commented
3826
3826
3827 :param cls:
3827 :param cls:
3828 :param revision:
3828 :param revision:
3829 """
3829 """
3830 q = Session().query(User).join(ChangesetComment.author)
3830 q = Session().query(User).join(ChangesetComment.author)
3831 if revision:
3831 if revision:
3832 q = q.filter(cls.revision == revision)
3832 q = q.filter(cls.revision == revision)
3833 elif pull_request_id:
3833 elif pull_request_id:
3834 q = q.filter(cls.pull_request_id == pull_request_id)
3834 q = q.filter(cls.pull_request_id == pull_request_id)
3835 return q.all()
3835 return q.all()
3836
3836
3837 @classmethod
3837 @classmethod
3838 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3838 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3839 if pr_version is None:
3839 if pr_version is None:
3840 return 0
3840 return 0
3841
3841
3842 if versions is not None:
3842 if versions is not None:
3843 num_versions = [x.pull_request_version_id for x in versions]
3843 num_versions = [x.pull_request_version_id for x in versions]
3844
3844
3845 num_versions = num_versions or []
3845 num_versions = num_versions or []
3846 try:
3846 try:
3847 return num_versions.index(pr_version) + 1
3847 return num_versions.index(pr_version) + 1
3848 except (IndexError, ValueError):
3848 except (IndexError, ValueError):
3849 return 0
3849 return 0
3850
3850
3851 @property
3851 @property
3852 def outdated(self):
3852 def outdated(self):
3853 return self.display_state == self.COMMENT_OUTDATED
3853 return self.display_state == self.COMMENT_OUTDATED
3854
3854
3855 @property
3855 @property
3856 def outdated_js(self):
3856 def outdated_js(self):
3857 return str_json(self.display_state == self.COMMENT_OUTDATED)
3857 return str_json(self.display_state == self.COMMENT_OUTDATED)
3858
3858
3859 @property
3859 @property
3860 def immutable(self):
3860 def immutable(self):
3861 return self.immutable_state == self.OP_IMMUTABLE
3861 return self.immutable_state == self.OP_IMMUTABLE
3862
3862
3863 def outdated_at_version(self, version: int) -> bool:
3863 def outdated_at_version(self, version: int) -> bool:
3864 """
3864 """
3865 Checks if comment is outdated for given pull request version
3865 Checks if comment is outdated for given pull request version
3866 """
3866 """
3867
3867
3868 def version_check():
3868 def version_check():
3869 return self.pull_request_version_id and self.pull_request_version_id != version
3869 return self.pull_request_version_id and self.pull_request_version_id != version
3870
3870
3871 if self.is_inline:
3871 if self.is_inline:
3872 return self.outdated and version_check()
3872 return self.outdated and version_check()
3873 else:
3873 else:
3874 # general comments don't have .outdated set, also latest don't have a version
3874 # general comments don't have .outdated set, also latest don't have a version
3875 return version_check()
3875 return version_check()
3876
3876
3877 def outdated_at_version_js(self, version):
3877 def outdated_at_version_js(self, version):
3878 """
3878 """
3879 Checks if comment is outdated for given pull request version
3879 Checks if comment is outdated for given pull request version
3880 """
3880 """
3881 return str_json(self.outdated_at_version(version))
3881 return str_json(self.outdated_at_version(version))
3882
3882
3883 def older_than_version(self, version: int) -> bool:
3883 def older_than_version(self, version: int) -> bool:
3884 """
3884 """
3885 Checks if comment is made from a previous version than given.
3885 Checks if comment is made from a previous version than given.
3886 Assumes self.pull_request_version.pull_request_version_id is an integer if not None.
3886 Assumes self.pull_request_version.pull_request_version_id is an integer if not None.
3887 """
3887 """
3888
3888
3889 # If version is None, return False as the current version cannot be less than None
3889 # If version is None, return False as the current version cannot be less than None
3890 if version is None:
3890 if version is None:
3891 return False
3891 return False
3892
3892
3893 # Ensure that the version is an integer to prevent TypeError on comparison
3893 # Ensure that the version is an integer to prevent TypeError on comparison
3894 if not isinstance(version, int):
3894 if not isinstance(version, int):
3895 raise ValueError("The provided version must be an integer.")
3895 raise ValueError("The provided version must be an integer.")
3896
3896
3897 # Initialize current version to 0 or pull_request_version_id if it's available
3897 # Initialize current version to 0 or pull_request_version_id if it's available
3898 cur_ver = 0
3898 cur_ver = 0
3899 if self.pull_request_version and self.pull_request_version.pull_request_version_id is not None:
3899 if self.pull_request_version and self.pull_request_version.pull_request_version_id is not None:
3900 cur_ver = self.pull_request_version.pull_request_version_id
3900 cur_ver = self.pull_request_version.pull_request_version_id
3901
3901
3902 # Return True if the current version is less than the given version
3902 # Return True if the current version is less than the given version
3903 return cur_ver < version
3903 return cur_ver < version
3904
3904
3905 def older_than_version_js(self, version):
3905 def older_than_version_js(self, version):
3906 """
3906 """
3907 Checks if comment is made from previous version than given
3907 Checks if comment is made from previous version than given
3908 """
3908 """
3909 return str_json(self.older_than_version(version))
3909 return str_json(self.older_than_version(version))
3910
3910
3911 @property
3911 @property
3912 def commit_id(self):
3912 def commit_id(self):
3913 """New style naming to stop using .revision"""
3913 """New style naming to stop using .revision"""
3914 return self.revision
3914 return self.revision
3915
3915
3916 @property
3916 @property
3917 def resolved(self):
3917 def resolved(self):
3918 return self.resolved_by[0] if self.resolved_by else None
3918 return self.resolved_by[0] if self.resolved_by else None
3919
3919
3920 @property
3920 @property
3921 def is_todo(self):
3921 def is_todo(self):
3922 return self.comment_type == self.COMMENT_TYPE_TODO
3922 return self.comment_type == self.COMMENT_TYPE_TODO
3923
3923
3924 @property
3924 @property
3925 def is_inline(self):
3925 def is_inline(self):
3926 if self.line_no and self.f_path:
3926 if self.line_no and self.f_path:
3927 return True
3927 return True
3928 return False
3928 return False
3929
3929
3930 @property
3930 @property
3931 def last_version(self):
3931 def last_version(self):
3932 version = 0
3932 version = 0
3933 if self.history:
3933 if self.history:
3934 version = self.history[-1].version
3934 version = self.history[-1].version
3935 return version
3935 return version
3936
3936
3937 def get_index_version(self, versions):
3937 def get_index_version(self, versions):
3938 return self.get_index_from_version(
3938 return self.get_index_from_version(
3939 self.pull_request_version_id, versions)
3939 self.pull_request_version_id, versions)
3940
3940
3941 @property
3941 @property
3942 def review_status(self):
3942 def review_status(self):
3943 if self.status_change:
3943 if self.status_change:
3944 return self.status_change[0].status
3944 return self.status_change[0].status
3945
3945
3946 @property
3946 @property
3947 def review_status_lbl(self):
3947 def review_status_lbl(self):
3948 if self.status_change:
3948 if self.status_change:
3949 return self.status_change[0].status_lbl
3949 return self.status_change[0].status_lbl
3950
3950
3951 def __repr__(self):
3951 def __repr__(self):
3952 if self.comment_id:
3952 if self.comment_id:
3953 return f'<DB:Comment #{self.comment_id}>'
3953 return f'<DB:Comment #{self.comment_id}>'
3954 else:
3954 else:
3955 return f'<DB:Comment at {id(self)!r}>'
3955 return f'<DB:Comment at {id(self)!r}>'
3956
3956
3957 def get_api_data(self):
3957 def get_api_data(self):
3958 comment = self
3958 comment = self
3959
3959
3960 data = {
3960 data = {
3961 'comment_id': comment.comment_id,
3961 'comment_id': comment.comment_id,
3962 'comment_type': comment.comment_type,
3962 'comment_type': comment.comment_type,
3963 'comment_text': comment.text,
3963 'comment_text': comment.text,
3964 'comment_status': comment.status_change,
3964 'comment_status': comment.status_change,
3965 'comment_f_path': comment.f_path,
3965 'comment_f_path': comment.f_path,
3966 'comment_lineno': comment.line_no,
3966 'comment_lineno': comment.line_no,
3967 'comment_author': comment.author,
3967 'comment_author': comment.author,
3968 'comment_created_on': comment.created_on,
3968 'comment_created_on': comment.created_on,
3969 'comment_resolved_by': self.resolved,
3969 'comment_resolved_by': self.resolved,
3970 'comment_commit_id': comment.revision,
3970 'comment_commit_id': comment.revision,
3971 'comment_pull_request_id': comment.pull_request_id,
3971 'comment_pull_request_id': comment.pull_request_id,
3972 'comment_last_version': self.last_version
3972 'comment_last_version': self.last_version
3973 }
3973 }
3974 return data
3974 return data
3975
3975
3976 def __json__(self):
3976 def __json__(self):
3977 data = dict()
3977 data = dict()
3978 data.update(self.get_api_data())
3978 data.update(self.get_api_data())
3979 return data
3979 return data
3980
3980
3981
3981
3982 class ChangesetCommentHistory(Base, BaseModel):
3982 class ChangesetCommentHistory(Base, BaseModel):
3983 __tablename__ = 'changeset_comments_history'
3983 __tablename__ = 'changeset_comments_history'
3984 __table_args__ = (
3984 __table_args__ = (
3985 Index('cch_comment_id_idx', 'comment_id'),
3985 Index('cch_comment_id_idx', 'comment_id'),
3986 base_table_args,
3986 base_table_args,
3987 )
3987 )
3988
3988
3989 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3989 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3990 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3990 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3991 version = Column("version", Integer(), nullable=False, default=0)
3991 version = Column("version", Integer(), nullable=False, default=0)
3992 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3992 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3993 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3993 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3995 deleted = Column('deleted', Boolean(), default=False)
3995 deleted = Column('deleted', Boolean(), default=False)
3996
3996
3997 author = relationship('User', lazy='joined')
3997 author = relationship('User', lazy='joined')
3998 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3998 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3999
3999
4000 @classmethod
4000 @classmethod
4001 def get_version(cls, comment_id):
4001 def get_version(cls, comment_id):
4002 q = Session().query(ChangesetCommentHistory).filter(
4002 q = Session().query(ChangesetCommentHistory).filter(
4003 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
4003 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
4004 if q.count() == 0:
4004 if q.count() == 0:
4005 return 1
4005 return 1
4006 elif q.count() >= q[0].version:
4006 elif q.count() >= q[0].version:
4007 return q.count() + 1
4007 return q.count() + 1
4008 else:
4008 else:
4009 return q[0].version + 1
4009 return q[0].version + 1
4010
4010
4011
4011
4012 class ChangesetStatus(Base, BaseModel):
4012 class ChangesetStatus(Base, BaseModel):
4013 __tablename__ = 'changeset_statuses'
4013 __tablename__ = 'changeset_statuses'
4014 __table_args__ = (
4014 __table_args__ = (
4015 Index('cs_revision_idx', 'revision'),
4015 Index('cs_revision_idx', 'revision'),
4016 Index('cs_version_idx', 'version'),
4016 Index('cs_version_idx', 'version'),
4017 UniqueConstraint('repo_id', 'revision', 'version'),
4017 UniqueConstraint('repo_id', 'revision', 'version'),
4018 base_table_args
4018 base_table_args
4019 )
4019 )
4020
4020
4021 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4021 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4022 STATUS_APPROVED = 'approved'
4022 STATUS_APPROVED = 'approved'
4023 STATUS_REJECTED = 'rejected'
4023 STATUS_REJECTED = 'rejected'
4024 STATUS_UNDER_REVIEW = 'under_review'
4024 STATUS_UNDER_REVIEW = 'under_review'
4025
4025
4026 STATUSES = [
4026 STATUSES = [
4027 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4027 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4028 (STATUS_APPROVED, _("Approved")),
4028 (STATUS_APPROVED, _("Approved")),
4029 (STATUS_REJECTED, _("Rejected")),
4029 (STATUS_REJECTED, _("Rejected")),
4030 (STATUS_UNDER_REVIEW, _("Under Review")),
4030 (STATUS_UNDER_REVIEW, _("Under Review")),
4031 ]
4031 ]
4032
4032
4033 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4033 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4034 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4034 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4036 revision = Column('revision', String(40), nullable=False)
4036 revision = Column('revision', String(40), nullable=False)
4037 status = Column('status', String(128), nullable=False, default=DEFAULT)
4037 status = Column('status', String(128), nullable=False, default=DEFAULT)
4038 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4038 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4039 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4039 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4040 version = Column('version', Integer(), nullable=False, default=0)
4040 version = Column('version', Integer(), nullable=False, default=0)
4041 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4041 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4042
4042
4043 author = relationship('User', lazy='select')
4043 author = relationship('User', lazy='select')
4044 repo = relationship('Repository', lazy='select')
4044 repo = relationship('Repository', lazy='select')
4045 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4045 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4046 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4046 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4047
4047
4048 def __repr__(self):
4048 def __repr__(self):
4049 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4049 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4050
4050
4051 @classmethod
4051 @classmethod
4052 def get_status_lbl(cls, value):
4052 def get_status_lbl(cls, value):
4053 return dict(cls.STATUSES).get(value)
4053 return dict(cls.STATUSES).get(value)
4054
4054
4055 @property
4055 @property
4056 def status_lbl(self):
4056 def status_lbl(self):
4057 return ChangesetStatus.get_status_lbl(self.status)
4057 return ChangesetStatus.get_status_lbl(self.status)
4058
4058
4059 def get_api_data(self):
4059 def get_api_data(self):
4060 status = self
4060 status = self
4061 data = {
4061 data = {
4062 'status_id': status.changeset_status_id,
4062 'status_id': status.changeset_status_id,
4063 'status': status.status,
4063 'status': status.status,
4064 }
4064 }
4065 return data
4065 return data
4066
4066
4067 def __json__(self):
4067 def __json__(self):
4068 data = dict()
4068 data = dict()
4069 data.update(self.get_api_data())
4069 data.update(self.get_api_data())
4070 return data
4070 return data
4071
4071
4072
4072
4073 class _SetState(object):
4073 class _SetState(object):
4074 """
4074 """
4075 Context processor allowing changing state for sensitive operation such as
4075 Context processor allowing changing state for sensitive operation such as
4076 pull request update or merge
4076 pull request update or merge
4077 """
4077 """
4078
4078
4079 def __init__(self, pull_request, pr_state, back_state=None):
4079 def __init__(self, pull_request, pr_state, back_state=None):
4080 self._pr = pull_request
4080 self._pr = pull_request
4081 self._org_state = back_state or pull_request.pull_request_state
4081 self._org_state = back_state or pull_request.pull_request_state
4082 self._pr_state = pr_state
4082 self._pr_state = pr_state
4083 self._current_state = None
4083 self._current_state = None
4084
4084
4085 def __enter__(self):
4085 def __enter__(self):
4086 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4086 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4087 self._pr, self._pr_state)
4087 self._pr, self._pr_state)
4088 self.set_pr_state(self._pr_state)
4088 self.set_pr_state(self._pr_state)
4089 return self
4089 return self
4090
4090
4091 def __exit__(self, exc_type, exc_val, exc_tb):
4091 def __exit__(self, exc_type, exc_val, exc_tb):
4092 if exc_val is not None or exc_type is not None:
4092 if exc_val is not None or exc_type is not None:
4093 log.error(traceback.format_tb(exc_tb))
4093 log.error(traceback.format_tb(exc_tb))
4094 return None
4094 return None
4095
4095
4096 self.set_pr_state(self._org_state)
4096 self.set_pr_state(self._org_state)
4097 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4097 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4098 self._pr, self._org_state)
4098 self._pr, self._org_state)
4099
4099
4100 @property
4100 @property
4101 def state(self):
4101 def state(self):
4102 return self._current_state
4102 return self._current_state
4103
4103
4104 def set_pr_state(self, pr_state):
4104 def set_pr_state(self, pr_state):
4105 try:
4105 try:
4106 self._pr.pull_request_state = pr_state
4106 self._pr.pull_request_state = pr_state
4107 Session().add(self._pr)
4107 Session().add(self._pr)
4108 Session().commit()
4108 Session().commit()
4109 self._current_state = pr_state
4109 self._current_state = pr_state
4110 except Exception:
4110 except Exception:
4111 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4111 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4112 raise
4112 raise
4113
4113
4114
4114
4115 class _PullRequestBase(BaseModel):
4115 class _PullRequestBase(BaseModel):
4116 """
4116 """
4117 Common attributes of pull request and version entries.
4117 Common attributes of pull request and version entries.
4118 """
4118 """
4119
4119
4120 # .status values
4120 # .status values
4121 STATUS_NEW = 'new'
4121 STATUS_NEW = 'new'
4122 STATUS_OPEN = 'open'
4122 STATUS_OPEN = 'open'
4123 STATUS_CLOSED = 'closed'
4123 STATUS_CLOSED = 'closed'
4124
4124
4125 # available states
4125 # available states
4126 STATE_CREATING = 'creating'
4126 STATE_CREATING = 'creating'
4127 STATE_UPDATING = 'updating'
4127 STATE_UPDATING = 'updating'
4128 STATE_MERGING = 'merging'
4128 STATE_MERGING = 'merging'
4129 STATE_CREATED = 'created'
4129 STATE_CREATED = 'created'
4130
4130
4131 title = Column('title', Unicode(255), nullable=True)
4131 title = Column('title', Unicode(255), nullable=True)
4132 description = Column(
4132 description = Column(
4133 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4133 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4134 nullable=True)
4134 nullable=True)
4135 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4135 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4136
4136
4137 # new/open/closed status of pull request (not approve/reject/etc)
4137 # new/open/closed status of pull request (not approve/reject/etc)
4138 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4138 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4139 created_on = Column(
4139 created_on = Column(
4140 'created_on', DateTime(timezone=False), nullable=False,
4140 'created_on', DateTime(timezone=False), nullable=False,
4141 default=datetime.datetime.now)
4141 default=datetime.datetime.now)
4142 updated_on = Column(
4142 updated_on = Column(
4143 'updated_on', DateTime(timezone=False), nullable=False,
4143 'updated_on', DateTime(timezone=False), nullable=False,
4144 default=datetime.datetime.now)
4144 default=datetime.datetime.now)
4145
4145
4146 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4146 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4147
4147
4148 @declared_attr
4148 @declared_attr
4149 def user_id(cls):
4149 def user_id(cls):
4150 return Column(
4150 return Column(
4151 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4151 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4152 unique=None)
4152 unique=None)
4153
4153
4154 # 500 revisions max
4154 # 500 revisions max
4155 _revisions = Column(
4155 _revisions = Column(
4156 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4156 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4157
4157
4158 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4158 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4159
4159
4160 @declared_attr
4160 @declared_attr
4161 def source_repo_id(cls):
4161 def source_repo_id(cls):
4162 # TODO: dan: rename column to source_repo_id
4162 # TODO: dan: rename column to source_repo_id
4163 return Column(
4163 return Column(
4164 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4164 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4165 nullable=False)
4165 nullable=False)
4166
4166
4167 @declared_attr
4167 @declared_attr
4168 def pr_source(cls):
4168 def pr_source(cls):
4169 return relationship(
4169 return relationship(
4170 'Repository',
4170 'Repository',
4171 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4171 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4172 overlaps="pull_requests_source"
4172 overlaps="pull_requests_source"
4173 )
4173 )
4174
4174
4175 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4175 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4176
4176
4177 @hybrid_property
4177 @hybrid_property
4178 def source_ref(self):
4178 def source_ref(self):
4179 return self._source_ref
4179 return self._source_ref
4180
4180
4181 @source_ref.setter
4181 @source_ref.setter
4182 def source_ref(self, val):
4182 def source_ref(self, val):
4183 parts = (val or '').split(':')
4183 parts = (val or '').split(':')
4184 if len(parts) != 3:
4184 if len(parts) != 3:
4185 raise ValueError(
4185 raise ValueError(
4186 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4186 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4187 self._source_ref = safe_str(val)
4187 self._source_ref = safe_str(val)
4188
4188
4189 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4189 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4190
4190
4191 @hybrid_property
4191 @hybrid_property
4192 def target_ref(self):
4192 def target_ref(self):
4193 return self._target_ref
4193 return self._target_ref
4194
4194
4195 @target_ref.setter
4195 @target_ref.setter
4196 def target_ref(self, val):
4196 def target_ref(self, val):
4197 parts = (val or '').split(':')
4197 parts = (val or '').split(':')
4198 if len(parts) != 3:
4198 if len(parts) != 3:
4199 raise ValueError(
4199 raise ValueError(
4200 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4200 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4201 self._target_ref = safe_str(val)
4201 self._target_ref = safe_str(val)
4202
4202
4203 @declared_attr
4203 @declared_attr
4204 def target_repo_id(cls):
4204 def target_repo_id(cls):
4205 # TODO: dan: rename column to target_repo_id
4205 # TODO: dan: rename column to target_repo_id
4206 return Column(
4206 return Column(
4207 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4207 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4208 nullable=False)
4208 nullable=False)
4209
4209
4210 @declared_attr
4210 @declared_attr
4211 def pr_target(cls):
4211 def pr_target(cls):
4212 return relationship(
4212 return relationship(
4213 'Repository',
4213 'Repository',
4214 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4214 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4215 overlaps="pull_requests_target"
4215 overlaps="pull_requests_target"
4216 )
4216 )
4217
4217
4218 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4218 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4219
4219
4220 # TODO: dan: rename column to last_merge_source_rev
4220 # TODO: dan: rename column to last_merge_source_rev
4221 _last_merge_source_rev = Column(
4221 _last_merge_source_rev = Column(
4222 'last_merge_org_rev', String(40), nullable=True)
4222 'last_merge_org_rev', String(40), nullable=True)
4223 # TODO: dan: rename column to last_merge_target_rev
4223 # TODO: dan: rename column to last_merge_target_rev
4224 _last_merge_target_rev = Column(
4224 _last_merge_target_rev = Column(
4225 'last_merge_other_rev', String(40), nullable=True)
4225 'last_merge_other_rev', String(40), nullable=True)
4226 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4226 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4227 last_merge_metadata = Column(
4227 last_merge_metadata = Column(
4228 'last_merge_metadata', MutationObj.as_mutable(
4228 'last_merge_metadata', MutationObj.as_mutable(
4229 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4229 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4230
4230
4231 merge_rev = Column('merge_rev', String(40), nullable=True)
4231 merge_rev = Column('merge_rev', String(40), nullable=True)
4232
4232
4233 reviewer_data = Column(
4233 reviewer_data = Column(
4234 'reviewer_data_json', MutationObj.as_mutable(
4234 'reviewer_data_json', MutationObj.as_mutable(
4235 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4235 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4236
4236
4237 @property
4237 @property
4238 def reviewer_data_json(self):
4238 def reviewer_data_json(self):
4239 return str_json(self.reviewer_data)
4239 return str_json(self.reviewer_data)
4240
4240
4241 @property
4241 @property
4242 def last_merge_metadata_parsed(self):
4242 def last_merge_metadata_parsed(self):
4243 metadata = {}
4243 metadata = {}
4244 if not self.last_merge_metadata:
4244 if not self.last_merge_metadata:
4245 return metadata
4245 return metadata
4246
4246
4247 if hasattr(self.last_merge_metadata, 'de_coerce'):
4247 if hasattr(self.last_merge_metadata, 'de_coerce'):
4248 for k, v in self.last_merge_metadata.de_coerce().items():
4248 for k, v in self.last_merge_metadata.de_coerce().items():
4249 if k in ['target_ref', 'source_ref']:
4249 if k in ['target_ref', 'source_ref']:
4250 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4250 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4251 else:
4251 else:
4252 if hasattr(v, 'de_coerce'):
4252 if hasattr(v, 'de_coerce'):
4253 metadata[k] = v.de_coerce()
4253 metadata[k] = v.de_coerce()
4254 else:
4254 else:
4255 metadata[k] = v
4255 metadata[k] = v
4256 return metadata
4256 return metadata
4257
4257
4258 @property
4258 @property
4259 def work_in_progress(self):
4259 def work_in_progress(self):
4260 """checks if pull request is work in progress by checking the title"""
4260 """checks if pull request is work in progress by checking the title"""
4261 title = self.title.upper()
4261 title = self.title.upper()
4262 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4262 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4263 return True
4263 return True
4264 return False
4264 return False
4265
4265
4266 @property
4266 @property
4267 def title_safe(self):
4267 def title_safe(self):
4268 return self.title\
4268 return self.title\
4269 .replace('{', '{{')\
4269 .replace('{', '{{')\
4270 .replace('}', '}}')
4270 .replace('}', '}}')
4271
4271
4272 @hybrid_property
4272 @hybrid_property
4273 def description_safe(self):
4273 def description_safe(self):
4274 from rhodecode.lib import helpers as h
4274 from rhodecode.lib import helpers as h
4275 return h.escape(self.description)
4275 return h.escape(self.description)
4276
4276
4277 @hybrid_property
4277 @hybrid_property
4278 def revisions(self):
4278 def revisions(self):
4279 return self._revisions.split(':') if self._revisions else []
4279 return self._revisions.split(':') if self._revisions else []
4280
4280
4281 @revisions.setter
4281 @revisions.setter
4282 def revisions(self, val):
4282 def revisions(self, val):
4283 self._revisions = ':'.join(val)
4283 self._revisions = ':'.join(val)
4284
4284
4285 @hybrid_property
4285 @hybrid_property
4286 def last_merge_status(self):
4286 def last_merge_status(self):
4287 return safe_int(self._last_merge_status)
4287 return safe_int(self._last_merge_status)
4288
4288
4289 @last_merge_status.setter
4289 @last_merge_status.setter
4290 def last_merge_status(self, val):
4290 def last_merge_status(self, val):
4291 self._last_merge_status = val
4291 self._last_merge_status = val
4292
4292
4293 @declared_attr
4293 @declared_attr
4294 def author(cls):
4294 def author(cls):
4295 return relationship(
4295 return relationship(
4296 'User', lazy='joined',
4296 'User', lazy='joined',
4297 #TODO, problem that is somehow :?
4297 #TODO, problem that is somehow :?
4298 #back_populates='user_pull_requests'
4298 #back_populates='user_pull_requests'
4299 )
4299 )
4300
4300
4301 @declared_attr
4301 @declared_attr
4302 def source_repo(cls):
4302 def source_repo(cls):
4303 return relationship(
4303 return relationship(
4304 'Repository',
4304 'Repository',
4305 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4305 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4306 overlaps="pr_source"
4306 overlaps="pr_source"
4307 )
4307 )
4308
4308
4309 @property
4309 @property
4310 def source_ref_parts(self):
4310 def source_ref_parts(self):
4311 return self.unicode_to_reference(self.source_ref)
4311 return self.unicode_to_reference(self.source_ref)
4312
4312
4313 @declared_attr
4313 @declared_attr
4314 def target_repo(cls):
4314 def target_repo(cls):
4315 return relationship(
4315 return relationship(
4316 'Repository',
4316 'Repository',
4317 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4317 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4318 overlaps="pr_target"
4318 overlaps="pr_target"
4319 )
4319 )
4320
4320
4321 @property
4321 @property
4322 def target_ref_parts(self):
4322 def target_ref_parts(self):
4323 return self.unicode_to_reference(self.target_ref)
4323 return self.unicode_to_reference(self.target_ref)
4324
4324
4325 @property
4325 @property
4326 def shadow_merge_ref(self):
4326 def shadow_merge_ref(self):
4327 return self.unicode_to_reference(self._shadow_merge_ref)
4327 return self.unicode_to_reference(self._shadow_merge_ref)
4328
4328
4329 @shadow_merge_ref.setter
4329 @shadow_merge_ref.setter
4330 def shadow_merge_ref(self, ref):
4330 def shadow_merge_ref(self, ref):
4331 self._shadow_merge_ref = self.reference_to_unicode(ref)
4331 self._shadow_merge_ref = self.reference_to_unicode(ref)
4332
4332
4333 @staticmethod
4333 @staticmethod
4334 def unicode_to_reference(raw):
4334 def unicode_to_reference(raw):
4335 return unicode_to_reference(raw)
4335 return unicode_to_reference(raw)
4336
4336
4337 @staticmethod
4337 @staticmethod
4338 def reference_to_unicode(ref):
4338 def reference_to_unicode(ref):
4339 return reference_to_unicode(ref)
4339 return reference_to_unicode(ref)
4340
4340
4341 def get_api_data(self, with_merge_state=True):
4341 def get_api_data(self, with_merge_state=True):
4342 from rhodecode.model.pull_request import PullRequestModel
4342 from rhodecode.model.pull_request import PullRequestModel
4343
4343
4344 pull_request = self
4344 pull_request = self
4345 if with_merge_state:
4345 if with_merge_state:
4346 merge_response, merge_status, msg = \
4346 merge_response, merge_status, msg = \
4347 PullRequestModel().merge_status(pull_request)
4347 PullRequestModel().merge_status(pull_request)
4348 merge_state = {
4348 merge_state = {
4349 'status': merge_status,
4349 'status': merge_status,
4350 'message': safe_str(msg),
4350 'message': safe_str(msg),
4351 }
4351 }
4352 else:
4352 else:
4353 merge_state = {'status': 'not_available',
4353 merge_state = {'status': 'not_available',
4354 'message': 'not_available'}
4354 'message': 'not_available'}
4355
4355
4356 merge_data = {
4356 merge_data = {
4357 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4357 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4358 'reference': (
4358 'reference': (
4359 pull_request.shadow_merge_ref.asdict()
4359 pull_request.shadow_merge_ref.asdict()
4360 if pull_request.shadow_merge_ref else None),
4360 if pull_request.shadow_merge_ref else None),
4361 }
4361 }
4362
4362
4363 data = {
4363 data = {
4364 'pull_request_id': pull_request.pull_request_id,
4364 'pull_request_id': pull_request.pull_request_id,
4365 'url': PullRequestModel().get_url(pull_request),
4365 'url': PullRequestModel().get_url(pull_request),
4366 'title': pull_request.title,
4366 'title': pull_request.title,
4367 'description': pull_request.description,
4367 'description': pull_request.description,
4368 'status': pull_request.status,
4368 'status': pull_request.status,
4369 'state': pull_request.pull_request_state,
4369 'state': pull_request.pull_request_state,
4370 'created_on': pull_request.created_on,
4370 'created_on': pull_request.created_on,
4371 'updated_on': pull_request.updated_on,
4371 'updated_on': pull_request.updated_on,
4372 'commit_ids': pull_request.revisions,
4372 'commit_ids': pull_request.revisions,
4373 'review_status': pull_request.calculated_review_status(),
4373 'review_status': pull_request.calculated_review_status(),
4374 'mergeable': merge_state,
4374 'mergeable': merge_state,
4375 'source': {
4375 'source': {
4376 'clone_url': pull_request.source_repo.clone_url(),
4376 'clone_url': pull_request.source_repo.clone_url(),
4377 'repository': pull_request.source_repo.repo_name,
4377 'repository': pull_request.source_repo.repo_name,
4378 'reference': {
4378 'reference': {
4379 'name': pull_request.source_ref_parts.name,
4379 'name': pull_request.source_ref_parts.name,
4380 'type': pull_request.source_ref_parts.type,
4380 'type': pull_request.source_ref_parts.type,
4381 'commit_id': pull_request.source_ref_parts.commit_id,
4381 'commit_id': pull_request.source_ref_parts.commit_id,
4382 },
4382 },
4383 },
4383 },
4384 'target': {
4384 'target': {
4385 'clone_url': pull_request.target_repo.clone_url(),
4385 'clone_url': pull_request.target_repo.clone_url(),
4386 'repository': pull_request.target_repo.repo_name,
4386 'repository': pull_request.target_repo.repo_name,
4387 'reference': {
4387 'reference': {
4388 'name': pull_request.target_ref_parts.name,
4388 'name': pull_request.target_ref_parts.name,
4389 'type': pull_request.target_ref_parts.type,
4389 'type': pull_request.target_ref_parts.type,
4390 'commit_id': pull_request.target_ref_parts.commit_id,
4390 'commit_id': pull_request.target_ref_parts.commit_id,
4391 },
4391 },
4392 },
4392 },
4393 'merge': merge_data,
4393 'merge': merge_data,
4394 'author': pull_request.author.get_api_data(include_secrets=False,
4394 'author': pull_request.author.get_api_data(include_secrets=False,
4395 details='basic'),
4395 details='basic'),
4396 'reviewers': [
4396 'reviewers': [
4397 {
4397 {
4398 'user': reviewer.get_api_data(include_secrets=False,
4398 'user': reviewer.get_api_data(include_secrets=False,
4399 details='basic'),
4399 details='basic'),
4400 'reasons': reasons,
4400 'reasons': reasons,
4401 'review_status': st[0][1].status if st else 'not_reviewed',
4401 'review_status': st[0][1].status if st else 'not_reviewed',
4402 }
4402 }
4403 for obj, reviewer, reasons, mandatory, st in
4403 for obj, reviewer, reasons, mandatory, st in
4404 pull_request.reviewers_statuses()
4404 pull_request.reviewers_statuses()
4405 ]
4405 ]
4406 }
4406 }
4407
4407
4408 return data
4408 return data
4409
4409
4410 def set_state(self, pull_request_state, final_state=None):
4410 def set_state(self, pull_request_state, final_state=None):
4411 """
4411 """
4412 # goes from initial state to updating to initial state.
4412 # goes from initial state to updating to initial state.
4413 # initial state can be changed by specifying back_state=
4413 # initial state can be changed by specifying back_state=
4414 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4414 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4415 pull_request.merge()
4415 pull_request.merge()
4416
4416
4417 :param pull_request_state:
4417 :param pull_request_state:
4418 :param final_state:
4418 :param final_state:
4419
4419
4420 """
4420 """
4421
4421
4422 return _SetState(self, pull_request_state, back_state=final_state)
4422 return _SetState(self, pull_request_state, back_state=final_state)
4423
4423
4424
4424
4425 class PullRequest(Base, _PullRequestBase):
4425 class PullRequest(Base, _PullRequestBase):
4426 __tablename__ = 'pull_requests'
4426 __tablename__ = 'pull_requests'
4427 __table_args__ = (
4427 __table_args__ = (
4428 base_table_args,
4428 base_table_args,
4429 )
4429 )
4430 LATEST_VER = 'latest'
4430 LATEST_VER = 'latest'
4431
4431
4432 pull_request_id = Column(
4432 pull_request_id = Column(
4433 'pull_request_id', Integer(), nullable=False, primary_key=True)
4433 'pull_request_id', Integer(), nullable=False, primary_key=True)
4434
4434
4435 def __repr__(self):
4435 def __repr__(self):
4436 if self.pull_request_id:
4436 if self.pull_request_id:
4437 return f'<DB:PullRequest #{self.pull_request_id}>'
4437 return f'<DB:PullRequest #{self.pull_request_id}>'
4438 else:
4438 else:
4439 return f'<DB:PullRequest at {id(self)!r}>'
4439 return f'<DB:PullRequest at {id(self)!r}>'
4440
4440
4441 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4441 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4442 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4442 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4443 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4443 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4444 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4444 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4445
4445
4446 @classmethod
4446 @classmethod
4447 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4447 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4448 internal_methods=None):
4448 internal_methods=None):
4449
4449
4450 class PullRequestDisplay(object):
4450 class PullRequestDisplay(object):
4451 """
4451 """
4452 Special object wrapper for showing PullRequest data via Versions
4452 Special object wrapper for showing PullRequest data via Versions
4453 It mimics PR object as close as possible. This is read only object
4453 It mimics PR object as close as possible. This is read only object
4454 just for display
4454 just for display
4455 """
4455 """
4456
4456
4457 def __init__(self, attrs, internal=None):
4457 def __init__(self, attrs, internal=None):
4458 self.attrs = attrs
4458 self.attrs = attrs
4459 # internal have priority over the given ones via attrs
4459 # internal have priority over the given ones via attrs
4460 self.internal = internal or ['versions']
4460 self.internal = internal or ['versions']
4461
4461
4462 def __getattr__(self, item):
4462 def __getattr__(self, item):
4463 if item in self.internal:
4463 if item in self.internal:
4464 return getattr(self, item)
4464 return getattr(self, item)
4465 try:
4465 try:
4466 return self.attrs[item]
4466 return self.attrs[item]
4467 except KeyError:
4467 except KeyError:
4468 raise AttributeError(
4468 raise AttributeError(
4469 '%s object has no attribute %s' % (self, item))
4469 '%s object has no attribute %s' % (self, item))
4470
4470
4471 def __repr__(self):
4471 def __repr__(self):
4472 pr_id = self.attrs.get('pull_request_id')
4472 pr_id = self.attrs.get('pull_request_id')
4473 return f'<DB:PullRequestDisplay #{pr_id}>'
4473 return f'<DB:PullRequestDisplay #{pr_id}>'
4474
4474
4475 def versions(self):
4475 def versions(self):
4476 return pull_request_obj.versions.order_by(
4476 return pull_request_obj.versions.order_by(
4477 PullRequestVersion.pull_request_version_id).all()
4477 PullRequestVersion.pull_request_version_id).all()
4478
4478
4479 def is_closed(self):
4479 def is_closed(self):
4480 return pull_request_obj.is_closed()
4480 return pull_request_obj.is_closed()
4481
4481
4482 def is_state_changing(self):
4482 def is_state_changing(self):
4483 return pull_request_obj.is_state_changing()
4483 return pull_request_obj.is_state_changing()
4484
4484
4485 @property
4485 @property
4486 def pull_request_version_id(self):
4486 def pull_request_version_id(self):
4487 return getattr(pull_request_obj, 'pull_request_version_id', None)
4487 return getattr(pull_request_obj, 'pull_request_version_id', None)
4488
4488
4489 @property
4489 @property
4490 def pull_request_last_version(self):
4490 def pull_request_last_version(self):
4491 return pull_request_obj.pull_request_last_version
4491 return pull_request_obj.pull_request_last_version
4492
4492
4493 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4493 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4494
4494
4495 attrs.author = StrictAttributeDict(
4495 attrs.author = StrictAttributeDict(
4496 pull_request_obj.author.get_api_data())
4496 pull_request_obj.author.get_api_data())
4497 if pull_request_obj.target_repo:
4497 if pull_request_obj.target_repo:
4498 attrs.target_repo = StrictAttributeDict(
4498 attrs.target_repo = StrictAttributeDict(
4499 pull_request_obj.target_repo.get_api_data())
4499 pull_request_obj.target_repo.get_api_data())
4500 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4500 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4501
4501
4502 if pull_request_obj.source_repo:
4502 if pull_request_obj.source_repo:
4503 attrs.source_repo = StrictAttributeDict(
4503 attrs.source_repo = StrictAttributeDict(
4504 pull_request_obj.source_repo.get_api_data())
4504 pull_request_obj.source_repo.get_api_data())
4505 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4505 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4506
4506
4507 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4507 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4508 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4508 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4509 attrs.revisions = pull_request_obj.revisions
4509 attrs.revisions = pull_request_obj.revisions
4510 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4510 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4511 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4511 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4512 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4512 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4513 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4513 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4514
4514
4515 return PullRequestDisplay(attrs, internal=internal_methods)
4515 return PullRequestDisplay(attrs, internal=internal_methods)
4516
4516
4517 def is_closed(self):
4517 def is_closed(self):
4518 return self.status == self.STATUS_CLOSED
4518 return self.status == self.STATUS_CLOSED
4519
4519
4520 def is_state_changing(self):
4520 def is_state_changing(self):
4521 return self.pull_request_state != PullRequest.STATE_CREATED
4521 return self.pull_request_state != PullRequest.STATE_CREATED
4522
4522
4523 def __json__(self):
4523 def __json__(self):
4524 return {
4524 return {
4525 'revisions': self.revisions,
4525 'revisions': self.revisions,
4526 'versions': self.versions_count
4526 'versions': self.versions_count
4527 }
4527 }
4528
4528
4529 def calculated_review_status(self):
4529 def calculated_review_status(self):
4530 from rhodecode.model.changeset_status import ChangesetStatusModel
4530 from rhodecode.model.changeset_status import ChangesetStatusModel
4531 return ChangesetStatusModel().calculated_review_status(self)
4531 return ChangesetStatusModel().calculated_review_status(self)
4532
4532
4533 def reviewers_statuses(self, user=None):
4533 def reviewers_statuses(self, user=None):
4534 from rhodecode.model.changeset_status import ChangesetStatusModel
4534 from rhodecode.model.changeset_status import ChangesetStatusModel
4535 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4535 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4536
4536
4537 def get_pull_request_reviewers(self, role=None):
4537 def get_pull_request_reviewers(self, role=None):
4538 qry = PullRequestReviewers.query()\
4538 qry = PullRequestReviewers.query()\
4539 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4539 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4540 if role:
4540 if role:
4541 qry = qry.filter(PullRequestReviewers.role == role)
4541 qry = qry.filter(PullRequestReviewers.role == role)
4542
4542
4543 return qry.all()
4543 return qry.all()
4544
4544
4545 @property
4545 @property
4546 def reviewers_count(self):
4546 def reviewers_count(self):
4547 qry = PullRequestReviewers.query()\
4547 qry = PullRequestReviewers.query()\
4548 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4548 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4549 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4549 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4550 return qry.count()
4550 return qry.count()
4551
4551
4552 @property
4552 @property
4553 def observers_count(self):
4553 def observers_count(self):
4554 qry = PullRequestReviewers.query()\
4554 qry = PullRequestReviewers.query()\
4555 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4555 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4556 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4556 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4557 return qry.count()
4557 return qry.count()
4558
4558
4559 def observers(self):
4559 def observers(self):
4560 qry = PullRequestReviewers.query()\
4560 qry = PullRequestReviewers.query()\
4561 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4561 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4562 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4562 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4563 .all()
4563 .all()
4564
4564
4565 for entry in qry:
4565 for entry in qry:
4566 yield entry, entry.user
4566 yield entry, entry.user
4567
4567
4568 @property
4568 @property
4569 def workspace_id(self):
4569 def workspace_id(self):
4570 from rhodecode.model.pull_request import PullRequestModel
4570 from rhodecode.model.pull_request import PullRequestModel
4571 return PullRequestModel()._workspace_id(self)
4571 return PullRequestModel()._workspace_id(self)
4572
4572
4573 def get_shadow_repo(self):
4573 def get_shadow_repo(self):
4574 workspace_id = self.workspace_id
4574 workspace_id = self.workspace_id
4575 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4575 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4576 if os.path.isdir(shadow_repository_path):
4576 if os.path.isdir(shadow_repository_path):
4577 vcs_obj = self.target_repo.scm_instance()
4577 vcs_obj = self.target_repo.scm_instance()
4578 return vcs_obj.get_shadow_instance(shadow_repository_path)
4578 return vcs_obj.get_shadow_instance(shadow_repository_path)
4579
4579
4580 @property
4580 @property
4581 def versions_count(self):
4581 def versions_count(self):
4582 """
4582 """
4583 return number of versions this PR have, e.g a PR that once been
4583 return number of versions this PR have, e.g a PR that once been
4584 updated will have 2 versions
4584 updated will have 2 versions
4585 """
4585 """
4586 return self.versions.count() + 1
4586 return self.versions.count() + 1
4587
4587
4588 @property
4588 @property
4589 def pull_request_last_version(self):
4589 def pull_request_last_version(self):
4590 return self.versions_count
4590 return self.versions_count
4591
4591
4592
4592
4593 class PullRequestVersion(Base, _PullRequestBase):
4593 class PullRequestVersion(Base, _PullRequestBase):
4594 __tablename__ = 'pull_request_versions'
4594 __tablename__ = 'pull_request_versions'
4595 __table_args__ = (
4595 __table_args__ = (
4596 base_table_args,
4596 base_table_args,
4597 )
4597 )
4598
4598
4599 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4599 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4600 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4600 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4601 pull_request = relationship('PullRequest', back_populates='versions')
4601 pull_request = relationship('PullRequest', back_populates='versions')
4602
4602
4603 def __repr__(self):
4603 def __repr__(self):
4604 if self.pull_request_version_id:
4604 if self.pull_request_version_id:
4605 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4605 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4606 else:
4606 else:
4607 return f'<DB:PullRequestVersion at {id(self)!r}>'
4607 return f'<DB:PullRequestVersion at {id(self)!r}>'
4608
4608
4609 @property
4609 @property
4610 def reviewers(self):
4610 def reviewers(self):
4611 return self.pull_request.reviewers
4611 return self.pull_request.reviewers
4612
4612
4613 @property
4613 @property
4614 def versions(self):
4614 def versions(self):
4615 return self.pull_request.versions
4615 return self.pull_request.versions
4616
4616
4617 def is_closed(self):
4617 def is_closed(self):
4618 # calculate from original
4618 # calculate from original
4619 return self.pull_request.status == self.STATUS_CLOSED
4619 return self.pull_request.status == self.STATUS_CLOSED
4620
4620
4621 def is_state_changing(self):
4621 def is_state_changing(self):
4622 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4622 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4623
4623
4624 def calculated_review_status(self):
4624 def calculated_review_status(self):
4625 return self.pull_request.calculated_review_status()
4625 return self.pull_request.calculated_review_status()
4626
4626
4627 def reviewers_statuses(self):
4627 def reviewers_statuses(self):
4628 return self.pull_request.reviewers_statuses()
4628 return self.pull_request.reviewers_statuses()
4629
4629
4630 def observers(self):
4630 def observers(self):
4631 return self.pull_request.observers()
4631 return self.pull_request.observers()
4632
4632
4633
4633
4634 class PullRequestReviewers(Base, BaseModel):
4634 class PullRequestReviewers(Base, BaseModel):
4635 __tablename__ = 'pull_request_reviewers'
4635 __tablename__ = 'pull_request_reviewers'
4636 __table_args__ = (
4636 __table_args__ = (
4637 base_table_args,
4637 base_table_args,
4638 )
4638 )
4639 ROLE_REVIEWER = 'reviewer'
4639 ROLE_REVIEWER = 'reviewer'
4640 ROLE_OBSERVER = 'observer'
4640 ROLE_OBSERVER = 'observer'
4641 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4641 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4642
4642
4643 @hybrid_property
4643 @hybrid_property
4644 def reasons(self):
4644 def reasons(self):
4645 if not self._reasons:
4645 if not self._reasons:
4646 return []
4646 return []
4647 return self._reasons
4647 return self._reasons
4648
4648
4649 @reasons.setter
4649 @reasons.setter
4650 def reasons(self, val):
4650 def reasons(self, val):
4651 val = val or []
4651 val = val or []
4652 if any(not isinstance(x, str) for x in val):
4652 if any(not isinstance(x, str) for x in val):
4653 raise Exception('invalid reasons type, must be list of strings')
4653 raise Exception('invalid reasons type, must be list of strings')
4654 self._reasons = val
4654 self._reasons = val
4655
4655
4656 pull_requests_reviewers_id = Column(
4656 pull_requests_reviewers_id = Column(
4657 'pull_requests_reviewers_id', Integer(), nullable=False,
4657 'pull_requests_reviewers_id', Integer(), nullable=False,
4658 primary_key=True)
4658 primary_key=True)
4659 pull_request_id = Column(
4659 pull_request_id = Column(
4660 "pull_request_id", Integer(),
4660 "pull_request_id", Integer(),
4661 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4661 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4662 user_id = Column(
4662 user_id = Column(
4663 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4663 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4664 _reasons = Column(
4664 _reasons = Column(
4665 'reason', MutationList.as_mutable(
4665 'reason', MutationList.as_mutable(
4666 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4666 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4667
4667
4668 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4668 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4669 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4669 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4670
4670
4671 user = relationship('User')
4671 user = relationship('User')
4672 pull_request = relationship('PullRequest', back_populates='reviewers')
4672 pull_request = relationship('PullRequest', back_populates='reviewers')
4673
4673
4674 rule_data = Column(
4674 rule_data = Column(
4675 'rule_data_json',
4675 'rule_data_json',
4676 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4676 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4677
4677
4678 def rule_user_group_data(self):
4678 def rule_user_group_data(self):
4679 """
4679 """
4680 Returns the voting user group rule data for this reviewer
4680 Returns the voting user group rule data for this reviewer
4681 """
4681 """
4682
4682
4683 if self.rule_data and 'vote_rule' in self.rule_data:
4683 if self.rule_data and 'vote_rule' in self.rule_data:
4684 user_group_data = {}
4684 user_group_data = {}
4685 if 'rule_user_group_entry_id' in self.rule_data:
4685 if 'rule_user_group_entry_id' in self.rule_data:
4686 # means a group with voting rules !
4686 # means a group with voting rules !
4687 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4687 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4688 user_group_data['name'] = self.rule_data['rule_name']
4688 user_group_data['name'] = self.rule_data['rule_name']
4689 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4689 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4690
4690
4691 return user_group_data
4691 return user_group_data
4692
4692
4693 @classmethod
4693 @classmethod
4694 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4694 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4695 qry = PullRequestReviewers.query()\
4695 qry = PullRequestReviewers.query()\
4696 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4696 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4697 if role:
4697 if role:
4698 qry = qry.filter(PullRequestReviewers.role == role)
4698 qry = qry.filter(PullRequestReviewers.role == role)
4699
4699
4700 return qry.all()
4700 return qry.all()
4701
4701
4702 def __repr__(self):
4702 def __repr__(self):
4703 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4703 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4704
4704
4705
4705
4706 class Notification(Base, BaseModel):
4706 class Notification(Base, BaseModel):
4707 __tablename__ = 'notifications'
4707 __tablename__ = 'notifications'
4708 __table_args__ = (
4708 __table_args__ = (
4709 Index('notification_type_idx', 'type'),
4709 Index('notification_type_idx', 'type'),
4710 base_table_args,
4710 base_table_args,
4711 )
4711 )
4712
4712
4713 TYPE_CHANGESET_COMMENT = 'cs_comment'
4713 TYPE_CHANGESET_COMMENT = 'cs_comment'
4714 TYPE_MESSAGE = 'message'
4714 TYPE_MESSAGE = 'message'
4715 TYPE_MENTION = 'mention'
4715 TYPE_MENTION = 'mention'
4716 TYPE_REGISTRATION = 'registration'
4716 TYPE_REGISTRATION = 'registration'
4717 TYPE_PULL_REQUEST = 'pull_request'
4717 TYPE_PULL_REQUEST = 'pull_request'
4718 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4718 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4719 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4719 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4720
4720
4721 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4721 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4722 subject = Column('subject', Unicode(512), nullable=True)
4722 subject = Column('subject', Unicode(512), nullable=True)
4723 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4723 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4724 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4724 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4725 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4725 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4726 type_ = Column('type', Unicode(255))
4726 type_ = Column('type', Unicode(255))
4727
4727
4728 created_by_user = relationship('User', back_populates='user_created_notifications')
4728 created_by_user = relationship('User', back_populates='user_created_notifications')
4729 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4729 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4730
4730
4731 @property
4731 @property
4732 def recipients(self):
4732 def recipients(self):
4733 return [x.user for x in UserNotification.query()\
4733 return [x.user for x in UserNotification.query()\
4734 .filter(UserNotification.notification == self)\
4734 .filter(UserNotification.notification == self)\
4735 .order_by(UserNotification.user_id.asc()).all()]
4735 .order_by(UserNotification.user_id.asc()).all()]
4736
4736
4737 @classmethod
4737 @classmethod
4738 def create(cls, created_by, subject, body, recipients, type_=None):
4738 def create(cls, created_by, subject, body, recipients, type_=None):
4739 if type_ is None:
4739 if type_ is None:
4740 type_ = Notification.TYPE_MESSAGE
4740 type_ = Notification.TYPE_MESSAGE
4741
4741
4742 notification = cls()
4742 notification = cls()
4743 notification.created_by_user = created_by
4743 notification.created_by_user = created_by
4744 notification.subject = subject
4744 notification.subject = subject
4745 notification.body = body
4745 notification.body = body
4746 notification.type_ = type_
4746 notification.type_ = type_
4747 notification.created_on = datetime.datetime.now()
4747 notification.created_on = datetime.datetime.now()
4748
4748
4749 # For each recipient link the created notification to his account
4749 # For each recipient link the created notification to his account
4750 for u in recipients:
4750 for u in recipients:
4751 assoc = UserNotification()
4751 assoc = UserNotification()
4752 assoc.user_id = u.user_id
4752 assoc.user_id = u.user_id
4753 assoc.notification = notification
4753 assoc.notification = notification
4754
4754
4755 # if created_by is inside recipients mark his notification
4755 # if created_by is inside recipients mark his notification
4756 # as read
4756 # as read
4757 if u.user_id == created_by.user_id:
4757 if u.user_id == created_by.user_id:
4758 assoc.read = True
4758 assoc.read = True
4759 Session().add(assoc)
4759 Session().add(assoc)
4760
4760
4761 Session().add(notification)
4761 Session().add(notification)
4762
4762
4763 return notification
4763 return notification
4764
4764
4765
4765
4766 class UserNotification(Base, BaseModel):
4766 class UserNotification(Base, BaseModel):
4767 __tablename__ = 'user_to_notification'
4767 __tablename__ = 'user_to_notification'
4768 __table_args__ = (
4768 __table_args__ = (
4769 UniqueConstraint('user_id', 'notification_id'),
4769 UniqueConstraint('user_id', 'notification_id'),
4770 base_table_args
4770 base_table_args
4771 )
4771 )
4772
4772
4773 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4773 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4774 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4774 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4775 read = Column('read', Boolean, default=False)
4775 read = Column('read', Boolean, default=False)
4776 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4776 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4777
4777
4778 user = relationship('User', lazy="joined", back_populates='notifications')
4778 user = relationship('User', lazy="joined", back_populates='notifications')
4779 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4779 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4780
4780
4781 def mark_as_read(self):
4781 def mark_as_read(self):
4782 self.read = True
4782 self.read = True
4783 Session().add(self)
4783 Session().add(self)
4784
4784
4785
4785
4786 class UserNotice(Base, BaseModel):
4786 class UserNotice(Base, BaseModel):
4787 __tablename__ = 'user_notices'
4787 __tablename__ = 'user_notices'
4788 __table_args__ = (
4788 __table_args__ = (
4789 base_table_args
4789 base_table_args
4790 )
4790 )
4791
4791
4792 NOTIFICATION_TYPE_MESSAGE = 'message'
4792 NOTIFICATION_TYPE_MESSAGE = 'message'
4793 NOTIFICATION_TYPE_NOTICE = 'notice'
4793 NOTIFICATION_TYPE_NOTICE = 'notice'
4794
4794
4795 NOTIFICATION_LEVEL_INFO = 'info'
4795 NOTIFICATION_LEVEL_INFO = 'info'
4796 NOTIFICATION_LEVEL_WARNING = 'warning'
4796 NOTIFICATION_LEVEL_WARNING = 'warning'
4797 NOTIFICATION_LEVEL_ERROR = 'error'
4797 NOTIFICATION_LEVEL_ERROR = 'error'
4798
4798
4799 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4799 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4800
4800
4801 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4801 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4802 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4802 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4803
4803
4804 notice_read = Column('notice_read', Boolean, default=False)
4804 notice_read = Column('notice_read', Boolean, default=False)
4805
4805
4806 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4806 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4807 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4807 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4808
4808
4809 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4809 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4810 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4810 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4811
4811
4812 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4812 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4813 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4813 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4814
4814
4815 @classmethod
4815 @classmethod
4816 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4816 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4817
4817
4818 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4818 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4819 cls.NOTIFICATION_LEVEL_WARNING,
4819 cls.NOTIFICATION_LEVEL_WARNING,
4820 cls.NOTIFICATION_LEVEL_INFO]:
4820 cls.NOTIFICATION_LEVEL_INFO]:
4821 return
4821 return
4822
4822
4823 from rhodecode.model.user import UserModel
4823 from rhodecode.model.user import UserModel
4824 user = UserModel().get_user(user)
4824 user = UserModel().get_user(user)
4825
4825
4826 new_notice = UserNotice()
4826 new_notice = UserNotice()
4827 if not allow_duplicate:
4827 if not allow_duplicate:
4828 existing_msg = UserNotice().query() \
4828 existing_msg = UserNotice().query() \
4829 .filter(UserNotice.user == user) \
4829 .filter(UserNotice.user == user) \
4830 .filter(UserNotice.notice_body == body) \
4830 .filter(UserNotice.notice_body == body) \
4831 .filter(UserNotice.notice_read == false()) \
4831 .filter(UserNotice.notice_read == false()) \
4832 .scalar()
4832 .scalar()
4833 if existing_msg:
4833 if existing_msg:
4834 log.warning('Ignoring duplicate notice for user %s', user)
4834 log.warning('Ignoring duplicate notice for user %s', user)
4835 return
4835 return
4836
4836
4837 new_notice.user = user
4837 new_notice.user = user
4838 new_notice.notice_subject = subject
4838 new_notice.notice_subject = subject
4839 new_notice.notice_body = body
4839 new_notice.notice_body = body
4840 new_notice.notification_level = notice_level
4840 new_notice.notification_level = notice_level
4841 Session().add(new_notice)
4841 Session().add(new_notice)
4842 Session().commit()
4842 Session().commit()
4843
4843
4844
4844
4845 class Gist(Base, BaseModel):
4845 class Gist(Base, BaseModel):
4846 __tablename__ = 'gists'
4846 __tablename__ = 'gists'
4847 __table_args__ = (
4847 __table_args__ = (
4848 Index('g_gist_access_id_idx', 'gist_access_id'),
4848 Index('g_gist_access_id_idx', 'gist_access_id'),
4849 Index('g_created_on_idx', 'created_on'),
4849 Index('g_created_on_idx', 'created_on'),
4850 base_table_args
4850 base_table_args
4851 )
4851 )
4852
4852
4853 GIST_PUBLIC = 'public'
4853 GIST_PUBLIC = 'public'
4854 GIST_PRIVATE = 'private'
4854 GIST_PRIVATE = 'private'
4855 DEFAULT_FILENAME = 'gistfile1.txt'
4855 DEFAULT_FILENAME = 'gistfile1.txt'
4856
4856
4857 ACL_LEVEL_PUBLIC = 'acl_public'
4857 ACL_LEVEL_PUBLIC = 'acl_public'
4858 ACL_LEVEL_PRIVATE = 'acl_private'
4858 ACL_LEVEL_PRIVATE = 'acl_private'
4859
4859
4860 gist_id = Column('gist_id', Integer(), primary_key=True)
4860 gist_id = Column('gist_id', Integer(), primary_key=True)
4861 gist_access_id = Column('gist_access_id', Unicode(250))
4861 gist_access_id = Column('gist_access_id', Unicode(250))
4862 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4862 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4863 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4863 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4864 gist_expires = Column('gist_expires', Float(53), nullable=False)
4864 gist_expires = Column('gist_expires', Float(53), nullable=False)
4865 gist_type = Column('gist_type', Unicode(128), nullable=False)
4865 gist_type = Column('gist_type', Unicode(128), nullable=False)
4866 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4866 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4867 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4867 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4868 acl_level = Column('acl_level', Unicode(128), nullable=True)
4868 acl_level = Column('acl_level', Unicode(128), nullable=True)
4869
4869
4870 owner = relationship('User', back_populates='user_gists')
4870 owner = relationship('User', back_populates='user_gists')
4871
4871
4872 def __repr__(self):
4872 def __repr__(self):
4873 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4873 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4874
4874
4875 @hybrid_property
4875 @hybrid_property
4876 def description_safe(self):
4876 def description_safe(self):
4877 from rhodecode.lib import helpers as h
4877 from rhodecode.lib import helpers as h
4878 return h.escape(self.gist_description)
4878 return h.escape(self.gist_description)
4879
4879
4880 @classmethod
4880 @classmethod
4881 def get_or_404(cls, id_):
4881 def get_or_404(cls, id_):
4882 from pyramid.httpexceptions import HTTPNotFound
4882 from pyramid.httpexceptions import HTTPNotFound
4883
4883
4884 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4884 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4885 if not res:
4885 if not res:
4886 log.debug('WARN: No DB entry with id %s', id_)
4886 log.debug('WARN: No DB entry with id %s', id_)
4887 raise HTTPNotFound()
4887 raise HTTPNotFound()
4888 return res
4888 return res
4889
4889
4890 @classmethod
4890 @classmethod
4891 def get_by_access_id(cls, gist_access_id):
4891 def get_by_access_id(cls, gist_access_id):
4892 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4892 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4893
4893
4894 def gist_url(self):
4894 def gist_url(self):
4895 from rhodecode.model.gist import GistModel
4895 from rhodecode.model.gist import GistModel
4896 return GistModel().get_url(self)
4896 return GistModel().get_url(self)
4897
4897
4898 @classmethod
4898 @classmethod
4899 def base_path(cls):
4899 def base_path(cls):
4900 """
4900 """
4901 Returns base path when all gists are stored
4901 Returns base path when all gists are stored
4902
4902
4903 :param cls:
4903 :param cls:
4904 """
4904 """
4905 from rhodecode.model.gist import GIST_STORE_LOC
4905 from rhodecode.model.gist import GIST_STORE_LOC
4906 q = Session().query(RhodeCodeUi)\
4906 q = Session().query(RhodeCodeUi)\
4907 .filter(RhodeCodeUi.ui_key == URL_SEP)
4907 .filter(RhodeCodeUi.ui_key == URL_SEP)
4908 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4908 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4909 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4909 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4910
4910
4911 def get_api_data(self):
4911 def get_api_data(self):
4912 """
4912 """
4913 Common function for generating gist related data for API
4913 Common function for generating gist related data for API
4914 """
4914 """
4915 gist = self
4915 gist = self
4916 data = {
4916 data = {
4917 'gist_id': gist.gist_id,
4917 'gist_id': gist.gist_id,
4918 'type': gist.gist_type,
4918 'type': gist.gist_type,
4919 'access_id': gist.gist_access_id,
4919 'access_id': gist.gist_access_id,
4920 'description': gist.gist_description,
4920 'description': gist.gist_description,
4921 'url': gist.gist_url(),
4921 'url': gist.gist_url(),
4922 'expires': gist.gist_expires,
4922 'expires': gist.gist_expires,
4923 'created_on': gist.created_on,
4923 'created_on': gist.created_on,
4924 'modified_at': gist.modified_at,
4924 'modified_at': gist.modified_at,
4925 'content': None,
4925 'content': None,
4926 'acl_level': gist.acl_level,
4926 'acl_level': gist.acl_level,
4927 }
4927 }
4928 return data
4928 return data
4929
4929
4930 def __json__(self):
4930 def __json__(self):
4931 data = dict(
4931 data = dict(
4932 )
4932 )
4933 data.update(self.get_api_data())
4933 data.update(self.get_api_data())
4934 return data
4934 return data
4935 # SCM functions
4935 # SCM functions
4936
4936
4937 def scm_instance(self, **kwargs):
4937 def scm_instance(self, **kwargs):
4938 """
4938 """
4939 Get an instance of VCS Repository
4939 Get an instance of VCS Repository
4940
4940
4941 :param kwargs:
4941 :param kwargs:
4942 """
4942 """
4943 from rhodecode.model.gist import GistModel
4943 from rhodecode.model.gist import GistModel
4944 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4944 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4945 return get_vcs_instance(
4945 return get_vcs_instance(
4946 repo_path=safe_str(full_repo_path), create=False,
4946 repo_path=safe_str(full_repo_path), create=False,
4947 _vcs_alias=GistModel.vcs_backend)
4947 _vcs_alias=GistModel.vcs_backend)
4948
4948
4949
4949
4950 class ExternalIdentity(Base, BaseModel):
4950 class ExternalIdentity(Base, BaseModel):
4951 __tablename__ = 'external_identities'
4951 __tablename__ = 'external_identities'
4952 __table_args__ = (
4952 __table_args__ = (
4953 Index('local_user_id_idx', 'local_user_id'),
4953 Index('local_user_id_idx', 'local_user_id'),
4954 Index('external_id_idx', 'external_id'),
4954 Index('external_id_idx', 'external_id'),
4955 base_table_args
4955 base_table_args
4956 )
4956 )
4957
4957
4958 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4958 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4959 external_username = Column('external_username', Unicode(1024), default='')
4959 external_username = Column('external_username', Unicode(1024), default='')
4960 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4960 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4961 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4961 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4962 access_token = Column('access_token', String(1024), default='')
4962 access_token = Column('access_token', String(1024), default='')
4963 alt_token = Column('alt_token', String(1024), default='')
4963 alt_token = Column('alt_token', String(1024), default='')
4964 token_secret = Column('token_secret', String(1024), default='')
4964 token_secret = Column('token_secret', String(1024), default='')
4965
4965
4966 @classmethod
4966 @classmethod
4967 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4967 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4968 """
4968 """
4969 Returns ExternalIdentity instance based on search params
4969 Returns ExternalIdentity instance based on search params
4970
4970
4971 :param external_id:
4971 :param external_id:
4972 :param provider_name:
4972 :param provider_name:
4973 :return: ExternalIdentity
4973 :return: ExternalIdentity
4974 """
4974 """
4975 query = cls.query()
4975 query = cls.query()
4976 query = query.filter(cls.external_id == external_id)
4976 query = query.filter(cls.external_id == external_id)
4977 query = query.filter(cls.provider_name == provider_name)
4977 query = query.filter(cls.provider_name == provider_name)
4978 if local_user_id:
4978 if local_user_id:
4979 query = query.filter(cls.local_user_id == local_user_id)
4979 query = query.filter(cls.local_user_id == local_user_id)
4980 return query.first()
4980 return query.first()
4981
4981
4982 @classmethod
4982 @classmethod
4983 def user_by_external_id_and_provider(cls, external_id, provider_name):
4983 def user_by_external_id_and_provider(cls, external_id, provider_name):
4984 """
4984 """
4985 Returns User instance based on search params
4985 Returns User instance based on search params
4986
4986
4987 :param external_id:
4987 :param external_id:
4988 :param provider_name:
4988 :param provider_name:
4989 :return: User
4989 :return: User
4990 """
4990 """
4991 query = User.query()
4991 query = User.query()
4992 query = query.filter(cls.external_id == external_id)
4992 query = query.filter(cls.external_id == external_id)
4993 query = query.filter(cls.provider_name == provider_name)
4993 query = query.filter(cls.provider_name == provider_name)
4994 query = query.filter(User.user_id == cls.local_user_id)
4994 query = query.filter(User.user_id == cls.local_user_id)
4995 return query.first()
4995 return query.first()
4996
4996
4997 @classmethod
4997 @classmethod
4998 def by_local_user_id(cls, local_user_id):
4998 def by_local_user_id(cls, local_user_id):
4999 """
4999 """
5000 Returns all tokens for user
5000 Returns all tokens for user
5001
5001
5002 :param local_user_id:
5002 :param local_user_id:
5003 :return: ExternalIdentity
5003 :return: ExternalIdentity
5004 """
5004 """
5005 query = cls.query()
5005 query = cls.query()
5006 query = query.filter(cls.local_user_id == local_user_id)
5006 query = query.filter(cls.local_user_id == local_user_id)
5007 return query
5007 return query
5008
5008
5009 @classmethod
5009 @classmethod
5010 def load_provider_plugin(cls, plugin_id):
5010 def load_provider_plugin(cls, plugin_id):
5011 from rhodecode.authentication.base import loadplugin
5011 from rhodecode.authentication.base import loadplugin
5012 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5012 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5013 auth_plugin = loadplugin(_plugin_id)
5013 auth_plugin = loadplugin(_plugin_id)
5014 return auth_plugin
5014 return auth_plugin
5015
5015
5016
5016
5017 class Integration(Base, BaseModel):
5017 class Integration(Base, BaseModel):
5018 __tablename__ = 'integrations'
5018 __tablename__ = 'integrations'
5019 __table_args__ = (
5019 __table_args__ = (
5020 base_table_args
5020 base_table_args
5021 )
5021 )
5022
5022
5023 integration_id = Column('integration_id', Integer(), primary_key=True)
5023 integration_id = Column('integration_id', Integer(), primary_key=True)
5024 integration_type = Column('integration_type', String(255))
5024 integration_type = Column('integration_type', String(255))
5025 enabled = Column('enabled', Boolean(), nullable=False)
5025 enabled = Column('enabled', Boolean(), nullable=False)
5026 name = Column('name', String(255), nullable=False)
5026 name = Column('name', String(255), nullable=False)
5027 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5027 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5028
5028
5029 settings = Column(
5029 settings = Column(
5030 'settings_json', MutationObj.as_mutable(
5030 'settings_json', MutationObj.as_mutable(
5031 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5031 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5032 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5032 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5033 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5033 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5034
5034
5035 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5035 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5036 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5036 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5037
5037
5038 @property
5038 @property
5039 def scope(self):
5039 def scope(self):
5040 if self.repo:
5040 if self.repo:
5041 return repr(self.repo)
5041 return repr(self.repo)
5042 if self.repo_group:
5042 if self.repo_group:
5043 if self.child_repos_only:
5043 if self.child_repos_only:
5044 return repr(self.repo_group) + ' (child repos only)'
5044 return repr(self.repo_group) + ' (child repos only)'
5045 else:
5045 else:
5046 return repr(self.repo_group) + ' (recursive)'
5046 return repr(self.repo_group) + ' (recursive)'
5047 if self.child_repos_only:
5047 if self.child_repos_only:
5048 return 'root_repos'
5048 return 'root_repos'
5049 return 'global'
5049 return 'global'
5050
5050
5051 def __repr__(self):
5051 def __repr__(self):
5052 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5052 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5053
5053
5054
5054
5055 class RepoReviewRuleUser(Base, BaseModel):
5055 class RepoReviewRuleUser(Base, BaseModel):
5056 __tablename__ = 'repo_review_rules_users'
5056 __tablename__ = 'repo_review_rules_users'
5057 __table_args__ = (
5057 __table_args__ = (
5058 base_table_args
5058 base_table_args
5059 )
5059 )
5060 ROLE_REVIEWER = 'reviewer'
5060 ROLE_REVIEWER = 'reviewer'
5061 ROLE_OBSERVER = 'observer'
5061 ROLE_OBSERVER = 'observer'
5062 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5062 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5063
5063
5064 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5064 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5065 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5065 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5066 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5066 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5067 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5067 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5068 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5068 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5069 user = relationship('User', back_populates='user_review_rules')
5069 user = relationship('User', back_populates='user_review_rules')
5070
5070
5071 def rule_data(self):
5071 def rule_data(self):
5072 return {
5072 return {
5073 'mandatory': self.mandatory,
5073 'mandatory': self.mandatory,
5074 'role': self.role,
5074 'role': self.role,
5075 }
5075 }
5076
5076
5077
5077
5078 class RepoReviewRuleUserGroup(Base, BaseModel):
5078 class RepoReviewRuleUserGroup(Base, BaseModel):
5079 __tablename__ = 'repo_review_rules_users_groups'
5079 __tablename__ = 'repo_review_rules_users_groups'
5080 __table_args__ = (
5080 __table_args__ = (
5081 base_table_args
5081 base_table_args
5082 )
5082 )
5083
5083
5084 VOTE_RULE_ALL = -1
5084 VOTE_RULE_ALL = -1
5085 ROLE_REVIEWER = 'reviewer'
5085 ROLE_REVIEWER = 'reviewer'
5086 ROLE_OBSERVER = 'observer'
5086 ROLE_OBSERVER = 'observer'
5087 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5087 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5088
5088
5089 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5089 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5090 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5090 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5091 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5091 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5092 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5092 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5093 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5093 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5094 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5094 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5095 users_group = relationship('UserGroup')
5095 users_group = relationship('UserGroup')
5096
5096
5097 def rule_data(self):
5097 def rule_data(self):
5098 return {
5098 return {
5099 'mandatory': self.mandatory,
5099 'mandatory': self.mandatory,
5100 'role': self.role,
5100 'role': self.role,
5101 'vote_rule': self.vote_rule
5101 'vote_rule': self.vote_rule
5102 }
5102 }
5103
5103
5104 @property
5104 @property
5105 def vote_rule_label(self):
5105 def vote_rule_label(self):
5106 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5106 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5107 return 'all must vote'
5107 return 'all must vote'
5108 else:
5108 else:
5109 return 'min. vote {}'.format(self.vote_rule)
5109 return 'min. vote {}'.format(self.vote_rule)
5110
5110
5111
5111
5112 class RepoReviewRule(Base, BaseModel):
5112 class RepoReviewRule(Base, BaseModel):
5113 __tablename__ = 'repo_review_rules'
5113 __tablename__ = 'repo_review_rules'
5114 __table_args__ = (
5114 __table_args__ = (
5115 base_table_args
5115 base_table_args
5116 )
5116 )
5117
5117
5118 repo_review_rule_id = Column(
5118 repo_review_rule_id = Column(
5119 'repo_review_rule_id', Integer(), primary_key=True)
5119 'repo_review_rule_id', Integer(), primary_key=True)
5120 repo_id = Column(
5120 repo_id = Column(
5121 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5121 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5122 repo = relationship('Repository', back_populates='review_rules')
5122 repo = relationship('Repository', back_populates='review_rules')
5123
5123
5124 review_rule_name = Column('review_rule_name', String(255))
5124 review_rule_name = Column('review_rule_name', String(255))
5125 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5125 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5126 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5126 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5127 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5127 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5128
5128
5129 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5129 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5130
5130
5131 # Legacy fields, just for backward compat
5131 # Legacy fields, just for backward compat
5132 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5132 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5133 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5133 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5134
5134
5135 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5135 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5136 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5136 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5137
5137
5138 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5138 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5139
5139
5140 rule_users = relationship('RepoReviewRuleUser')
5140 rule_users = relationship('RepoReviewRuleUser')
5141 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5141 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5142
5142
5143 def _validate_pattern(self, value):
5143 def _validate_pattern(self, value):
5144 re.compile('^' + glob2re(value) + '$')
5144 re.compile('^' + glob2re(value) + '$')
5145
5145
5146 @hybrid_property
5146 @hybrid_property
5147 def source_branch_pattern(self):
5147 def source_branch_pattern(self):
5148 return self._branch_pattern or '*'
5148 return self._branch_pattern or '*'
5149
5149
5150 @source_branch_pattern.setter
5150 @source_branch_pattern.setter
5151 def source_branch_pattern(self, value):
5151 def source_branch_pattern(self, value):
5152 self._validate_pattern(value)
5152 self._validate_pattern(value)
5153 self._branch_pattern = value or '*'
5153 self._branch_pattern = value or '*'
5154
5154
5155 @hybrid_property
5155 @hybrid_property
5156 def target_branch_pattern(self):
5156 def target_branch_pattern(self):
5157 return self._target_branch_pattern or '*'
5157 return self._target_branch_pattern or '*'
5158
5158
5159 @target_branch_pattern.setter
5159 @target_branch_pattern.setter
5160 def target_branch_pattern(self, value):
5160 def target_branch_pattern(self, value):
5161 self._validate_pattern(value)
5161 self._validate_pattern(value)
5162 self._target_branch_pattern = value or '*'
5162 self._target_branch_pattern = value or '*'
5163
5163
5164 @hybrid_property
5164 @hybrid_property
5165 def file_pattern(self):
5165 def file_pattern(self):
5166 return self._file_pattern or '*'
5166 return self._file_pattern or '*'
5167
5167
5168 @file_pattern.setter
5168 @file_pattern.setter
5169 def file_pattern(self, value):
5169 def file_pattern(self, value):
5170 self._validate_pattern(value)
5170 self._validate_pattern(value)
5171 self._file_pattern = value or '*'
5171 self._file_pattern = value or '*'
5172
5172
5173 @hybrid_property
5173 @hybrid_property
5174 def forbid_pr_author_to_review(self):
5174 def forbid_pr_author_to_review(self):
5175 return self.pr_author == 'forbid_pr_author'
5175 return self.pr_author == 'forbid_pr_author'
5176
5176
5177 @hybrid_property
5177 @hybrid_property
5178 def include_pr_author_to_review(self):
5178 def include_pr_author_to_review(self):
5179 return self.pr_author == 'include_pr_author'
5179 return self.pr_author == 'include_pr_author'
5180
5180
5181 @hybrid_property
5181 @hybrid_property
5182 def forbid_commit_author_to_review(self):
5182 def forbid_commit_author_to_review(self):
5183 return self.commit_author == 'forbid_commit_author'
5183 return self.commit_author == 'forbid_commit_author'
5184
5184
5185 @hybrid_property
5185 @hybrid_property
5186 def include_commit_author_to_review(self):
5186 def include_commit_author_to_review(self):
5187 return self.commit_author == 'include_commit_author'
5187 return self.commit_author == 'include_commit_author'
5188
5188
5189 def matches(self, source_branch, target_branch, files_changed):
5189 def matches(self, source_branch, target_branch, files_changed):
5190 """
5190 """
5191 Check if this review rule matches a branch/files in a pull request
5191 Check if this review rule matches a branch/files in a pull request
5192
5192
5193 :param source_branch: source branch name for the commit
5193 :param source_branch: source branch name for the commit
5194 :param target_branch: target branch name for the commit
5194 :param target_branch: target branch name for the commit
5195 :param files_changed: list of file paths changed in the pull request
5195 :param files_changed: list of file paths changed in the pull request
5196 """
5196 """
5197
5197
5198 source_branch = source_branch or ''
5198 source_branch = source_branch or ''
5199 target_branch = target_branch or ''
5199 target_branch = target_branch or ''
5200 files_changed = files_changed or []
5200 files_changed = files_changed or []
5201
5201
5202 branch_matches = True
5202 branch_matches = True
5203 if source_branch or target_branch:
5203 if source_branch or target_branch:
5204 if self.source_branch_pattern == '*':
5204 if self.source_branch_pattern == '*':
5205 source_branch_match = True
5205 source_branch_match = True
5206 else:
5206 else:
5207 if self.source_branch_pattern.startswith('re:'):
5207 if self.source_branch_pattern.startswith('re:'):
5208 source_pattern = self.source_branch_pattern[3:]
5208 source_pattern = self.source_branch_pattern[3:]
5209 else:
5209 else:
5210 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5210 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5211 source_branch_regex = re.compile(source_pattern)
5211 source_branch_regex = re.compile(source_pattern)
5212 source_branch_match = bool(source_branch_regex.search(source_branch))
5212 source_branch_match = bool(source_branch_regex.search(source_branch))
5213 if self.target_branch_pattern == '*':
5213 if self.target_branch_pattern == '*':
5214 target_branch_match = True
5214 target_branch_match = True
5215 else:
5215 else:
5216 if self.target_branch_pattern.startswith('re:'):
5216 if self.target_branch_pattern.startswith('re:'):
5217 target_pattern = self.target_branch_pattern[3:]
5217 target_pattern = self.target_branch_pattern[3:]
5218 else:
5218 else:
5219 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5219 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5220 target_branch_regex = re.compile(target_pattern)
5220 target_branch_regex = re.compile(target_pattern)
5221 target_branch_match = bool(target_branch_regex.search(target_branch))
5221 target_branch_match = bool(target_branch_regex.search(target_branch))
5222
5222
5223 branch_matches = source_branch_match and target_branch_match
5223 branch_matches = source_branch_match and target_branch_match
5224
5224
5225 files_matches = True
5225 files_matches = True
5226 if self.file_pattern != '*':
5226 if self.file_pattern != '*':
5227 files_matches = False
5227 files_matches = False
5228 if self.file_pattern.startswith('re:'):
5228 if self.file_pattern.startswith('re:'):
5229 file_pattern = self.file_pattern[3:]
5229 file_pattern = self.file_pattern[3:]
5230 else:
5230 else:
5231 file_pattern = glob2re(self.file_pattern)
5231 file_pattern = glob2re(self.file_pattern)
5232 file_regex = re.compile(file_pattern)
5232 file_regex = re.compile(file_pattern)
5233 for file_data in files_changed:
5233 for file_data in files_changed:
5234 filename = file_data.get('filename')
5234 filename = file_data.get('filename')
5235
5235
5236 if file_regex.search(filename):
5236 if file_regex.search(filename):
5237 files_matches = True
5237 files_matches = True
5238 break
5238 break
5239
5239
5240 return branch_matches and files_matches
5240 return branch_matches and files_matches
5241
5241
5242 @property
5242 @property
5243 def review_users(self):
5243 def review_users(self):
5244 """ Returns the users which this rule applies to """
5244 """ Returns the users which this rule applies to """
5245
5245
5246 users = collections.OrderedDict()
5246 users = collections.OrderedDict()
5247
5247
5248 for rule_user in self.rule_users:
5248 for rule_user in self.rule_users:
5249 if rule_user.user.active:
5249 if rule_user.user.active:
5250 if rule_user.user not in users:
5250 if rule_user.user not in users:
5251 users[rule_user.user.username] = {
5251 users[rule_user.user.username] = {
5252 'user': rule_user.user,
5252 'user': rule_user.user,
5253 'source': 'user',
5253 'source': 'user',
5254 'source_data': {},
5254 'source_data': {},
5255 'data': rule_user.rule_data()
5255 'data': rule_user.rule_data()
5256 }
5256 }
5257
5257
5258 for rule_user_group in self.rule_user_groups:
5258 for rule_user_group in self.rule_user_groups:
5259 source_data = {
5259 source_data = {
5260 'user_group_id': rule_user_group.users_group.users_group_id,
5260 'user_group_id': rule_user_group.users_group.users_group_id,
5261 'name': rule_user_group.users_group.users_group_name,
5261 'name': rule_user_group.users_group.users_group_name,
5262 'members': len(rule_user_group.users_group.members)
5262 'members': len(rule_user_group.users_group.members)
5263 }
5263 }
5264 for member in rule_user_group.users_group.members:
5264 for member in rule_user_group.users_group.members:
5265 if member.user.active:
5265 if member.user.active:
5266 key = member.user.username
5266 key = member.user.username
5267 if key in users:
5267 if key in users:
5268 # skip this member as we have him already
5268 # skip this member as we have him already
5269 # this prevents from override the "first" matched
5269 # this prevents from override the "first" matched
5270 # users with duplicates in multiple groups
5270 # users with duplicates in multiple groups
5271 continue
5271 continue
5272
5272
5273 users[key] = {
5273 users[key] = {
5274 'user': member.user,
5274 'user': member.user,
5275 'source': 'user_group',
5275 'source': 'user_group',
5276 'source_data': source_data,
5276 'source_data': source_data,
5277 'data': rule_user_group.rule_data()
5277 'data': rule_user_group.rule_data()
5278 }
5278 }
5279
5279
5280 return users
5280 return users
5281
5281
5282 def user_group_vote_rule(self, user_id):
5282 def user_group_vote_rule(self, user_id):
5283
5283
5284 rules = []
5284 rules = []
5285 if not self.rule_user_groups:
5285 if not self.rule_user_groups:
5286 return rules
5286 return rules
5287
5287
5288 for user_group in self.rule_user_groups:
5288 for user_group in self.rule_user_groups:
5289 user_group_members = [x.user_id for x in user_group.users_group.members]
5289 user_group_members = [x.user_id for x in user_group.users_group.members]
5290 if user_id in user_group_members:
5290 if user_id in user_group_members:
5291 rules.append(user_group)
5291 rules.append(user_group)
5292 return rules
5292 return rules
5293
5293
5294 def __repr__(self):
5294 def __repr__(self):
5295 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5295 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5296
5296
5297
5297
5298 class ScheduleEntry(Base, BaseModel):
5298 class ScheduleEntry(Base, BaseModel):
5299 __tablename__ = 'schedule_entries'
5299 __tablename__ = 'schedule_entries'
5300 __table_args__ = (
5300 __table_args__ = (
5301 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5301 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5302 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5302 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5303 base_table_args,
5303 base_table_args,
5304 )
5304 )
5305 SCHEDULE_TYPE_INTEGER = "integer"
5305 SCHEDULE_TYPE_INTEGER = "integer"
5306 SCHEDULE_TYPE_CRONTAB = "crontab"
5306 SCHEDULE_TYPE_CRONTAB = "crontab"
5307
5307
5308 schedule_types = [SCHEDULE_TYPE_CRONTAB, SCHEDULE_TYPE_INTEGER]
5308 schedule_types = [SCHEDULE_TYPE_CRONTAB, SCHEDULE_TYPE_INTEGER]
5309 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5309 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5310
5310
5311 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5311 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5312 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5312 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5313 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5313 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5314
5314
5315 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5315 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5316 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5316 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5317
5317
5318 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5318 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5319 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5319 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5320
5320
5321 # task
5321 # task
5322 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5322 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5323 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5323 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5324 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5324 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5325 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5325 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5326
5326
5327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5328 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5328 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5329
5329
5330 @hybrid_property
5330 @hybrid_property
5331 def schedule_type(self):
5331 def schedule_type(self):
5332 return self._schedule_type
5332 return self._schedule_type
5333
5333
5334 @schedule_type.setter
5334 @schedule_type.setter
5335 def schedule_type(self, val):
5335 def schedule_type(self, val):
5336 if val not in self.schedule_types:
5336 if val not in self.schedule_types:
5337 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5337 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5338 val, self.schedule_type))
5338 val, self.schedule_type))
5339
5339
5340 self._schedule_type = val
5340 self._schedule_type = val
5341
5341
5342 @classmethod
5342 @classmethod
5343 def get_uid(cls, obj):
5343 def get_uid(cls, obj):
5344 args = obj.task_args
5344 args = obj.task_args
5345 kwargs = obj.task_kwargs
5345 kwargs = obj.task_kwargs
5346 if isinstance(args, JsonRaw):
5346 if isinstance(args, JsonRaw):
5347 try:
5347 try:
5348 args = json.loads(args)
5348 args = json.loads(args)
5349 except ValueError:
5349 except ValueError:
5350 args = tuple()
5350 args = tuple()
5351
5351
5352 if isinstance(kwargs, JsonRaw):
5352 if isinstance(kwargs, JsonRaw):
5353 try:
5353 try:
5354 kwargs = json.loads(kwargs)
5354 kwargs = json.loads(kwargs)
5355 except ValueError:
5355 except ValueError:
5356 kwargs = dict()
5356 kwargs = dict()
5357
5357
5358 dot_notation = obj.task_dot_notation
5358 dot_notation = obj.task_dot_notation
5359 val = '.'.join(map(safe_str, [
5359 val = '.'.join(map(safe_str, [
5360 sorted(dot_notation), args, sorted(kwargs.items())]))
5360 sorted(dot_notation), args, sorted(kwargs.items())]))
5361 return sha1(safe_bytes(val))
5361 return sha1(safe_bytes(val))
5362
5362
5363 @classmethod
5363 @classmethod
5364 def get_by_schedule_name(cls, schedule_name):
5364 def get_by_schedule_name(cls, schedule_name):
5365 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5365 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5366
5366
5367 @classmethod
5367 @classmethod
5368 def get_by_schedule_id(cls, schedule_id):
5368 def get_by_schedule_id(cls, schedule_id):
5369 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5369 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5370
5370
5371 @property
5371 @property
5372 def task(self):
5372 def task(self):
5373 return self.task_dot_notation
5373 return self.task_dot_notation
5374
5374
5375 @property
5375 @property
5376 def schedule(self):
5376 def schedule(self):
5377 from rhodecode.lib.celerylib.utils import raw_2_schedule
5377 from rhodecode.lib.celerylib.utils import raw_2_schedule
5378 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5378 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5379 return schedule
5379 return schedule
5380
5380
5381 @property
5381 @property
5382 def args(self):
5382 def args(self):
5383 try:
5383 try:
5384 return list(self.task_args or [])
5384 return list(self.task_args or [])
5385 except ValueError:
5385 except ValueError:
5386 return list()
5386 return list()
5387
5387
5388 @property
5388 @property
5389 def kwargs(self):
5389 def kwargs(self):
5390 try:
5390 try:
5391 return dict(self.task_kwargs or {})
5391 return dict(self.task_kwargs or {})
5392 except ValueError:
5392 except ValueError:
5393 return dict()
5393 return dict()
5394
5394
5395 def _as_raw(self, val, indent=False):
5395 def _as_raw(self, val, indent=False):
5396 if hasattr(val, 'de_coerce'):
5396 if hasattr(val, 'de_coerce'):
5397 val = val.de_coerce()
5397 val = val.de_coerce()
5398 if val:
5398 if val:
5399 if indent:
5399 if indent:
5400 val = ext_json.formatted_str_json(val)
5400 val = ext_json.formatted_str_json(val)
5401 else:
5401 else:
5402 val = ext_json.str_json(val)
5402 val = ext_json.str_json(val)
5403
5403
5404 return val
5404 return val
5405
5405
5406 @property
5406 @property
5407 def schedule_definition_raw(self):
5407 def schedule_definition_raw(self):
5408 return self._as_raw(self.schedule_definition)
5408 return self._as_raw(self.schedule_definition)
5409
5409
5410 def args_raw(self, indent=False):
5410 def args_raw(self, indent=False):
5411 return self._as_raw(self.task_args, indent)
5411 return self._as_raw(self.task_args, indent)
5412
5412
5413 def kwargs_raw(self, indent=False):
5413 def kwargs_raw(self, indent=False):
5414 return self._as_raw(self.task_kwargs, indent)
5414 return self._as_raw(self.task_kwargs, indent)
5415
5415
5416 def __repr__(self):
5416 def __repr__(self):
5417 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5417 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5418
5418
5419
5419
5420 @event.listens_for(ScheduleEntry, 'before_update')
5420 @event.listens_for(ScheduleEntry, 'before_update')
5421 def update_task_uid(mapper, connection, target):
5421 def update_task_uid(mapper, connection, target):
5422 target.task_uid = ScheduleEntry.get_uid(target)
5422 target.task_uid = ScheduleEntry.get_uid(target)
5423
5423
5424
5424
5425 @event.listens_for(ScheduleEntry, 'before_insert')
5425 @event.listens_for(ScheduleEntry, 'before_insert')
5426 def set_task_uid(mapper, connection, target):
5426 def set_task_uid(mapper, connection, target):
5427 target.task_uid = ScheduleEntry.get_uid(target)
5427 target.task_uid = ScheduleEntry.get_uid(target)
5428
5428
5429
5429
5430 class _BaseBranchPerms(BaseModel):
5430 class _BaseBranchPerms(BaseModel):
5431 @classmethod
5431 @classmethod
5432 def compute_hash(cls, value):
5432 def compute_hash(cls, value):
5433 return sha1_safe(value)
5433 return sha1_safe(value)
5434
5434
5435 @hybrid_property
5435 @hybrid_property
5436 def branch_pattern(self):
5436 def branch_pattern(self):
5437 return self._branch_pattern or '*'
5437 return self._branch_pattern or '*'
5438
5438
5439 @hybrid_property
5439 @hybrid_property
5440 def branch_hash(self):
5440 def branch_hash(self):
5441 return self._branch_hash
5441 return self._branch_hash
5442
5442
5443 def _validate_glob(self, value):
5443 def _validate_glob(self, value):
5444 re.compile('^' + glob2re(value) + '$')
5444 re.compile('^' + glob2re(value) + '$')
5445
5445
5446 @branch_pattern.setter
5446 @branch_pattern.setter
5447 def branch_pattern(self, value):
5447 def branch_pattern(self, value):
5448 self._validate_glob(value)
5448 self._validate_glob(value)
5449 self._branch_pattern = value or '*'
5449 self._branch_pattern = value or '*'
5450 # set the Hash when setting the branch pattern
5450 # set the Hash when setting the branch pattern
5451 self._branch_hash = self.compute_hash(self._branch_pattern)
5451 self._branch_hash = self.compute_hash(self._branch_pattern)
5452
5452
5453 def matches(self, branch):
5453 def matches(self, branch):
5454 """
5454 """
5455 Check if this the branch matches entry
5455 Check if this the branch matches entry
5456
5456
5457 :param branch: branch name for the commit
5457 :param branch: branch name for the commit
5458 """
5458 """
5459
5459
5460 branch = branch or ''
5460 branch = branch or ''
5461
5461
5462 branch_matches = True
5462 branch_matches = True
5463 if branch:
5463 if branch:
5464 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5464 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5465 branch_matches = bool(branch_regex.search(branch))
5465 branch_matches = bool(branch_regex.search(branch))
5466
5466
5467 return branch_matches
5467 return branch_matches
5468
5468
5469
5469
5470 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5470 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5471 __tablename__ = 'user_to_repo_branch_permissions'
5471 __tablename__ = 'user_to_repo_branch_permissions'
5472 __table_args__ = (
5472 __table_args__ = (
5473 base_table_args
5473 base_table_args
5474 )
5474 )
5475
5475
5476 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5476 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5477
5477
5478 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5478 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5479 repo = relationship('Repository', back_populates='user_branch_perms')
5479 repo = relationship('Repository', back_populates='user_branch_perms')
5480
5480
5481 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5481 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5482 permission = relationship('Permission')
5482 permission = relationship('Permission')
5483
5483
5484 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5484 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5485 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5485 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5486
5486
5487 rule_order = Column('rule_order', Integer(), nullable=False)
5487 rule_order = Column('rule_order', Integer(), nullable=False)
5488 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5488 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5489 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5489 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5490
5490
5491 def __repr__(self):
5491 def __repr__(self):
5492 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5492 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5493
5493
5494
5494
5495 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5495 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5496 __tablename__ = 'user_group_to_repo_branch_permissions'
5496 __tablename__ = 'user_group_to_repo_branch_permissions'
5497 __table_args__ = (
5497 __table_args__ = (
5498 base_table_args
5498 base_table_args
5499 )
5499 )
5500
5500
5501 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5501 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5502
5502
5503 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5503 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5504 repo = relationship('Repository', back_populates='user_group_branch_perms')
5504 repo = relationship('Repository', back_populates='user_group_branch_perms')
5505
5505
5506 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5506 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5507 permission = relationship('Permission')
5507 permission = relationship('Permission')
5508
5508
5509 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)
5509 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)
5510 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5510 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5511
5511
5512 rule_order = Column('rule_order', Integer(), nullable=False)
5512 rule_order = Column('rule_order', Integer(), nullable=False)
5513 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5513 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5514 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5514 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5515
5515
5516 def __repr__(self):
5516 def __repr__(self):
5517 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5517 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5518
5518
5519
5519
5520 class UserBookmark(Base, BaseModel):
5520 class UserBookmark(Base, BaseModel):
5521 __tablename__ = 'user_bookmarks'
5521 __tablename__ = 'user_bookmarks'
5522 __table_args__ = (
5522 __table_args__ = (
5523 UniqueConstraint('user_id', 'bookmark_repo_id'),
5523 UniqueConstraint('user_id', 'bookmark_repo_id'),
5524 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5524 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5525 UniqueConstraint('user_id', 'bookmark_position'),
5525 UniqueConstraint('user_id', 'bookmark_position'),
5526 base_table_args
5526 base_table_args
5527 )
5527 )
5528
5528
5529 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5529 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5531 position = Column("bookmark_position", Integer(), nullable=False)
5531 position = Column("bookmark_position", Integer(), nullable=False)
5532 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5532 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5533 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5533 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5534 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5534 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5535
5535
5536 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5536 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5537 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5537 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5538
5538
5539 user = relationship("User")
5539 user = relationship("User")
5540
5540
5541 repository = relationship("Repository")
5541 repository = relationship("Repository")
5542 repository_group = relationship("RepoGroup")
5542 repository_group = relationship("RepoGroup")
5543
5543
5544 @classmethod
5544 @classmethod
5545 def get_by_position_for_user(cls, position, user_id):
5545 def get_by_position_for_user(cls, position, user_id):
5546 return cls.query() \
5546 return cls.query() \
5547 .filter(UserBookmark.user_id == user_id) \
5547 .filter(UserBookmark.user_id == user_id) \
5548 .filter(UserBookmark.position == position).scalar()
5548 .filter(UserBookmark.position == position).scalar()
5549
5549
5550 @classmethod
5550 @classmethod
5551 def get_bookmarks_for_user(cls, user_id, cache=True):
5551 def get_bookmarks_for_user(cls, user_id, cache=True):
5552 bookmarks = cls.query() \
5552 bookmarks = cls.query() \
5553 .filter(UserBookmark.user_id == user_id) \
5553 .filter(UserBookmark.user_id == user_id) \
5554 .options(joinedload(UserBookmark.repository)) \
5554 .options(joinedload(UserBookmark.repository)) \
5555 .options(joinedload(UserBookmark.repository_group)) \
5555 .options(joinedload(UserBookmark.repository_group)) \
5556 .order_by(UserBookmark.position.asc())
5556 .order_by(UserBookmark.position.asc())
5557
5557
5558 if cache:
5558 if cache:
5559 bookmarks = bookmarks.options(
5559 bookmarks = bookmarks.options(
5560 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5560 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5561 )
5561 )
5562
5562
5563 return bookmarks.all()
5563 return bookmarks.all()
5564
5564
5565 def __repr__(self):
5565 def __repr__(self):
5566 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5566 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5567
5567
5568
5568
5569 class FileStore(Base, BaseModel):
5569 class FileStore(Base, BaseModel):
5570 __tablename__ = 'file_store'
5570 __tablename__ = 'file_store'
5571 __table_args__ = (
5571 __table_args__ = (
5572 base_table_args
5572 base_table_args
5573 )
5573 )
5574
5574
5575 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5575 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5576 file_uid = Column('file_uid', String(1024), nullable=False)
5576 file_uid = Column('file_uid', String(1024), nullable=False)
5577 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5577 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5578 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5578 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5579 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5579 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5580
5580
5581 # sha256 hash
5581 # sha256 hash
5582 file_hash = Column('file_hash', String(512), nullable=False)
5582 file_hash = Column('file_hash', String(512), nullable=False)
5583 file_size = Column('file_size', BigInteger(), nullable=False)
5583 file_size = Column('file_size', BigInteger(), nullable=False)
5584
5584
5585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5586 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5586 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5587 accessed_count = Column('accessed_count', Integer(), default=0)
5587 accessed_count = Column('accessed_count', Integer(), default=0)
5588
5588
5589 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5589 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5590
5590
5591 # if repo/repo_group reference is set, check for permissions
5591 # if repo/repo_group reference is set, check for permissions
5592 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5592 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5593
5593
5594 # hidden defines an attachment that should be hidden from showing in artifact listing
5594 # hidden defines an attachment that should be hidden from showing in artifact listing
5595 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5595 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5596
5596
5597 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5597 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5598 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5598 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5599
5599
5600 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5600 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5601
5601
5602 # scope limited to user, which requester have access to
5602 # scope limited to user, which requester have access to
5603 scope_user_id = Column(
5603 scope_user_id = Column(
5604 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5604 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5605 nullable=True, unique=None, default=None)
5605 nullable=True, unique=None, default=None)
5606 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5606 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5607
5607
5608 # scope limited to user group, which requester have access to
5608 # scope limited to user group, which requester have access to
5609 scope_user_group_id = Column(
5609 scope_user_group_id = Column(
5610 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5610 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5611 nullable=True, unique=None, default=None)
5611 nullable=True, unique=None, default=None)
5612 user_group = relationship('UserGroup', lazy='joined')
5612 user_group = relationship('UserGroup', lazy='joined')
5613
5613
5614 # scope limited to repo, which requester have access to
5614 # scope limited to repo, which requester have access to
5615 scope_repo_id = Column(
5615 scope_repo_id = Column(
5616 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5616 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5617 nullable=True, unique=None, default=None)
5617 nullable=True, unique=None, default=None)
5618 repo = relationship('Repository', lazy='joined')
5618 repo = relationship('Repository', lazy='joined')
5619
5619
5620 # scope limited to repo group, which requester have access to
5620 # scope limited to repo group, which requester have access to
5621 scope_repo_group_id = Column(
5621 scope_repo_group_id = Column(
5622 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5622 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5623 nullable=True, unique=None, default=None)
5623 nullable=True, unique=None, default=None)
5624 repo_group = relationship('RepoGroup', lazy='joined')
5624 repo_group = relationship('RepoGroup', lazy='joined')
5625
5625
5626 @classmethod
5626 @classmethod
5627 def get_scope(cls, scope_type, scope_id):
5627 def get_scope(cls, scope_type, scope_id):
5628 if scope_type == 'repo':
5628 if scope_type == 'repo':
5629 return f'repo:{scope_id}'
5629 return f'repo:{scope_id}'
5630 elif scope_type == 'repo-group':
5630 elif scope_type == 'repo-group':
5631 return f'repo-group:{scope_id}'
5631 return f'repo-group:{scope_id}'
5632 elif scope_type == 'user':
5632 elif scope_type == 'user':
5633 return f'user:{scope_id}'
5633 return f'user:{scope_id}'
5634 elif scope_type == 'user-group':
5634 elif scope_type == 'user-group':
5635 return f'user-group:{scope_id}'
5635 return f'user-group:{scope_id}'
5636 else:
5636 else:
5637 return scope_type
5637 return scope_type
5638
5638
5639 @classmethod
5639 @classmethod
5640 def get_by_store_uid(cls, file_store_uid, safe=False):
5640 def get_by_store_uid(cls, file_store_uid, safe=False):
5641 if safe:
5641 if safe:
5642 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5642 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5643 else:
5643 else:
5644 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5644 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5645
5645
5646 @classmethod
5646 @classmethod
5647 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5647 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5648 file_description='', enabled=True, hidden=False, check_acl=True,
5648 file_description='', enabled=True, hidden=False, check_acl=True,
5649 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5649 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5650
5650
5651 store_entry = FileStore()
5651 store_entry = FileStore()
5652 store_entry.file_uid = file_uid
5652 store_entry.file_uid = file_uid
5653 store_entry.file_display_name = file_display_name
5653 store_entry.file_display_name = file_display_name
5654 store_entry.file_org_name = filename
5654 store_entry.file_org_name = filename
5655 store_entry.file_size = file_size
5655 store_entry.file_size = file_size
5656 store_entry.file_hash = file_hash
5656 store_entry.file_hash = file_hash
5657 store_entry.file_description = file_description
5657 store_entry.file_description = file_description
5658
5658
5659 store_entry.check_acl = check_acl
5659 store_entry.check_acl = check_acl
5660 store_entry.enabled = enabled
5660 store_entry.enabled = enabled
5661 store_entry.hidden = hidden
5661 store_entry.hidden = hidden
5662
5662
5663 store_entry.user_id = user_id
5663 store_entry.user_id = user_id
5664 store_entry.scope_user_id = scope_user_id
5664 store_entry.scope_user_id = scope_user_id
5665 store_entry.scope_repo_id = scope_repo_id
5665 store_entry.scope_repo_id = scope_repo_id
5666 store_entry.scope_repo_group_id = scope_repo_group_id
5666 store_entry.scope_repo_group_id = scope_repo_group_id
5667
5667
5668 return store_entry
5668 return store_entry
5669
5669
5670 @classmethod
5670 @classmethod
5671 def store_metadata(cls, file_store_id, args, commit=True):
5671 def store_metadata(cls, file_store_id, args, commit=True):
5672 file_store = FileStore.get(file_store_id)
5672 file_store = FileStore.get(file_store_id)
5673 if file_store is None:
5673 if file_store is None:
5674 return
5674 return
5675
5675
5676 for section, key, value, value_type in args:
5676 for section, key, value, value_type in args:
5677 has_key = FileStoreMetadata().query() \
5677 has_key = FileStoreMetadata().query() \
5678 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5678 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5679 .filter(FileStoreMetadata.file_store_meta_section == section) \
5679 .filter(FileStoreMetadata.file_store_meta_section == section) \
5680 .filter(FileStoreMetadata.file_store_meta_key == key) \
5680 .filter(FileStoreMetadata.file_store_meta_key == key) \
5681 .scalar()
5681 .scalar()
5682 if has_key:
5682 if has_key:
5683 msg = 'key `{}` already defined under section `{}` for this file.'\
5683 msg = 'key `{}` already defined under section `{}` for this file.'\
5684 .format(key, section)
5684 .format(key, section)
5685 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5685 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5686
5686
5687 # NOTE(marcink): raises ArtifactMetadataBadValueType
5687 # NOTE(marcink): raises ArtifactMetadataBadValueType
5688 FileStoreMetadata.valid_value_type(value_type)
5688 FileStoreMetadata.valid_value_type(value_type)
5689
5689
5690 meta_entry = FileStoreMetadata()
5690 meta_entry = FileStoreMetadata()
5691 meta_entry.file_store = file_store
5691 meta_entry.file_store = file_store
5692 meta_entry.file_store_meta_section = section
5692 meta_entry.file_store_meta_section = section
5693 meta_entry.file_store_meta_key = key
5693 meta_entry.file_store_meta_key = key
5694 meta_entry.file_store_meta_value_type = value_type
5694 meta_entry.file_store_meta_value_type = value_type
5695 meta_entry.file_store_meta_value = value
5695 meta_entry.file_store_meta_value = value
5696
5696
5697 Session().add(meta_entry)
5697 Session().add(meta_entry)
5698
5698
5699 try:
5699 try:
5700 if commit:
5700 if commit:
5701 Session().commit()
5701 Session().commit()
5702 except IntegrityError:
5702 except IntegrityError:
5703 Session().rollback()
5703 Session().rollback()
5704 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5704 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5705
5705
5706 @classmethod
5706 @classmethod
5707 def bump_access_counter(cls, file_uid, commit=True):
5707 def bump_access_counter(cls, file_uid, commit=True):
5708 FileStore().query()\
5708 FileStore().query()\
5709 .filter(FileStore.file_uid == file_uid)\
5709 .filter(FileStore.file_uid == file_uid)\
5710 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5710 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5711 FileStore.accessed_on: datetime.datetime.now()})
5711 FileStore.accessed_on: datetime.datetime.now()})
5712 if commit:
5712 if commit:
5713 Session().commit()
5713 Session().commit()
5714
5714
5715 def __json__(self):
5715 def __json__(self):
5716 data = {
5716 data = {
5717 'filename': self.file_display_name,
5717 'filename': self.file_display_name,
5718 'filename_org': self.file_org_name,
5718 'filename_org': self.file_org_name,
5719 'file_uid': self.file_uid,
5719 'file_uid': self.file_uid,
5720 'description': self.file_description,
5720 'description': self.file_description,
5721 'hidden': self.hidden,
5721 'hidden': self.hidden,
5722 'size': self.file_size,
5722 'size': self.file_size,
5723 'created_on': self.created_on,
5723 'created_on': self.created_on,
5724 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5724 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5725 'downloaded_times': self.accessed_count,
5725 'downloaded_times': self.accessed_count,
5726 'sha256': self.file_hash,
5726 'sha256': self.file_hash,
5727 'metadata': self.file_metadata,
5727 'metadata': self.file_metadata,
5728 }
5728 }
5729
5729
5730 return data
5730 return data
5731
5731
5732 def __repr__(self):
5732 def __repr__(self):
5733 return f'<FileStore({self.file_store_id})>'
5733 return f'<FileStore({self.file_store_id})>'
5734
5734
5735
5735
5736 class FileStoreMetadata(Base, BaseModel):
5736 class FileStoreMetadata(Base, BaseModel):
5737 __tablename__ = 'file_store_metadata'
5737 __tablename__ = 'file_store_metadata'
5738 __table_args__ = (
5738 __table_args__ = (
5739 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5739 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5740 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5740 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5741 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5741 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5742 base_table_args
5742 base_table_args
5743 )
5743 )
5744 SETTINGS_TYPES = {
5744 SETTINGS_TYPES = {
5745 'str': safe_str,
5745 'str': safe_str,
5746 'int': safe_int,
5746 'int': safe_int,
5747 'unicode': safe_str,
5747 'unicode': safe_str,
5748 'bool': str2bool,
5748 'bool': str2bool,
5749 'list': functools.partial(aslist, sep=',')
5749 'list': functools.partial(aslist, sep=',')
5750 }
5750 }
5751
5751
5752 file_store_meta_id = Column(
5752 file_store_meta_id = Column(
5753 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5753 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5754 primary_key=True)
5754 primary_key=True)
5755 _file_store_meta_section = Column(
5755 _file_store_meta_section = Column(
5756 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5756 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5757 nullable=True, unique=None, default=None)
5757 nullable=True, unique=None, default=None)
5758 _file_store_meta_section_hash = Column(
5758 _file_store_meta_section_hash = Column(
5759 "file_store_meta_section_hash", String(255),
5759 "file_store_meta_section_hash", String(255),
5760 nullable=True, unique=None, default=None)
5760 nullable=True, unique=None, default=None)
5761 _file_store_meta_key = Column(
5761 _file_store_meta_key = Column(
5762 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5762 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5763 nullable=True, unique=None, default=None)
5763 nullable=True, unique=None, default=None)
5764 _file_store_meta_key_hash = Column(
5764 _file_store_meta_key_hash = Column(
5765 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5765 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5766 _file_store_meta_value = Column(
5766 _file_store_meta_value = Column(
5767 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5767 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5768 nullable=True, unique=None, default=None)
5768 nullable=True, unique=None, default=None)
5769 _file_store_meta_value_type = Column(
5769 _file_store_meta_value_type = Column(
5770 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5770 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5771 default='unicode')
5771 default='unicode')
5772
5772
5773 file_store_id = Column(
5773 file_store_id = Column(
5774 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5774 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5775 nullable=True, unique=None, default=None)
5775 nullable=True, unique=None, default=None)
5776
5776
5777 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5777 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5778
5778
5779 @classmethod
5779 @classmethod
5780 def valid_value_type(cls, value):
5780 def valid_value_type(cls, value):
5781 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5781 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5782 raise ArtifactMetadataBadValueType(
5782 raise ArtifactMetadataBadValueType(
5783 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5783 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5784
5784
5785 @hybrid_property
5785 @hybrid_property
5786 def file_store_meta_section(self):
5786 def file_store_meta_section(self):
5787 return self._file_store_meta_section
5787 return self._file_store_meta_section
5788
5788
5789 @file_store_meta_section.setter
5789 @file_store_meta_section.setter
5790 def file_store_meta_section(self, value):
5790 def file_store_meta_section(self, value):
5791 self._file_store_meta_section = value
5791 self._file_store_meta_section = value
5792 self._file_store_meta_section_hash = _hash_key(value)
5792 self._file_store_meta_section_hash = _hash_key(value)
5793
5793
5794 @hybrid_property
5794 @hybrid_property
5795 def file_store_meta_key(self):
5795 def file_store_meta_key(self):
5796 return self._file_store_meta_key
5796 return self._file_store_meta_key
5797
5797
5798 @file_store_meta_key.setter
5798 @file_store_meta_key.setter
5799 def file_store_meta_key(self, value):
5799 def file_store_meta_key(self, value):
5800 self._file_store_meta_key = value
5800 self._file_store_meta_key = value
5801 self._file_store_meta_key_hash = _hash_key(value)
5801 self._file_store_meta_key_hash = _hash_key(value)
5802
5802
5803 @hybrid_property
5803 @hybrid_property
5804 def file_store_meta_value(self):
5804 def file_store_meta_value(self):
5805 val = self._file_store_meta_value
5805 val = self._file_store_meta_value
5806
5806
5807 if self._file_store_meta_value_type:
5807 if self._file_store_meta_value_type:
5808 # e.g unicode.encrypted == unicode
5808 # e.g unicode.encrypted == unicode
5809 _type = self._file_store_meta_value_type.split('.')[0]
5809 _type = self._file_store_meta_value_type.split('.')[0]
5810 # decode the encrypted value if it's encrypted field type
5810 # decode the encrypted value if it's encrypted field type
5811 if '.encrypted' in self._file_store_meta_value_type:
5811 if '.encrypted' in self._file_store_meta_value_type:
5812 cipher = EncryptedTextValue()
5812 cipher = EncryptedTextValue()
5813 val = safe_str(cipher.process_result_value(val, None))
5813 val = safe_str(cipher.process_result_value(val, None))
5814 # do final type conversion
5814 # do final type conversion
5815 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5815 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5816 val = converter(val)
5816 val = converter(val)
5817
5817
5818 return val
5818 return val
5819
5819
5820 @file_store_meta_value.setter
5820 @file_store_meta_value.setter
5821 def file_store_meta_value(self, val):
5821 def file_store_meta_value(self, val):
5822 val = safe_str(val)
5822 val = safe_str(val)
5823 # encode the encrypted value
5823 # encode the encrypted value
5824 if '.encrypted' in self.file_store_meta_value_type:
5824 if '.encrypted' in self.file_store_meta_value_type:
5825 cipher = EncryptedTextValue()
5825 cipher = EncryptedTextValue()
5826 val = safe_str(cipher.process_bind_param(val, None))
5826 val = safe_str(cipher.process_bind_param(val, None))
5827 self._file_store_meta_value = val
5827 self._file_store_meta_value = val
5828
5828
5829 @hybrid_property
5829 @hybrid_property
5830 def file_store_meta_value_type(self):
5830 def file_store_meta_value_type(self):
5831 return self._file_store_meta_value_type
5831 return self._file_store_meta_value_type
5832
5832
5833 @file_store_meta_value_type.setter
5833 @file_store_meta_value_type.setter
5834 def file_store_meta_value_type(self, val):
5834 def file_store_meta_value_type(self, val):
5835 # e.g unicode.encrypted
5835 # e.g unicode.encrypted
5836 self.valid_value_type(val)
5836 self.valid_value_type(val)
5837 self._file_store_meta_value_type = val
5837 self._file_store_meta_value_type = val
5838
5838
5839 def __json__(self):
5839 def __json__(self):
5840 data = {
5840 data = {
5841 'artifact': self.file_store.file_uid,
5841 'artifact': self.file_store.file_uid,
5842 'section': self.file_store_meta_section,
5842 'section': self.file_store_meta_section,
5843 'key': self.file_store_meta_key,
5843 'key': self.file_store_meta_key,
5844 'value': self.file_store_meta_value,
5844 'value': self.file_store_meta_value,
5845 }
5845 }
5846
5846
5847 return data
5847 return data
5848
5848
5849 def __repr__(self):
5849 def __repr__(self):
5850 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5850 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5851 self.file_store_meta_key, self.file_store_meta_value)
5851 self.file_store_meta_key, self.file_store_meta_value)
5852
5852
5853
5853
5854 class DbMigrateVersion(Base, BaseModel):
5854 class DbMigrateVersion(Base, BaseModel):
5855 __tablename__ = 'db_migrate_version'
5855 __tablename__ = 'db_migrate_version'
5856 __table_args__ = (
5856 __table_args__ = (
5857 base_table_args,
5857 base_table_args,
5858 )
5858 )
5859
5859
5860 repository_id = Column('repository_id', String(250), primary_key=True)
5860 repository_id = Column('repository_id', String(250), primary_key=True)
5861 repository_path = Column('repository_path', Text)
5861 repository_path = Column('repository_path', Text)
5862 version = Column('version', Integer)
5862 version = Column('version', Integer)
5863
5863
5864 @classmethod
5864 @classmethod
5865 def set_version(cls, version):
5865 def set_version(cls, version):
5866 """
5866 """
5867 Helper for forcing a different version, usually for debugging purposes via ishell.
5867 Helper for forcing a different version, usually for debugging purposes via ishell.
5868 """
5868 """
5869 ver = DbMigrateVersion.query().first()
5869 ver = DbMigrateVersion.query().first()
5870 ver.version = version
5870 ver.version = version
5871 Session().commit()
5871 Session().commit()
5872
5872
5873
5873
5874 class DbSession(Base, BaseModel):
5874 class DbSession(Base, BaseModel):
5875 __tablename__ = 'db_session'
5875 __tablename__ = 'db_session'
5876 __table_args__ = (
5876 __table_args__ = (
5877 base_table_args,
5877 base_table_args,
5878 )
5878 )
5879
5879
5880 def __repr__(self):
5880 def __repr__(self):
5881 return f'<DB:DbSession({self.id})>'
5881 return f'<DB:DbSession({self.id})>'
5882
5882
5883 id = Column('id', Integer())
5883 id = Column('id', Integer())
5884 namespace = Column('namespace', String(255), primary_key=True)
5884 namespace = Column('namespace', String(255), primary_key=True)
5885 accessed = Column('accessed', DateTime, nullable=False)
5885 accessed = Column('accessed', DateTime, nullable=False)
5886 created = Column('created', DateTime, nullable=False)
5886 created = Column('created', DateTime, nullable=False)
5887 data = Column('data', PickleType, nullable=False)
5887 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now