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