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