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