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