##// END OF EJS Templates
fix(comments): fixed outdated comments logic broken by latest changes
super-admin -
r5220:e9dc7693 default
parent child Browse files
Show More
@@ -1,5885 +1,5877 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 #back_populates="pr_source"
1782 #back_populates="pr_source"
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 #back_populates="pr_target"
1788 #back_populates="pr_target"
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 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2037 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2038 repo_id=self.repo_id)
2038 repo_id=self.repo_id)
2039 return CacheKey.query()\
2039 return CacheKey.query()\
2040 .filter(CacheKey.cache_args == invalidation_namespace)\
2040 .filter(CacheKey.cache_args == invalidation_namespace)\
2041 .order_by(CacheKey.cache_key)\
2041 .order_by(CacheKey.cache_key)\
2042 .all()
2042 .all()
2043
2043
2044 @property
2044 @property
2045 def cached_diffs_relative_dir(self):
2045 def cached_diffs_relative_dir(self):
2046 """
2046 """
2047 Return a relative to the repository store path of cached diffs
2047 Return a relative to the repository store path of cached diffs
2048 used for safe display for users, who shouldn't know the absolute store
2048 used for safe display for users, who shouldn't know the absolute store
2049 path
2049 path
2050 """
2050 """
2051 return os.path.join(
2051 return os.path.join(
2052 os.path.dirname(self.repo_name),
2052 os.path.dirname(self.repo_name),
2053 self.cached_diffs_dir.split(os.path.sep)[-1])
2053 self.cached_diffs_dir.split(os.path.sep)[-1])
2054
2054
2055 @property
2055 @property
2056 def cached_diffs_dir(self):
2056 def cached_diffs_dir(self):
2057 path = self.repo_full_path
2057 path = self.repo_full_path
2058 return os.path.join(
2058 return os.path.join(
2059 os.path.dirname(path),
2059 os.path.dirname(path),
2060 f'.__shadow_diff_cache_repo_{self.repo_id}')
2060 f'.__shadow_diff_cache_repo_{self.repo_id}')
2061
2061
2062 def cached_diffs(self):
2062 def cached_diffs(self):
2063 diff_cache_dir = self.cached_diffs_dir
2063 diff_cache_dir = self.cached_diffs_dir
2064 if os.path.isdir(diff_cache_dir):
2064 if os.path.isdir(diff_cache_dir):
2065 return os.listdir(diff_cache_dir)
2065 return os.listdir(diff_cache_dir)
2066 return []
2066 return []
2067
2067
2068 def shadow_repos(self):
2068 def shadow_repos(self):
2069 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2069 shadow_repos_pattern = f'.__shadow_repo_{self.repo_id}'
2070 return [
2070 return [
2071 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2071 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2072 if x.startswith(shadow_repos_pattern)
2072 if x.startswith(shadow_repos_pattern)
2073 ]
2073 ]
2074
2074
2075 def get_new_name(self, repo_name):
2075 def get_new_name(self, repo_name):
2076 """
2076 """
2077 returns new full repository name based on assigned group and new new
2077 returns new full repository name based on assigned group and new new
2078
2078
2079 :param repo_name:
2079 :param repo_name:
2080 """
2080 """
2081 path_prefix = self.group.full_path_splitted if self.group else []
2081 path_prefix = self.group.full_path_splitted if self.group else []
2082 return self.NAME_SEP.join(path_prefix + [repo_name])
2082 return self.NAME_SEP.join(path_prefix + [repo_name])
2083
2083
2084 @property
2084 @property
2085 def _config(self):
2085 def _config(self):
2086 """
2086 """
2087 Returns db based config object.
2087 Returns db based config object.
2088 """
2088 """
2089 from rhodecode.lib.utils import make_db_config
2089 from rhodecode.lib.utils import make_db_config
2090 return make_db_config(clear_session=False, repo=self)
2090 return make_db_config(clear_session=False, repo=self)
2091
2091
2092 def permissions(self, with_admins=True, with_owner=True,
2092 def permissions(self, with_admins=True, with_owner=True,
2093 expand_from_user_groups=False):
2093 expand_from_user_groups=False):
2094 """
2094 """
2095 Permissions for repositories
2095 Permissions for repositories
2096 """
2096 """
2097 _admin_perm = 'repository.admin'
2097 _admin_perm = 'repository.admin'
2098
2098
2099 owner_row = []
2099 owner_row = []
2100 if with_owner:
2100 if with_owner:
2101 usr = AttributeDict(self.user.get_dict())
2101 usr = AttributeDict(self.user.get_dict())
2102 usr.owner_row = True
2102 usr.owner_row = True
2103 usr.permission = _admin_perm
2103 usr.permission = _admin_perm
2104 usr.permission_id = None
2104 usr.permission_id = None
2105 owner_row.append(usr)
2105 owner_row.append(usr)
2106
2106
2107 super_admin_ids = []
2107 super_admin_ids = []
2108 super_admin_rows = []
2108 super_admin_rows = []
2109 if with_admins:
2109 if with_admins:
2110 for usr in User.get_all_super_admins():
2110 for usr in User.get_all_super_admins():
2111 super_admin_ids.append(usr.user_id)
2111 super_admin_ids.append(usr.user_id)
2112 # if this admin is also owner, don't double the record
2112 # if this admin is also owner, don't double the record
2113 if usr.user_id == owner_row[0].user_id:
2113 if usr.user_id == owner_row[0].user_id:
2114 owner_row[0].admin_row = True
2114 owner_row[0].admin_row = True
2115 else:
2115 else:
2116 usr = AttributeDict(usr.get_dict())
2116 usr = AttributeDict(usr.get_dict())
2117 usr.admin_row = True
2117 usr.admin_row = True
2118 usr.permission = _admin_perm
2118 usr.permission = _admin_perm
2119 usr.permission_id = None
2119 usr.permission_id = None
2120 super_admin_rows.append(usr)
2120 super_admin_rows.append(usr)
2121
2121
2122 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2122 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2123 q = q.options(joinedload(UserRepoToPerm.repository),
2123 q = q.options(joinedload(UserRepoToPerm.repository),
2124 joinedload(UserRepoToPerm.user),
2124 joinedload(UserRepoToPerm.user),
2125 joinedload(UserRepoToPerm.permission),)
2125 joinedload(UserRepoToPerm.permission),)
2126
2126
2127 # get owners and admins and permissions. We do a trick of re-writing
2127 # get owners and admins and permissions. We do a trick of re-writing
2128 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2128 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2129 # has a global reference and changing one object propagates to all
2129 # has a global reference and changing one object propagates to all
2130 # others. This means if admin is also an owner admin_row that change
2130 # others. This means if admin is also an owner admin_row that change
2131 # would propagate to both objects
2131 # would propagate to both objects
2132 perm_rows = []
2132 perm_rows = []
2133 for _usr in q.all():
2133 for _usr in q.all():
2134 usr = AttributeDict(_usr.user.get_dict())
2134 usr = AttributeDict(_usr.user.get_dict())
2135 # if this user is also owner/admin, mark as duplicate record
2135 # if this user is also owner/admin, mark as duplicate record
2136 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2136 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2137 usr.duplicate_perm = True
2137 usr.duplicate_perm = True
2138 # also check if this permission is maybe used by branch_permissions
2138 # also check if this permission is maybe used by branch_permissions
2139 if _usr.branch_perm_entry:
2139 if _usr.branch_perm_entry:
2140 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2140 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2141
2141
2142 usr.permission = _usr.permission.permission_name
2142 usr.permission = _usr.permission.permission_name
2143 usr.permission_id = _usr.repo_to_perm_id
2143 usr.permission_id = _usr.repo_to_perm_id
2144 perm_rows.append(usr)
2144 perm_rows.append(usr)
2145
2145
2146 # filter the perm rows by 'default' first and then sort them by
2146 # filter the perm rows by 'default' first and then sort them by
2147 # admin,write,read,none permissions sorted again alphabetically in
2147 # admin,write,read,none permissions sorted again alphabetically in
2148 # each group
2148 # each group
2149 perm_rows = sorted(perm_rows, key=display_user_sort)
2149 perm_rows = sorted(perm_rows, key=display_user_sort)
2150
2150
2151 user_groups_rows = []
2151 user_groups_rows = []
2152 if expand_from_user_groups:
2152 if expand_from_user_groups:
2153 for ug in self.permission_user_groups(with_members=True):
2153 for ug in self.permission_user_groups(with_members=True):
2154 for user_data in ug.members:
2154 for user_data in ug.members:
2155 user_groups_rows.append(user_data)
2155 user_groups_rows.append(user_data)
2156
2156
2157 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2157 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2158
2158
2159 def permission_user_groups(self, with_members=True):
2159 def permission_user_groups(self, with_members=True):
2160 q = UserGroupRepoToPerm.query()\
2160 q = UserGroupRepoToPerm.query()\
2161 .filter(UserGroupRepoToPerm.repository == self)
2161 .filter(UserGroupRepoToPerm.repository == self)
2162 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2162 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2163 joinedload(UserGroupRepoToPerm.users_group),
2163 joinedload(UserGroupRepoToPerm.users_group),
2164 joinedload(UserGroupRepoToPerm.permission),)
2164 joinedload(UserGroupRepoToPerm.permission),)
2165
2165
2166 perm_rows = []
2166 perm_rows = []
2167 for _user_group in q.all():
2167 for _user_group in q.all():
2168 entry = AttributeDict(_user_group.users_group.get_dict())
2168 entry = AttributeDict(_user_group.users_group.get_dict())
2169 entry.permission = _user_group.permission.permission_name
2169 entry.permission = _user_group.permission.permission_name
2170 if with_members:
2170 if with_members:
2171 entry.members = [x.user.get_dict()
2171 entry.members = [x.user.get_dict()
2172 for x in _user_group.users_group.members]
2172 for x in _user_group.users_group.members]
2173 perm_rows.append(entry)
2173 perm_rows.append(entry)
2174
2174
2175 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2175 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2176 return perm_rows
2176 return perm_rows
2177
2177
2178 def get_api_data(self, include_secrets=False):
2178 def get_api_data(self, include_secrets=False):
2179 """
2179 """
2180 Common function for generating repo api data
2180 Common function for generating repo api data
2181
2181
2182 :param include_secrets: See :meth:`User.get_api_data`.
2182 :param include_secrets: See :meth:`User.get_api_data`.
2183
2183
2184 """
2184 """
2185 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2185 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2186 # move this methods on models level.
2186 # move this methods on models level.
2187 from rhodecode.model.settings import SettingsModel
2187 from rhodecode.model.settings import SettingsModel
2188 from rhodecode.model.repo import RepoModel
2188 from rhodecode.model.repo import RepoModel
2189
2189
2190 repo = self
2190 repo = self
2191 _user_id, _time, _reason = self.locked
2191 _user_id, _time, _reason = self.locked
2192
2192
2193 data = {
2193 data = {
2194 'repo_id': repo.repo_id,
2194 'repo_id': repo.repo_id,
2195 'repo_name': repo.repo_name,
2195 'repo_name': repo.repo_name,
2196 'repo_type': repo.repo_type,
2196 'repo_type': repo.repo_type,
2197 'clone_uri': repo.clone_uri or '',
2197 'clone_uri': repo.clone_uri or '',
2198 'push_uri': repo.push_uri or '',
2198 'push_uri': repo.push_uri or '',
2199 'url': RepoModel().get_url(self),
2199 'url': RepoModel().get_url(self),
2200 'private': repo.private,
2200 'private': repo.private,
2201 'created_on': repo.created_on,
2201 'created_on': repo.created_on,
2202 'description': repo.description_safe,
2202 'description': repo.description_safe,
2203 'landing_rev': repo.landing_rev,
2203 'landing_rev': repo.landing_rev,
2204 'owner': repo.user.username,
2204 'owner': repo.user.username,
2205 'fork_of': repo.fork.repo_name if repo.fork else None,
2205 'fork_of': repo.fork.repo_name if repo.fork else None,
2206 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2206 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2207 'enable_statistics': repo.enable_statistics,
2207 'enable_statistics': repo.enable_statistics,
2208 'enable_locking': repo.enable_locking,
2208 'enable_locking': repo.enable_locking,
2209 'enable_downloads': repo.enable_downloads,
2209 'enable_downloads': repo.enable_downloads,
2210 'last_changeset': repo.changeset_cache,
2210 'last_changeset': repo.changeset_cache,
2211 'locked_by': User.get(_user_id).get_api_data(
2211 'locked_by': User.get(_user_id).get_api_data(
2212 include_secrets=include_secrets) if _user_id else None,
2212 include_secrets=include_secrets) if _user_id else None,
2213 'locked_date': time_to_datetime(_time) if _time else None,
2213 'locked_date': time_to_datetime(_time) if _time else None,
2214 'lock_reason': _reason if _reason else None,
2214 'lock_reason': _reason if _reason else None,
2215 }
2215 }
2216
2216
2217 # TODO: mikhail: should be per-repo settings here
2217 # TODO: mikhail: should be per-repo settings here
2218 rc_config = SettingsModel().get_all_settings()
2218 rc_config = SettingsModel().get_all_settings()
2219 repository_fields = str2bool(
2219 repository_fields = str2bool(
2220 rc_config.get('rhodecode_repository_fields'))
2220 rc_config.get('rhodecode_repository_fields'))
2221 if repository_fields:
2221 if repository_fields:
2222 for f in self.extra_fields:
2222 for f in self.extra_fields:
2223 data[f.field_key_prefixed] = f.field_value
2223 data[f.field_key_prefixed] = f.field_value
2224
2224
2225 return data
2225 return data
2226
2226
2227 @classmethod
2227 @classmethod
2228 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2228 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2229 if not lock_time:
2229 if not lock_time:
2230 lock_time = time.time()
2230 lock_time = time.time()
2231 if not lock_reason:
2231 if not lock_reason:
2232 lock_reason = cls.LOCK_AUTOMATIC
2232 lock_reason = cls.LOCK_AUTOMATIC
2233 repo.locked = [user_id, lock_time, lock_reason]
2233 repo.locked = [user_id, lock_time, lock_reason]
2234 Session().add(repo)
2234 Session().add(repo)
2235 Session().commit()
2235 Session().commit()
2236
2236
2237 @classmethod
2237 @classmethod
2238 def unlock(cls, repo):
2238 def unlock(cls, repo):
2239 repo.locked = None
2239 repo.locked = None
2240 Session().add(repo)
2240 Session().add(repo)
2241 Session().commit()
2241 Session().commit()
2242
2242
2243 @classmethod
2243 @classmethod
2244 def getlock(cls, repo):
2244 def getlock(cls, repo):
2245 return repo.locked
2245 return repo.locked
2246
2246
2247 def get_locking_state(self, action, user_id, only_when_enabled=True):
2247 def get_locking_state(self, action, user_id, only_when_enabled=True):
2248 """
2248 """
2249 Checks locking on this repository, if locking is enabled and lock is
2249 Checks locking on this repository, if locking is enabled and lock is
2250 present returns a tuple of make_lock, locked, locked_by.
2250 present returns a tuple of make_lock, locked, locked_by.
2251 make_lock can have 3 states None (do nothing) True, make lock
2251 make_lock can have 3 states None (do nothing) True, make lock
2252 False release lock, This value is later propagated to hooks, which
2252 False release lock, This value is later propagated to hooks, which
2253 do the locking. Think about this as signals passed to hooks what to do.
2253 do the locking. Think about this as signals passed to hooks what to do.
2254
2254
2255 """
2255 """
2256 # TODO: johbo: This is part of the business logic and should be moved
2256 # TODO: johbo: This is part of the business logic and should be moved
2257 # into the RepositoryModel.
2257 # into the RepositoryModel.
2258
2258
2259 if action not in ('push', 'pull'):
2259 if action not in ('push', 'pull'):
2260 raise ValueError("Invalid action value: %s" % repr(action))
2260 raise ValueError("Invalid action value: %s" % repr(action))
2261
2261
2262 # defines if locked error should be thrown to user
2262 # defines if locked error should be thrown to user
2263 currently_locked = False
2263 currently_locked = False
2264 # defines if new lock should be made, tri-state
2264 # defines if new lock should be made, tri-state
2265 make_lock = None
2265 make_lock = None
2266 repo = self
2266 repo = self
2267 user = User.get(user_id)
2267 user = User.get(user_id)
2268
2268
2269 lock_info = repo.locked
2269 lock_info = repo.locked
2270
2270
2271 if repo and (repo.enable_locking or not only_when_enabled):
2271 if repo and (repo.enable_locking or not only_when_enabled):
2272 if action == 'push':
2272 if action == 'push':
2273 # check if it's already locked !, if it is compare users
2273 # check if it's already locked !, if it is compare users
2274 locked_by_user_id = lock_info[0]
2274 locked_by_user_id = lock_info[0]
2275 if user.user_id == locked_by_user_id:
2275 if user.user_id == locked_by_user_id:
2276 log.debug(
2276 log.debug(
2277 'Got `push` action from user %s, now unlocking', user)
2277 'Got `push` action from user %s, now unlocking', user)
2278 # unlock if we have push from user who locked
2278 # unlock if we have push from user who locked
2279 make_lock = False
2279 make_lock = False
2280 else:
2280 else:
2281 # we're not the same user who locked, ban with
2281 # we're not the same user who locked, ban with
2282 # code defined in settings (default is 423 HTTP Locked) !
2282 # code defined in settings (default is 423 HTTP Locked) !
2283 log.debug('Repo %s is currently locked by %s', repo, user)
2283 log.debug('Repo %s is currently locked by %s', repo, user)
2284 currently_locked = True
2284 currently_locked = True
2285 elif action == 'pull':
2285 elif action == 'pull':
2286 # [0] user [1] date
2286 # [0] user [1] date
2287 if lock_info[0] and lock_info[1]:
2287 if lock_info[0] and lock_info[1]:
2288 log.debug('Repo %s is currently locked by %s', repo, user)
2288 log.debug('Repo %s is currently locked by %s', repo, user)
2289 currently_locked = True
2289 currently_locked = True
2290 else:
2290 else:
2291 log.debug('Setting lock on repo %s by %s', repo, user)
2291 log.debug('Setting lock on repo %s by %s', repo, user)
2292 make_lock = True
2292 make_lock = True
2293
2293
2294 else:
2294 else:
2295 log.debug('Repository %s do not have locking enabled', repo)
2295 log.debug('Repository %s do not have locking enabled', repo)
2296
2296
2297 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2297 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2298 make_lock, currently_locked, lock_info)
2298 make_lock, currently_locked, lock_info)
2299
2299
2300 from rhodecode.lib.auth import HasRepoPermissionAny
2300 from rhodecode.lib.auth import HasRepoPermissionAny
2301 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2301 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2302 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2302 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2303 # if we don't have at least write permission we cannot make a lock
2303 # if we don't have at least write permission we cannot make a lock
2304 log.debug('lock state reset back to FALSE due to lack '
2304 log.debug('lock state reset back to FALSE due to lack '
2305 'of at least read permission')
2305 'of at least read permission')
2306 make_lock = False
2306 make_lock = False
2307
2307
2308 return make_lock, currently_locked, lock_info
2308 return make_lock, currently_locked, lock_info
2309
2309
2310 @property
2310 @property
2311 def last_commit_cache_update_diff(self):
2311 def last_commit_cache_update_diff(self):
2312 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2312 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2313
2313
2314 @classmethod
2314 @classmethod
2315 def _load_commit_change(cls, last_commit_cache):
2315 def _load_commit_change(cls, last_commit_cache):
2316 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2316 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2317 empty_date = datetime.datetime.fromtimestamp(0)
2317 empty_date = datetime.datetime.fromtimestamp(0)
2318 date_latest = last_commit_cache.get('date', empty_date)
2318 date_latest = last_commit_cache.get('date', empty_date)
2319 try:
2319 try:
2320 return parse_datetime(date_latest)
2320 return parse_datetime(date_latest)
2321 except Exception:
2321 except Exception:
2322 return empty_date
2322 return empty_date
2323
2323
2324 @property
2324 @property
2325 def last_commit_change(self):
2325 def last_commit_change(self):
2326 return self._load_commit_change(self.changeset_cache)
2326 return self._load_commit_change(self.changeset_cache)
2327
2327
2328 @property
2328 @property
2329 def last_db_change(self):
2329 def last_db_change(self):
2330 return self.updated_on
2330 return self.updated_on
2331
2331
2332 @property
2332 @property
2333 def clone_uri_hidden(self):
2333 def clone_uri_hidden(self):
2334 clone_uri = self.clone_uri
2334 clone_uri = self.clone_uri
2335 if clone_uri:
2335 if clone_uri:
2336 import urlobject
2336 import urlobject
2337 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2337 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2338 if url_obj.password:
2338 if url_obj.password:
2339 clone_uri = url_obj.with_password('*****')
2339 clone_uri = url_obj.with_password('*****')
2340 return clone_uri
2340 return clone_uri
2341
2341
2342 @property
2342 @property
2343 def push_uri_hidden(self):
2343 def push_uri_hidden(self):
2344 push_uri = self.push_uri
2344 push_uri = self.push_uri
2345 if push_uri:
2345 if push_uri:
2346 import urlobject
2346 import urlobject
2347 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2347 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2348 if url_obj.password:
2348 if url_obj.password:
2349 push_uri = url_obj.with_password('*****')
2349 push_uri = url_obj.with_password('*****')
2350 return push_uri
2350 return push_uri
2351
2351
2352 def clone_url(self, **override):
2352 def clone_url(self, **override):
2353 from rhodecode.model.settings import SettingsModel
2353 from rhodecode.model.settings import SettingsModel
2354
2354
2355 uri_tmpl = None
2355 uri_tmpl = None
2356 if 'with_id' in override:
2356 if 'with_id' in override:
2357 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2357 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2358 del override['with_id']
2358 del override['with_id']
2359
2359
2360 if 'uri_tmpl' in override:
2360 if 'uri_tmpl' in override:
2361 uri_tmpl = override['uri_tmpl']
2361 uri_tmpl = override['uri_tmpl']
2362 del override['uri_tmpl']
2362 del override['uri_tmpl']
2363
2363
2364 ssh = False
2364 ssh = False
2365 if 'ssh' in override:
2365 if 'ssh' in override:
2366 ssh = True
2366 ssh = True
2367 del override['ssh']
2367 del override['ssh']
2368
2368
2369 # we didn't override our tmpl from **overrides
2369 # we didn't override our tmpl from **overrides
2370 request = get_current_request()
2370 request = get_current_request()
2371 if not uri_tmpl:
2371 if not uri_tmpl:
2372 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2372 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2373 rc_config = request.call_context.rc_config
2373 rc_config = request.call_context.rc_config
2374 else:
2374 else:
2375 rc_config = SettingsModel().get_all_settings(cache=True)
2375 rc_config = SettingsModel().get_all_settings(cache=True)
2376
2376
2377 if ssh:
2377 if ssh:
2378 uri_tmpl = rc_config.get(
2378 uri_tmpl = rc_config.get(
2379 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2379 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2380
2380
2381 else:
2381 else:
2382 uri_tmpl = rc_config.get(
2382 uri_tmpl = rc_config.get(
2383 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2383 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2384
2384
2385 return get_clone_url(request=request,
2385 return get_clone_url(request=request,
2386 uri_tmpl=uri_tmpl,
2386 uri_tmpl=uri_tmpl,
2387 repo_name=self.repo_name,
2387 repo_name=self.repo_name,
2388 repo_id=self.repo_id,
2388 repo_id=self.repo_id,
2389 repo_type=self.repo_type,
2389 repo_type=self.repo_type,
2390 **override)
2390 **override)
2391
2391
2392 def set_state(self, state):
2392 def set_state(self, state):
2393 self.repo_state = state
2393 self.repo_state = state
2394 Session().add(self)
2394 Session().add(self)
2395 #==========================================================================
2395 #==========================================================================
2396 # SCM PROPERTIES
2396 # SCM PROPERTIES
2397 #==========================================================================
2397 #==========================================================================
2398
2398
2399 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2399 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2400 return get_commit_safe(
2400 return get_commit_safe(
2401 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2401 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2402 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2402 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2403
2403
2404 def get_changeset(self, rev=None, pre_load=None):
2404 def get_changeset(self, rev=None, pre_load=None):
2405 warnings.warn("Use get_commit", DeprecationWarning)
2405 warnings.warn("Use get_commit", DeprecationWarning)
2406 commit_id = None
2406 commit_id = None
2407 commit_idx = None
2407 commit_idx = None
2408 if isinstance(rev, str):
2408 if isinstance(rev, str):
2409 commit_id = rev
2409 commit_id = rev
2410 else:
2410 else:
2411 commit_idx = rev
2411 commit_idx = rev
2412 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2412 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2413 pre_load=pre_load)
2413 pre_load=pre_load)
2414
2414
2415 def get_landing_commit(self):
2415 def get_landing_commit(self):
2416 """
2416 """
2417 Returns landing commit, or if that doesn't exist returns the tip
2417 Returns landing commit, or if that doesn't exist returns the tip
2418 """
2418 """
2419 _rev_type, _rev = self.landing_rev
2419 _rev_type, _rev = self.landing_rev
2420 commit = self.get_commit(_rev)
2420 commit = self.get_commit(_rev)
2421 if isinstance(commit, EmptyCommit):
2421 if isinstance(commit, EmptyCommit):
2422 return self.get_commit()
2422 return self.get_commit()
2423 return commit
2423 return commit
2424
2424
2425 def flush_commit_cache(self):
2425 def flush_commit_cache(self):
2426 self.update_commit_cache(cs_cache={'raw_id':'0'})
2426 self.update_commit_cache(cs_cache={'raw_id':'0'})
2427 self.update_commit_cache()
2427 self.update_commit_cache()
2428
2428
2429 def update_commit_cache(self, cs_cache=None, config=None):
2429 def update_commit_cache(self, cs_cache=None, config=None):
2430 """
2430 """
2431 Update cache of last commit for repository
2431 Update cache of last commit for repository
2432 cache_keys should be::
2432 cache_keys should be::
2433
2433
2434 source_repo_id
2434 source_repo_id
2435 short_id
2435 short_id
2436 raw_id
2436 raw_id
2437 revision
2437 revision
2438 parents
2438 parents
2439 message
2439 message
2440 date
2440 date
2441 author
2441 author
2442 updated_on
2442 updated_on
2443
2443
2444 """
2444 """
2445 from rhodecode.lib.vcs.backends.base import BaseCommit
2445 from rhodecode.lib.vcs.backends.base import BaseCommit
2446 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2446 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2447 empty_date = datetime.datetime.fromtimestamp(0)
2447 empty_date = datetime.datetime.fromtimestamp(0)
2448 repo_commit_count = 0
2448 repo_commit_count = 0
2449
2449
2450 if cs_cache is None:
2450 if cs_cache is None:
2451 # use no-cache version here
2451 # use no-cache version here
2452 try:
2452 try:
2453 scm_repo = self.scm_instance(cache=False, config=config)
2453 scm_repo = self.scm_instance(cache=False, config=config)
2454 except VCSError:
2454 except VCSError:
2455 scm_repo = None
2455 scm_repo = None
2456 empty = scm_repo is None or scm_repo.is_empty()
2456 empty = scm_repo is None or scm_repo.is_empty()
2457
2457
2458 if not empty:
2458 if not empty:
2459 cs_cache = scm_repo.get_commit(
2459 cs_cache = scm_repo.get_commit(
2460 pre_load=["author", "date", "message", "parents", "branch"])
2460 pre_load=["author", "date", "message", "parents", "branch"])
2461 repo_commit_count = scm_repo.count()
2461 repo_commit_count = scm_repo.count()
2462 else:
2462 else:
2463 cs_cache = EmptyCommit()
2463 cs_cache = EmptyCommit()
2464
2464
2465 if isinstance(cs_cache, BaseCommit):
2465 if isinstance(cs_cache, BaseCommit):
2466 cs_cache = cs_cache.__json__()
2466 cs_cache = cs_cache.__json__()
2467
2467
2468 def is_outdated(new_cs_cache):
2468 def is_outdated(new_cs_cache):
2469 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2469 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2470 new_cs_cache['revision'] != self.changeset_cache['revision']):
2470 new_cs_cache['revision'] != self.changeset_cache['revision']):
2471 return True
2471 return True
2472 return False
2472 return False
2473
2473
2474 # check if we have maybe already latest cached revision
2474 # check if we have maybe already latest cached revision
2475 if is_outdated(cs_cache) or not self.changeset_cache:
2475 if is_outdated(cs_cache) or not self.changeset_cache:
2476 _current_datetime = datetime.datetime.utcnow()
2476 _current_datetime = datetime.datetime.utcnow()
2477 last_change = cs_cache.get('date') or _current_datetime
2477 last_change = cs_cache.get('date') or _current_datetime
2478 # we check if last update is newer than the new value
2478 # we check if last update is newer than the new value
2479 # if yes, we use the current timestamp instead. Imagine you get
2479 # if yes, we use the current timestamp instead. Imagine you get
2480 # old commit pushed 1y ago, we'd set last update 1y to ago.
2480 # old commit pushed 1y ago, we'd set last update 1y to ago.
2481 last_change_timestamp = datetime_to_time(last_change)
2481 last_change_timestamp = datetime_to_time(last_change)
2482 current_timestamp = datetime_to_time(last_change)
2482 current_timestamp = datetime_to_time(last_change)
2483 if last_change_timestamp > current_timestamp and not empty:
2483 if last_change_timestamp > current_timestamp and not empty:
2484 cs_cache['date'] = _current_datetime
2484 cs_cache['date'] = _current_datetime
2485
2485
2486 # also store size of repo
2486 # also store size of repo
2487 cs_cache['repo_commit_count'] = repo_commit_count
2487 cs_cache['repo_commit_count'] = repo_commit_count
2488
2488
2489 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2489 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2490 cs_cache['updated_on'] = time.time()
2490 cs_cache['updated_on'] = time.time()
2491 self.changeset_cache = cs_cache
2491 self.changeset_cache = cs_cache
2492 self.updated_on = last_change
2492 self.updated_on = last_change
2493 Session().add(self)
2493 Session().add(self)
2494 Session().commit()
2494 Session().commit()
2495
2495
2496 else:
2496 else:
2497 if empty:
2497 if empty:
2498 cs_cache = EmptyCommit().__json__()
2498 cs_cache = EmptyCommit().__json__()
2499 else:
2499 else:
2500 cs_cache = self.changeset_cache
2500 cs_cache = self.changeset_cache
2501
2501
2502 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2502 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2503
2503
2504 cs_cache['updated_on'] = time.time()
2504 cs_cache['updated_on'] = time.time()
2505 self.changeset_cache = cs_cache
2505 self.changeset_cache = cs_cache
2506 self.updated_on = _date_latest
2506 self.updated_on = _date_latest
2507 Session().add(self)
2507 Session().add(self)
2508 Session().commit()
2508 Session().commit()
2509
2509
2510 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2510 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2511 self.repo_name, cs_cache, _date_latest)
2511 self.repo_name, cs_cache, _date_latest)
2512
2512
2513 @property
2513 @property
2514 def tip(self):
2514 def tip(self):
2515 return self.get_commit('tip')
2515 return self.get_commit('tip')
2516
2516
2517 @property
2517 @property
2518 def author(self):
2518 def author(self):
2519 return self.tip.author
2519 return self.tip.author
2520
2520
2521 @property
2521 @property
2522 def last_change(self):
2522 def last_change(self):
2523 return self.scm_instance().last_change
2523 return self.scm_instance().last_change
2524
2524
2525 def get_comments(self, revisions=None):
2525 def get_comments(self, revisions=None):
2526 """
2526 """
2527 Returns comments for this repository grouped by revisions
2527 Returns comments for this repository grouped by revisions
2528
2528
2529 :param revisions: filter query by revisions only
2529 :param revisions: filter query by revisions only
2530 """
2530 """
2531 cmts = ChangesetComment.query()\
2531 cmts = ChangesetComment.query()\
2532 .filter(ChangesetComment.repo == self)
2532 .filter(ChangesetComment.repo == self)
2533 if revisions:
2533 if revisions:
2534 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2534 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2535 grouped = collections.defaultdict(list)
2535 grouped = collections.defaultdict(list)
2536 for cmt in cmts.all():
2536 for cmt in cmts.all():
2537 grouped[cmt.revision].append(cmt)
2537 grouped[cmt.revision].append(cmt)
2538 return grouped
2538 return grouped
2539
2539
2540 def statuses(self, revisions=None):
2540 def statuses(self, revisions=None):
2541 """
2541 """
2542 Returns statuses for this repository
2542 Returns statuses for this repository
2543
2543
2544 :param revisions: list of revisions to get statuses for
2544 :param revisions: list of revisions to get statuses for
2545 """
2545 """
2546 statuses = ChangesetStatus.query()\
2546 statuses = ChangesetStatus.query()\
2547 .filter(ChangesetStatus.repo == self)\
2547 .filter(ChangesetStatus.repo == self)\
2548 .filter(ChangesetStatus.version == 0)
2548 .filter(ChangesetStatus.version == 0)
2549
2549
2550 if revisions:
2550 if revisions:
2551 # Try doing the filtering in chunks to avoid hitting limits
2551 # Try doing the filtering in chunks to avoid hitting limits
2552 size = 500
2552 size = 500
2553 status_results = []
2553 status_results = []
2554 for chunk in range(0, len(revisions), size):
2554 for chunk in range(0, len(revisions), size):
2555 status_results += statuses.filter(
2555 status_results += statuses.filter(
2556 ChangesetStatus.revision.in_(
2556 ChangesetStatus.revision.in_(
2557 revisions[chunk: chunk+size])
2557 revisions[chunk: chunk+size])
2558 ).all()
2558 ).all()
2559 else:
2559 else:
2560 status_results = statuses.all()
2560 status_results = statuses.all()
2561
2561
2562 grouped = {}
2562 grouped = {}
2563
2563
2564 # maybe we have open new pullrequest without a status?
2564 # maybe we have open new pullrequest without a status?
2565 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2565 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2566 status_lbl = ChangesetStatus.get_status_lbl(stat)
2566 status_lbl = ChangesetStatus.get_status_lbl(stat)
2567 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2567 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2568 for rev in pr.revisions:
2568 for rev in pr.revisions:
2569 pr_id = pr.pull_request_id
2569 pr_id = pr.pull_request_id
2570 pr_repo = pr.target_repo.repo_name
2570 pr_repo = pr.target_repo.repo_name
2571 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2571 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2572
2572
2573 for stat in status_results:
2573 for stat in status_results:
2574 pr_id = pr_repo = None
2574 pr_id = pr_repo = None
2575 if stat.pull_request:
2575 if stat.pull_request:
2576 pr_id = stat.pull_request.pull_request_id
2576 pr_id = stat.pull_request.pull_request_id
2577 pr_repo = stat.pull_request.target_repo.repo_name
2577 pr_repo = stat.pull_request.target_repo.repo_name
2578 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2578 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2579 pr_id, pr_repo]
2579 pr_id, pr_repo]
2580 return grouped
2580 return grouped
2581
2581
2582 # ==========================================================================
2582 # ==========================================================================
2583 # SCM CACHE INSTANCE
2583 # SCM CACHE INSTANCE
2584 # ==========================================================================
2584 # ==========================================================================
2585
2585
2586 def scm_instance(self, **kwargs):
2586 def scm_instance(self, **kwargs):
2587 import rhodecode
2587 import rhodecode
2588
2588
2589 # Passing a config will not hit the cache currently only used
2589 # Passing a config will not hit the cache currently only used
2590 # for repo2dbmapper
2590 # for repo2dbmapper
2591 config = kwargs.pop('config', None)
2591 config = kwargs.pop('config', None)
2592 cache = kwargs.pop('cache', None)
2592 cache = kwargs.pop('cache', None)
2593 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2593 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2594 if vcs_full_cache is not None:
2594 if vcs_full_cache is not None:
2595 # allows override global config
2595 # allows override global config
2596 full_cache = vcs_full_cache
2596 full_cache = vcs_full_cache
2597 else:
2597 else:
2598 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2598 full_cache = rhodecode.ConfigGet().get_bool('vcs_full_cache')
2599 # if cache is NOT defined use default global, else we have a full
2599 # if cache is NOT defined use default global, else we have a full
2600 # control over cache behaviour
2600 # control over cache behaviour
2601 if cache is None and full_cache and not config:
2601 if cache is None and full_cache and not config:
2602 log.debug('Initializing pure cached instance for %s', self.repo_path)
2602 log.debug('Initializing pure cached instance for %s', self.repo_path)
2603 return self._get_instance_cached()
2603 return self._get_instance_cached()
2604
2604
2605 # cache here is sent to the "vcs server"
2605 # cache here is sent to the "vcs server"
2606 return self._get_instance(cache=bool(cache), config=config)
2606 return self._get_instance(cache=bool(cache), config=config)
2607
2607
2608 def _get_instance_cached(self):
2608 def _get_instance_cached(self):
2609 from rhodecode.lib import rc_cache
2609 from rhodecode.lib import rc_cache
2610
2610
2611 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2611 cache_namespace_uid = f'repo_instance.{self.repo_id}'
2612 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2612 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2613 repo_id=self.repo_id)
2613 repo_id=self.repo_id)
2614 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2614 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2615
2615
2616 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2616 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2617 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2617 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2618 return self._get_instance(repo_state_uid=_cache_state_uid)
2618 return self._get_instance(repo_state_uid=_cache_state_uid)
2619
2619
2620 # we must use thread scoped cache here,
2620 # we must use thread scoped cache here,
2621 # because each thread of gevent needs it's own not shared connection and cache
2621 # because each thread of gevent needs it's own not shared connection and cache
2622 # we also alter `args` so the cache key is individual for every green thread.
2622 # we also alter `args` so the cache key is individual for every green thread.
2623 inv_context_manager = rc_cache.InvalidationContext(
2623 inv_context_manager = rc_cache.InvalidationContext(
2624 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2624 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2625 thread_scoped=True)
2625 thread_scoped=True)
2626 with inv_context_manager as invalidation_context:
2626 with inv_context_manager as invalidation_context:
2627 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2627 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2628 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2628 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2629
2629
2630 # re-compute and store cache if we get invalidate signal
2630 # re-compute and store cache if we get invalidate signal
2631 if invalidation_context.should_invalidate():
2631 if invalidation_context.should_invalidate():
2632 instance = get_instance_cached.refresh(*args)
2632 instance = get_instance_cached.refresh(*args)
2633 else:
2633 else:
2634 instance = get_instance_cached(*args)
2634 instance = get_instance_cached(*args)
2635
2635
2636 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2636 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2637 return instance
2637 return instance
2638
2638
2639 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2639 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2640 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2640 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2641 self.repo_type, self.repo_path, cache)
2641 self.repo_type, self.repo_path, cache)
2642 config = config or self._config
2642 config = config or self._config
2643 custom_wire = {
2643 custom_wire = {
2644 'cache': cache, # controls the vcs.remote cache
2644 'cache': cache, # controls the vcs.remote cache
2645 'repo_state_uid': repo_state_uid
2645 'repo_state_uid': repo_state_uid
2646 }
2646 }
2647 repo = get_vcs_instance(
2647 repo = get_vcs_instance(
2648 repo_path=safe_str(self.repo_full_path),
2648 repo_path=safe_str(self.repo_full_path),
2649 config=config,
2649 config=config,
2650 with_wire=custom_wire,
2650 with_wire=custom_wire,
2651 create=False,
2651 create=False,
2652 _vcs_alias=self.repo_type)
2652 _vcs_alias=self.repo_type)
2653 if repo is not None:
2653 if repo is not None:
2654 repo.count() # cache rebuild
2654 repo.count() # cache rebuild
2655 return repo
2655 return repo
2656
2656
2657 def get_shadow_repository_path(self, workspace_id):
2657 def get_shadow_repository_path(self, workspace_id):
2658 from rhodecode.lib.vcs.backends.base import BaseRepository
2658 from rhodecode.lib.vcs.backends.base import BaseRepository
2659 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2659 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2660 self.repo_full_path, self.repo_id, workspace_id)
2660 self.repo_full_path, self.repo_id, workspace_id)
2661 return shadow_repo_path
2661 return shadow_repo_path
2662
2662
2663 def __json__(self):
2663 def __json__(self):
2664 return {'landing_rev': self.landing_rev}
2664 return {'landing_rev': self.landing_rev}
2665
2665
2666 def get_dict(self):
2666 def get_dict(self):
2667
2667
2668 # Since we transformed `repo_name` to a hybrid property, we need to
2668 # Since we transformed `repo_name` to a hybrid property, we need to
2669 # keep compatibility with the code which uses `repo_name` field.
2669 # keep compatibility with the code which uses `repo_name` field.
2670
2670
2671 result = super(Repository, self).get_dict()
2671 result = super(Repository, self).get_dict()
2672 result['repo_name'] = result.pop('_repo_name', None)
2672 result['repo_name'] = result.pop('_repo_name', None)
2673 result.pop('_changeset_cache', '')
2673 result.pop('_changeset_cache', '')
2674 return result
2674 return result
2675
2675
2676
2676
2677 class RepoGroup(Base, BaseModel):
2677 class RepoGroup(Base, BaseModel):
2678 __tablename__ = 'groups'
2678 __tablename__ = 'groups'
2679 __table_args__ = (
2679 __table_args__ = (
2680 UniqueConstraint('group_name', 'group_parent_id'),
2680 UniqueConstraint('group_name', 'group_parent_id'),
2681 base_table_args,
2681 base_table_args,
2682 )
2682 )
2683
2683
2684 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2684 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2685
2685
2686 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2686 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2687 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2687 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2688 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2688 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2689 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2689 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2690 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2690 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2691 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2691 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2692 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2692 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2693 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2693 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2694 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2694 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2695 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2695 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2696 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2696 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2697
2697
2698 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2698 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id', back_populates='group')
2699 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2699 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all', back_populates='group')
2700 parent_group = relationship('RepoGroup', remote_side=group_id)
2700 parent_group = relationship('RepoGroup', remote_side=group_id)
2701 user = relationship('User', back_populates='repository_groups')
2701 user = relationship('User', back_populates='repository_groups')
2702 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2702 integrations = relationship('Integration', cascade="all, delete-orphan", back_populates='repo_group')
2703
2703
2704 # no cascade, set NULL
2704 # no cascade, set NULL
2705 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2705 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id', viewonly=True)
2706
2706
2707 def __init__(self, group_name='', parent_group=None):
2707 def __init__(self, group_name='', parent_group=None):
2708 self.group_name = group_name
2708 self.group_name = group_name
2709 self.parent_group = parent_group
2709 self.parent_group = parent_group
2710
2710
2711 def __repr__(self):
2711 def __repr__(self):
2712 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2712 return f"<{self.cls_name}('id:{self.group_id}:{self.group_name}')>"
2713
2713
2714 @hybrid_property
2714 @hybrid_property
2715 def group_name(self):
2715 def group_name(self):
2716 return self._group_name
2716 return self._group_name
2717
2717
2718 @group_name.setter
2718 @group_name.setter
2719 def group_name(self, value):
2719 def group_name(self, value):
2720 self._group_name = value
2720 self._group_name = value
2721 self.group_name_hash = self.hash_repo_group_name(value)
2721 self.group_name_hash = self.hash_repo_group_name(value)
2722
2722
2723 @classmethod
2723 @classmethod
2724 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2724 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2725 from rhodecode.lib.vcs.backends.base import EmptyCommit
2725 from rhodecode.lib.vcs.backends.base import EmptyCommit
2726 dummy = EmptyCommit().__json__()
2726 dummy = EmptyCommit().__json__()
2727 if not changeset_cache_raw:
2727 if not changeset_cache_raw:
2728 dummy['source_repo_id'] = repo_id
2728 dummy['source_repo_id'] = repo_id
2729 return json.loads(json.dumps(dummy))
2729 return json.loads(json.dumps(dummy))
2730
2730
2731 try:
2731 try:
2732 return json.loads(changeset_cache_raw)
2732 return json.loads(changeset_cache_raw)
2733 except TypeError:
2733 except TypeError:
2734 return dummy
2734 return dummy
2735 except Exception:
2735 except Exception:
2736 log.error(traceback.format_exc())
2736 log.error(traceback.format_exc())
2737 return dummy
2737 return dummy
2738
2738
2739 @hybrid_property
2739 @hybrid_property
2740 def changeset_cache(self):
2740 def changeset_cache(self):
2741 return self._load_changeset_cache('', self._changeset_cache)
2741 return self._load_changeset_cache('', self._changeset_cache)
2742
2742
2743 @changeset_cache.setter
2743 @changeset_cache.setter
2744 def changeset_cache(self, val):
2744 def changeset_cache(self, val):
2745 try:
2745 try:
2746 self._changeset_cache = json.dumps(val)
2746 self._changeset_cache = json.dumps(val)
2747 except Exception:
2747 except Exception:
2748 log.error(traceback.format_exc())
2748 log.error(traceback.format_exc())
2749
2749
2750 @validates('group_parent_id')
2750 @validates('group_parent_id')
2751 def validate_group_parent_id(self, key, val):
2751 def validate_group_parent_id(self, key, val):
2752 """
2752 """
2753 Check cycle references for a parent group to self
2753 Check cycle references for a parent group to self
2754 """
2754 """
2755 if self.group_id and val:
2755 if self.group_id and val:
2756 assert val != self.group_id
2756 assert val != self.group_id
2757
2757
2758 return val
2758 return val
2759
2759
2760 @hybrid_property
2760 @hybrid_property
2761 def description_safe(self):
2761 def description_safe(self):
2762 from rhodecode.lib import helpers as h
2762 from rhodecode.lib import helpers as h
2763 return h.escape(self.group_description)
2763 return h.escape(self.group_description)
2764
2764
2765 @classmethod
2765 @classmethod
2766 def hash_repo_group_name(cls, repo_group_name):
2766 def hash_repo_group_name(cls, repo_group_name):
2767 val = remove_formatting(repo_group_name)
2767 val = remove_formatting(repo_group_name)
2768 val = safe_str(val).lower()
2768 val = safe_str(val).lower()
2769 chars = []
2769 chars = []
2770 for c in val:
2770 for c in val:
2771 if c not in string.ascii_letters:
2771 if c not in string.ascii_letters:
2772 c = str(ord(c))
2772 c = str(ord(c))
2773 chars.append(c)
2773 chars.append(c)
2774
2774
2775 return ''.join(chars)
2775 return ''.join(chars)
2776
2776
2777 @classmethod
2777 @classmethod
2778 def _generate_choice(cls, repo_group):
2778 def _generate_choice(cls, repo_group):
2779 from webhelpers2.html import literal as _literal
2779 from webhelpers2.html import literal as _literal
2780
2780
2781 def _name(k):
2781 def _name(k):
2782 return _literal(cls.CHOICES_SEPARATOR.join(k))
2782 return _literal(cls.CHOICES_SEPARATOR.join(k))
2783
2783
2784 return repo_group.group_id, _name(repo_group.full_path_splitted)
2784 return repo_group.group_id, _name(repo_group.full_path_splitted)
2785
2785
2786 @classmethod
2786 @classmethod
2787 def groups_choices(cls, groups=None, show_empty_group=True):
2787 def groups_choices(cls, groups=None, show_empty_group=True):
2788 if not groups:
2788 if not groups:
2789 groups = cls.query().all()
2789 groups = cls.query().all()
2790
2790
2791 repo_groups = []
2791 repo_groups = []
2792 if show_empty_group:
2792 if show_empty_group:
2793 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2793 repo_groups = [(-1, '-- %s --' % _('No parent'))]
2794
2794
2795 repo_groups.extend([cls._generate_choice(x) for x in groups])
2795 repo_groups.extend([cls._generate_choice(x) for x in groups])
2796
2796
2797 repo_groups = sorted(
2797 repo_groups = sorted(
2798 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2798 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2799 return repo_groups
2799 return repo_groups
2800
2800
2801 @classmethod
2801 @classmethod
2802 def url_sep(cls):
2802 def url_sep(cls):
2803 return URL_SEP
2803 return URL_SEP
2804
2804
2805 @classmethod
2805 @classmethod
2806 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2806 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2807 if case_insensitive:
2807 if case_insensitive:
2808 gr = cls.query().filter(func.lower(cls.group_name)
2808 gr = cls.query().filter(func.lower(cls.group_name)
2809 == func.lower(group_name))
2809 == func.lower(group_name))
2810 else:
2810 else:
2811 gr = cls.query().filter(cls.group_name == group_name)
2811 gr = cls.query().filter(cls.group_name == group_name)
2812 if cache:
2812 if cache:
2813 name_key = _hash_key(group_name)
2813 name_key = _hash_key(group_name)
2814 gr = gr.options(
2814 gr = gr.options(
2815 FromCache("sql_cache_short", f"get_group_{name_key}"))
2815 FromCache("sql_cache_short", f"get_group_{name_key}"))
2816 return gr.scalar()
2816 return gr.scalar()
2817
2817
2818 @classmethod
2818 @classmethod
2819 def get_user_personal_repo_group(cls, user_id):
2819 def get_user_personal_repo_group(cls, user_id):
2820 user = User.get(user_id)
2820 user = User.get(user_id)
2821 if user.username == User.DEFAULT_USER:
2821 if user.username == User.DEFAULT_USER:
2822 return None
2822 return None
2823
2823
2824 return cls.query()\
2824 return cls.query()\
2825 .filter(cls.personal == true()) \
2825 .filter(cls.personal == true()) \
2826 .filter(cls.user == user) \
2826 .filter(cls.user == user) \
2827 .order_by(cls.group_id.asc()) \
2827 .order_by(cls.group_id.asc()) \
2828 .first()
2828 .first()
2829
2829
2830 @classmethod
2830 @classmethod
2831 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2831 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2832 case_insensitive=True):
2832 case_insensitive=True):
2833 q = RepoGroup.query()
2833 q = RepoGroup.query()
2834
2834
2835 if not isinstance(user_id, Optional):
2835 if not isinstance(user_id, Optional):
2836 q = q.filter(RepoGroup.user_id == user_id)
2836 q = q.filter(RepoGroup.user_id == user_id)
2837
2837
2838 if not isinstance(group_id, Optional):
2838 if not isinstance(group_id, Optional):
2839 q = q.filter(RepoGroup.group_parent_id == group_id)
2839 q = q.filter(RepoGroup.group_parent_id == group_id)
2840
2840
2841 if case_insensitive:
2841 if case_insensitive:
2842 q = q.order_by(func.lower(RepoGroup.group_name))
2842 q = q.order_by(func.lower(RepoGroup.group_name))
2843 else:
2843 else:
2844 q = q.order_by(RepoGroup.group_name)
2844 q = q.order_by(RepoGroup.group_name)
2845 return q.all()
2845 return q.all()
2846
2846
2847 @property
2847 @property
2848 def parents(self, parents_recursion_limit=10):
2848 def parents(self, parents_recursion_limit=10):
2849 groups = []
2849 groups = []
2850 if self.parent_group is None:
2850 if self.parent_group is None:
2851 return groups
2851 return groups
2852 cur_gr = self.parent_group
2852 cur_gr = self.parent_group
2853 groups.insert(0, cur_gr)
2853 groups.insert(0, cur_gr)
2854 cnt = 0
2854 cnt = 0
2855 while 1:
2855 while 1:
2856 cnt += 1
2856 cnt += 1
2857 gr = getattr(cur_gr, 'parent_group', None)
2857 gr = getattr(cur_gr, 'parent_group', None)
2858 cur_gr = cur_gr.parent_group
2858 cur_gr = cur_gr.parent_group
2859 if gr is None:
2859 if gr is None:
2860 break
2860 break
2861 if cnt == parents_recursion_limit:
2861 if cnt == parents_recursion_limit:
2862 # this will prevent accidental infinit loops
2862 # this will prevent accidental infinit loops
2863 log.error('more than %s parents found for group %s, stopping '
2863 log.error('more than %s parents found for group %s, stopping '
2864 'recursive parent fetching', parents_recursion_limit, self)
2864 'recursive parent fetching', parents_recursion_limit, self)
2865 break
2865 break
2866
2866
2867 groups.insert(0, gr)
2867 groups.insert(0, gr)
2868 return groups
2868 return groups
2869
2869
2870 @property
2870 @property
2871 def last_commit_cache_update_diff(self):
2871 def last_commit_cache_update_diff(self):
2872 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2872 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2873
2873
2874 @classmethod
2874 @classmethod
2875 def _load_commit_change(cls, last_commit_cache):
2875 def _load_commit_change(cls, last_commit_cache):
2876 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2876 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2877 empty_date = datetime.datetime.fromtimestamp(0)
2877 empty_date = datetime.datetime.fromtimestamp(0)
2878 date_latest = last_commit_cache.get('date', empty_date)
2878 date_latest = last_commit_cache.get('date', empty_date)
2879 try:
2879 try:
2880 return parse_datetime(date_latest)
2880 return parse_datetime(date_latest)
2881 except Exception:
2881 except Exception:
2882 return empty_date
2882 return empty_date
2883
2883
2884 @property
2884 @property
2885 def last_commit_change(self):
2885 def last_commit_change(self):
2886 return self._load_commit_change(self.changeset_cache)
2886 return self._load_commit_change(self.changeset_cache)
2887
2887
2888 @property
2888 @property
2889 def last_db_change(self):
2889 def last_db_change(self):
2890 return self.updated_on
2890 return self.updated_on
2891
2891
2892 @property
2892 @property
2893 def children(self):
2893 def children(self):
2894 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2894 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2895
2895
2896 @property
2896 @property
2897 def name(self):
2897 def name(self):
2898 return self.group_name.split(RepoGroup.url_sep())[-1]
2898 return self.group_name.split(RepoGroup.url_sep())[-1]
2899
2899
2900 @property
2900 @property
2901 def full_path(self):
2901 def full_path(self):
2902 return self.group_name
2902 return self.group_name
2903
2903
2904 @property
2904 @property
2905 def full_path_splitted(self):
2905 def full_path_splitted(self):
2906 return self.group_name.split(RepoGroup.url_sep())
2906 return self.group_name.split(RepoGroup.url_sep())
2907
2907
2908 @property
2908 @property
2909 def repositories(self):
2909 def repositories(self):
2910 return Repository.query()\
2910 return Repository.query()\
2911 .filter(Repository.group == self)\
2911 .filter(Repository.group == self)\
2912 .order_by(Repository.repo_name)
2912 .order_by(Repository.repo_name)
2913
2913
2914 @property
2914 @property
2915 def repositories_recursive_count(self):
2915 def repositories_recursive_count(self):
2916 cnt = self.repositories.count()
2916 cnt = self.repositories.count()
2917
2917
2918 def children_count(group):
2918 def children_count(group):
2919 cnt = 0
2919 cnt = 0
2920 for child in group.children:
2920 for child in group.children:
2921 cnt += child.repositories.count()
2921 cnt += child.repositories.count()
2922 cnt += children_count(child)
2922 cnt += children_count(child)
2923 return cnt
2923 return cnt
2924
2924
2925 return cnt + children_count(self)
2925 return cnt + children_count(self)
2926
2926
2927 def _recursive_objects(self, include_repos=True, include_groups=True):
2927 def _recursive_objects(self, include_repos=True, include_groups=True):
2928 all_ = []
2928 all_ = []
2929
2929
2930 def _get_members(root_gr):
2930 def _get_members(root_gr):
2931 if include_repos:
2931 if include_repos:
2932 for r in root_gr.repositories:
2932 for r in root_gr.repositories:
2933 all_.append(r)
2933 all_.append(r)
2934 childs = root_gr.children.all()
2934 childs = root_gr.children.all()
2935 if childs:
2935 if childs:
2936 for gr in childs:
2936 for gr in childs:
2937 if include_groups:
2937 if include_groups:
2938 all_.append(gr)
2938 all_.append(gr)
2939 _get_members(gr)
2939 _get_members(gr)
2940
2940
2941 root_group = []
2941 root_group = []
2942 if include_groups:
2942 if include_groups:
2943 root_group = [self]
2943 root_group = [self]
2944
2944
2945 _get_members(self)
2945 _get_members(self)
2946 return root_group + all_
2946 return root_group + all_
2947
2947
2948 def recursive_groups_and_repos(self):
2948 def recursive_groups_and_repos(self):
2949 """
2949 """
2950 Recursive return all groups, with repositories in those groups
2950 Recursive return all groups, with repositories in those groups
2951 """
2951 """
2952 return self._recursive_objects()
2952 return self._recursive_objects()
2953
2953
2954 def recursive_groups(self):
2954 def recursive_groups(self):
2955 """
2955 """
2956 Returns all children groups for this group including children of children
2956 Returns all children groups for this group including children of children
2957 """
2957 """
2958 return self._recursive_objects(include_repos=False)
2958 return self._recursive_objects(include_repos=False)
2959
2959
2960 def recursive_repos(self):
2960 def recursive_repos(self):
2961 """
2961 """
2962 Returns all children repositories for this group
2962 Returns all children repositories for this group
2963 """
2963 """
2964 return self._recursive_objects(include_groups=False)
2964 return self._recursive_objects(include_groups=False)
2965
2965
2966 def get_new_name(self, group_name):
2966 def get_new_name(self, group_name):
2967 """
2967 """
2968 returns new full group name based on parent and new name
2968 returns new full group name based on parent and new name
2969
2969
2970 :param group_name:
2970 :param group_name:
2971 """
2971 """
2972 path_prefix = (self.parent_group.full_path_splitted if
2972 path_prefix = (self.parent_group.full_path_splitted if
2973 self.parent_group else [])
2973 self.parent_group else [])
2974 return RepoGroup.url_sep().join(path_prefix + [group_name])
2974 return RepoGroup.url_sep().join(path_prefix + [group_name])
2975
2975
2976 def update_commit_cache(self, config=None):
2976 def update_commit_cache(self, config=None):
2977 """
2977 """
2978 Update cache of last commit for newest repository inside this repository group.
2978 Update cache of last commit for newest repository inside this repository group.
2979 cache_keys should be::
2979 cache_keys should be::
2980
2980
2981 source_repo_id
2981 source_repo_id
2982 short_id
2982 short_id
2983 raw_id
2983 raw_id
2984 revision
2984 revision
2985 parents
2985 parents
2986 message
2986 message
2987 date
2987 date
2988 author
2988 author
2989
2989
2990 """
2990 """
2991 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2991 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2992 empty_date = datetime.datetime.fromtimestamp(0)
2992 empty_date = datetime.datetime.fromtimestamp(0)
2993
2993
2994 def repo_groups_and_repos(root_gr):
2994 def repo_groups_and_repos(root_gr):
2995 for _repo in root_gr.repositories:
2995 for _repo in root_gr.repositories:
2996 yield _repo
2996 yield _repo
2997 for child_group in root_gr.children.all():
2997 for child_group in root_gr.children.all():
2998 yield child_group
2998 yield child_group
2999
2999
3000 latest_repo_cs_cache = {}
3000 latest_repo_cs_cache = {}
3001 for obj in repo_groups_and_repos(self):
3001 for obj in repo_groups_and_repos(self):
3002 repo_cs_cache = obj.changeset_cache
3002 repo_cs_cache = obj.changeset_cache
3003 date_latest = latest_repo_cs_cache.get('date', empty_date)
3003 date_latest = latest_repo_cs_cache.get('date', empty_date)
3004 date_current = repo_cs_cache.get('date', empty_date)
3004 date_current = repo_cs_cache.get('date', empty_date)
3005 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3005 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3006 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3006 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3007 latest_repo_cs_cache = repo_cs_cache
3007 latest_repo_cs_cache = repo_cs_cache
3008 if hasattr(obj, 'repo_id'):
3008 if hasattr(obj, 'repo_id'):
3009 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3009 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3010 else:
3010 else:
3011 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3011 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3012
3012
3013 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3013 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3014
3014
3015 latest_repo_cs_cache['updated_on'] = time.time()
3015 latest_repo_cs_cache['updated_on'] = time.time()
3016 self.changeset_cache = latest_repo_cs_cache
3016 self.changeset_cache = latest_repo_cs_cache
3017 self.updated_on = _date_latest
3017 self.updated_on = _date_latest
3018 Session().add(self)
3018 Session().add(self)
3019 Session().commit()
3019 Session().commit()
3020
3020
3021 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3021 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3022 self.group_name, latest_repo_cs_cache, _date_latest)
3022 self.group_name, latest_repo_cs_cache, _date_latest)
3023
3023
3024 def permissions(self, with_admins=True, with_owner=True,
3024 def permissions(self, with_admins=True, with_owner=True,
3025 expand_from_user_groups=False):
3025 expand_from_user_groups=False):
3026 """
3026 """
3027 Permissions for repository groups
3027 Permissions for repository groups
3028 """
3028 """
3029 _admin_perm = 'group.admin'
3029 _admin_perm = 'group.admin'
3030
3030
3031 owner_row = []
3031 owner_row = []
3032 if with_owner:
3032 if with_owner:
3033 usr = AttributeDict(self.user.get_dict())
3033 usr = AttributeDict(self.user.get_dict())
3034 usr.owner_row = True
3034 usr.owner_row = True
3035 usr.permission = _admin_perm
3035 usr.permission = _admin_perm
3036 owner_row.append(usr)
3036 owner_row.append(usr)
3037
3037
3038 super_admin_ids = []
3038 super_admin_ids = []
3039 super_admin_rows = []
3039 super_admin_rows = []
3040 if with_admins:
3040 if with_admins:
3041 for usr in User.get_all_super_admins():
3041 for usr in User.get_all_super_admins():
3042 super_admin_ids.append(usr.user_id)
3042 super_admin_ids.append(usr.user_id)
3043 # if this admin is also owner, don't double the record
3043 # if this admin is also owner, don't double the record
3044 if usr.user_id == owner_row[0].user_id:
3044 if usr.user_id == owner_row[0].user_id:
3045 owner_row[0].admin_row = True
3045 owner_row[0].admin_row = True
3046 else:
3046 else:
3047 usr = AttributeDict(usr.get_dict())
3047 usr = AttributeDict(usr.get_dict())
3048 usr.admin_row = True
3048 usr.admin_row = True
3049 usr.permission = _admin_perm
3049 usr.permission = _admin_perm
3050 super_admin_rows.append(usr)
3050 super_admin_rows.append(usr)
3051
3051
3052 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3052 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3053 q = q.options(joinedload(UserRepoGroupToPerm.group),
3053 q = q.options(joinedload(UserRepoGroupToPerm.group),
3054 joinedload(UserRepoGroupToPerm.user),
3054 joinedload(UserRepoGroupToPerm.user),
3055 joinedload(UserRepoGroupToPerm.permission),)
3055 joinedload(UserRepoGroupToPerm.permission),)
3056
3056
3057 # get owners and admins and permissions. We do a trick of re-writing
3057 # get owners and admins and permissions. We do a trick of re-writing
3058 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3058 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3059 # has a global reference and changing one object propagates to all
3059 # has a global reference and changing one object propagates to all
3060 # others. This means if admin is also an owner admin_row that change
3060 # others. This means if admin is also an owner admin_row that change
3061 # would propagate to both objects
3061 # would propagate to both objects
3062 perm_rows = []
3062 perm_rows = []
3063 for _usr in q.all():
3063 for _usr in q.all():
3064 usr = AttributeDict(_usr.user.get_dict())
3064 usr = AttributeDict(_usr.user.get_dict())
3065 # if this user is also owner/admin, mark as duplicate record
3065 # if this user is also owner/admin, mark as duplicate record
3066 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3066 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3067 usr.duplicate_perm = True
3067 usr.duplicate_perm = True
3068 usr.permission = _usr.permission.permission_name
3068 usr.permission = _usr.permission.permission_name
3069 perm_rows.append(usr)
3069 perm_rows.append(usr)
3070
3070
3071 # filter the perm rows by 'default' first and then sort them by
3071 # filter the perm rows by 'default' first and then sort them by
3072 # admin,write,read,none permissions sorted again alphabetically in
3072 # admin,write,read,none permissions sorted again alphabetically in
3073 # each group
3073 # each group
3074 perm_rows = sorted(perm_rows, key=display_user_sort)
3074 perm_rows = sorted(perm_rows, key=display_user_sort)
3075
3075
3076 user_groups_rows = []
3076 user_groups_rows = []
3077 if expand_from_user_groups:
3077 if expand_from_user_groups:
3078 for ug in self.permission_user_groups(with_members=True):
3078 for ug in self.permission_user_groups(with_members=True):
3079 for user_data in ug.members:
3079 for user_data in ug.members:
3080 user_groups_rows.append(user_data)
3080 user_groups_rows.append(user_data)
3081
3081
3082 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3082 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3083
3083
3084 def permission_user_groups(self, with_members=False):
3084 def permission_user_groups(self, with_members=False):
3085 q = UserGroupRepoGroupToPerm.query()\
3085 q = UserGroupRepoGroupToPerm.query()\
3086 .filter(UserGroupRepoGroupToPerm.group == self)
3086 .filter(UserGroupRepoGroupToPerm.group == self)
3087 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3087 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3088 joinedload(UserGroupRepoGroupToPerm.users_group),
3088 joinedload(UserGroupRepoGroupToPerm.users_group),
3089 joinedload(UserGroupRepoGroupToPerm.permission),)
3089 joinedload(UserGroupRepoGroupToPerm.permission),)
3090
3090
3091 perm_rows = []
3091 perm_rows = []
3092 for _user_group in q.all():
3092 for _user_group in q.all():
3093 entry = AttributeDict(_user_group.users_group.get_dict())
3093 entry = AttributeDict(_user_group.users_group.get_dict())
3094 entry.permission = _user_group.permission.permission_name
3094 entry.permission = _user_group.permission.permission_name
3095 if with_members:
3095 if with_members:
3096 entry.members = [x.user.get_dict()
3096 entry.members = [x.user.get_dict()
3097 for x in _user_group.users_group.members]
3097 for x in _user_group.users_group.members]
3098 perm_rows.append(entry)
3098 perm_rows.append(entry)
3099
3099
3100 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3100 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3101 return perm_rows
3101 return perm_rows
3102
3102
3103 def get_api_data(self):
3103 def get_api_data(self):
3104 """
3104 """
3105 Common function for generating api data
3105 Common function for generating api data
3106
3106
3107 """
3107 """
3108 group = self
3108 group = self
3109 data = {
3109 data = {
3110 'group_id': group.group_id,
3110 'group_id': group.group_id,
3111 'group_name': group.group_name,
3111 'group_name': group.group_name,
3112 'group_description': group.description_safe,
3112 'group_description': group.description_safe,
3113 'parent_group': group.parent_group.group_name if group.parent_group else None,
3113 'parent_group': group.parent_group.group_name if group.parent_group else None,
3114 'repositories': [x.repo_name for x in group.repositories],
3114 'repositories': [x.repo_name for x in group.repositories],
3115 'owner': group.user.username,
3115 'owner': group.user.username,
3116 }
3116 }
3117 return data
3117 return data
3118
3118
3119 def get_dict(self):
3119 def get_dict(self):
3120 # Since we transformed `group_name` to a hybrid property, we need to
3120 # Since we transformed `group_name` to a hybrid property, we need to
3121 # keep compatibility with the code which uses `group_name` field.
3121 # keep compatibility with the code which uses `group_name` field.
3122 result = super(RepoGroup, self).get_dict()
3122 result = super(RepoGroup, self).get_dict()
3123 result['group_name'] = result.pop('_group_name', None)
3123 result['group_name'] = result.pop('_group_name', None)
3124 result.pop('_changeset_cache', '')
3124 result.pop('_changeset_cache', '')
3125 return result
3125 return result
3126
3126
3127
3127
3128 class Permission(Base, BaseModel):
3128 class Permission(Base, BaseModel):
3129 __tablename__ = 'permissions'
3129 __tablename__ = 'permissions'
3130 __table_args__ = (
3130 __table_args__ = (
3131 Index('p_perm_name_idx', 'permission_name'),
3131 Index('p_perm_name_idx', 'permission_name'),
3132 base_table_args,
3132 base_table_args,
3133 )
3133 )
3134
3134
3135 PERMS = [
3135 PERMS = [
3136 ('hg.admin', _('RhodeCode Super Administrator')),
3136 ('hg.admin', _('RhodeCode Super Administrator')),
3137
3137
3138 ('repository.none', _('Repository no access')),
3138 ('repository.none', _('Repository no access')),
3139 ('repository.read', _('Repository read access')),
3139 ('repository.read', _('Repository read access')),
3140 ('repository.write', _('Repository write access')),
3140 ('repository.write', _('Repository write access')),
3141 ('repository.admin', _('Repository admin access')),
3141 ('repository.admin', _('Repository admin access')),
3142
3142
3143 ('group.none', _('Repository group no access')),
3143 ('group.none', _('Repository group no access')),
3144 ('group.read', _('Repository group read access')),
3144 ('group.read', _('Repository group read access')),
3145 ('group.write', _('Repository group write access')),
3145 ('group.write', _('Repository group write access')),
3146 ('group.admin', _('Repository group admin access')),
3146 ('group.admin', _('Repository group admin access')),
3147
3147
3148 ('usergroup.none', _('User group no access')),
3148 ('usergroup.none', _('User group no access')),
3149 ('usergroup.read', _('User group read access')),
3149 ('usergroup.read', _('User group read access')),
3150 ('usergroup.write', _('User group write access')),
3150 ('usergroup.write', _('User group write access')),
3151 ('usergroup.admin', _('User group admin access')),
3151 ('usergroup.admin', _('User group admin access')),
3152
3152
3153 ('branch.none', _('Branch no permissions')),
3153 ('branch.none', _('Branch no permissions')),
3154 ('branch.merge', _('Branch access by web merge')),
3154 ('branch.merge', _('Branch access by web merge')),
3155 ('branch.push', _('Branch access by push')),
3155 ('branch.push', _('Branch access by push')),
3156 ('branch.push_force', _('Branch access by push with force')),
3156 ('branch.push_force', _('Branch access by push with force')),
3157
3157
3158 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3158 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3159 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3159 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3160
3160
3161 ('hg.usergroup.create.false', _('User Group creation disabled')),
3161 ('hg.usergroup.create.false', _('User Group creation disabled')),
3162 ('hg.usergroup.create.true', _('User Group creation enabled')),
3162 ('hg.usergroup.create.true', _('User Group creation enabled')),
3163
3163
3164 ('hg.create.none', _('Repository creation disabled')),
3164 ('hg.create.none', _('Repository creation disabled')),
3165 ('hg.create.repository', _('Repository creation enabled')),
3165 ('hg.create.repository', _('Repository creation enabled')),
3166 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3166 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3167 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3167 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3168
3168
3169 ('hg.fork.none', _('Repository forking disabled')),
3169 ('hg.fork.none', _('Repository forking disabled')),
3170 ('hg.fork.repository', _('Repository forking enabled')),
3170 ('hg.fork.repository', _('Repository forking enabled')),
3171
3171
3172 ('hg.register.none', _('Registration disabled')),
3172 ('hg.register.none', _('Registration disabled')),
3173 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3173 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3174 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3174 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3175
3175
3176 ('hg.password_reset.enabled', _('Password reset enabled')),
3176 ('hg.password_reset.enabled', _('Password reset enabled')),
3177 ('hg.password_reset.hidden', _('Password reset hidden')),
3177 ('hg.password_reset.hidden', _('Password reset hidden')),
3178 ('hg.password_reset.disabled', _('Password reset disabled')),
3178 ('hg.password_reset.disabled', _('Password reset disabled')),
3179
3179
3180 ('hg.extern_activate.manual', _('Manual activation of external account')),
3180 ('hg.extern_activate.manual', _('Manual activation of external account')),
3181 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3181 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3182
3182
3183 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3183 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3184 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3184 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3185 ]
3185 ]
3186
3186
3187 # definition of system default permissions for DEFAULT user, created on
3187 # definition of system default permissions for DEFAULT user, created on
3188 # system setup
3188 # system setup
3189 DEFAULT_USER_PERMISSIONS = [
3189 DEFAULT_USER_PERMISSIONS = [
3190 # object perms
3190 # object perms
3191 'repository.read',
3191 'repository.read',
3192 'group.read',
3192 'group.read',
3193 'usergroup.read',
3193 'usergroup.read',
3194 # branch, for backward compat we need same value as before so forced pushed
3194 # branch, for backward compat we need same value as before so forced pushed
3195 'branch.push_force',
3195 'branch.push_force',
3196 # global
3196 # global
3197 'hg.create.repository',
3197 'hg.create.repository',
3198 'hg.repogroup.create.false',
3198 'hg.repogroup.create.false',
3199 'hg.usergroup.create.false',
3199 'hg.usergroup.create.false',
3200 'hg.create.write_on_repogroup.true',
3200 'hg.create.write_on_repogroup.true',
3201 'hg.fork.repository',
3201 'hg.fork.repository',
3202 'hg.register.manual_activate',
3202 'hg.register.manual_activate',
3203 'hg.password_reset.enabled',
3203 'hg.password_reset.enabled',
3204 'hg.extern_activate.auto',
3204 'hg.extern_activate.auto',
3205 'hg.inherit_default_perms.true',
3205 'hg.inherit_default_perms.true',
3206 ]
3206 ]
3207
3207
3208 # defines which permissions are more important higher the more important
3208 # defines which permissions are more important higher the more important
3209 # Weight defines which permissions are more important.
3209 # Weight defines which permissions are more important.
3210 # The higher number the more important.
3210 # The higher number the more important.
3211 PERM_WEIGHTS = {
3211 PERM_WEIGHTS = {
3212 'repository.none': 0,
3212 'repository.none': 0,
3213 'repository.read': 1,
3213 'repository.read': 1,
3214 'repository.write': 3,
3214 'repository.write': 3,
3215 'repository.admin': 4,
3215 'repository.admin': 4,
3216
3216
3217 'group.none': 0,
3217 'group.none': 0,
3218 'group.read': 1,
3218 'group.read': 1,
3219 'group.write': 3,
3219 'group.write': 3,
3220 'group.admin': 4,
3220 'group.admin': 4,
3221
3221
3222 'usergroup.none': 0,
3222 'usergroup.none': 0,
3223 'usergroup.read': 1,
3223 'usergroup.read': 1,
3224 'usergroup.write': 3,
3224 'usergroup.write': 3,
3225 'usergroup.admin': 4,
3225 'usergroup.admin': 4,
3226
3226
3227 'branch.none': 0,
3227 'branch.none': 0,
3228 'branch.merge': 1,
3228 'branch.merge': 1,
3229 'branch.push': 3,
3229 'branch.push': 3,
3230 'branch.push_force': 4,
3230 'branch.push_force': 4,
3231
3231
3232 'hg.repogroup.create.false': 0,
3232 'hg.repogroup.create.false': 0,
3233 'hg.repogroup.create.true': 1,
3233 'hg.repogroup.create.true': 1,
3234
3234
3235 'hg.usergroup.create.false': 0,
3235 'hg.usergroup.create.false': 0,
3236 'hg.usergroup.create.true': 1,
3236 'hg.usergroup.create.true': 1,
3237
3237
3238 'hg.fork.none': 0,
3238 'hg.fork.none': 0,
3239 'hg.fork.repository': 1,
3239 'hg.fork.repository': 1,
3240 'hg.create.none': 0,
3240 'hg.create.none': 0,
3241 'hg.create.repository': 1
3241 'hg.create.repository': 1
3242 }
3242 }
3243
3243
3244 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3244 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3245 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3245 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3246 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3246 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3247
3247
3248 def __repr__(self):
3248 def __repr__(self):
3249 return "<%s('%s:%s')>" % (
3249 return "<%s('%s:%s')>" % (
3250 self.cls_name, self.permission_id, self.permission_name
3250 self.cls_name, self.permission_id, self.permission_name
3251 )
3251 )
3252
3252
3253 @classmethod
3253 @classmethod
3254 def get_by_key(cls, key):
3254 def get_by_key(cls, key):
3255 return cls.query().filter(cls.permission_name == key).scalar()
3255 return cls.query().filter(cls.permission_name == key).scalar()
3256
3256
3257 @classmethod
3257 @classmethod
3258 def get_default_repo_perms(cls, user_id, repo_id=None):
3258 def get_default_repo_perms(cls, user_id, repo_id=None):
3259 q = Session().query(UserRepoToPerm, Repository, Permission)\
3259 q = Session().query(UserRepoToPerm, Repository, Permission)\
3260 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3260 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3261 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3261 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3262 .filter(UserRepoToPerm.user_id == user_id)
3262 .filter(UserRepoToPerm.user_id == user_id)
3263 if repo_id:
3263 if repo_id:
3264 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3264 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3265 return q.all()
3265 return q.all()
3266
3266
3267 @classmethod
3267 @classmethod
3268 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3268 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3269 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3269 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3270 .join(
3270 .join(
3271 Permission,
3271 Permission,
3272 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3272 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3273 .join(
3273 .join(
3274 UserRepoToPerm,
3274 UserRepoToPerm,
3275 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3275 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3276 .filter(UserRepoToPerm.user_id == user_id)
3276 .filter(UserRepoToPerm.user_id == user_id)
3277
3277
3278 if repo_id:
3278 if repo_id:
3279 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3279 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3280 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3280 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3281
3281
3282 @classmethod
3282 @classmethod
3283 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3283 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3284 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3284 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3285 .join(
3285 .join(
3286 Permission,
3286 Permission,
3287 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3287 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3288 .join(
3288 .join(
3289 Repository,
3289 Repository,
3290 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3290 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3291 .join(
3291 .join(
3292 UserGroup,
3292 UserGroup,
3293 UserGroupRepoToPerm.users_group_id ==
3293 UserGroupRepoToPerm.users_group_id ==
3294 UserGroup.users_group_id)\
3294 UserGroup.users_group_id)\
3295 .join(
3295 .join(
3296 UserGroupMember,
3296 UserGroupMember,
3297 UserGroupRepoToPerm.users_group_id ==
3297 UserGroupRepoToPerm.users_group_id ==
3298 UserGroupMember.users_group_id)\
3298 UserGroupMember.users_group_id)\
3299 .filter(
3299 .filter(
3300 UserGroupMember.user_id == user_id,
3300 UserGroupMember.user_id == user_id,
3301 UserGroup.users_group_active == true())
3301 UserGroup.users_group_active == true())
3302 if repo_id:
3302 if repo_id:
3303 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3303 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3304 return q.all()
3304 return q.all()
3305
3305
3306 @classmethod
3306 @classmethod
3307 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3307 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3308 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3308 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3309 .join(
3309 .join(
3310 Permission,
3310 Permission,
3311 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3311 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3312 .join(
3312 .join(
3313 UserGroupRepoToPerm,
3313 UserGroupRepoToPerm,
3314 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3314 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3315 .join(
3315 .join(
3316 UserGroup,
3316 UserGroup,
3317 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3317 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3318 .join(
3318 .join(
3319 UserGroupMember,
3319 UserGroupMember,
3320 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3320 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3321 .filter(
3321 .filter(
3322 UserGroupMember.user_id == user_id,
3322 UserGroupMember.user_id == user_id,
3323 UserGroup.users_group_active == true())
3323 UserGroup.users_group_active == true())
3324
3324
3325 if repo_id:
3325 if repo_id:
3326 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3326 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3327 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3327 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3328
3328
3329 @classmethod
3329 @classmethod
3330 def get_default_group_perms(cls, user_id, repo_group_id=None):
3330 def get_default_group_perms(cls, user_id, repo_group_id=None):
3331 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3331 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3332 .join(
3332 .join(
3333 Permission,
3333 Permission,
3334 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3334 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3335 .join(
3335 .join(
3336 RepoGroup,
3336 RepoGroup,
3337 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3337 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3338 .filter(UserRepoGroupToPerm.user_id == user_id)
3338 .filter(UserRepoGroupToPerm.user_id == user_id)
3339 if repo_group_id:
3339 if repo_group_id:
3340 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3340 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3341 return q.all()
3341 return q.all()
3342
3342
3343 @classmethod
3343 @classmethod
3344 def get_default_group_perms_from_user_group(
3344 def get_default_group_perms_from_user_group(
3345 cls, user_id, repo_group_id=None):
3345 cls, user_id, repo_group_id=None):
3346 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3346 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3347 .join(
3347 .join(
3348 Permission,
3348 Permission,
3349 UserGroupRepoGroupToPerm.permission_id ==
3349 UserGroupRepoGroupToPerm.permission_id ==
3350 Permission.permission_id)\
3350 Permission.permission_id)\
3351 .join(
3351 .join(
3352 RepoGroup,
3352 RepoGroup,
3353 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3353 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3354 .join(
3354 .join(
3355 UserGroup,
3355 UserGroup,
3356 UserGroupRepoGroupToPerm.users_group_id ==
3356 UserGroupRepoGroupToPerm.users_group_id ==
3357 UserGroup.users_group_id)\
3357 UserGroup.users_group_id)\
3358 .join(
3358 .join(
3359 UserGroupMember,
3359 UserGroupMember,
3360 UserGroupRepoGroupToPerm.users_group_id ==
3360 UserGroupRepoGroupToPerm.users_group_id ==
3361 UserGroupMember.users_group_id)\
3361 UserGroupMember.users_group_id)\
3362 .filter(
3362 .filter(
3363 UserGroupMember.user_id == user_id,
3363 UserGroupMember.user_id == user_id,
3364 UserGroup.users_group_active == true())
3364 UserGroup.users_group_active == true())
3365 if repo_group_id:
3365 if repo_group_id:
3366 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3366 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3367 return q.all()
3367 return q.all()
3368
3368
3369 @classmethod
3369 @classmethod
3370 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3370 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3371 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3371 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3372 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3372 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3373 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3373 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3374 .filter(UserUserGroupToPerm.user_id == user_id)
3374 .filter(UserUserGroupToPerm.user_id == user_id)
3375 if user_group_id:
3375 if user_group_id:
3376 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3376 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3377 return q.all()
3377 return q.all()
3378
3378
3379 @classmethod
3379 @classmethod
3380 def get_default_user_group_perms_from_user_group(
3380 def get_default_user_group_perms_from_user_group(
3381 cls, user_id, user_group_id=None):
3381 cls, user_id, user_group_id=None):
3382 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3382 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3383 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3383 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3384 .join(
3384 .join(
3385 Permission,
3385 Permission,
3386 UserGroupUserGroupToPerm.permission_id ==
3386 UserGroupUserGroupToPerm.permission_id ==
3387 Permission.permission_id)\
3387 Permission.permission_id)\
3388 .join(
3388 .join(
3389 TargetUserGroup,
3389 TargetUserGroup,
3390 UserGroupUserGroupToPerm.target_user_group_id ==
3390 UserGroupUserGroupToPerm.target_user_group_id ==
3391 TargetUserGroup.users_group_id)\
3391 TargetUserGroup.users_group_id)\
3392 .join(
3392 .join(
3393 UserGroup,
3393 UserGroup,
3394 UserGroupUserGroupToPerm.user_group_id ==
3394 UserGroupUserGroupToPerm.user_group_id ==
3395 UserGroup.users_group_id)\
3395 UserGroup.users_group_id)\
3396 .join(
3396 .join(
3397 UserGroupMember,
3397 UserGroupMember,
3398 UserGroupUserGroupToPerm.user_group_id ==
3398 UserGroupUserGroupToPerm.user_group_id ==
3399 UserGroupMember.users_group_id)\
3399 UserGroupMember.users_group_id)\
3400 .filter(
3400 .filter(
3401 UserGroupMember.user_id == user_id,
3401 UserGroupMember.user_id == user_id,
3402 UserGroup.users_group_active == true())
3402 UserGroup.users_group_active == true())
3403 if user_group_id:
3403 if user_group_id:
3404 q = q.filter(
3404 q = q.filter(
3405 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3405 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3406
3406
3407 return q.all()
3407 return q.all()
3408
3408
3409
3409
3410 class UserRepoToPerm(Base, BaseModel):
3410 class UserRepoToPerm(Base, BaseModel):
3411 __tablename__ = 'repo_to_perm'
3411 __tablename__ = 'repo_to_perm'
3412 __table_args__ = (
3412 __table_args__ = (
3413 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3413 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3414 base_table_args
3414 base_table_args
3415 )
3415 )
3416
3416
3417 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3417 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3419 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3419 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3421
3421
3422 user = relationship('User', back_populates="repo_to_perm")
3422 user = relationship('User', back_populates="repo_to_perm")
3423 repository = relationship('Repository', back_populates="repo_to_perm")
3423 repository = relationship('Repository', back_populates="repo_to_perm")
3424 permission = relationship('Permission')
3424 permission = relationship('Permission')
3425
3425
3426 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3426 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined', back_populates='user_repo_to_perm')
3427
3427
3428 @classmethod
3428 @classmethod
3429 def create(cls, user, repository, permission):
3429 def create(cls, user, repository, permission):
3430 n = cls()
3430 n = cls()
3431 n.user = user
3431 n.user = user
3432 n.repository = repository
3432 n.repository = repository
3433 n.permission = permission
3433 n.permission = permission
3434 Session().add(n)
3434 Session().add(n)
3435 return n
3435 return n
3436
3436
3437 def __repr__(self):
3437 def __repr__(self):
3438 return f'<{self.user} => {self.repository} >'
3438 return f'<{self.user} => {self.repository} >'
3439
3439
3440
3440
3441 class UserUserGroupToPerm(Base, BaseModel):
3441 class UserUserGroupToPerm(Base, BaseModel):
3442 __tablename__ = 'user_user_group_to_perm'
3442 __tablename__ = 'user_user_group_to_perm'
3443 __table_args__ = (
3443 __table_args__ = (
3444 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3444 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3445 base_table_args
3445 base_table_args
3446 )
3446 )
3447
3447
3448 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3448 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3449 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3449 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3450 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3450 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3451 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3451 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3452
3452
3453 user = relationship('User', back_populates='user_group_to_perm')
3453 user = relationship('User', back_populates='user_group_to_perm')
3454 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3454 user_group = relationship('UserGroup', back_populates='user_user_group_to_perm')
3455 permission = relationship('Permission')
3455 permission = relationship('Permission')
3456
3456
3457 @classmethod
3457 @classmethod
3458 def create(cls, user, user_group, permission):
3458 def create(cls, user, user_group, permission):
3459 n = cls()
3459 n = cls()
3460 n.user = user
3460 n.user = user
3461 n.user_group = user_group
3461 n.user_group = user_group
3462 n.permission = permission
3462 n.permission = permission
3463 Session().add(n)
3463 Session().add(n)
3464 return n
3464 return n
3465
3465
3466 def __repr__(self):
3466 def __repr__(self):
3467 return f'<{self.user} => {self.user_group} >'
3467 return f'<{self.user} => {self.user_group} >'
3468
3468
3469
3469
3470 class UserToPerm(Base, BaseModel):
3470 class UserToPerm(Base, BaseModel):
3471 __tablename__ = 'user_to_perm'
3471 __tablename__ = 'user_to_perm'
3472 __table_args__ = (
3472 __table_args__ = (
3473 UniqueConstraint('user_id', 'permission_id'),
3473 UniqueConstraint('user_id', 'permission_id'),
3474 base_table_args
3474 base_table_args
3475 )
3475 )
3476
3476
3477 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3477 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3479 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3479 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3480
3480
3481 user = relationship('User', back_populates='user_perms')
3481 user = relationship('User', back_populates='user_perms')
3482 permission = relationship('Permission', lazy='joined')
3482 permission = relationship('Permission', lazy='joined')
3483
3483
3484 def __repr__(self):
3484 def __repr__(self):
3485 return f'<{self.user} => {self.permission} >'
3485 return f'<{self.user} => {self.permission} >'
3486
3486
3487
3487
3488 class UserGroupRepoToPerm(Base, BaseModel):
3488 class UserGroupRepoToPerm(Base, BaseModel):
3489 __tablename__ = 'users_group_repo_to_perm'
3489 __tablename__ = 'users_group_repo_to_perm'
3490 __table_args__ = (
3490 __table_args__ = (
3491 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3491 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3492 base_table_args
3492 base_table_args
3493 )
3493 )
3494
3494
3495 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3495 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3496 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3496 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3499
3499
3500 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3500 users_group = relationship('UserGroup', back_populates='users_group_repo_to_perm')
3501 permission = relationship('Permission')
3501 permission = relationship('Permission')
3502 repository = relationship('Repository', back_populates='users_group_to_perm')
3502 repository = relationship('Repository', back_populates='users_group_to_perm')
3503 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3503 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all', back_populates='user_group_repo_to_perm')
3504
3504
3505 @classmethod
3505 @classmethod
3506 def create(cls, users_group, repository, permission):
3506 def create(cls, users_group, repository, permission):
3507 n = cls()
3507 n = cls()
3508 n.users_group = users_group
3508 n.users_group = users_group
3509 n.repository = repository
3509 n.repository = repository
3510 n.permission = permission
3510 n.permission = permission
3511 Session().add(n)
3511 Session().add(n)
3512 return n
3512 return n
3513
3513
3514 def __repr__(self):
3514 def __repr__(self):
3515 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3515 return f'<UserGroupRepoToPerm:{self.users_group} => {self.repository} >'
3516
3516
3517
3517
3518 class UserGroupUserGroupToPerm(Base, BaseModel):
3518 class UserGroupUserGroupToPerm(Base, BaseModel):
3519 __tablename__ = 'user_group_user_group_to_perm'
3519 __tablename__ = 'user_group_user_group_to_perm'
3520 __table_args__ = (
3520 __table_args__ = (
3521 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3521 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3522 CheckConstraint('target_user_group_id != user_group_id'),
3522 CheckConstraint('target_user_group_id != user_group_id'),
3523 base_table_args
3523 base_table_args
3524 )
3524 )
3525
3525
3526 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)
3526 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)
3527 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3527 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3528 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3528 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3529 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3529 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3530
3530
3531 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3531 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id', back_populates='user_group_user_group_to_perm')
3532 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3532 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3533 permission = relationship('Permission')
3533 permission = relationship('Permission')
3534
3534
3535 @classmethod
3535 @classmethod
3536 def create(cls, target_user_group, user_group, permission):
3536 def create(cls, target_user_group, user_group, permission):
3537 n = cls()
3537 n = cls()
3538 n.target_user_group = target_user_group
3538 n.target_user_group = target_user_group
3539 n.user_group = user_group
3539 n.user_group = user_group
3540 n.permission = permission
3540 n.permission = permission
3541 Session().add(n)
3541 Session().add(n)
3542 return n
3542 return n
3543
3543
3544 def __repr__(self):
3544 def __repr__(self):
3545 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3545 return f'<UserGroupUserGroup:{self.target_user_group} => {self.user_group} >'
3546
3546
3547
3547
3548 class UserGroupToPerm(Base, BaseModel):
3548 class UserGroupToPerm(Base, BaseModel):
3549 __tablename__ = 'users_group_to_perm'
3549 __tablename__ = 'users_group_to_perm'
3550 __table_args__ = (
3550 __table_args__ = (
3551 UniqueConstraint('users_group_id', 'permission_id',),
3551 UniqueConstraint('users_group_id', 'permission_id',),
3552 base_table_args
3552 base_table_args
3553 )
3553 )
3554
3554
3555 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3555 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3556 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3556 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3557 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3557 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3558
3558
3559 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3559 users_group = relationship('UserGroup', back_populates='users_group_to_perm')
3560 permission = relationship('Permission')
3560 permission = relationship('Permission')
3561
3561
3562
3562
3563 class UserRepoGroupToPerm(Base, BaseModel):
3563 class UserRepoGroupToPerm(Base, BaseModel):
3564 __tablename__ = 'user_repo_group_to_perm'
3564 __tablename__ = 'user_repo_group_to_perm'
3565 __table_args__ = (
3565 __table_args__ = (
3566 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3566 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3567 base_table_args
3567 base_table_args
3568 )
3568 )
3569
3569
3570 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3570 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3571 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3571 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3572 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3572 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3573 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3573 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3574
3574
3575 user = relationship('User', back_populates='repo_group_to_perm')
3575 user = relationship('User', back_populates='repo_group_to_perm')
3576 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3576 group = relationship('RepoGroup', back_populates='repo_group_to_perm')
3577 permission = relationship('Permission')
3577 permission = relationship('Permission')
3578
3578
3579 @classmethod
3579 @classmethod
3580 def create(cls, user, repository_group, permission):
3580 def create(cls, user, repository_group, permission):
3581 n = cls()
3581 n = cls()
3582 n.user = user
3582 n.user = user
3583 n.group = repository_group
3583 n.group = repository_group
3584 n.permission = permission
3584 n.permission = permission
3585 Session().add(n)
3585 Session().add(n)
3586 return n
3586 return n
3587
3587
3588
3588
3589 class UserGroupRepoGroupToPerm(Base, BaseModel):
3589 class UserGroupRepoGroupToPerm(Base, BaseModel):
3590 __tablename__ = 'users_group_repo_group_to_perm'
3590 __tablename__ = 'users_group_repo_group_to_perm'
3591 __table_args__ = (
3591 __table_args__ = (
3592 UniqueConstraint('users_group_id', 'group_id'),
3592 UniqueConstraint('users_group_id', 'group_id'),
3593 base_table_args
3593 base_table_args
3594 )
3594 )
3595
3595
3596 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)
3596 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)
3597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3598 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3598 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3599 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3599 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3600
3600
3601 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3601 users_group = relationship('UserGroup', back_populates='users_group_repo_group_to_perm')
3602 permission = relationship('Permission')
3602 permission = relationship('Permission')
3603 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3603 group = relationship('RepoGroup', back_populates='users_group_to_perm')
3604
3604
3605 @classmethod
3605 @classmethod
3606 def create(cls, user_group, repository_group, permission):
3606 def create(cls, user_group, repository_group, permission):
3607 n = cls()
3607 n = cls()
3608 n.users_group = user_group
3608 n.users_group = user_group
3609 n.group = repository_group
3609 n.group = repository_group
3610 n.permission = permission
3610 n.permission = permission
3611 Session().add(n)
3611 Session().add(n)
3612 return n
3612 return n
3613
3613
3614 def __repr__(self):
3614 def __repr__(self):
3615 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3615 return '<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3616
3616
3617
3617
3618 class Statistics(Base, BaseModel):
3618 class Statistics(Base, BaseModel):
3619 __tablename__ = 'statistics'
3619 __tablename__ = 'statistics'
3620 __table_args__ = (
3620 __table_args__ = (
3621 base_table_args
3621 base_table_args
3622 )
3622 )
3623
3623
3624 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3624 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3625 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3625 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3626 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3626 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3627 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3627 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False) #JSON data
3628 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3628 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False) #JSON data
3629 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3629 languages = Column("languages", LargeBinary(1000000), nullable=False) #JSON data
3630
3630
3631 repository = relationship('Repository', single_parent=True, viewonly=True)
3631 repository = relationship('Repository', single_parent=True, viewonly=True)
3632
3632
3633
3633
3634 class UserFollowing(Base, BaseModel):
3634 class UserFollowing(Base, BaseModel):
3635 __tablename__ = 'user_followings'
3635 __tablename__ = 'user_followings'
3636 __table_args__ = (
3636 __table_args__ = (
3637 UniqueConstraint('user_id', 'follows_repository_id'),
3637 UniqueConstraint('user_id', 'follows_repository_id'),
3638 UniqueConstraint('user_id', 'follows_user_id'),
3638 UniqueConstraint('user_id', 'follows_user_id'),
3639 base_table_args
3639 base_table_args
3640 )
3640 )
3641
3641
3642 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3642 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3643 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3643 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3644 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3644 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3645 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3645 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3646 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3646 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3647
3647
3648 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3648 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id', back_populates='followings')
3649
3649
3650 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3650 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3651 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3651 follows_repository = relationship('Repository', order_by='Repository.repo_name', back_populates='followers')
3652
3652
3653 @classmethod
3653 @classmethod
3654 def get_repo_followers(cls, repo_id):
3654 def get_repo_followers(cls, repo_id):
3655 return cls.query().filter(cls.follows_repo_id == repo_id)
3655 return cls.query().filter(cls.follows_repo_id == repo_id)
3656
3656
3657
3657
3658 class CacheKey(Base, BaseModel):
3658 class CacheKey(Base, BaseModel):
3659 __tablename__ = 'cache_invalidation'
3659 __tablename__ = 'cache_invalidation'
3660 __table_args__ = (
3660 __table_args__ = (
3661 UniqueConstraint('cache_key'),
3661 UniqueConstraint('cache_key'),
3662 Index('key_idx', 'cache_key'),
3662 Index('key_idx', 'cache_key'),
3663 Index('cache_args_idx', 'cache_args'),
3663 Index('cache_args_idx', 'cache_args'),
3664 base_table_args,
3664 base_table_args,
3665 )
3665 )
3666
3666
3667 CACHE_TYPE_FEED = 'FEED'
3667 CACHE_TYPE_FEED = 'FEED'
3668
3668
3669 # namespaces used to register process/thread aware caches
3669 # namespaces used to register process/thread aware caches
3670 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3670 REPO_INVALIDATION_NAMESPACE = 'repo_cache.v1:{repo_id}'
3671
3671
3672 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3672 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3673 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3673 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3674 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3674 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3675 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3675 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3676 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3676 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3677
3677
3678 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3678 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3679 self.cache_key = cache_key
3679 self.cache_key = cache_key
3680 self.cache_args = cache_args
3680 self.cache_args = cache_args
3681 self.cache_active = False
3681 self.cache_active = False
3682 # first key should be same for all entries, since all workers should share it
3682 # first key should be same for all entries, since all workers should share it
3683 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3683 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3684
3684
3685 def __repr__(self):
3685 def __repr__(self):
3686 return "<%s('%s:%s[%s]')>" % (
3686 return "<%s('%s:%s[%s]')>" % (
3687 self.cls_name,
3687 self.cls_name,
3688 self.cache_id, self.cache_key, self.cache_active)
3688 self.cache_id, self.cache_key, self.cache_active)
3689
3689
3690 def _cache_key_partition(self):
3690 def _cache_key_partition(self):
3691 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3691 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3692 return prefix, repo_name, suffix
3692 return prefix, repo_name, suffix
3693
3693
3694 def get_prefix(self):
3694 def get_prefix(self):
3695 """
3695 """
3696 Try to extract prefix from existing cache key. The key could consist
3696 Try to extract prefix from existing cache key. The key could consist
3697 of prefix, repo_name, suffix
3697 of prefix, repo_name, suffix
3698 """
3698 """
3699 # this returns prefix, repo_name, suffix
3699 # this returns prefix, repo_name, suffix
3700 return self._cache_key_partition()[0]
3700 return self._cache_key_partition()[0]
3701
3701
3702 def get_suffix(self):
3702 def get_suffix(self):
3703 """
3703 """
3704 get suffix that might have been used in _get_cache_key to
3704 get suffix that might have been used in _get_cache_key to
3705 generate self.cache_key. Only used for informational purposes
3705 generate self.cache_key. Only used for informational purposes
3706 in repo_edit.mako.
3706 in repo_edit.mako.
3707 """
3707 """
3708 # prefix, repo_name, suffix
3708 # prefix, repo_name, suffix
3709 return self._cache_key_partition()[2]
3709 return self._cache_key_partition()[2]
3710
3710
3711 @classmethod
3711 @classmethod
3712 def generate_new_state_uid(cls, based_on=None):
3712 def generate_new_state_uid(cls, based_on=None):
3713 if based_on:
3713 if based_on:
3714 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3714 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3715 else:
3715 else:
3716 return str(uuid.uuid4())
3716 return str(uuid.uuid4())
3717
3717
3718 @classmethod
3718 @classmethod
3719 def delete_all_cache(cls):
3719 def delete_all_cache(cls):
3720 """
3720 """
3721 Delete all cache keys from database.
3721 Delete all cache keys from database.
3722 Should only be run when all instances are down and all entries
3722 Should only be run when all instances are down and all entries
3723 thus stale.
3723 thus stale.
3724 """
3724 """
3725 cls.query().delete()
3725 cls.query().delete()
3726 Session().commit()
3726 Session().commit()
3727
3727
3728 @classmethod
3728 @classmethod
3729 def set_invalidate(cls, cache_uid, delete=False):
3729 def set_invalidate(cls, cache_uid, delete=False):
3730 """
3730 """
3731 Mark all caches of a repo as invalid in the database.
3731 Mark all caches of a repo as invalid in the database.
3732 """
3732 """
3733
3733
3734 try:
3734 try:
3735 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3735 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3736 if delete:
3736 if delete:
3737 qry.delete()
3737 qry.delete()
3738 log.debug('cache objects deleted for cache args %s',
3738 log.debug('cache objects deleted for cache args %s',
3739 safe_str(cache_uid))
3739 safe_str(cache_uid))
3740 else:
3740 else:
3741 qry.update({"cache_active": False,
3741 qry.update({"cache_active": False,
3742 "cache_state_uid": cls.generate_new_state_uid()})
3742 "cache_state_uid": cls.generate_new_state_uid()})
3743 log.debug('cache objects marked as invalid for cache args %s',
3743 log.debug('cache objects marked as invalid for cache args %s',
3744 safe_str(cache_uid))
3744 safe_str(cache_uid))
3745
3745
3746 Session().commit()
3746 Session().commit()
3747 except Exception:
3747 except Exception:
3748 log.exception(
3748 log.exception(
3749 'Cache key invalidation failed for cache args %s',
3749 'Cache key invalidation failed for cache args %s',
3750 safe_str(cache_uid))
3750 safe_str(cache_uid))
3751 Session().rollback()
3751 Session().rollback()
3752
3752
3753 @classmethod
3753 @classmethod
3754 def get_active_cache(cls, cache_key):
3754 def get_active_cache(cls, cache_key):
3755 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3755 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3756 if inv_obj:
3756 if inv_obj:
3757 return inv_obj
3757 return inv_obj
3758 return None
3758 return None
3759
3759
3760 @classmethod
3760 @classmethod
3761 def get_namespace_map(cls, namespace):
3761 def get_namespace_map(cls, namespace):
3762 return {
3762 return {
3763 x.cache_key: x
3763 x.cache_key: x
3764 for x in cls.query().filter(cls.cache_args == namespace)}
3764 for x in cls.query().filter(cls.cache_args == namespace)}
3765
3765
3766
3766
3767 class ChangesetComment(Base, BaseModel):
3767 class ChangesetComment(Base, BaseModel):
3768 __tablename__ = 'changeset_comments'
3768 __tablename__ = 'changeset_comments'
3769 __table_args__ = (
3769 __table_args__ = (
3770 Index('cc_revision_idx', 'revision'),
3770 Index('cc_revision_idx', 'revision'),
3771 base_table_args,
3771 base_table_args,
3772 )
3772 )
3773
3773
3774 COMMENT_OUTDATED = 'comment_outdated'
3774 COMMENT_OUTDATED = 'comment_outdated'
3775 COMMENT_TYPE_NOTE = 'note'
3775 COMMENT_TYPE_NOTE = 'note'
3776 COMMENT_TYPE_TODO = 'todo'
3776 COMMENT_TYPE_TODO = 'todo'
3777 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3777 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3778
3778
3779 OP_IMMUTABLE = 'immutable'
3779 OP_IMMUTABLE = 'immutable'
3780 OP_CHANGEABLE = 'changeable'
3780 OP_CHANGEABLE = 'changeable'
3781
3781
3782 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3782 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3783 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3783 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3784 revision = Column('revision', String(40), nullable=True)
3784 revision = Column('revision', String(40), nullable=True)
3785 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3785 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3786 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3786 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3787 line_no = Column('line_no', Unicode(10), nullable=True)
3787 line_no = Column('line_no', Unicode(10), nullable=True)
3788 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3788 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3789 f_path = Column('f_path', Unicode(1000), nullable=True)
3789 f_path = Column('f_path', Unicode(1000), nullable=True)
3790 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3790 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3791 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3791 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3792 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3792 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3793 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3793 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3794 renderer = Column('renderer', Unicode(64), nullable=True)
3794 renderer = Column('renderer', Unicode(64), nullable=True)
3795 display_state = Column('display_state', Unicode(128), nullable=True)
3795 display_state = Column('display_state', Unicode(128), nullable=True)
3796 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3796 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3797 draft = Column('draft', Boolean(), nullable=True, default=False)
3797 draft = Column('draft', Boolean(), nullable=True, default=False)
3798
3798
3799 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3799 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3800 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3800 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3801
3801
3802 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3802 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3803 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3803 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3804
3804
3805 author = relationship('User', lazy='select', back_populates='user_comments')
3805 author = relationship('User', lazy='select', back_populates='user_comments')
3806 repo = relationship('Repository', back_populates='comments')
3806 repo = relationship('Repository', back_populates='comments')
3807 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3807 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select', back_populates='comment')
3808 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3808 pull_request = relationship('PullRequest', lazy='select', back_populates='comments')
3809 pull_request_version = relationship('PullRequestVersion', lazy='select')
3809 pull_request_version = relationship('PullRequestVersion', lazy='select')
3810 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3810 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version', back_populates="comment")
3811
3811
3812 @classmethod
3812 @classmethod
3813 def get_users(cls, revision=None, pull_request_id=None):
3813 def get_users(cls, revision=None, pull_request_id=None):
3814 """
3814 """
3815 Returns user associated with this ChangesetComment. ie those
3815 Returns user associated with this ChangesetComment. ie those
3816 who actually commented
3816 who actually commented
3817
3817
3818 :param cls:
3818 :param cls:
3819 :param revision:
3819 :param revision:
3820 """
3820 """
3821 q = Session().query(User).join(ChangesetComment.author)
3821 q = Session().query(User).join(ChangesetComment.author)
3822 if revision:
3822 if revision:
3823 q = q.filter(cls.revision == revision)
3823 q = q.filter(cls.revision == revision)
3824 elif pull_request_id:
3824 elif pull_request_id:
3825 q = q.filter(cls.pull_request_id == pull_request_id)
3825 q = q.filter(cls.pull_request_id == pull_request_id)
3826 return q.all()
3826 return q.all()
3827
3827
3828 @classmethod
3828 @classmethod
3829 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3829 def get_index_from_version(cls, pr_version, versions=None, num_versions=None) -> int:
3830 if pr_version is None:
3830 if pr_version is None:
3831 return 0
3831 return 0
3832
3832
3833 if versions is not None:
3833 if versions is not None:
3834 num_versions = [x.pull_request_version_id for x in versions]
3834 num_versions = [x.pull_request_version_id for x in versions]
3835
3835
3836 num_versions = num_versions or []
3836 num_versions = num_versions or []
3837 try:
3837 try:
3838 return num_versions.index(pr_version) + 1
3838 return num_versions.index(pr_version) + 1
3839 except (IndexError, ValueError):
3839 except (IndexError, ValueError):
3840 return 0
3840 return 0
3841
3841
3842 @property
3842 @property
3843 def outdated(self):
3843 def outdated(self):
3844 return self.display_state == self.COMMENT_OUTDATED
3844 return self.display_state == self.COMMENT_OUTDATED
3845
3845
3846 @property
3846 @property
3847 def outdated_js(self):
3847 def outdated_js(self):
3848 return str_json(self.display_state == self.COMMENT_OUTDATED)
3848 return str_json(self.display_state == self.COMMENT_OUTDATED)
3849
3849
3850 @property
3850 @property
3851 def immutable(self):
3851 def immutable(self):
3852 return self.immutable_state == self.OP_IMMUTABLE
3852 return self.immutable_state == self.OP_IMMUTABLE
3853
3853
3854 def outdated_at_version(self, version):
3854 def outdated_at_version(self, version: int) -> bool:
3855 """
3855 """
3856 Checks if comment is outdated for given pull request version
3856 Checks if comment is outdated for given pull request version
3857 """
3857 """
3858
3858
3859 # If version is None, return False as the current version cannot be less than None
3860 if version is None:
3861 return False
3862
3863 # Ensure that the version is an integer to prevent TypeError on comparison
3864 if not isinstance(version, int):
3865 raise ValueError("The provided version must be an integer.")
3866
3867 def version_check():
3859 def version_check():
3868 return self.pull_request_version_id and self.pull_request_version_id != version
3860 return self.pull_request_version_id and self.pull_request_version_id != version
3869
3861
3870 if self.is_inline:
3862 if self.is_inline:
3871 return self.outdated and version_check()
3863 return self.outdated and version_check()
3872 else:
3864 else:
3873 # general comments don't have .outdated set, also latest don't have a version
3865 # general comments don't have .outdated set, also latest don't have a version
3874 return version_check()
3866 return version_check()
3875
3867
3876 def outdated_at_version_js(self, version):
3868 def outdated_at_version_js(self, version):
3877 """
3869 """
3878 Checks if comment is outdated for given pull request version
3870 Checks if comment is outdated for given pull request version
3879 """
3871 """
3880 return str_json(self.outdated_at_version(version))
3872 return str_json(self.outdated_at_version(version))
3881
3873
3882 def older_than_version(self, version: int) -> bool:
3874 def older_than_version(self, version: int) -> bool:
3883 """
3875 """
3884 Checks if comment is made from a previous version than given.
3876 Checks if comment is made from a previous version than given.
3885 Assumes self.pull_request_version.pull_request_version_id is an integer if not None.
3877 Assumes self.pull_request_version.pull_request_version_id is an integer if not None.
3886 """
3878 """
3887
3879
3888 # If version is None, return False as the current version cannot be less than None
3880 # If version is None, return False as the current version cannot be less than None
3889 if version is None:
3881 if version is None:
3890 return False
3882 return False
3891
3883
3892 # Ensure that the version is an integer to prevent TypeError on comparison
3884 # Ensure that the version is an integer to prevent TypeError on comparison
3893 if not isinstance(version, int):
3885 if not isinstance(version, int):
3894 raise ValueError("The provided version must be an integer.")
3886 raise ValueError("The provided version must be an integer.")
3895
3887
3896 # Initialize current version to 0 or pull_request_version_id if it's available
3888 # Initialize current version to 0 or pull_request_version_id if it's available
3897 cur_ver = 0
3889 cur_ver = 0
3898 if self.pull_request_version and self.pull_request_version.pull_request_version_id is not None:
3890 if self.pull_request_version and self.pull_request_version.pull_request_version_id is not None:
3899 cur_ver = self.pull_request_version.pull_request_version_id
3891 cur_ver = self.pull_request_version.pull_request_version_id
3900
3892
3901 # Return True if the current version is less than the given version
3893 # Return True if the current version is less than the given version
3902 return cur_ver < version
3894 return cur_ver < version
3903
3895
3904 def older_than_version_js(self, version):
3896 def older_than_version_js(self, version):
3905 """
3897 """
3906 Checks if comment is made from previous version than given
3898 Checks if comment is made from previous version than given
3907 """
3899 """
3908 return str_json(self.older_than_version(version))
3900 return str_json(self.older_than_version(version))
3909
3901
3910 @property
3902 @property
3911 def commit_id(self):
3903 def commit_id(self):
3912 """New style naming to stop using .revision"""
3904 """New style naming to stop using .revision"""
3913 return self.revision
3905 return self.revision
3914
3906
3915 @property
3907 @property
3916 def resolved(self):
3908 def resolved(self):
3917 return self.resolved_by[0] if self.resolved_by else None
3909 return self.resolved_by[0] if self.resolved_by else None
3918
3910
3919 @property
3911 @property
3920 def is_todo(self):
3912 def is_todo(self):
3921 return self.comment_type == self.COMMENT_TYPE_TODO
3913 return self.comment_type == self.COMMENT_TYPE_TODO
3922
3914
3923 @property
3915 @property
3924 def is_inline(self):
3916 def is_inline(self):
3925 if self.line_no and self.f_path:
3917 if self.line_no and self.f_path:
3926 return True
3918 return True
3927 return False
3919 return False
3928
3920
3929 @property
3921 @property
3930 def last_version(self):
3922 def last_version(self):
3931 version = 0
3923 version = 0
3932 if self.history:
3924 if self.history:
3933 version = self.history[-1].version
3925 version = self.history[-1].version
3934 return version
3926 return version
3935
3927
3936 def get_index_version(self, versions):
3928 def get_index_version(self, versions):
3937 return self.get_index_from_version(
3929 return self.get_index_from_version(
3938 self.pull_request_version_id, versions)
3930 self.pull_request_version_id, versions)
3939
3931
3940 @property
3932 @property
3941 def review_status(self):
3933 def review_status(self):
3942 if self.status_change:
3934 if self.status_change:
3943 return self.status_change[0].status
3935 return self.status_change[0].status
3944
3936
3945 @property
3937 @property
3946 def review_status_lbl(self):
3938 def review_status_lbl(self):
3947 if self.status_change:
3939 if self.status_change:
3948 return self.status_change[0].status_lbl
3940 return self.status_change[0].status_lbl
3949
3941
3950 def __repr__(self):
3942 def __repr__(self):
3951 if self.comment_id:
3943 if self.comment_id:
3952 return f'<DB:Comment #{self.comment_id}>'
3944 return f'<DB:Comment #{self.comment_id}>'
3953 else:
3945 else:
3954 return f'<DB:Comment at {id(self)!r}>'
3946 return f'<DB:Comment at {id(self)!r}>'
3955
3947
3956 def get_api_data(self):
3948 def get_api_data(self):
3957 comment = self
3949 comment = self
3958
3950
3959 data = {
3951 data = {
3960 'comment_id': comment.comment_id,
3952 'comment_id': comment.comment_id,
3961 'comment_type': comment.comment_type,
3953 'comment_type': comment.comment_type,
3962 'comment_text': comment.text,
3954 'comment_text': comment.text,
3963 'comment_status': comment.status_change,
3955 'comment_status': comment.status_change,
3964 'comment_f_path': comment.f_path,
3956 'comment_f_path': comment.f_path,
3965 'comment_lineno': comment.line_no,
3957 'comment_lineno': comment.line_no,
3966 'comment_author': comment.author,
3958 'comment_author': comment.author,
3967 'comment_created_on': comment.created_on,
3959 'comment_created_on': comment.created_on,
3968 'comment_resolved_by': self.resolved,
3960 'comment_resolved_by': self.resolved,
3969 'comment_commit_id': comment.revision,
3961 'comment_commit_id': comment.revision,
3970 'comment_pull_request_id': comment.pull_request_id,
3962 'comment_pull_request_id': comment.pull_request_id,
3971 'comment_last_version': self.last_version
3963 'comment_last_version': self.last_version
3972 }
3964 }
3973 return data
3965 return data
3974
3966
3975 def __json__(self):
3967 def __json__(self):
3976 data = dict()
3968 data = dict()
3977 data.update(self.get_api_data())
3969 data.update(self.get_api_data())
3978 return data
3970 return data
3979
3971
3980
3972
3981 class ChangesetCommentHistory(Base, BaseModel):
3973 class ChangesetCommentHistory(Base, BaseModel):
3982 __tablename__ = 'changeset_comments_history'
3974 __tablename__ = 'changeset_comments_history'
3983 __table_args__ = (
3975 __table_args__ = (
3984 Index('cch_comment_id_idx', 'comment_id'),
3976 Index('cch_comment_id_idx', 'comment_id'),
3985 base_table_args,
3977 base_table_args,
3986 )
3978 )
3987
3979
3988 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3980 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3989 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3981 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3990 version = Column("version", Integer(), nullable=False, default=0)
3982 version = Column("version", Integer(), nullable=False, default=0)
3991 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3983 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3992 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3984 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3985 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3994 deleted = Column('deleted', Boolean(), default=False)
3986 deleted = Column('deleted', Boolean(), default=False)
3995
3987
3996 author = relationship('User', lazy='joined')
3988 author = relationship('User', lazy='joined')
3997 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3989 comment = relationship('ChangesetComment', cascade="all, delete", back_populates="history")
3998
3990
3999 @classmethod
3991 @classmethod
4000 def get_version(cls, comment_id):
3992 def get_version(cls, comment_id):
4001 q = Session().query(ChangesetCommentHistory).filter(
3993 q = Session().query(ChangesetCommentHistory).filter(
4002 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3994 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
4003 if q.count() == 0:
3995 if q.count() == 0:
4004 return 1
3996 return 1
4005 elif q.count() >= q[0].version:
3997 elif q.count() >= q[0].version:
4006 return q.count() + 1
3998 return q.count() + 1
4007 else:
3999 else:
4008 return q[0].version + 1
4000 return q[0].version + 1
4009
4001
4010
4002
4011 class ChangesetStatus(Base, BaseModel):
4003 class ChangesetStatus(Base, BaseModel):
4012 __tablename__ = 'changeset_statuses'
4004 __tablename__ = 'changeset_statuses'
4013 __table_args__ = (
4005 __table_args__ = (
4014 Index('cs_revision_idx', 'revision'),
4006 Index('cs_revision_idx', 'revision'),
4015 Index('cs_version_idx', 'version'),
4007 Index('cs_version_idx', 'version'),
4016 UniqueConstraint('repo_id', 'revision', 'version'),
4008 UniqueConstraint('repo_id', 'revision', 'version'),
4017 base_table_args
4009 base_table_args
4018 )
4010 )
4019
4011
4020 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4012 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
4021 STATUS_APPROVED = 'approved'
4013 STATUS_APPROVED = 'approved'
4022 STATUS_REJECTED = 'rejected'
4014 STATUS_REJECTED = 'rejected'
4023 STATUS_UNDER_REVIEW = 'under_review'
4015 STATUS_UNDER_REVIEW = 'under_review'
4024
4016
4025 STATUSES = [
4017 STATUSES = [
4026 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4018 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
4027 (STATUS_APPROVED, _("Approved")),
4019 (STATUS_APPROVED, _("Approved")),
4028 (STATUS_REJECTED, _("Rejected")),
4020 (STATUS_REJECTED, _("Rejected")),
4029 (STATUS_UNDER_REVIEW, _("Under Review")),
4021 (STATUS_UNDER_REVIEW, _("Under Review")),
4030 ]
4022 ]
4031
4023
4032 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4024 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4033 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4025 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4026 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4035 revision = Column('revision', String(40), nullable=False)
4027 revision = Column('revision', String(40), nullable=False)
4036 status = Column('status', String(128), nullable=False, default=DEFAULT)
4028 status = Column('status', String(128), nullable=False, default=DEFAULT)
4037 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4029 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4038 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4030 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4039 version = Column('version', Integer(), nullable=False, default=0)
4031 version = Column('version', Integer(), nullable=False, default=0)
4040 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4032 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4041
4033
4042 author = relationship('User', lazy='select')
4034 author = relationship('User', lazy='select')
4043 repo = relationship('Repository', lazy='select')
4035 repo = relationship('Repository', lazy='select')
4044 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4036 comment = relationship('ChangesetComment', lazy='select', back_populates='status_change')
4045 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4037 pull_request = relationship('PullRequest', lazy='select', back_populates='statuses')
4046
4038
4047 def __repr__(self):
4039 def __repr__(self):
4048 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4040 return f"<{self.cls_name}('{self.status}[v{self.version}]:{self.author}')>"
4049
4041
4050 @classmethod
4042 @classmethod
4051 def get_status_lbl(cls, value):
4043 def get_status_lbl(cls, value):
4052 return dict(cls.STATUSES).get(value)
4044 return dict(cls.STATUSES).get(value)
4053
4045
4054 @property
4046 @property
4055 def status_lbl(self):
4047 def status_lbl(self):
4056 return ChangesetStatus.get_status_lbl(self.status)
4048 return ChangesetStatus.get_status_lbl(self.status)
4057
4049
4058 def get_api_data(self):
4050 def get_api_data(self):
4059 status = self
4051 status = self
4060 data = {
4052 data = {
4061 'status_id': status.changeset_status_id,
4053 'status_id': status.changeset_status_id,
4062 'status': status.status,
4054 'status': status.status,
4063 }
4055 }
4064 return data
4056 return data
4065
4057
4066 def __json__(self):
4058 def __json__(self):
4067 data = dict()
4059 data = dict()
4068 data.update(self.get_api_data())
4060 data.update(self.get_api_data())
4069 return data
4061 return data
4070
4062
4071
4063
4072 class _SetState(object):
4064 class _SetState(object):
4073 """
4065 """
4074 Context processor allowing changing state for sensitive operation such as
4066 Context processor allowing changing state for sensitive operation such as
4075 pull request update or merge
4067 pull request update or merge
4076 """
4068 """
4077
4069
4078 def __init__(self, pull_request, pr_state, back_state=None):
4070 def __init__(self, pull_request, pr_state, back_state=None):
4079 self._pr = pull_request
4071 self._pr = pull_request
4080 self._org_state = back_state or pull_request.pull_request_state
4072 self._org_state = back_state or pull_request.pull_request_state
4081 self._pr_state = pr_state
4073 self._pr_state = pr_state
4082 self._current_state = None
4074 self._current_state = None
4083
4075
4084 def __enter__(self):
4076 def __enter__(self):
4085 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4077 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4086 self._pr, self._pr_state)
4078 self._pr, self._pr_state)
4087 self.set_pr_state(self._pr_state)
4079 self.set_pr_state(self._pr_state)
4088 return self
4080 return self
4089
4081
4090 def __exit__(self, exc_type, exc_val, exc_tb):
4082 def __exit__(self, exc_type, exc_val, exc_tb):
4091 if exc_val is not None or exc_type is not None:
4083 if exc_val is not None or exc_type is not None:
4092 log.error(traceback.format_tb(exc_tb))
4084 log.error(traceback.format_tb(exc_tb))
4093 return None
4085 return None
4094
4086
4095 self.set_pr_state(self._org_state)
4087 self.set_pr_state(self._org_state)
4096 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4088 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4097 self._pr, self._org_state)
4089 self._pr, self._org_state)
4098
4090
4099 @property
4091 @property
4100 def state(self):
4092 def state(self):
4101 return self._current_state
4093 return self._current_state
4102
4094
4103 def set_pr_state(self, pr_state):
4095 def set_pr_state(self, pr_state):
4104 try:
4096 try:
4105 self._pr.pull_request_state = pr_state
4097 self._pr.pull_request_state = pr_state
4106 Session().add(self._pr)
4098 Session().add(self._pr)
4107 Session().commit()
4099 Session().commit()
4108 self._current_state = pr_state
4100 self._current_state = pr_state
4109 except Exception:
4101 except Exception:
4110 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4102 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4111 raise
4103 raise
4112
4104
4113
4105
4114 class _PullRequestBase(BaseModel):
4106 class _PullRequestBase(BaseModel):
4115 """
4107 """
4116 Common attributes of pull request and version entries.
4108 Common attributes of pull request and version entries.
4117 """
4109 """
4118
4110
4119 # .status values
4111 # .status values
4120 STATUS_NEW = 'new'
4112 STATUS_NEW = 'new'
4121 STATUS_OPEN = 'open'
4113 STATUS_OPEN = 'open'
4122 STATUS_CLOSED = 'closed'
4114 STATUS_CLOSED = 'closed'
4123
4115
4124 # available states
4116 # available states
4125 STATE_CREATING = 'creating'
4117 STATE_CREATING = 'creating'
4126 STATE_UPDATING = 'updating'
4118 STATE_UPDATING = 'updating'
4127 STATE_MERGING = 'merging'
4119 STATE_MERGING = 'merging'
4128 STATE_CREATED = 'created'
4120 STATE_CREATED = 'created'
4129
4121
4130 title = Column('title', Unicode(255), nullable=True)
4122 title = Column('title', Unicode(255), nullable=True)
4131 description = Column(
4123 description = Column(
4132 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4124 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4133 nullable=True)
4125 nullable=True)
4134 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4126 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4135
4127
4136 # new/open/closed status of pull request (not approve/reject/etc)
4128 # new/open/closed status of pull request (not approve/reject/etc)
4137 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4129 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4138 created_on = Column(
4130 created_on = Column(
4139 'created_on', DateTime(timezone=False), nullable=False,
4131 'created_on', DateTime(timezone=False), nullable=False,
4140 default=datetime.datetime.now)
4132 default=datetime.datetime.now)
4141 updated_on = Column(
4133 updated_on = Column(
4142 'updated_on', DateTime(timezone=False), nullable=False,
4134 'updated_on', DateTime(timezone=False), nullable=False,
4143 default=datetime.datetime.now)
4135 default=datetime.datetime.now)
4144
4136
4145 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4137 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4146
4138
4147 @declared_attr
4139 @declared_attr
4148 def user_id(cls):
4140 def user_id(cls):
4149 return Column(
4141 return Column(
4150 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4142 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4151 unique=None)
4143 unique=None)
4152
4144
4153 # 500 revisions max
4145 # 500 revisions max
4154 _revisions = Column(
4146 _revisions = Column(
4155 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4147 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4156
4148
4157 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4149 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4158
4150
4159 @declared_attr
4151 @declared_attr
4160 def source_repo_id(cls):
4152 def source_repo_id(cls):
4161 # TODO: dan: rename column to source_repo_id
4153 # TODO: dan: rename column to source_repo_id
4162 return Column(
4154 return Column(
4163 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4155 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4164 nullable=False)
4156 nullable=False)
4165
4157
4166 @declared_attr
4158 @declared_attr
4167 def pr_source(cls):
4159 def pr_source(cls):
4168 return relationship(
4160 return relationship(
4169 'Repository',
4161 'Repository',
4170 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4162 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4171 overlaps="pull_requests_source"
4163 overlaps="pull_requests_source"
4172 )
4164 )
4173
4165
4174 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4166 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4175
4167
4176 @hybrid_property
4168 @hybrid_property
4177 def source_ref(self):
4169 def source_ref(self):
4178 return self._source_ref
4170 return self._source_ref
4179
4171
4180 @source_ref.setter
4172 @source_ref.setter
4181 def source_ref(self, val):
4173 def source_ref(self, val):
4182 parts = (val or '').split(':')
4174 parts = (val or '').split(':')
4183 if len(parts) != 3:
4175 if len(parts) != 3:
4184 raise ValueError(
4176 raise ValueError(
4185 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4177 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4186 self._source_ref = safe_str(val)
4178 self._source_ref = safe_str(val)
4187
4179
4188 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4180 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4189
4181
4190 @hybrid_property
4182 @hybrid_property
4191 def target_ref(self):
4183 def target_ref(self):
4192 return self._target_ref
4184 return self._target_ref
4193
4185
4194 @target_ref.setter
4186 @target_ref.setter
4195 def target_ref(self, val):
4187 def target_ref(self, val):
4196 parts = (val or '').split(':')
4188 parts = (val or '').split(':')
4197 if len(parts) != 3:
4189 if len(parts) != 3:
4198 raise ValueError(
4190 raise ValueError(
4199 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4191 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4200 self._target_ref = safe_str(val)
4192 self._target_ref = safe_str(val)
4201
4193
4202 @declared_attr
4194 @declared_attr
4203 def target_repo_id(cls):
4195 def target_repo_id(cls):
4204 # TODO: dan: rename column to target_repo_id
4196 # TODO: dan: rename column to target_repo_id
4205 return Column(
4197 return Column(
4206 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4198 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4207 nullable=False)
4199 nullable=False)
4208
4200
4209 @declared_attr
4201 @declared_attr
4210 def pr_target(cls):
4202 def pr_target(cls):
4211 return relationship(
4203 return relationship(
4212 'Repository',
4204 'Repository',
4213 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4205 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id',
4214 overlaps="pull_requests_target"
4206 overlaps="pull_requests_target"
4215 )
4207 )
4216
4208
4217 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4209 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4218
4210
4219 # TODO: dan: rename column to last_merge_source_rev
4211 # TODO: dan: rename column to last_merge_source_rev
4220 _last_merge_source_rev = Column(
4212 _last_merge_source_rev = Column(
4221 'last_merge_org_rev', String(40), nullable=True)
4213 'last_merge_org_rev', String(40), nullable=True)
4222 # TODO: dan: rename column to last_merge_target_rev
4214 # TODO: dan: rename column to last_merge_target_rev
4223 _last_merge_target_rev = Column(
4215 _last_merge_target_rev = Column(
4224 'last_merge_other_rev', String(40), nullable=True)
4216 'last_merge_other_rev', String(40), nullable=True)
4225 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4217 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4226 last_merge_metadata = Column(
4218 last_merge_metadata = Column(
4227 'last_merge_metadata', MutationObj.as_mutable(
4219 'last_merge_metadata', MutationObj.as_mutable(
4228 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4220 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4229
4221
4230 merge_rev = Column('merge_rev', String(40), nullable=True)
4222 merge_rev = Column('merge_rev', String(40), nullable=True)
4231
4223
4232 reviewer_data = Column(
4224 reviewer_data = Column(
4233 'reviewer_data_json', MutationObj.as_mutable(
4225 'reviewer_data_json', MutationObj.as_mutable(
4234 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4226 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4235
4227
4236 @property
4228 @property
4237 def reviewer_data_json(self):
4229 def reviewer_data_json(self):
4238 return str_json(self.reviewer_data)
4230 return str_json(self.reviewer_data)
4239
4231
4240 @property
4232 @property
4241 def last_merge_metadata_parsed(self):
4233 def last_merge_metadata_parsed(self):
4242 metadata = {}
4234 metadata = {}
4243 if not self.last_merge_metadata:
4235 if not self.last_merge_metadata:
4244 return metadata
4236 return metadata
4245
4237
4246 if hasattr(self.last_merge_metadata, 'de_coerce'):
4238 if hasattr(self.last_merge_metadata, 'de_coerce'):
4247 for k, v in self.last_merge_metadata.de_coerce().items():
4239 for k, v in self.last_merge_metadata.de_coerce().items():
4248 if k in ['target_ref', 'source_ref']:
4240 if k in ['target_ref', 'source_ref']:
4249 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4241 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4250 else:
4242 else:
4251 if hasattr(v, 'de_coerce'):
4243 if hasattr(v, 'de_coerce'):
4252 metadata[k] = v.de_coerce()
4244 metadata[k] = v.de_coerce()
4253 else:
4245 else:
4254 metadata[k] = v
4246 metadata[k] = v
4255 return metadata
4247 return metadata
4256
4248
4257 @property
4249 @property
4258 def work_in_progress(self):
4250 def work_in_progress(self):
4259 """checks if pull request is work in progress by checking the title"""
4251 """checks if pull request is work in progress by checking the title"""
4260 title = self.title.upper()
4252 title = self.title.upper()
4261 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4253 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4262 return True
4254 return True
4263 return False
4255 return False
4264
4256
4265 @property
4257 @property
4266 def title_safe(self):
4258 def title_safe(self):
4267 return self.title\
4259 return self.title\
4268 .replace('{', '{{')\
4260 .replace('{', '{{')\
4269 .replace('}', '}}')
4261 .replace('}', '}}')
4270
4262
4271 @hybrid_property
4263 @hybrid_property
4272 def description_safe(self):
4264 def description_safe(self):
4273 from rhodecode.lib import helpers as h
4265 from rhodecode.lib import helpers as h
4274 return h.escape(self.description)
4266 return h.escape(self.description)
4275
4267
4276 @hybrid_property
4268 @hybrid_property
4277 def revisions(self):
4269 def revisions(self):
4278 return self._revisions.split(':') if self._revisions else []
4270 return self._revisions.split(':') if self._revisions else []
4279
4271
4280 @revisions.setter
4272 @revisions.setter
4281 def revisions(self, val):
4273 def revisions(self, val):
4282 self._revisions = ':'.join(val)
4274 self._revisions = ':'.join(val)
4283
4275
4284 @hybrid_property
4276 @hybrid_property
4285 def last_merge_status(self):
4277 def last_merge_status(self):
4286 return safe_int(self._last_merge_status)
4278 return safe_int(self._last_merge_status)
4287
4279
4288 @last_merge_status.setter
4280 @last_merge_status.setter
4289 def last_merge_status(self, val):
4281 def last_merge_status(self, val):
4290 self._last_merge_status = val
4282 self._last_merge_status = val
4291
4283
4292 @declared_attr
4284 @declared_attr
4293 def author(cls):
4285 def author(cls):
4294 return relationship(
4286 return relationship(
4295 'User', lazy='joined',
4287 'User', lazy='joined',
4296 #TODO, problem that is somehow :?
4288 #TODO, problem that is somehow :?
4297 #back_populates='user_pull_requests'
4289 #back_populates='user_pull_requests'
4298 )
4290 )
4299
4291
4300 @declared_attr
4292 @declared_attr
4301 def source_repo(cls):
4293 def source_repo(cls):
4302 return relationship(
4294 return relationship(
4303 'Repository',
4295 'Repository',
4304 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4296 primaryjoin=f'{cls.__name__}.source_repo_id==Repository.repo_id',
4305 #back_populates=''
4297 #back_populates=''
4306 )
4298 )
4307
4299
4308 @property
4300 @property
4309 def source_ref_parts(self):
4301 def source_ref_parts(self):
4310 return self.unicode_to_reference(self.source_ref)
4302 return self.unicode_to_reference(self.source_ref)
4311
4303
4312 @declared_attr
4304 @declared_attr
4313 def target_repo(cls):
4305 def target_repo(cls):
4314 return relationship(
4306 return relationship(
4315 'Repository',
4307 'Repository',
4316 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id'
4308 primaryjoin=f'{cls.__name__}.target_repo_id==Repository.repo_id'
4317 )
4309 )
4318
4310
4319 @property
4311 @property
4320 def target_ref_parts(self):
4312 def target_ref_parts(self):
4321 return self.unicode_to_reference(self.target_ref)
4313 return self.unicode_to_reference(self.target_ref)
4322
4314
4323 @property
4315 @property
4324 def shadow_merge_ref(self):
4316 def shadow_merge_ref(self):
4325 return self.unicode_to_reference(self._shadow_merge_ref)
4317 return self.unicode_to_reference(self._shadow_merge_ref)
4326
4318
4327 @shadow_merge_ref.setter
4319 @shadow_merge_ref.setter
4328 def shadow_merge_ref(self, ref):
4320 def shadow_merge_ref(self, ref):
4329 self._shadow_merge_ref = self.reference_to_unicode(ref)
4321 self._shadow_merge_ref = self.reference_to_unicode(ref)
4330
4322
4331 @staticmethod
4323 @staticmethod
4332 def unicode_to_reference(raw):
4324 def unicode_to_reference(raw):
4333 return unicode_to_reference(raw)
4325 return unicode_to_reference(raw)
4334
4326
4335 @staticmethod
4327 @staticmethod
4336 def reference_to_unicode(ref):
4328 def reference_to_unicode(ref):
4337 return reference_to_unicode(ref)
4329 return reference_to_unicode(ref)
4338
4330
4339 def get_api_data(self, with_merge_state=True):
4331 def get_api_data(self, with_merge_state=True):
4340 from rhodecode.model.pull_request import PullRequestModel
4332 from rhodecode.model.pull_request import PullRequestModel
4341
4333
4342 pull_request = self
4334 pull_request = self
4343 if with_merge_state:
4335 if with_merge_state:
4344 merge_response, merge_status, msg = \
4336 merge_response, merge_status, msg = \
4345 PullRequestModel().merge_status(pull_request)
4337 PullRequestModel().merge_status(pull_request)
4346 merge_state = {
4338 merge_state = {
4347 'status': merge_status,
4339 'status': merge_status,
4348 'message': safe_str(msg),
4340 'message': safe_str(msg),
4349 }
4341 }
4350 else:
4342 else:
4351 merge_state = {'status': 'not_available',
4343 merge_state = {'status': 'not_available',
4352 'message': 'not_available'}
4344 'message': 'not_available'}
4353
4345
4354 merge_data = {
4346 merge_data = {
4355 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4347 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4356 'reference': (
4348 'reference': (
4357 pull_request.shadow_merge_ref.asdict()
4349 pull_request.shadow_merge_ref.asdict()
4358 if pull_request.shadow_merge_ref else None),
4350 if pull_request.shadow_merge_ref else None),
4359 }
4351 }
4360
4352
4361 data = {
4353 data = {
4362 'pull_request_id': pull_request.pull_request_id,
4354 'pull_request_id': pull_request.pull_request_id,
4363 'url': PullRequestModel().get_url(pull_request),
4355 'url': PullRequestModel().get_url(pull_request),
4364 'title': pull_request.title,
4356 'title': pull_request.title,
4365 'description': pull_request.description,
4357 'description': pull_request.description,
4366 'status': pull_request.status,
4358 'status': pull_request.status,
4367 'state': pull_request.pull_request_state,
4359 'state': pull_request.pull_request_state,
4368 'created_on': pull_request.created_on,
4360 'created_on': pull_request.created_on,
4369 'updated_on': pull_request.updated_on,
4361 'updated_on': pull_request.updated_on,
4370 'commit_ids': pull_request.revisions,
4362 'commit_ids': pull_request.revisions,
4371 'review_status': pull_request.calculated_review_status(),
4363 'review_status': pull_request.calculated_review_status(),
4372 'mergeable': merge_state,
4364 'mergeable': merge_state,
4373 'source': {
4365 'source': {
4374 'clone_url': pull_request.source_repo.clone_url(),
4366 'clone_url': pull_request.source_repo.clone_url(),
4375 'repository': pull_request.source_repo.repo_name,
4367 'repository': pull_request.source_repo.repo_name,
4376 'reference': {
4368 'reference': {
4377 'name': pull_request.source_ref_parts.name,
4369 'name': pull_request.source_ref_parts.name,
4378 'type': pull_request.source_ref_parts.type,
4370 'type': pull_request.source_ref_parts.type,
4379 'commit_id': pull_request.source_ref_parts.commit_id,
4371 'commit_id': pull_request.source_ref_parts.commit_id,
4380 },
4372 },
4381 },
4373 },
4382 'target': {
4374 'target': {
4383 'clone_url': pull_request.target_repo.clone_url(),
4375 'clone_url': pull_request.target_repo.clone_url(),
4384 'repository': pull_request.target_repo.repo_name,
4376 'repository': pull_request.target_repo.repo_name,
4385 'reference': {
4377 'reference': {
4386 'name': pull_request.target_ref_parts.name,
4378 'name': pull_request.target_ref_parts.name,
4387 'type': pull_request.target_ref_parts.type,
4379 'type': pull_request.target_ref_parts.type,
4388 'commit_id': pull_request.target_ref_parts.commit_id,
4380 'commit_id': pull_request.target_ref_parts.commit_id,
4389 },
4381 },
4390 },
4382 },
4391 'merge': merge_data,
4383 'merge': merge_data,
4392 'author': pull_request.author.get_api_data(include_secrets=False,
4384 'author': pull_request.author.get_api_data(include_secrets=False,
4393 details='basic'),
4385 details='basic'),
4394 'reviewers': [
4386 'reviewers': [
4395 {
4387 {
4396 'user': reviewer.get_api_data(include_secrets=False,
4388 'user': reviewer.get_api_data(include_secrets=False,
4397 details='basic'),
4389 details='basic'),
4398 'reasons': reasons,
4390 'reasons': reasons,
4399 'review_status': st[0][1].status if st else 'not_reviewed',
4391 'review_status': st[0][1].status if st else 'not_reviewed',
4400 }
4392 }
4401 for obj, reviewer, reasons, mandatory, st in
4393 for obj, reviewer, reasons, mandatory, st in
4402 pull_request.reviewers_statuses()
4394 pull_request.reviewers_statuses()
4403 ]
4395 ]
4404 }
4396 }
4405
4397
4406 return data
4398 return data
4407
4399
4408 def set_state(self, pull_request_state, final_state=None):
4400 def set_state(self, pull_request_state, final_state=None):
4409 """
4401 """
4410 # goes from initial state to updating to initial state.
4402 # goes from initial state to updating to initial state.
4411 # initial state can be changed by specifying back_state=
4403 # initial state can be changed by specifying back_state=
4412 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4404 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4413 pull_request.merge()
4405 pull_request.merge()
4414
4406
4415 :param pull_request_state:
4407 :param pull_request_state:
4416 :param final_state:
4408 :param final_state:
4417
4409
4418 """
4410 """
4419
4411
4420 return _SetState(self, pull_request_state, back_state=final_state)
4412 return _SetState(self, pull_request_state, back_state=final_state)
4421
4413
4422
4414
4423 class PullRequest(Base, _PullRequestBase):
4415 class PullRequest(Base, _PullRequestBase):
4424 __tablename__ = 'pull_requests'
4416 __tablename__ = 'pull_requests'
4425 __table_args__ = (
4417 __table_args__ = (
4426 base_table_args,
4418 base_table_args,
4427 )
4419 )
4428 LATEST_VER = 'latest'
4420 LATEST_VER = 'latest'
4429
4421
4430 pull_request_id = Column(
4422 pull_request_id = Column(
4431 'pull_request_id', Integer(), nullable=False, primary_key=True)
4423 'pull_request_id', Integer(), nullable=False, primary_key=True)
4432
4424
4433 def __repr__(self):
4425 def __repr__(self):
4434 if self.pull_request_id:
4426 if self.pull_request_id:
4435 return f'<DB:PullRequest #{self.pull_request_id}>'
4427 return f'<DB:PullRequest #{self.pull_request_id}>'
4436 else:
4428 else:
4437 return f'<DB:PullRequest at {id(self)!r}>'
4429 return f'<DB:PullRequest at {id(self)!r}>'
4438
4430
4439 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4431 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
4440 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4432 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
4441 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4433 comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
4442 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4434 versions = relationship('PullRequestVersion', cascade="all, delete-orphan", lazy='dynamic', back_populates='pull_request')
4443
4435
4444 @classmethod
4436 @classmethod
4445 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4437 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4446 internal_methods=None):
4438 internal_methods=None):
4447
4439
4448 class PullRequestDisplay(object):
4440 class PullRequestDisplay(object):
4449 """
4441 """
4450 Special object wrapper for showing PullRequest data via Versions
4442 Special object wrapper for showing PullRequest data via Versions
4451 It mimics PR object as close as possible. This is read only object
4443 It mimics PR object as close as possible. This is read only object
4452 just for display
4444 just for display
4453 """
4445 """
4454
4446
4455 def __init__(self, attrs, internal=None):
4447 def __init__(self, attrs, internal=None):
4456 self.attrs = attrs
4448 self.attrs = attrs
4457 # internal have priority over the given ones via attrs
4449 # internal have priority over the given ones via attrs
4458 self.internal = internal or ['versions']
4450 self.internal = internal or ['versions']
4459
4451
4460 def __getattr__(self, item):
4452 def __getattr__(self, item):
4461 if item in self.internal:
4453 if item in self.internal:
4462 return getattr(self, item)
4454 return getattr(self, item)
4463 try:
4455 try:
4464 return self.attrs[item]
4456 return self.attrs[item]
4465 except KeyError:
4457 except KeyError:
4466 raise AttributeError(
4458 raise AttributeError(
4467 '%s object has no attribute %s' % (self, item))
4459 '%s object has no attribute %s' % (self, item))
4468
4460
4469 def __repr__(self):
4461 def __repr__(self):
4470 pr_id = self.attrs.get('pull_request_id')
4462 pr_id = self.attrs.get('pull_request_id')
4471 return f'<DB:PullRequestDisplay #{pr_id}>'
4463 return f'<DB:PullRequestDisplay #{pr_id}>'
4472
4464
4473 def versions(self):
4465 def versions(self):
4474 return pull_request_obj.versions.order_by(
4466 return pull_request_obj.versions.order_by(
4475 PullRequestVersion.pull_request_version_id).all()
4467 PullRequestVersion.pull_request_version_id).all()
4476
4468
4477 def is_closed(self):
4469 def is_closed(self):
4478 return pull_request_obj.is_closed()
4470 return pull_request_obj.is_closed()
4479
4471
4480 def is_state_changing(self):
4472 def is_state_changing(self):
4481 return pull_request_obj.is_state_changing()
4473 return pull_request_obj.is_state_changing()
4482
4474
4483 @property
4475 @property
4484 def pull_request_version_id(self):
4476 def pull_request_version_id(self):
4485 return getattr(pull_request_obj, 'pull_request_version_id', None)
4477 return getattr(pull_request_obj, 'pull_request_version_id', None)
4486
4478
4487 @property
4479 @property
4488 def pull_request_last_version(self):
4480 def pull_request_last_version(self):
4489 return pull_request_obj.pull_request_last_version
4481 return pull_request_obj.pull_request_last_version
4490
4482
4491 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4483 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4492
4484
4493 attrs.author = StrictAttributeDict(
4485 attrs.author = StrictAttributeDict(
4494 pull_request_obj.author.get_api_data())
4486 pull_request_obj.author.get_api_data())
4495 if pull_request_obj.target_repo:
4487 if pull_request_obj.target_repo:
4496 attrs.target_repo = StrictAttributeDict(
4488 attrs.target_repo = StrictAttributeDict(
4497 pull_request_obj.target_repo.get_api_data())
4489 pull_request_obj.target_repo.get_api_data())
4498 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4490 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4499
4491
4500 if pull_request_obj.source_repo:
4492 if pull_request_obj.source_repo:
4501 attrs.source_repo = StrictAttributeDict(
4493 attrs.source_repo = StrictAttributeDict(
4502 pull_request_obj.source_repo.get_api_data())
4494 pull_request_obj.source_repo.get_api_data())
4503 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4495 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4504
4496
4505 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4497 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4506 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4498 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4507 attrs.revisions = pull_request_obj.revisions
4499 attrs.revisions = pull_request_obj.revisions
4508 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4500 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4509 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4501 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4510 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4502 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4511 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4503 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4512
4504
4513 return PullRequestDisplay(attrs, internal=internal_methods)
4505 return PullRequestDisplay(attrs, internal=internal_methods)
4514
4506
4515 def is_closed(self):
4507 def is_closed(self):
4516 return self.status == self.STATUS_CLOSED
4508 return self.status == self.STATUS_CLOSED
4517
4509
4518 def is_state_changing(self):
4510 def is_state_changing(self):
4519 return self.pull_request_state != PullRequest.STATE_CREATED
4511 return self.pull_request_state != PullRequest.STATE_CREATED
4520
4512
4521 def __json__(self):
4513 def __json__(self):
4522 return {
4514 return {
4523 'revisions': self.revisions,
4515 'revisions': self.revisions,
4524 'versions': self.versions_count
4516 'versions': self.versions_count
4525 }
4517 }
4526
4518
4527 def calculated_review_status(self):
4519 def calculated_review_status(self):
4528 from rhodecode.model.changeset_status import ChangesetStatusModel
4520 from rhodecode.model.changeset_status import ChangesetStatusModel
4529 return ChangesetStatusModel().calculated_review_status(self)
4521 return ChangesetStatusModel().calculated_review_status(self)
4530
4522
4531 def reviewers_statuses(self, user=None):
4523 def reviewers_statuses(self, user=None):
4532 from rhodecode.model.changeset_status import ChangesetStatusModel
4524 from rhodecode.model.changeset_status import ChangesetStatusModel
4533 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4525 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4534
4526
4535 def get_pull_request_reviewers(self, role=None):
4527 def get_pull_request_reviewers(self, role=None):
4536 qry = PullRequestReviewers.query()\
4528 qry = PullRequestReviewers.query()\
4537 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4529 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4538 if role:
4530 if role:
4539 qry = qry.filter(PullRequestReviewers.role == role)
4531 qry = qry.filter(PullRequestReviewers.role == role)
4540
4532
4541 return qry.all()
4533 return qry.all()
4542
4534
4543 @property
4535 @property
4544 def reviewers_count(self):
4536 def reviewers_count(self):
4545 qry = PullRequestReviewers.query()\
4537 qry = PullRequestReviewers.query()\
4546 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4538 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4547 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4539 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4548 return qry.count()
4540 return qry.count()
4549
4541
4550 @property
4542 @property
4551 def observers_count(self):
4543 def observers_count(self):
4552 qry = PullRequestReviewers.query()\
4544 qry = PullRequestReviewers.query()\
4553 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4545 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4554 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4546 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4555 return qry.count()
4547 return qry.count()
4556
4548
4557 def observers(self):
4549 def observers(self):
4558 qry = PullRequestReviewers.query()\
4550 qry = PullRequestReviewers.query()\
4559 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4551 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4560 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4552 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4561 .all()
4553 .all()
4562
4554
4563 for entry in qry:
4555 for entry in qry:
4564 yield entry, entry.user
4556 yield entry, entry.user
4565
4557
4566 @property
4558 @property
4567 def workspace_id(self):
4559 def workspace_id(self):
4568 from rhodecode.model.pull_request import PullRequestModel
4560 from rhodecode.model.pull_request import PullRequestModel
4569 return PullRequestModel()._workspace_id(self)
4561 return PullRequestModel()._workspace_id(self)
4570
4562
4571 def get_shadow_repo(self):
4563 def get_shadow_repo(self):
4572 workspace_id = self.workspace_id
4564 workspace_id = self.workspace_id
4573 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4565 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4574 if os.path.isdir(shadow_repository_path):
4566 if os.path.isdir(shadow_repository_path):
4575 vcs_obj = self.target_repo.scm_instance()
4567 vcs_obj = self.target_repo.scm_instance()
4576 return vcs_obj.get_shadow_instance(shadow_repository_path)
4568 return vcs_obj.get_shadow_instance(shadow_repository_path)
4577
4569
4578 @property
4570 @property
4579 def versions_count(self):
4571 def versions_count(self):
4580 """
4572 """
4581 return number of versions this PR have, e.g a PR that once been
4573 return number of versions this PR have, e.g a PR that once been
4582 updated will have 2 versions
4574 updated will have 2 versions
4583 """
4575 """
4584 return self.versions.count() + 1
4576 return self.versions.count() + 1
4585
4577
4586 @property
4578 @property
4587 def pull_request_last_version(self):
4579 def pull_request_last_version(self):
4588 return self.versions_count
4580 return self.versions_count
4589
4581
4590
4582
4591 class PullRequestVersion(Base, _PullRequestBase):
4583 class PullRequestVersion(Base, _PullRequestBase):
4592 __tablename__ = 'pull_request_versions'
4584 __tablename__ = 'pull_request_versions'
4593 __table_args__ = (
4585 __table_args__ = (
4594 base_table_args,
4586 base_table_args,
4595 )
4587 )
4596
4588
4597 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4589 pull_request_version_id = Column('pull_request_version_id', Integer(), nullable=False, primary_key=True)
4598 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4590 pull_request_id = Column('pull_request_id', Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
4599 pull_request = relationship('PullRequest', back_populates='versions')
4591 pull_request = relationship('PullRequest', back_populates='versions')
4600
4592
4601 def __repr__(self):
4593 def __repr__(self):
4602 if self.pull_request_version_id:
4594 if self.pull_request_version_id:
4603 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4595 return f'<DB:PullRequestVersion #{self.pull_request_version_id}>'
4604 else:
4596 else:
4605 return f'<DB:PullRequestVersion at {id(self)!r}>'
4597 return f'<DB:PullRequestVersion at {id(self)!r}>'
4606
4598
4607 @property
4599 @property
4608 def reviewers(self):
4600 def reviewers(self):
4609 return self.pull_request.reviewers
4601 return self.pull_request.reviewers
4610
4602
4611 @property
4603 @property
4612 def versions(self):
4604 def versions(self):
4613 return self.pull_request.versions
4605 return self.pull_request.versions
4614
4606
4615 def is_closed(self):
4607 def is_closed(self):
4616 # calculate from original
4608 # calculate from original
4617 return self.pull_request.status == self.STATUS_CLOSED
4609 return self.pull_request.status == self.STATUS_CLOSED
4618
4610
4619 def is_state_changing(self):
4611 def is_state_changing(self):
4620 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4612 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4621
4613
4622 def calculated_review_status(self):
4614 def calculated_review_status(self):
4623 return self.pull_request.calculated_review_status()
4615 return self.pull_request.calculated_review_status()
4624
4616
4625 def reviewers_statuses(self):
4617 def reviewers_statuses(self):
4626 return self.pull_request.reviewers_statuses()
4618 return self.pull_request.reviewers_statuses()
4627
4619
4628 def observers(self):
4620 def observers(self):
4629 return self.pull_request.observers()
4621 return self.pull_request.observers()
4630
4622
4631
4623
4632 class PullRequestReviewers(Base, BaseModel):
4624 class PullRequestReviewers(Base, BaseModel):
4633 __tablename__ = 'pull_request_reviewers'
4625 __tablename__ = 'pull_request_reviewers'
4634 __table_args__ = (
4626 __table_args__ = (
4635 base_table_args,
4627 base_table_args,
4636 )
4628 )
4637 ROLE_REVIEWER = 'reviewer'
4629 ROLE_REVIEWER = 'reviewer'
4638 ROLE_OBSERVER = 'observer'
4630 ROLE_OBSERVER = 'observer'
4639 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4631 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4640
4632
4641 @hybrid_property
4633 @hybrid_property
4642 def reasons(self):
4634 def reasons(self):
4643 if not self._reasons:
4635 if not self._reasons:
4644 return []
4636 return []
4645 return self._reasons
4637 return self._reasons
4646
4638
4647 @reasons.setter
4639 @reasons.setter
4648 def reasons(self, val):
4640 def reasons(self, val):
4649 val = val or []
4641 val = val or []
4650 if any(not isinstance(x, str) for x in val):
4642 if any(not isinstance(x, str) for x in val):
4651 raise Exception('invalid reasons type, must be list of strings')
4643 raise Exception('invalid reasons type, must be list of strings')
4652 self._reasons = val
4644 self._reasons = val
4653
4645
4654 pull_requests_reviewers_id = Column(
4646 pull_requests_reviewers_id = Column(
4655 'pull_requests_reviewers_id', Integer(), nullable=False,
4647 'pull_requests_reviewers_id', Integer(), nullable=False,
4656 primary_key=True)
4648 primary_key=True)
4657 pull_request_id = Column(
4649 pull_request_id = Column(
4658 "pull_request_id", Integer(),
4650 "pull_request_id", Integer(),
4659 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4651 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4660 user_id = Column(
4652 user_id = Column(
4661 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4653 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4662 _reasons = Column(
4654 _reasons = Column(
4663 'reason', MutationList.as_mutable(
4655 'reason', MutationList.as_mutable(
4664 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4656 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4665
4657
4666 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4658 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4667 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4659 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4668
4660
4669 user = relationship('User')
4661 user = relationship('User')
4670 pull_request = relationship('PullRequest', back_populates='reviewers')
4662 pull_request = relationship('PullRequest', back_populates='reviewers')
4671
4663
4672 rule_data = Column(
4664 rule_data = Column(
4673 'rule_data_json',
4665 'rule_data_json',
4674 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4666 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4675
4667
4676 def rule_user_group_data(self):
4668 def rule_user_group_data(self):
4677 """
4669 """
4678 Returns the voting user group rule data for this reviewer
4670 Returns the voting user group rule data for this reviewer
4679 """
4671 """
4680
4672
4681 if self.rule_data and 'vote_rule' in self.rule_data:
4673 if self.rule_data and 'vote_rule' in self.rule_data:
4682 user_group_data = {}
4674 user_group_data = {}
4683 if 'rule_user_group_entry_id' in self.rule_data:
4675 if 'rule_user_group_entry_id' in self.rule_data:
4684 # means a group with voting rules !
4676 # means a group with voting rules !
4685 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4677 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4686 user_group_data['name'] = self.rule_data['rule_name']
4678 user_group_data['name'] = self.rule_data['rule_name']
4687 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4679 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4688
4680
4689 return user_group_data
4681 return user_group_data
4690
4682
4691 @classmethod
4683 @classmethod
4692 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4684 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4693 qry = PullRequestReviewers.query()\
4685 qry = PullRequestReviewers.query()\
4694 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4686 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4695 if role:
4687 if role:
4696 qry = qry.filter(PullRequestReviewers.role == role)
4688 qry = qry.filter(PullRequestReviewers.role == role)
4697
4689
4698 return qry.all()
4690 return qry.all()
4699
4691
4700 def __repr__(self):
4692 def __repr__(self):
4701 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4693 return f"<{self.cls_name}('id:{self.pull_requests_reviewers_id}')>"
4702
4694
4703
4695
4704 class Notification(Base, BaseModel):
4696 class Notification(Base, BaseModel):
4705 __tablename__ = 'notifications'
4697 __tablename__ = 'notifications'
4706 __table_args__ = (
4698 __table_args__ = (
4707 Index('notification_type_idx', 'type'),
4699 Index('notification_type_idx', 'type'),
4708 base_table_args,
4700 base_table_args,
4709 )
4701 )
4710
4702
4711 TYPE_CHANGESET_COMMENT = 'cs_comment'
4703 TYPE_CHANGESET_COMMENT = 'cs_comment'
4712 TYPE_MESSAGE = 'message'
4704 TYPE_MESSAGE = 'message'
4713 TYPE_MENTION = 'mention'
4705 TYPE_MENTION = 'mention'
4714 TYPE_REGISTRATION = 'registration'
4706 TYPE_REGISTRATION = 'registration'
4715 TYPE_PULL_REQUEST = 'pull_request'
4707 TYPE_PULL_REQUEST = 'pull_request'
4716 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4708 TYPE_PULL_REQUEST_COMMENT = 'pull_request_comment'
4717 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4709 TYPE_PULL_REQUEST_UPDATE = 'pull_request_update'
4718
4710
4719 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4711 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4720 subject = Column('subject', Unicode(512), nullable=True)
4712 subject = Column('subject', Unicode(512), nullable=True)
4721 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4713 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4722 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4714 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4723 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4715 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4724 type_ = Column('type', Unicode(255))
4716 type_ = Column('type', Unicode(255))
4725
4717
4726 created_by_user = relationship('User', back_populates='user_created_notifications')
4718 created_by_user = relationship('User', back_populates='user_created_notifications')
4727 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4719 notifications_to_users = relationship('UserNotification', lazy='joined', cascade="all, delete-orphan", back_populates='notification')
4728
4720
4729 @property
4721 @property
4730 def recipients(self):
4722 def recipients(self):
4731 return [x.user for x in UserNotification.query()\
4723 return [x.user for x in UserNotification.query()\
4732 .filter(UserNotification.notification == self)\
4724 .filter(UserNotification.notification == self)\
4733 .order_by(UserNotification.user_id.asc()).all()]
4725 .order_by(UserNotification.user_id.asc()).all()]
4734
4726
4735 @classmethod
4727 @classmethod
4736 def create(cls, created_by, subject, body, recipients, type_=None):
4728 def create(cls, created_by, subject, body, recipients, type_=None):
4737 if type_ is None:
4729 if type_ is None:
4738 type_ = Notification.TYPE_MESSAGE
4730 type_ = Notification.TYPE_MESSAGE
4739
4731
4740 notification = cls()
4732 notification = cls()
4741 notification.created_by_user = created_by
4733 notification.created_by_user = created_by
4742 notification.subject = subject
4734 notification.subject = subject
4743 notification.body = body
4735 notification.body = body
4744 notification.type_ = type_
4736 notification.type_ = type_
4745 notification.created_on = datetime.datetime.now()
4737 notification.created_on = datetime.datetime.now()
4746
4738
4747 # For each recipient link the created notification to his account
4739 # For each recipient link the created notification to his account
4748 for u in recipients:
4740 for u in recipients:
4749 assoc = UserNotification()
4741 assoc = UserNotification()
4750 assoc.user_id = u.user_id
4742 assoc.user_id = u.user_id
4751 assoc.notification = notification
4743 assoc.notification = notification
4752
4744
4753 # if created_by is inside recipients mark his notification
4745 # if created_by is inside recipients mark his notification
4754 # as read
4746 # as read
4755 if u.user_id == created_by.user_id:
4747 if u.user_id == created_by.user_id:
4756 assoc.read = True
4748 assoc.read = True
4757 Session().add(assoc)
4749 Session().add(assoc)
4758
4750
4759 Session().add(notification)
4751 Session().add(notification)
4760
4752
4761 return notification
4753 return notification
4762
4754
4763
4755
4764 class UserNotification(Base, BaseModel):
4756 class UserNotification(Base, BaseModel):
4765 __tablename__ = 'user_to_notification'
4757 __tablename__ = 'user_to_notification'
4766 __table_args__ = (
4758 __table_args__ = (
4767 UniqueConstraint('user_id', 'notification_id'),
4759 UniqueConstraint('user_id', 'notification_id'),
4768 base_table_args
4760 base_table_args
4769 )
4761 )
4770
4762
4771 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4763 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4772 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4764 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4773 read = Column('read', Boolean, default=False)
4765 read = Column('read', Boolean, default=False)
4774 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4766 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4775
4767
4776 user = relationship('User', lazy="joined", back_populates='notifications')
4768 user = relationship('User', lazy="joined", back_populates='notifications')
4777 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4769 notification = relationship('Notification', lazy="joined", order_by=lambda: Notification.created_on.desc(), back_populates='notifications_to_users')
4778
4770
4779 def mark_as_read(self):
4771 def mark_as_read(self):
4780 self.read = True
4772 self.read = True
4781 Session().add(self)
4773 Session().add(self)
4782
4774
4783
4775
4784 class UserNotice(Base, BaseModel):
4776 class UserNotice(Base, BaseModel):
4785 __tablename__ = 'user_notices'
4777 __tablename__ = 'user_notices'
4786 __table_args__ = (
4778 __table_args__ = (
4787 base_table_args
4779 base_table_args
4788 )
4780 )
4789
4781
4790 NOTIFICATION_TYPE_MESSAGE = 'message'
4782 NOTIFICATION_TYPE_MESSAGE = 'message'
4791 NOTIFICATION_TYPE_NOTICE = 'notice'
4783 NOTIFICATION_TYPE_NOTICE = 'notice'
4792
4784
4793 NOTIFICATION_LEVEL_INFO = 'info'
4785 NOTIFICATION_LEVEL_INFO = 'info'
4794 NOTIFICATION_LEVEL_WARNING = 'warning'
4786 NOTIFICATION_LEVEL_WARNING = 'warning'
4795 NOTIFICATION_LEVEL_ERROR = 'error'
4787 NOTIFICATION_LEVEL_ERROR = 'error'
4796
4788
4797 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4789 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4798
4790
4799 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4791 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4800 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4792 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4801
4793
4802 notice_read = Column('notice_read', Boolean, default=False)
4794 notice_read = Column('notice_read', Boolean, default=False)
4803
4795
4804 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4796 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4805 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4797 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4806
4798
4807 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4799 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4808 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4800 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4809
4801
4810 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4802 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4811 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4803 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4812
4804
4813 @classmethod
4805 @classmethod
4814 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4806 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4815
4807
4816 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4808 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4817 cls.NOTIFICATION_LEVEL_WARNING,
4809 cls.NOTIFICATION_LEVEL_WARNING,
4818 cls.NOTIFICATION_LEVEL_INFO]:
4810 cls.NOTIFICATION_LEVEL_INFO]:
4819 return
4811 return
4820
4812
4821 from rhodecode.model.user import UserModel
4813 from rhodecode.model.user import UserModel
4822 user = UserModel().get_user(user)
4814 user = UserModel().get_user(user)
4823
4815
4824 new_notice = UserNotice()
4816 new_notice = UserNotice()
4825 if not allow_duplicate:
4817 if not allow_duplicate:
4826 existing_msg = UserNotice().query() \
4818 existing_msg = UserNotice().query() \
4827 .filter(UserNotice.user == user) \
4819 .filter(UserNotice.user == user) \
4828 .filter(UserNotice.notice_body == body) \
4820 .filter(UserNotice.notice_body == body) \
4829 .filter(UserNotice.notice_read == false()) \
4821 .filter(UserNotice.notice_read == false()) \
4830 .scalar()
4822 .scalar()
4831 if existing_msg:
4823 if existing_msg:
4832 log.warning('Ignoring duplicate notice for user %s', user)
4824 log.warning('Ignoring duplicate notice for user %s', user)
4833 return
4825 return
4834
4826
4835 new_notice.user = user
4827 new_notice.user = user
4836 new_notice.notice_subject = subject
4828 new_notice.notice_subject = subject
4837 new_notice.notice_body = body
4829 new_notice.notice_body = body
4838 new_notice.notification_level = notice_level
4830 new_notice.notification_level = notice_level
4839 Session().add(new_notice)
4831 Session().add(new_notice)
4840 Session().commit()
4832 Session().commit()
4841
4833
4842
4834
4843 class Gist(Base, BaseModel):
4835 class Gist(Base, BaseModel):
4844 __tablename__ = 'gists'
4836 __tablename__ = 'gists'
4845 __table_args__ = (
4837 __table_args__ = (
4846 Index('g_gist_access_id_idx', 'gist_access_id'),
4838 Index('g_gist_access_id_idx', 'gist_access_id'),
4847 Index('g_created_on_idx', 'created_on'),
4839 Index('g_created_on_idx', 'created_on'),
4848 base_table_args
4840 base_table_args
4849 )
4841 )
4850
4842
4851 GIST_PUBLIC = 'public'
4843 GIST_PUBLIC = 'public'
4852 GIST_PRIVATE = 'private'
4844 GIST_PRIVATE = 'private'
4853 DEFAULT_FILENAME = 'gistfile1.txt'
4845 DEFAULT_FILENAME = 'gistfile1.txt'
4854
4846
4855 ACL_LEVEL_PUBLIC = 'acl_public'
4847 ACL_LEVEL_PUBLIC = 'acl_public'
4856 ACL_LEVEL_PRIVATE = 'acl_private'
4848 ACL_LEVEL_PRIVATE = 'acl_private'
4857
4849
4858 gist_id = Column('gist_id', Integer(), primary_key=True)
4850 gist_id = Column('gist_id', Integer(), primary_key=True)
4859 gist_access_id = Column('gist_access_id', Unicode(250))
4851 gist_access_id = Column('gist_access_id', Unicode(250))
4860 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4852 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4861 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4853 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4862 gist_expires = Column('gist_expires', Float(53), nullable=False)
4854 gist_expires = Column('gist_expires', Float(53), nullable=False)
4863 gist_type = Column('gist_type', Unicode(128), nullable=False)
4855 gist_type = Column('gist_type', Unicode(128), nullable=False)
4864 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4856 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4865 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4857 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4866 acl_level = Column('acl_level', Unicode(128), nullable=True)
4858 acl_level = Column('acl_level', Unicode(128), nullable=True)
4867
4859
4868 owner = relationship('User', back_populates='user_gists')
4860 owner = relationship('User', back_populates='user_gists')
4869
4861
4870 def __repr__(self):
4862 def __repr__(self):
4871 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4863 return f'<Gist:[{self.gist_type}]{self.gist_access_id}>'
4872
4864
4873 @hybrid_property
4865 @hybrid_property
4874 def description_safe(self):
4866 def description_safe(self):
4875 from rhodecode.lib import helpers as h
4867 from rhodecode.lib import helpers as h
4876 return h.escape(self.gist_description)
4868 return h.escape(self.gist_description)
4877
4869
4878 @classmethod
4870 @classmethod
4879 def get_or_404(cls, id_):
4871 def get_or_404(cls, id_):
4880 from pyramid.httpexceptions import HTTPNotFound
4872 from pyramid.httpexceptions import HTTPNotFound
4881
4873
4882 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4874 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4883 if not res:
4875 if not res:
4884 log.debug('WARN: No DB entry with id %s', id_)
4876 log.debug('WARN: No DB entry with id %s', id_)
4885 raise HTTPNotFound()
4877 raise HTTPNotFound()
4886 return res
4878 return res
4887
4879
4888 @classmethod
4880 @classmethod
4889 def get_by_access_id(cls, gist_access_id):
4881 def get_by_access_id(cls, gist_access_id):
4890 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4882 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4891
4883
4892 def gist_url(self):
4884 def gist_url(self):
4893 from rhodecode.model.gist import GistModel
4885 from rhodecode.model.gist import GistModel
4894 return GistModel().get_url(self)
4886 return GistModel().get_url(self)
4895
4887
4896 @classmethod
4888 @classmethod
4897 def base_path(cls):
4889 def base_path(cls):
4898 """
4890 """
4899 Returns base path when all gists are stored
4891 Returns base path when all gists are stored
4900
4892
4901 :param cls:
4893 :param cls:
4902 """
4894 """
4903 from rhodecode.model.gist import GIST_STORE_LOC
4895 from rhodecode.model.gist import GIST_STORE_LOC
4904 q = Session().query(RhodeCodeUi)\
4896 q = Session().query(RhodeCodeUi)\
4905 .filter(RhodeCodeUi.ui_key == URL_SEP)
4897 .filter(RhodeCodeUi.ui_key == URL_SEP)
4906 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4898 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4907 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4899 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4908
4900
4909 def get_api_data(self):
4901 def get_api_data(self):
4910 """
4902 """
4911 Common function for generating gist related data for API
4903 Common function for generating gist related data for API
4912 """
4904 """
4913 gist = self
4905 gist = self
4914 data = {
4906 data = {
4915 'gist_id': gist.gist_id,
4907 'gist_id': gist.gist_id,
4916 'type': gist.gist_type,
4908 'type': gist.gist_type,
4917 'access_id': gist.gist_access_id,
4909 'access_id': gist.gist_access_id,
4918 'description': gist.gist_description,
4910 'description': gist.gist_description,
4919 'url': gist.gist_url(),
4911 'url': gist.gist_url(),
4920 'expires': gist.gist_expires,
4912 'expires': gist.gist_expires,
4921 'created_on': gist.created_on,
4913 'created_on': gist.created_on,
4922 'modified_at': gist.modified_at,
4914 'modified_at': gist.modified_at,
4923 'content': None,
4915 'content': None,
4924 'acl_level': gist.acl_level,
4916 'acl_level': gist.acl_level,
4925 }
4917 }
4926 return data
4918 return data
4927
4919
4928 def __json__(self):
4920 def __json__(self):
4929 data = dict(
4921 data = dict(
4930 )
4922 )
4931 data.update(self.get_api_data())
4923 data.update(self.get_api_data())
4932 return data
4924 return data
4933 # SCM functions
4925 # SCM functions
4934
4926
4935 def scm_instance(self, **kwargs):
4927 def scm_instance(self, **kwargs):
4936 """
4928 """
4937 Get an instance of VCS Repository
4929 Get an instance of VCS Repository
4938
4930
4939 :param kwargs:
4931 :param kwargs:
4940 """
4932 """
4941 from rhodecode.model.gist import GistModel
4933 from rhodecode.model.gist import GistModel
4942 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4934 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4943 return get_vcs_instance(
4935 return get_vcs_instance(
4944 repo_path=safe_str(full_repo_path), create=False,
4936 repo_path=safe_str(full_repo_path), create=False,
4945 _vcs_alias=GistModel.vcs_backend)
4937 _vcs_alias=GistModel.vcs_backend)
4946
4938
4947
4939
4948 class ExternalIdentity(Base, BaseModel):
4940 class ExternalIdentity(Base, BaseModel):
4949 __tablename__ = 'external_identities'
4941 __tablename__ = 'external_identities'
4950 __table_args__ = (
4942 __table_args__ = (
4951 Index('local_user_id_idx', 'local_user_id'),
4943 Index('local_user_id_idx', 'local_user_id'),
4952 Index('external_id_idx', 'external_id'),
4944 Index('external_id_idx', 'external_id'),
4953 base_table_args
4945 base_table_args
4954 )
4946 )
4955
4947
4956 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4948 external_id = Column('external_id', Unicode(255), default='', primary_key=True)
4957 external_username = Column('external_username', Unicode(1024), default='')
4949 external_username = Column('external_username', Unicode(1024), default='')
4958 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4950 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4959 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4951 provider_name = Column('provider_name', Unicode(255), default='', primary_key=True)
4960 access_token = Column('access_token', String(1024), default='')
4952 access_token = Column('access_token', String(1024), default='')
4961 alt_token = Column('alt_token', String(1024), default='')
4953 alt_token = Column('alt_token', String(1024), default='')
4962 token_secret = Column('token_secret', String(1024), default='')
4954 token_secret = Column('token_secret', String(1024), default='')
4963
4955
4964 @classmethod
4956 @classmethod
4965 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4957 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4966 """
4958 """
4967 Returns ExternalIdentity instance based on search params
4959 Returns ExternalIdentity instance based on search params
4968
4960
4969 :param external_id:
4961 :param external_id:
4970 :param provider_name:
4962 :param provider_name:
4971 :return: ExternalIdentity
4963 :return: ExternalIdentity
4972 """
4964 """
4973 query = cls.query()
4965 query = cls.query()
4974 query = query.filter(cls.external_id == external_id)
4966 query = query.filter(cls.external_id == external_id)
4975 query = query.filter(cls.provider_name == provider_name)
4967 query = query.filter(cls.provider_name == provider_name)
4976 if local_user_id:
4968 if local_user_id:
4977 query = query.filter(cls.local_user_id == local_user_id)
4969 query = query.filter(cls.local_user_id == local_user_id)
4978 return query.first()
4970 return query.first()
4979
4971
4980 @classmethod
4972 @classmethod
4981 def user_by_external_id_and_provider(cls, external_id, provider_name):
4973 def user_by_external_id_and_provider(cls, external_id, provider_name):
4982 """
4974 """
4983 Returns User instance based on search params
4975 Returns User instance based on search params
4984
4976
4985 :param external_id:
4977 :param external_id:
4986 :param provider_name:
4978 :param provider_name:
4987 :return: User
4979 :return: User
4988 """
4980 """
4989 query = User.query()
4981 query = User.query()
4990 query = query.filter(cls.external_id == external_id)
4982 query = query.filter(cls.external_id == external_id)
4991 query = query.filter(cls.provider_name == provider_name)
4983 query = query.filter(cls.provider_name == provider_name)
4992 query = query.filter(User.user_id == cls.local_user_id)
4984 query = query.filter(User.user_id == cls.local_user_id)
4993 return query.first()
4985 return query.first()
4994
4986
4995 @classmethod
4987 @classmethod
4996 def by_local_user_id(cls, local_user_id):
4988 def by_local_user_id(cls, local_user_id):
4997 """
4989 """
4998 Returns all tokens for user
4990 Returns all tokens for user
4999
4991
5000 :param local_user_id:
4992 :param local_user_id:
5001 :return: ExternalIdentity
4993 :return: ExternalIdentity
5002 """
4994 """
5003 query = cls.query()
4995 query = cls.query()
5004 query = query.filter(cls.local_user_id == local_user_id)
4996 query = query.filter(cls.local_user_id == local_user_id)
5005 return query
4997 return query
5006
4998
5007 @classmethod
4999 @classmethod
5008 def load_provider_plugin(cls, plugin_id):
5000 def load_provider_plugin(cls, plugin_id):
5009 from rhodecode.authentication.base import loadplugin
5001 from rhodecode.authentication.base import loadplugin
5010 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5002 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
5011 auth_plugin = loadplugin(_plugin_id)
5003 auth_plugin = loadplugin(_plugin_id)
5012 return auth_plugin
5004 return auth_plugin
5013
5005
5014
5006
5015 class Integration(Base, BaseModel):
5007 class Integration(Base, BaseModel):
5016 __tablename__ = 'integrations'
5008 __tablename__ = 'integrations'
5017 __table_args__ = (
5009 __table_args__ = (
5018 base_table_args
5010 base_table_args
5019 )
5011 )
5020
5012
5021 integration_id = Column('integration_id', Integer(), primary_key=True)
5013 integration_id = Column('integration_id', Integer(), primary_key=True)
5022 integration_type = Column('integration_type', String(255))
5014 integration_type = Column('integration_type', String(255))
5023 enabled = Column('enabled', Boolean(), nullable=False)
5015 enabled = Column('enabled', Boolean(), nullable=False)
5024 name = Column('name', String(255), nullable=False)
5016 name = Column('name', String(255), nullable=False)
5025 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5017 child_repos_only = Column('child_repos_only', Boolean(), nullable=False, default=False)
5026
5018
5027 settings = Column(
5019 settings = Column(
5028 'settings_json', MutationObj.as_mutable(
5020 'settings_json', MutationObj.as_mutable(
5029 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5021 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
5030 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5022 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
5031 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5023 repo = relationship('Repository', lazy='joined', back_populates='integrations')
5032
5024
5033 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5025 repo_group_id = Column('repo_group_id', Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
5034 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5026 repo_group = relationship('RepoGroup', lazy='joined', back_populates='integrations')
5035
5027
5036 @property
5028 @property
5037 def scope(self):
5029 def scope(self):
5038 if self.repo:
5030 if self.repo:
5039 return repr(self.repo)
5031 return repr(self.repo)
5040 if self.repo_group:
5032 if self.repo_group:
5041 if self.child_repos_only:
5033 if self.child_repos_only:
5042 return repr(self.repo_group) + ' (child repos only)'
5034 return repr(self.repo_group) + ' (child repos only)'
5043 else:
5035 else:
5044 return repr(self.repo_group) + ' (recursive)'
5036 return repr(self.repo_group) + ' (recursive)'
5045 if self.child_repos_only:
5037 if self.child_repos_only:
5046 return 'root_repos'
5038 return 'root_repos'
5047 return 'global'
5039 return 'global'
5048
5040
5049 def __repr__(self):
5041 def __repr__(self):
5050 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5042 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5051
5043
5052
5044
5053 class RepoReviewRuleUser(Base, BaseModel):
5045 class RepoReviewRuleUser(Base, BaseModel):
5054 __tablename__ = 'repo_review_rules_users'
5046 __tablename__ = 'repo_review_rules_users'
5055 __table_args__ = (
5047 __table_args__ = (
5056 base_table_args
5048 base_table_args
5057 )
5049 )
5058 ROLE_REVIEWER = 'reviewer'
5050 ROLE_REVIEWER = 'reviewer'
5059 ROLE_OBSERVER = 'observer'
5051 ROLE_OBSERVER = 'observer'
5060 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5052 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5061
5053
5062 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5054 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5063 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5055 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5065 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5057 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5066 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5058 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5067 user = relationship('User', back_populates='user_review_rules')
5059 user = relationship('User', back_populates='user_review_rules')
5068
5060
5069 def rule_data(self):
5061 def rule_data(self):
5070 return {
5062 return {
5071 'mandatory': self.mandatory,
5063 'mandatory': self.mandatory,
5072 'role': self.role,
5064 'role': self.role,
5073 }
5065 }
5074
5066
5075
5067
5076 class RepoReviewRuleUserGroup(Base, BaseModel):
5068 class RepoReviewRuleUserGroup(Base, BaseModel):
5077 __tablename__ = 'repo_review_rules_users_groups'
5069 __tablename__ = 'repo_review_rules_users_groups'
5078 __table_args__ = (
5070 __table_args__ = (
5079 base_table_args
5071 base_table_args
5080 )
5072 )
5081
5073
5082 VOTE_RULE_ALL = -1
5074 VOTE_RULE_ALL = -1
5083 ROLE_REVIEWER = 'reviewer'
5075 ROLE_REVIEWER = 'reviewer'
5084 ROLE_OBSERVER = 'observer'
5076 ROLE_OBSERVER = 'observer'
5085 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5077 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5086
5078
5087 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5079 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5088 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5080 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5089 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5081 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5090 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5082 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5091 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5083 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5092 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5084 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5093 users_group = relationship('UserGroup')
5085 users_group = relationship('UserGroup')
5094
5086
5095 def rule_data(self):
5087 def rule_data(self):
5096 return {
5088 return {
5097 'mandatory': self.mandatory,
5089 'mandatory': self.mandatory,
5098 'role': self.role,
5090 'role': self.role,
5099 'vote_rule': self.vote_rule
5091 'vote_rule': self.vote_rule
5100 }
5092 }
5101
5093
5102 @property
5094 @property
5103 def vote_rule_label(self):
5095 def vote_rule_label(self):
5104 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5096 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5105 return 'all must vote'
5097 return 'all must vote'
5106 else:
5098 else:
5107 return 'min. vote {}'.format(self.vote_rule)
5099 return 'min. vote {}'.format(self.vote_rule)
5108
5100
5109
5101
5110 class RepoReviewRule(Base, BaseModel):
5102 class RepoReviewRule(Base, BaseModel):
5111 __tablename__ = 'repo_review_rules'
5103 __tablename__ = 'repo_review_rules'
5112 __table_args__ = (
5104 __table_args__ = (
5113 base_table_args
5105 base_table_args
5114 )
5106 )
5115
5107
5116 repo_review_rule_id = Column(
5108 repo_review_rule_id = Column(
5117 'repo_review_rule_id', Integer(), primary_key=True)
5109 'repo_review_rule_id', Integer(), primary_key=True)
5118 repo_id = Column(
5110 repo_id = Column(
5119 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5111 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5120 repo = relationship('Repository', back_populates='review_rules')
5112 repo = relationship('Repository', back_populates='review_rules')
5121
5113
5122 review_rule_name = Column('review_rule_name', String(255))
5114 review_rule_name = Column('review_rule_name', String(255))
5123 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5115 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5124 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5116 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5125 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5117 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default='*') # glob
5126
5118
5127 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5119 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5128
5120
5129 # Legacy fields, just for backward compat
5121 # Legacy fields, just for backward compat
5130 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5122 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5131 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5123 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5132
5124
5133 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5125 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5134 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5126 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5135
5127
5136 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5128 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5137
5129
5138 rule_users = relationship('RepoReviewRuleUser')
5130 rule_users = relationship('RepoReviewRuleUser')
5139 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5131 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5140
5132
5141 def _validate_pattern(self, value):
5133 def _validate_pattern(self, value):
5142 re.compile('^' + glob2re(value) + '$')
5134 re.compile('^' + glob2re(value) + '$')
5143
5135
5144 @hybrid_property
5136 @hybrid_property
5145 def source_branch_pattern(self):
5137 def source_branch_pattern(self):
5146 return self._branch_pattern or '*'
5138 return self._branch_pattern or '*'
5147
5139
5148 @source_branch_pattern.setter
5140 @source_branch_pattern.setter
5149 def source_branch_pattern(self, value):
5141 def source_branch_pattern(self, value):
5150 self._validate_pattern(value)
5142 self._validate_pattern(value)
5151 self._branch_pattern = value or '*'
5143 self._branch_pattern = value or '*'
5152
5144
5153 @hybrid_property
5145 @hybrid_property
5154 def target_branch_pattern(self):
5146 def target_branch_pattern(self):
5155 return self._target_branch_pattern or '*'
5147 return self._target_branch_pattern or '*'
5156
5148
5157 @target_branch_pattern.setter
5149 @target_branch_pattern.setter
5158 def target_branch_pattern(self, value):
5150 def target_branch_pattern(self, value):
5159 self._validate_pattern(value)
5151 self._validate_pattern(value)
5160 self._target_branch_pattern = value or '*'
5152 self._target_branch_pattern = value or '*'
5161
5153
5162 @hybrid_property
5154 @hybrid_property
5163 def file_pattern(self):
5155 def file_pattern(self):
5164 return self._file_pattern or '*'
5156 return self._file_pattern or '*'
5165
5157
5166 @file_pattern.setter
5158 @file_pattern.setter
5167 def file_pattern(self, value):
5159 def file_pattern(self, value):
5168 self._validate_pattern(value)
5160 self._validate_pattern(value)
5169 self._file_pattern = value or '*'
5161 self._file_pattern = value or '*'
5170
5162
5171 @hybrid_property
5163 @hybrid_property
5172 def forbid_pr_author_to_review(self):
5164 def forbid_pr_author_to_review(self):
5173 return self.pr_author == 'forbid_pr_author'
5165 return self.pr_author == 'forbid_pr_author'
5174
5166
5175 @hybrid_property
5167 @hybrid_property
5176 def include_pr_author_to_review(self):
5168 def include_pr_author_to_review(self):
5177 return self.pr_author == 'include_pr_author'
5169 return self.pr_author == 'include_pr_author'
5178
5170
5179 @hybrid_property
5171 @hybrid_property
5180 def forbid_commit_author_to_review(self):
5172 def forbid_commit_author_to_review(self):
5181 return self.commit_author == 'forbid_commit_author'
5173 return self.commit_author == 'forbid_commit_author'
5182
5174
5183 @hybrid_property
5175 @hybrid_property
5184 def include_commit_author_to_review(self):
5176 def include_commit_author_to_review(self):
5185 return self.commit_author == 'include_commit_author'
5177 return self.commit_author == 'include_commit_author'
5186
5178
5187 def matches(self, source_branch, target_branch, files_changed):
5179 def matches(self, source_branch, target_branch, files_changed):
5188 """
5180 """
5189 Check if this review rule matches a branch/files in a pull request
5181 Check if this review rule matches a branch/files in a pull request
5190
5182
5191 :param source_branch: source branch name for the commit
5183 :param source_branch: source branch name for the commit
5192 :param target_branch: target branch name for the commit
5184 :param target_branch: target branch name for the commit
5193 :param files_changed: list of file paths changed in the pull request
5185 :param files_changed: list of file paths changed in the pull request
5194 """
5186 """
5195
5187
5196 source_branch = source_branch or ''
5188 source_branch = source_branch or ''
5197 target_branch = target_branch or ''
5189 target_branch = target_branch or ''
5198 files_changed = files_changed or []
5190 files_changed = files_changed or []
5199
5191
5200 branch_matches = True
5192 branch_matches = True
5201 if source_branch or target_branch:
5193 if source_branch or target_branch:
5202 if self.source_branch_pattern == '*':
5194 if self.source_branch_pattern == '*':
5203 source_branch_match = True
5195 source_branch_match = True
5204 else:
5196 else:
5205 if self.source_branch_pattern.startswith('re:'):
5197 if self.source_branch_pattern.startswith('re:'):
5206 source_pattern = self.source_branch_pattern[3:]
5198 source_pattern = self.source_branch_pattern[3:]
5207 else:
5199 else:
5208 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5200 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5209 source_branch_regex = re.compile(source_pattern)
5201 source_branch_regex = re.compile(source_pattern)
5210 source_branch_match = bool(source_branch_regex.search(source_branch))
5202 source_branch_match = bool(source_branch_regex.search(source_branch))
5211 if self.target_branch_pattern == '*':
5203 if self.target_branch_pattern == '*':
5212 target_branch_match = True
5204 target_branch_match = True
5213 else:
5205 else:
5214 if self.target_branch_pattern.startswith('re:'):
5206 if self.target_branch_pattern.startswith('re:'):
5215 target_pattern = self.target_branch_pattern[3:]
5207 target_pattern = self.target_branch_pattern[3:]
5216 else:
5208 else:
5217 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5209 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5218 target_branch_regex = re.compile(target_pattern)
5210 target_branch_regex = re.compile(target_pattern)
5219 target_branch_match = bool(target_branch_regex.search(target_branch))
5211 target_branch_match = bool(target_branch_regex.search(target_branch))
5220
5212
5221 branch_matches = source_branch_match and target_branch_match
5213 branch_matches = source_branch_match and target_branch_match
5222
5214
5223 files_matches = True
5215 files_matches = True
5224 if self.file_pattern != '*':
5216 if self.file_pattern != '*':
5225 files_matches = False
5217 files_matches = False
5226 if self.file_pattern.startswith('re:'):
5218 if self.file_pattern.startswith('re:'):
5227 file_pattern = self.file_pattern[3:]
5219 file_pattern = self.file_pattern[3:]
5228 else:
5220 else:
5229 file_pattern = glob2re(self.file_pattern)
5221 file_pattern = glob2re(self.file_pattern)
5230 file_regex = re.compile(file_pattern)
5222 file_regex = re.compile(file_pattern)
5231 for file_data in files_changed:
5223 for file_data in files_changed:
5232 filename = file_data.get('filename')
5224 filename = file_data.get('filename')
5233
5225
5234 if file_regex.search(filename):
5226 if file_regex.search(filename):
5235 files_matches = True
5227 files_matches = True
5236 break
5228 break
5237
5229
5238 return branch_matches and files_matches
5230 return branch_matches and files_matches
5239
5231
5240 @property
5232 @property
5241 def review_users(self):
5233 def review_users(self):
5242 """ Returns the users which this rule applies to """
5234 """ Returns the users which this rule applies to """
5243
5235
5244 users = collections.OrderedDict()
5236 users = collections.OrderedDict()
5245
5237
5246 for rule_user in self.rule_users:
5238 for rule_user in self.rule_users:
5247 if rule_user.user.active:
5239 if rule_user.user.active:
5248 if rule_user.user not in users:
5240 if rule_user.user not in users:
5249 users[rule_user.user.username] = {
5241 users[rule_user.user.username] = {
5250 'user': rule_user.user,
5242 'user': rule_user.user,
5251 'source': 'user',
5243 'source': 'user',
5252 'source_data': {},
5244 'source_data': {},
5253 'data': rule_user.rule_data()
5245 'data': rule_user.rule_data()
5254 }
5246 }
5255
5247
5256 for rule_user_group in self.rule_user_groups:
5248 for rule_user_group in self.rule_user_groups:
5257 source_data = {
5249 source_data = {
5258 'user_group_id': rule_user_group.users_group.users_group_id,
5250 'user_group_id': rule_user_group.users_group.users_group_id,
5259 'name': rule_user_group.users_group.users_group_name,
5251 'name': rule_user_group.users_group.users_group_name,
5260 'members': len(rule_user_group.users_group.members)
5252 'members': len(rule_user_group.users_group.members)
5261 }
5253 }
5262 for member in rule_user_group.users_group.members:
5254 for member in rule_user_group.users_group.members:
5263 if member.user.active:
5255 if member.user.active:
5264 key = member.user.username
5256 key = member.user.username
5265 if key in users:
5257 if key in users:
5266 # skip this member as we have him already
5258 # skip this member as we have him already
5267 # this prevents from override the "first" matched
5259 # this prevents from override the "first" matched
5268 # users with duplicates in multiple groups
5260 # users with duplicates in multiple groups
5269 continue
5261 continue
5270
5262
5271 users[key] = {
5263 users[key] = {
5272 'user': member.user,
5264 'user': member.user,
5273 'source': 'user_group',
5265 'source': 'user_group',
5274 'source_data': source_data,
5266 'source_data': source_data,
5275 'data': rule_user_group.rule_data()
5267 'data': rule_user_group.rule_data()
5276 }
5268 }
5277
5269
5278 return users
5270 return users
5279
5271
5280 def user_group_vote_rule(self, user_id):
5272 def user_group_vote_rule(self, user_id):
5281
5273
5282 rules = []
5274 rules = []
5283 if not self.rule_user_groups:
5275 if not self.rule_user_groups:
5284 return rules
5276 return rules
5285
5277
5286 for user_group in self.rule_user_groups:
5278 for user_group in self.rule_user_groups:
5287 user_group_members = [x.user_id for x in user_group.users_group.members]
5279 user_group_members = [x.user_id for x in user_group.users_group.members]
5288 if user_id in user_group_members:
5280 if user_id in user_group_members:
5289 rules.append(user_group)
5281 rules.append(user_group)
5290 return rules
5282 return rules
5291
5283
5292 def __repr__(self):
5284 def __repr__(self):
5293 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5285 return f'<RepoReviewerRule(id={self.repo_review_rule_id}, repo={self.repo!r})>'
5294
5286
5295
5287
5296 class ScheduleEntry(Base, BaseModel):
5288 class ScheduleEntry(Base, BaseModel):
5297 __tablename__ = 'schedule_entries'
5289 __tablename__ = 'schedule_entries'
5298 __table_args__ = (
5290 __table_args__ = (
5299 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5291 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5300 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5292 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5301 base_table_args,
5293 base_table_args,
5302 )
5294 )
5303 SCHEDULE_TYPE_INTEGER = "integer"
5295 SCHEDULE_TYPE_INTEGER = "integer"
5304 SCHEDULE_TYPE_CRONTAB = "crontab"
5296 SCHEDULE_TYPE_CRONTAB = "crontab"
5305
5297
5306 schedule_types = [SCHEDULE_TYPE_CRONTAB, SCHEDULE_TYPE_INTEGER]
5298 schedule_types = [SCHEDULE_TYPE_CRONTAB, SCHEDULE_TYPE_INTEGER]
5307 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5299 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5308
5300
5309 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5301 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5310 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5302 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5311 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5303 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5312
5304
5313 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5305 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5314 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5306 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5315
5307
5316 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5308 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5317 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5309 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5318
5310
5319 # task
5311 # task
5320 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5312 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5321 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5313 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5322 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5314 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5323 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5315 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5324
5316
5325 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5317 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5326 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5318 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5327
5319
5328 @hybrid_property
5320 @hybrid_property
5329 def schedule_type(self):
5321 def schedule_type(self):
5330 return self._schedule_type
5322 return self._schedule_type
5331
5323
5332 @schedule_type.setter
5324 @schedule_type.setter
5333 def schedule_type(self, val):
5325 def schedule_type(self, val):
5334 if val not in self.schedule_types:
5326 if val not in self.schedule_types:
5335 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5327 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5336 val, self.schedule_type))
5328 val, self.schedule_type))
5337
5329
5338 self._schedule_type = val
5330 self._schedule_type = val
5339
5331
5340 @classmethod
5332 @classmethod
5341 def get_uid(cls, obj):
5333 def get_uid(cls, obj):
5342 args = obj.task_args
5334 args = obj.task_args
5343 kwargs = obj.task_kwargs
5335 kwargs = obj.task_kwargs
5344 if isinstance(args, JsonRaw):
5336 if isinstance(args, JsonRaw):
5345 try:
5337 try:
5346 args = json.loads(args)
5338 args = json.loads(args)
5347 except ValueError:
5339 except ValueError:
5348 args = tuple()
5340 args = tuple()
5349
5341
5350 if isinstance(kwargs, JsonRaw):
5342 if isinstance(kwargs, JsonRaw):
5351 try:
5343 try:
5352 kwargs = json.loads(kwargs)
5344 kwargs = json.loads(kwargs)
5353 except ValueError:
5345 except ValueError:
5354 kwargs = dict()
5346 kwargs = dict()
5355
5347
5356 dot_notation = obj.task_dot_notation
5348 dot_notation = obj.task_dot_notation
5357 val = '.'.join(map(safe_str, [
5349 val = '.'.join(map(safe_str, [
5358 sorted(dot_notation), args, sorted(kwargs.items())]))
5350 sorted(dot_notation), args, sorted(kwargs.items())]))
5359 return sha1(safe_bytes(val))
5351 return sha1(safe_bytes(val))
5360
5352
5361 @classmethod
5353 @classmethod
5362 def get_by_schedule_name(cls, schedule_name):
5354 def get_by_schedule_name(cls, schedule_name):
5363 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5355 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5364
5356
5365 @classmethod
5357 @classmethod
5366 def get_by_schedule_id(cls, schedule_id):
5358 def get_by_schedule_id(cls, schedule_id):
5367 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5359 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5368
5360
5369 @property
5361 @property
5370 def task(self):
5362 def task(self):
5371 return self.task_dot_notation
5363 return self.task_dot_notation
5372
5364
5373 @property
5365 @property
5374 def schedule(self):
5366 def schedule(self):
5375 from rhodecode.lib.celerylib.utils import raw_2_schedule
5367 from rhodecode.lib.celerylib.utils import raw_2_schedule
5376 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5368 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5377 return schedule
5369 return schedule
5378
5370
5379 @property
5371 @property
5380 def args(self):
5372 def args(self):
5381 try:
5373 try:
5382 return list(self.task_args or [])
5374 return list(self.task_args or [])
5383 except ValueError:
5375 except ValueError:
5384 return list()
5376 return list()
5385
5377
5386 @property
5378 @property
5387 def kwargs(self):
5379 def kwargs(self):
5388 try:
5380 try:
5389 return dict(self.task_kwargs or {})
5381 return dict(self.task_kwargs or {})
5390 except ValueError:
5382 except ValueError:
5391 return dict()
5383 return dict()
5392
5384
5393 def _as_raw(self, val, indent=False):
5385 def _as_raw(self, val, indent=False):
5394 if hasattr(val, 'de_coerce'):
5386 if hasattr(val, 'de_coerce'):
5395 val = val.de_coerce()
5387 val = val.de_coerce()
5396 if val:
5388 if val:
5397 if indent:
5389 if indent:
5398 val = ext_json.formatted_str_json(val)
5390 val = ext_json.formatted_str_json(val)
5399 else:
5391 else:
5400 val = ext_json.str_json(val)
5392 val = ext_json.str_json(val)
5401
5393
5402 return val
5394 return val
5403
5395
5404 @property
5396 @property
5405 def schedule_definition_raw(self):
5397 def schedule_definition_raw(self):
5406 return self._as_raw(self.schedule_definition)
5398 return self._as_raw(self.schedule_definition)
5407
5399
5408 def args_raw(self, indent=False):
5400 def args_raw(self, indent=False):
5409 return self._as_raw(self.task_args, indent)
5401 return self._as_raw(self.task_args, indent)
5410
5402
5411 def kwargs_raw(self, indent=False):
5403 def kwargs_raw(self, indent=False):
5412 return self._as_raw(self.task_kwargs, indent)
5404 return self._as_raw(self.task_kwargs, indent)
5413
5405
5414 def __repr__(self):
5406 def __repr__(self):
5415 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5407 return f'<DB:ScheduleEntry({self.schedule_entry_id}:{self.schedule_name})>'
5416
5408
5417
5409
5418 @event.listens_for(ScheduleEntry, 'before_update')
5410 @event.listens_for(ScheduleEntry, 'before_update')
5419 def update_task_uid(mapper, connection, target):
5411 def update_task_uid(mapper, connection, target):
5420 target.task_uid = ScheduleEntry.get_uid(target)
5412 target.task_uid = ScheduleEntry.get_uid(target)
5421
5413
5422
5414
5423 @event.listens_for(ScheduleEntry, 'before_insert')
5415 @event.listens_for(ScheduleEntry, 'before_insert')
5424 def set_task_uid(mapper, connection, target):
5416 def set_task_uid(mapper, connection, target):
5425 target.task_uid = ScheduleEntry.get_uid(target)
5417 target.task_uid = ScheduleEntry.get_uid(target)
5426
5418
5427
5419
5428 class _BaseBranchPerms(BaseModel):
5420 class _BaseBranchPerms(BaseModel):
5429 @classmethod
5421 @classmethod
5430 def compute_hash(cls, value):
5422 def compute_hash(cls, value):
5431 return sha1_safe(value)
5423 return sha1_safe(value)
5432
5424
5433 @hybrid_property
5425 @hybrid_property
5434 def branch_pattern(self):
5426 def branch_pattern(self):
5435 return self._branch_pattern or '*'
5427 return self._branch_pattern or '*'
5436
5428
5437 @hybrid_property
5429 @hybrid_property
5438 def branch_hash(self):
5430 def branch_hash(self):
5439 return self._branch_hash
5431 return self._branch_hash
5440
5432
5441 def _validate_glob(self, value):
5433 def _validate_glob(self, value):
5442 re.compile('^' + glob2re(value) + '$')
5434 re.compile('^' + glob2re(value) + '$')
5443
5435
5444 @branch_pattern.setter
5436 @branch_pattern.setter
5445 def branch_pattern(self, value):
5437 def branch_pattern(self, value):
5446 self._validate_glob(value)
5438 self._validate_glob(value)
5447 self._branch_pattern = value or '*'
5439 self._branch_pattern = value or '*'
5448 # set the Hash when setting the branch pattern
5440 # set the Hash when setting the branch pattern
5449 self._branch_hash = self.compute_hash(self._branch_pattern)
5441 self._branch_hash = self.compute_hash(self._branch_pattern)
5450
5442
5451 def matches(self, branch):
5443 def matches(self, branch):
5452 """
5444 """
5453 Check if this the branch matches entry
5445 Check if this the branch matches entry
5454
5446
5455 :param branch: branch name for the commit
5447 :param branch: branch name for the commit
5456 """
5448 """
5457
5449
5458 branch = branch or ''
5450 branch = branch or ''
5459
5451
5460 branch_matches = True
5452 branch_matches = True
5461 if branch:
5453 if branch:
5462 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5454 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5463 branch_matches = bool(branch_regex.search(branch))
5455 branch_matches = bool(branch_regex.search(branch))
5464
5456
5465 return branch_matches
5457 return branch_matches
5466
5458
5467
5459
5468 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5460 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5469 __tablename__ = 'user_to_repo_branch_permissions'
5461 __tablename__ = 'user_to_repo_branch_permissions'
5470 __table_args__ = (
5462 __table_args__ = (
5471 base_table_args
5463 base_table_args
5472 )
5464 )
5473
5465
5474 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5466 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5475
5467
5476 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5468 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5477 repo = relationship('Repository', back_populates='user_branch_perms')
5469 repo = relationship('Repository', back_populates='user_branch_perms')
5478
5470
5479 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5471 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5480 permission = relationship('Permission')
5472 permission = relationship('Permission')
5481
5473
5482 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5474 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5483 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5475 user_repo_to_perm = relationship('UserRepoToPerm', back_populates='branch_perm_entry')
5484
5476
5485 rule_order = Column('rule_order', Integer(), nullable=False)
5477 rule_order = Column('rule_order', Integer(), nullable=False)
5486 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5478 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5487 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5479 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5488
5480
5489 def __repr__(self):
5481 def __repr__(self):
5490 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5482 return f'<UserBranchPermission({self.user_repo_to_perm} => {self.branch_pattern!r})>'
5491
5483
5492
5484
5493 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5485 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5494 __tablename__ = 'user_group_to_repo_branch_permissions'
5486 __tablename__ = 'user_group_to_repo_branch_permissions'
5495 __table_args__ = (
5487 __table_args__ = (
5496 base_table_args
5488 base_table_args
5497 )
5489 )
5498
5490
5499 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5491 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5500
5492
5501 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5493 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5502 repo = relationship('Repository', back_populates='user_group_branch_perms')
5494 repo = relationship('Repository', back_populates='user_group_branch_perms')
5503
5495
5504 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5496 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5505 permission = relationship('Permission')
5497 permission = relationship('Permission')
5506
5498
5507 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)
5499 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)
5508 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5500 user_group_repo_to_perm = relationship('UserGroupRepoToPerm', back_populates='user_group_branch_perms')
5509
5501
5510 rule_order = Column('rule_order', Integer(), nullable=False)
5502 rule_order = Column('rule_order', Integer(), nullable=False)
5511 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5503 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default='*') # glob
5512 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5504 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5513
5505
5514 def __repr__(self):
5506 def __repr__(self):
5515 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5507 return f'<UserBranchPermission({self.user_group_repo_to_perm} => {self.branch_pattern!r})>'
5516
5508
5517
5509
5518 class UserBookmark(Base, BaseModel):
5510 class UserBookmark(Base, BaseModel):
5519 __tablename__ = 'user_bookmarks'
5511 __tablename__ = 'user_bookmarks'
5520 __table_args__ = (
5512 __table_args__ = (
5521 UniqueConstraint('user_id', 'bookmark_repo_id'),
5513 UniqueConstraint('user_id', 'bookmark_repo_id'),
5522 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5514 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5523 UniqueConstraint('user_id', 'bookmark_position'),
5515 UniqueConstraint('user_id', 'bookmark_position'),
5524 base_table_args
5516 base_table_args
5525 )
5517 )
5526
5518
5527 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5519 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5528 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5529 position = Column("bookmark_position", Integer(), nullable=False)
5521 position = Column("bookmark_position", Integer(), nullable=False)
5530 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5522 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5531 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5523 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5532 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5524 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5533
5525
5534 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5526 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5535 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5527 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5536
5528
5537 user = relationship("User")
5529 user = relationship("User")
5538
5530
5539 repository = relationship("Repository")
5531 repository = relationship("Repository")
5540 repository_group = relationship("RepoGroup")
5532 repository_group = relationship("RepoGroup")
5541
5533
5542 @classmethod
5534 @classmethod
5543 def get_by_position_for_user(cls, position, user_id):
5535 def get_by_position_for_user(cls, position, user_id):
5544 return cls.query() \
5536 return cls.query() \
5545 .filter(UserBookmark.user_id == user_id) \
5537 .filter(UserBookmark.user_id == user_id) \
5546 .filter(UserBookmark.position == position).scalar()
5538 .filter(UserBookmark.position == position).scalar()
5547
5539
5548 @classmethod
5540 @classmethod
5549 def get_bookmarks_for_user(cls, user_id, cache=True):
5541 def get_bookmarks_for_user(cls, user_id, cache=True):
5550 bookmarks = cls.query() \
5542 bookmarks = cls.query() \
5551 .filter(UserBookmark.user_id == user_id) \
5543 .filter(UserBookmark.user_id == user_id) \
5552 .options(joinedload(UserBookmark.repository)) \
5544 .options(joinedload(UserBookmark.repository)) \
5553 .options(joinedload(UserBookmark.repository_group)) \
5545 .options(joinedload(UserBookmark.repository_group)) \
5554 .order_by(UserBookmark.position.asc())
5546 .order_by(UserBookmark.position.asc())
5555
5547
5556 if cache:
5548 if cache:
5557 bookmarks = bookmarks.options(
5549 bookmarks = bookmarks.options(
5558 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5550 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5559 )
5551 )
5560
5552
5561 return bookmarks.all()
5553 return bookmarks.all()
5562
5554
5563 def __repr__(self):
5555 def __repr__(self):
5564 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5556 return f'<UserBookmark({self.position} @ {self.redirect_url!r})>'
5565
5557
5566
5558
5567 class FileStore(Base, BaseModel):
5559 class FileStore(Base, BaseModel):
5568 __tablename__ = 'file_store'
5560 __tablename__ = 'file_store'
5569 __table_args__ = (
5561 __table_args__ = (
5570 base_table_args
5562 base_table_args
5571 )
5563 )
5572
5564
5573 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5565 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5574 file_uid = Column('file_uid', String(1024), nullable=False)
5566 file_uid = Column('file_uid', String(1024), nullable=False)
5575 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5567 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5576 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5568 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5577 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5569 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5578
5570
5579 # sha256 hash
5571 # sha256 hash
5580 file_hash = Column('file_hash', String(512), nullable=False)
5572 file_hash = Column('file_hash', String(512), nullable=False)
5581 file_size = Column('file_size', BigInteger(), nullable=False)
5573 file_size = Column('file_size', BigInteger(), nullable=False)
5582
5574
5583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5575 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5584 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5576 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5585 accessed_count = Column('accessed_count', Integer(), default=0)
5577 accessed_count = Column('accessed_count', Integer(), default=0)
5586
5578
5587 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5579 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5588
5580
5589 # if repo/repo_group reference is set, check for permissions
5581 # if repo/repo_group reference is set, check for permissions
5590 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5582 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5591
5583
5592 # hidden defines an attachment that should be hidden from showing in artifact listing
5584 # hidden defines an attachment that should be hidden from showing in artifact listing
5593 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5585 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5594
5586
5595 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5587 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5596 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5588 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id', back_populates='artifacts')
5597
5589
5598 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5590 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5599
5591
5600 # scope limited to user, which requester have access to
5592 # scope limited to user, which requester have access to
5601 scope_user_id = Column(
5593 scope_user_id = Column(
5602 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5594 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5603 nullable=True, unique=None, default=None)
5595 nullable=True, unique=None, default=None)
5604 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5596 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id', back_populates='scope_artifacts')
5605
5597
5606 # scope limited to user group, which requester have access to
5598 # scope limited to user group, which requester have access to
5607 scope_user_group_id = Column(
5599 scope_user_group_id = Column(
5608 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5600 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5609 nullable=True, unique=None, default=None)
5601 nullable=True, unique=None, default=None)
5610 user_group = relationship('UserGroup', lazy='joined')
5602 user_group = relationship('UserGroup', lazy='joined')
5611
5603
5612 # scope limited to repo, which requester have access to
5604 # scope limited to repo, which requester have access to
5613 scope_repo_id = Column(
5605 scope_repo_id = Column(
5614 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5606 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5615 nullable=True, unique=None, default=None)
5607 nullable=True, unique=None, default=None)
5616 repo = relationship('Repository', lazy='joined')
5608 repo = relationship('Repository', lazy='joined')
5617
5609
5618 # scope limited to repo group, which requester have access to
5610 # scope limited to repo group, which requester have access to
5619 scope_repo_group_id = Column(
5611 scope_repo_group_id = Column(
5620 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5612 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5621 nullable=True, unique=None, default=None)
5613 nullable=True, unique=None, default=None)
5622 repo_group = relationship('RepoGroup', lazy='joined')
5614 repo_group = relationship('RepoGroup', lazy='joined')
5623
5615
5624 @classmethod
5616 @classmethod
5625 def get_scope(cls, scope_type, scope_id):
5617 def get_scope(cls, scope_type, scope_id):
5626 if scope_type == 'repo':
5618 if scope_type == 'repo':
5627 return f'repo:{scope_id}'
5619 return f'repo:{scope_id}'
5628 elif scope_type == 'repo-group':
5620 elif scope_type == 'repo-group':
5629 return f'repo-group:{scope_id}'
5621 return f'repo-group:{scope_id}'
5630 elif scope_type == 'user':
5622 elif scope_type == 'user':
5631 return f'user:{scope_id}'
5623 return f'user:{scope_id}'
5632 elif scope_type == 'user-group':
5624 elif scope_type == 'user-group':
5633 return f'user-group:{scope_id}'
5625 return f'user-group:{scope_id}'
5634 else:
5626 else:
5635 return scope_type
5627 return scope_type
5636
5628
5637 @classmethod
5629 @classmethod
5638 def get_by_store_uid(cls, file_store_uid, safe=False):
5630 def get_by_store_uid(cls, file_store_uid, safe=False):
5639 if safe:
5631 if safe:
5640 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5632 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5641 else:
5633 else:
5642 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5634 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5643
5635
5644 @classmethod
5636 @classmethod
5645 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5637 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5646 file_description='', enabled=True, hidden=False, check_acl=True,
5638 file_description='', enabled=True, hidden=False, check_acl=True,
5647 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5639 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5648
5640
5649 store_entry = FileStore()
5641 store_entry = FileStore()
5650 store_entry.file_uid = file_uid
5642 store_entry.file_uid = file_uid
5651 store_entry.file_display_name = file_display_name
5643 store_entry.file_display_name = file_display_name
5652 store_entry.file_org_name = filename
5644 store_entry.file_org_name = filename
5653 store_entry.file_size = file_size
5645 store_entry.file_size = file_size
5654 store_entry.file_hash = file_hash
5646 store_entry.file_hash = file_hash
5655 store_entry.file_description = file_description
5647 store_entry.file_description = file_description
5656
5648
5657 store_entry.check_acl = check_acl
5649 store_entry.check_acl = check_acl
5658 store_entry.enabled = enabled
5650 store_entry.enabled = enabled
5659 store_entry.hidden = hidden
5651 store_entry.hidden = hidden
5660
5652
5661 store_entry.user_id = user_id
5653 store_entry.user_id = user_id
5662 store_entry.scope_user_id = scope_user_id
5654 store_entry.scope_user_id = scope_user_id
5663 store_entry.scope_repo_id = scope_repo_id
5655 store_entry.scope_repo_id = scope_repo_id
5664 store_entry.scope_repo_group_id = scope_repo_group_id
5656 store_entry.scope_repo_group_id = scope_repo_group_id
5665
5657
5666 return store_entry
5658 return store_entry
5667
5659
5668 @classmethod
5660 @classmethod
5669 def store_metadata(cls, file_store_id, args, commit=True):
5661 def store_metadata(cls, file_store_id, args, commit=True):
5670 file_store = FileStore.get(file_store_id)
5662 file_store = FileStore.get(file_store_id)
5671 if file_store is None:
5663 if file_store is None:
5672 return
5664 return
5673
5665
5674 for section, key, value, value_type in args:
5666 for section, key, value, value_type in args:
5675 has_key = FileStoreMetadata().query() \
5667 has_key = FileStoreMetadata().query() \
5676 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5668 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5677 .filter(FileStoreMetadata.file_store_meta_section == section) \
5669 .filter(FileStoreMetadata.file_store_meta_section == section) \
5678 .filter(FileStoreMetadata.file_store_meta_key == key) \
5670 .filter(FileStoreMetadata.file_store_meta_key == key) \
5679 .scalar()
5671 .scalar()
5680 if has_key:
5672 if has_key:
5681 msg = 'key `{}` already defined under section `{}` for this file.'\
5673 msg = 'key `{}` already defined under section `{}` for this file.'\
5682 .format(key, section)
5674 .format(key, section)
5683 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5675 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5684
5676
5685 # NOTE(marcink): raises ArtifactMetadataBadValueType
5677 # NOTE(marcink): raises ArtifactMetadataBadValueType
5686 FileStoreMetadata.valid_value_type(value_type)
5678 FileStoreMetadata.valid_value_type(value_type)
5687
5679
5688 meta_entry = FileStoreMetadata()
5680 meta_entry = FileStoreMetadata()
5689 meta_entry.file_store = file_store
5681 meta_entry.file_store = file_store
5690 meta_entry.file_store_meta_section = section
5682 meta_entry.file_store_meta_section = section
5691 meta_entry.file_store_meta_key = key
5683 meta_entry.file_store_meta_key = key
5692 meta_entry.file_store_meta_value_type = value_type
5684 meta_entry.file_store_meta_value_type = value_type
5693 meta_entry.file_store_meta_value = value
5685 meta_entry.file_store_meta_value = value
5694
5686
5695 Session().add(meta_entry)
5687 Session().add(meta_entry)
5696
5688
5697 try:
5689 try:
5698 if commit:
5690 if commit:
5699 Session().commit()
5691 Session().commit()
5700 except IntegrityError:
5692 except IntegrityError:
5701 Session().rollback()
5693 Session().rollback()
5702 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5694 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5703
5695
5704 @classmethod
5696 @classmethod
5705 def bump_access_counter(cls, file_uid, commit=True):
5697 def bump_access_counter(cls, file_uid, commit=True):
5706 FileStore().query()\
5698 FileStore().query()\
5707 .filter(FileStore.file_uid == file_uid)\
5699 .filter(FileStore.file_uid == file_uid)\
5708 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5700 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5709 FileStore.accessed_on: datetime.datetime.now()})
5701 FileStore.accessed_on: datetime.datetime.now()})
5710 if commit:
5702 if commit:
5711 Session().commit()
5703 Session().commit()
5712
5704
5713 def __json__(self):
5705 def __json__(self):
5714 data = {
5706 data = {
5715 'filename': self.file_display_name,
5707 'filename': self.file_display_name,
5716 'filename_org': self.file_org_name,
5708 'filename_org': self.file_org_name,
5717 'file_uid': self.file_uid,
5709 'file_uid': self.file_uid,
5718 'description': self.file_description,
5710 'description': self.file_description,
5719 'hidden': self.hidden,
5711 'hidden': self.hidden,
5720 'size': self.file_size,
5712 'size': self.file_size,
5721 'created_on': self.created_on,
5713 'created_on': self.created_on,
5722 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5714 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5723 'downloaded_times': self.accessed_count,
5715 'downloaded_times': self.accessed_count,
5724 'sha256': self.file_hash,
5716 'sha256': self.file_hash,
5725 'metadata': self.file_metadata,
5717 'metadata': self.file_metadata,
5726 }
5718 }
5727
5719
5728 return data
5720 return data
5729
5721
5730 def __repr__(self):
5722 def __repr__(self):
5731 return f'<FileStore({self.file_store_id})>'
5723 return f'<FileStore({self.file_store_id})>'
5732
5724
5733
5725
5734 class FileStoreMetadata(Base, BaseModel):
5726 class FileStoreMetadata(Base, BaseModel):
5735 __tablename__ = 'file_store_metadata'
5727 __tablename__ = 'file_store_metadata'
5736 __table_args__ = (
5728 __table_args__ = (
5737 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5729 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5738 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5730 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5739 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5731 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5740 base_table_args
5732 base_table_args
5741 )
5733 )
5742 SETTINGS_TYPES = {
5734 SETTINGS_TYPES = {
5743 'str': safe_str,
5735 'str': safe_str,
5744 'int': safe_int,
5736 'int': safe_int,
5745 'unicode': safe_str,
5737 'unicode': safe_str,
5746 'bool': str2bool,
5738 'bool': str2bool,
5747 'list': functools.partial(aslist, sep=',')
5739 'list': functools.partial(aslist, sep=',')
5748 }
5740 }
5749
5741
5750 file_store_meta_id = Column(
5742 file_store_meta_id = Column(
5751 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5743 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5752 primary_key=True)
5744 primary_key=True)
5753 _file_store_meta_section = Column(
5745 _file_store_meta_section = Column(
5754 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5746 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5755 nullable=True, unique=None, default=None)
5747 nullable=True, unique=None, default=None)
5756 _file_store_meta_section_hash = Column(
5748 _file_store_meta_section_hash = Column(
5757 "file_store_meta_section_hash", String(255),
5749 "file_store_meta_section_hash", String(255),
5758 nullable=True, unique=None, default=None)
5750 nullable=True, unique=None, default=None)
5759 _file_store_meta_key = Column(
5751 _file_store_meta_key = Column(
5760 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5752 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5761 nullable=True, unique=None, default=None)
5753 nullable=True, unique=None, default=None)
5762 _file_store_meta_key_hash = Column(
5754 _file_store_meta_key_hash = Column(
5763 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5755 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5764 _file_store_meta_value = Column(
5756 _file_store_meta_value = Column(
5765 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5757 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5766 nullable=True, unique=None, default=None)
5758 nullable=True, unique=None, default=None)
5767 _file_store_meta_value_type = Column(
5759 _file_store_meta_value_type = Column(
5768 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5760 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5769 default='unicode')
5761 default='unicode')
5770
5762
5771 file_store_id = Column(
5763 file_store_id = Column(
5772 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5764 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5773 nullable=True, unique=None, default=None)
5765 nullable=True, unique=None, default=None)
5774
5766
5775 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5767 file_store = relationship('FileStore', lazy='joined', viewonly=True)
5776
5768
5777 @classmethod
5769 @classmethod
5778 def valid_value_type(cls, value):
5770 def valid_value_type(cls, value):
5779 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5771 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5780 raise ArtifactMetadataBadValueType(
5772 raise ArtifactMetadataBadValueType(
5781 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5773 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5782
5774
5783 @hybrid_property
5775 @hybrid_property
5784 def file_store_meta_section(self):
5776 def file_store_meta_section(self):
5785 return self._file_store_meta_section
5777 return self._file_store_meta_section
5786
5778
5787 @file_store_meta_section.setter
5779 @file_store_meta_section.setter
5788 def file_store_meta_section(self, value):
5780 def file_store_meta_section(self, value):
5789 self._file_store_meta_section = value
5781 self._file_store_meta_section = value
5790 self._file_store_meta_section_hash = _hash_key(value)
5782 self._file_store_meta_section_hash = _hash_key(value)
5791
5783
5792 @hybrid_property
5784 @hybrid_property
5793 def file_store_meta_key(self):
5785 def file_store_meta_key(self):
5794 return self._file_store_meta_key
5786 return self._file_store_meta_key
5795
5787
5796 @file_store_meta_key.setter
5788 @file_store_meta_key.setter
5797 def file_store_meta_key(self, value):
5789 def file_store_meta_key(self, value):
5798 self._file_store_meta_key = value
5790 self._file_store_meta_key = value
5799 self._file_store_meta_key_hash = _hash_key(value)
5791 self._file_store_meta_key_hash = _hash_key(value)
5800
5792
5801 @hybrid_property
5793 @hybrid_property
5802 def file_store_meta_value(self):
5794 def file_store_meta_value(self):
5803 val = self._file_store_meta_value
5795 val = self._file_store_meta_value
5804
5796
5805 if self._file_store_meta_value_type:
5797 if self._file_store_meta_value_type:
5806 # e.g unicode.encrypted == unicode
5798 # e.g unicode.encrypted == unicode
5807 _type = self._file_store_meta_value_type.split('.')[0]
5799 _type = self._file_store_meta_value_type.split('.')[0]
5808 # decode the encrypted value if it's encrypted field type
5800 # decode the encrypted value if it's encrypted field type
5809 if '.encrypted' in self._file_store_meta_value_type:
5801 if '.encrypted' in self._file_store_meta_value_type:
5810 cipher = EncryptedTextValue()
5802 cipher = EncryptedTextValue()
5811 val = safe_str(cipher.process_result_value(val, None))
5803 val = safe_str(cipher.process_result_value(val, None))
5812 # do final type conversion
5804 # do final type conversion
5813 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5805 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5814 val = converter(val)
5806 val = converter(val)
5815
5807
5816 return val
5808 return val
5817
5809
5818 @file_store_meta_value.setter
5810 @file_store_meta_value.setter
5819 def file_store_meta_value(self, val):
5811 def file_store_meta_value(self, val):
5820 val = safe_str(val)
5812 val = safe_str(val)
5821 # encode the encrypted value
5813 # encode the encrypted value
5822 if '.encrypted' in self.file_store_meta_value_type:
5814 if '.encrypted' in self.file_store_meta_value_type:
5823 cipher = EncryptedTextValue()
5815 cipher = EncryptedTextValue()
5824 val = safe_str(cipher.process_bind_param(val, None))
5816 val = safe_str(cipher.process_bind_param(val, None))
5825 self._file_store_meta_value = val
5817 self._file_store_meta_value = val
5826
5818
5827 @hybrid_property
5819 @hybrid_property
5828 def file_store_meta_value_type(self):
5820 def file_store_meta_value_type(self):
5829 return self._file_store_meta_value_type
5821 return self._file_store_meta_value_type
5830
5822
5831 @file_store_meta_value_type.setter
5823 @file_store_meta_value_type.setter
5832 def file_store_meta_value_type(self, val):
5824 def file_store_meta_value_type(self, val):
5833 # e.g unicode.encrypted
5825 # e.g unicode.encrypted
5834 self.valid_value_type(val)
5826 self.valid_value_type(val)
5835 self._file_store_meta_value_type = val
5827 self._file_store_meta_value_type = val
5836
5828
5837 def __json__(self):
5829 def __json__(self):
5838 data = {
5830 data = {
5839 'artifact': self.file_store.file_uid,
5831 'artifact': self.file_store.file_uid,
5840 'section': self.file_store_meta_section,
5832 'section': self.file_store_meta_section,
5841 'key': self.file_store_meta_key,
5833 'key': self.file_store_meta_key,
5842 'value': self.file_store_meta_value,
5834 'value': self.file_store_meta_value,
5843 }
5835 }
5844
5836
5845 return data
5837 return data
5846
5838
5847 def __repr__(self):
5839 def __repr__(self):
5848 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5840 return '<%s[%s]%s=>%s]>' % (self.cls_name, self.file_store_meta_section,
5849 self.file_store_meta_key, self.file_store_meta_value)
5841 self.file_store_meta_key, self.file_store_meta_value)
5850
5842
5851
5843
5852 class DbMigrateVersion(Base, BaseModel):
5844 class DbMigrateVersion(Base, BaseModel):
5853 __tablename__ = 'db_migrate_version'
5845 __tablename__ = 'db_migrate_version'
5854 __table_args__ = (
5846 __table_args__ = (
5855 base_table_args,
5847 base_table_args,
5856 )
5848 )
5857
5849
5858 repository_id = Column('repository_id', String(250), primary_key=True)
5850 repository_id = Column('repository_id', String(250), primary_key=True)
5859 repository_path = Column('repository_path', Text)
5851 repository_path = Column('repository_path', Text)
5860 version = Column('version', Integer)
5852 version = Column('version', Integer)
5861
5853
5862 @classmethod
5854 @classmethod
5863 def set_version(cls, version):
5855 def set_version(cls, version):
5864 """
5856 """
5865 Helper for forcing a different version, usually for debugging purposes via ishell.
5857 Helper for forcing a different version, usually for debugging purposes via ishell.
5866 """
5858 """
5867 ver = DbMigrateVersion.query().first()
5859 ver = DbMigrateVersion.query().first()
5868 ver.version = version
5860 ver.version = version
5869 Session().commit()
5861 Session().commit()
5870
5862
5871
5863
5872 class DbSession(Base, BaseModel):
5864 class DbSession(Base, BaseModel):
5873 __tablename__ = 'db_session'
5865 __tablename__ = 'db_session'
5874 __table_args__ = (
5866 __table_args__ = (
5875 base_table_args,
5867 base_table_args,
5876 )
5868 )
5877
5869
5878 def __repr__(self):
5870 def __repr__(self):
5879 return f'<DB:DbSession({self.id})>'
5871 return f'<DB:DbSession({self.id})>'
5880
5872
5881 id = Column('id', Integer())
5873 id = Column('id', Integer())
5882 namespace = Column('namespace', String(255), primary_key=True)
5874 namespace = Column('namespace', String(255), primary_key=True)
5883 accessed = Column('accessed', DateTime, nullable=False)
5875 accessed = Column('accessed', DateTime, nullable=False)
5884 created = Column('created', DateTime, nullable=False)
5876 created = Column('created', DateTime, nullable=False)
5885 data = Column('data', PickleType, nullable=False)
5877 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now