##// END OF EJS Templates
permissions: move refresh operation as a flag to the get_default_user...
marcink -
r1764:f0bc9388 default
parent child Browse files
Show More
@@ -1,249 +1,244 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 """
23 23 permissions controller for RhodeCode Enterprise
24 24 """
25 25
26 26
27 27 import logging
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.model.db import User, UserIpMap
40 40 from rhodecode.model.forms import (
41 41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
42 42 from rhodecode.model.meta import Session
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.settings import SettingsModel
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class PermissionsController(BaseController):
50 50 """REST Controller styled on the Atom Publishing Protocol"""
51 51 # To properly map this controller, ensure your config/routing.py
52 52 # file has a resource setup:
53 53 # map.resource('permission', 'permissions')
54 54
55 55 @LoginRequired()
56 56 def __before__(self):
57 57 super(PermissionsController, self).__before__()
58 58
59 59 def __load_data(self):
60 60 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
61 61
62 62 @HasPermissionAllDecorator('hg.admin')
63 63 def permission_application(self):
64 64 c.active = 'application'
65 65 self.__load_data()
66 66
67 c.user = User.get_default_user()
68
69 # TODO: johbo: The default user might be based on outdated state which
70 # has been loaded from the cache. A call to refresh() ensures that the
71 # latest state from the database is used.
72 Session().refresh(c.user)
67 c.user = User.get_default_user(refresh=True)
73 68
74 69 app_settings = SettingsModel().get_all_settings()
75 70 defaults = {
76 71 'anonymous': c.user.active,
77 72 'default_register_message': app_settings.get(
78 73 'rhodecode_register_message')
79 74 }
80 75 defaults.update(c.user.get_default_perms())
81 76
82 77 return htmlfill.render(
83 78 render('admin/permissions/permissions.mako'),
84 79 defaults=defaults,
85 80 encoding="UTF-8",
86 81 force_defaults=False)
87 82
88 83 @HasPermissionAllDecorator('hg.admin')
89 84 @auth.CSRFRequired()
90 85 def permission_application_update(self):
91 86 c.active = 'application'
92 87 self.__load_data()
93 88 _form = ApplicationPermissionsForm(
94 89 [x[0] for x in c.register_choices],
95 90 [x[0] for x in c.password_reset_choices],
96 91 [x[0] for x in c.extern_activate_choices])()
97 92
98 93 try:
99 94 form_result = _form.to_python(dict(request.POST))
100 95 form_result.update({'perm_user_name': User.DEFAULT_USER})
101 96 PermissionModel().update_application_permissions(form_result)
102 97
103 98 settings = [
104 99 ('register_message', 'default_register_message'),
105 100 ]
106 101 for setting, form_key in settings:
107 102 sett = SettingsModel().create_or_update_setting(
108 103 setting, form_result[form_key])
109 104 Session().add(sett)
110 105
111 106 Session().commit()
112 107 h.flash(_('Application permissions updated successfully'),
113 108 category='success')
114 109
115 110 except formencode.Invalid as errors:
116 111 defaults = errors.value
117 112
118 113 return htmlfill.render(
119 114 render('admin/permissions/permissions.mako'),
120 115 defaults=defaults,
121 116 errors=errors.error_dict or {},
122 117 prefix_error=False,
123 118 encoding="UTF-8",
124 119 force_defaults=False)
125 120 except Exception:
126 121 log.exception("Exception during update of permissions")
127 122 h.flash(_('Error occurred during update of permissions'),
128 123 category='error')
129 124
130 125 return redirect(url('admin_permissions_application'))
131 126
132 127 @HasPermissionAllDecorator('hg.admin')
133 128 def permission_objects(self):
134 129 c.active = 'objects'
135 130 self.__load_data()
136 131 c.user = User.get_default_user()
137 132 defaults = {}
138 133 defaults.update(c.user.get_default_perms())
139 134 return htmlfill.render(
140 135 render('admin/permissions/permissions.mako'),
141 136 defaults=defaults,
142 137 encoding="UTF-8",
143 138 force_defaults=False)
144 139
145 140 @HasPermissionAllDecorator('hg.admin')
146 141 @auth.CSRFRequired()
147 142 def permission_objects_update(self):
148 143 c.active = 'objects'
149 144 self.__load_data()
150 145 _form = ObjectPermissionsForm(
151 146 [x[0] for x in c.repo_perms_choices],
152 147 [x[0] for x in c.group_perms_choices],
153 148 [x[0] for x in c.user_group_perms_choices])()
154 149
155 150 try:
156 151 form_result = _form.to_python(dict(request.POST))
157 152 form_result.update({'perm_user_name': User.DEFAULT_USER})
158 153 PermissionModel().update_object_permissions(form_result)
159 154
160 155 Session().commit()
161 156 h.flash(_('Object permissions updated successfully'),
162 157 category='success')
163 158
164 159 except formencode.Invalid as errors:
165 160 defaults = errors.value
166 161
167 162 return htmlfill.render(
168 163 render('admin/permissions/permissions.mako'),
169 164 defaults=defaults,
170 165 errors=errors.error_dict or {},
171 166 prefix_error=False,
172 167 encoding="UTF-8",
173 168 force_defaults=False)
174 169 except Exception:
175 170 log.exception("Exception during update of permissions")
176 171 h.flash(_('Error occurred during update of permissions'),
177 172 category='error')
178 173
179 174 return redirect(url('admin_permissions_object'))
180 175
181 176 @HasPermissionAllDecorator('hg.admin')
182 177 def permission_global(self):
183 178 c.active = 'global'
184 179 self.__load_data()
185 180
186 181 c.user = User.get_default_user()
187 182 defaults = {}
188 183 defaults.update(c.user.get_default_perms())
189 184
190 185 return htmlfill.render(
191 186 render('admin/permissions/permissions.mako'),
192 187 defaults=defaults,
193 188 encoding="UTF-8",
194 189 force_defaults=False)
195 190
196 191 @HasPermissionAllDecorator('hg.admin')
197 192 @auth.CSRFRequired()
198 193 def permission_global_update(self):
199 194 c.active = 'global'
200 195 self.__load_data()
201 196 _form = UserPermissionsForm(
202 197 [x[0] for x in c.repo_create_choices],
203 198 [x[0] for x in c.repo_create_on_write_choices],
204 199 [x[0] for x in c.repo_group_create_choices],
205 200 [x[0] for x in c.user_group_create_choices],
206 201 [x[0] for x in c.fork_choices],
207 202 [x[0] for x in c.inherit_default_permission_choices])()
208 203
209 204 try:
210 205 form_result = _form.to_python(dict(request.POST))
211 206 form_result.update({'perm_user_name': User.DEFAULT_USER})
212 207 PermissionModel().update_user_permissions(form_result)
213 208
214 209 Session().commit()
215 210 h.flash(_('Global permissions updated successfully'),
216 211 category='success')
217 212
218 213 except formencode.Invalid as errors:
219 214 defaults = errors.value
220 215
221 216 return htmlfill.render(
222 217 render('admin/permissions/permissions.mako'),
223 218 defaults=defaults,
224 219 errors=errors.error_dict or {},
225 220 prefix_error=False,
226 221 encoding="UTF-8",
227 222 force_defaults=False)
228 223 except Exception:
229 224 log.exception("Exception during update of permissions")
230 225 h.flash(_('Error occurred during update of permissions'),
231 226 category='error')
232 227
233 228 return redirect(url('admin_permissions_global'))
234 229
235 230 @HasPermissionAllDecorator('hg.admin')
236 231 def permission_ips(self):
237 232 c.active = 'ips'
238 233 c.user = User.get_default_user()
239 234 c.user_ip_map = (
240 235 UserIpMap.query().filter(UserIpMap.user == c.user).all())
241 236
242 237 return render('admin/permissions/permissions.mako')
243 238
244 239 @HasPermissionAllDecorator('hg.admin')
245 240 def permission_perms(self):
246 241 c.active = 'perms'
247 242 c.user = User.get_default_user()
248 243 c.perm_user = c.user.AuthUser
249 244 return render('admin/permissions/permissions.mako')
@@ -1,3987 +1,3993 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37
38 38 from sqlalchemy import *
39 39 from sqlalchemy.ext.declarative import declared_attr
40 40 from sqlalchemy.ext.hybrid import hybrid_property
41 41 from sqlalchemy.orm import (
42 42 relationship, joinedload, class_mapper, validates, aliased)
43 43 from sqlalchemy.sql.expression import true
44 44 from beaker.cache import cache_region
45 45 from zope.cachedescriptors.property import Lazy as LazyProperty
46 46
47 47 from pylons import url
48 48 from pylons.i18n.translation import lazy_ugettext as _
49 49
50 50 from rhodecode.lib.vcs import get_vcs_instance
51 51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 52 from rhodecode.lib.utils2 import (
53 53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 55 glob2re, StrictAttributeDict, cleaned_uri)
56 56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 57 from rhodecode.lib.ext_json import json
58 58 from rhodecode.lib.caching_query import FromCache
59 59 from rhodecode.lib.encrypt import AESCipher
60 60
61 61 from rhodecode.model.meta import Base, Session
62 62
63 63 URL_SEP = '/'
64 64 log = logging.getLogger(__name__)
65 65
66 66 # =============================================================================
67 67 # BASE CLASSES
68 68 # =============================================================================
69 69
70 70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 71 # beaker.session.secret if first is not set.
72 72 # and initialized at environment.py
73 73 ENCRYPTION_KEY = None
74 74
75 75 # used to sort permissions by types, '#' used here is not allowed to be in
76 76 # usernames, and it's very early in sorted string.printable table.
77 77 PERMISSION_TYPE_SORT = {
78 78 'admin': '####',
79 79 'write': '###',
80 80 'read': '##',
81 81 'none': '#',
82 82 }
83 83
84 84
85 85 def display_sort(obj):
86 86 """
87 87 Sort function used to sort permissions in .permissions() function of
88 88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 89 of all other resources
90 90 """
91 91
92 92 if obj.username == User.DEFAULT_USER:
93 93 return '#####'
94 94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 95 return prefix + obj.username
96 96
97 97
98 98 def _hash_key(k):
99 99 return md5_safe(k)
100 100
101 101
102 102 class EncryptedTextValue(TypeDecorator):
103 103 """
104 104 Special column for encrypted long text data, use like::
105 105
106 106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 107
108 108 This column is intelligent so if value is in unencrypted form it return
109 109 unencrypted form, but on save it always encrypts
110 110 """
111 111 impl = Text
112 112
113 113 def process_bind_param(self, value, dialect):
114 114 if not value:
115 115 return value
116 116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 117 # protect against double encrypting if someone manually starts
118 118 # doing
119 119 raise ValueError('value needs to be in unencrypted format, ie. '
120 120 'not starting with enc$aes')
121 121 return 'enc$aes_hmac$%s' % AESCipher(
122 122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 123
124 124 def process_result_value(self, value, dialect):
125 125 import rhodecode
126 126
127 127 if not value:
128 128 return value
129 129
130 130 parts = value.split('$', 3)
131 131 if not len(parts) == 3:
132 132 # probably not encrypted values
133 133 return value
134 134 else:
135 135 if parts[0] != 'enc':
136 136 # parts ok but without our header ?
137 137 return value
138 138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 139 'rhodecode.encrypted_values.strict') or True)
140 140 # at that stage we know it's our encryption
141 141 if parts[1] == 'aes':
142 142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 143 elif parts[1] == 'aes_hmac':
144 144 decrypted_data = AESCipher(
145 145 ENCRYPTION_KEY, hmac=True,
146 146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 147 else:
148 148 raise ValueError(
149 149 'Encryption type part is wrong, must be `aes` '
150 150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 151 return decrypted_data
152 152
153 153
154 154 class BaseModel(object):
155 155 """
156 156 Base Model for all classes
157 157 """
158 158
159 159 @classmethod
160 160 def _get_keys(cls):
161 161 """return column names for this model """
162 162 return class_mapper(cls).c.keys()
163 163
164 164 def get_dict(self):
165 165 """
166 166 return dict with keys and values corresponding
167 167 to this model data """
168 168
169 169 d = {}
170 170 for k in self._get_keys():
171 171 d[k] = getattr(self, k)
172 172
173 173 # also use __json__() if present to get additional fields
174 174 _json_attr = getattr(self, '__json__', None)
175 175 if _json_attr:
176 176 # update with attributes from __json__
177 177 if callable(_json_attr):
178 178 _json_attr = _json_attr()
179 179 for k, val in _json_attr.iteritems():
180 180 d[k] = val
181 181 return d
182 182
183 183 def get_appstruct(self):
184 184 """return list with keys and values tuples corresponding
185 185 to this model data """
186 186
187 187 l = []
188 188 for k in self._get_keys():
189 189 l.append((k, getattr(self, k),))
190 190 return l
191 191
192 192 def populate_obj(self, populate_dict):
193 193 """populate model with data from given populate_dict"""
194 194
195 195 for k in self._get_keys():
196 196 if k in populate_dict:
197 197 setattr(self, k, populate_dict[k])
198 198
199 199 @classmethod
200 200 def query(cls):
201 201 return Session().query(cls)
202 202
203 203 @classmethod
204 204 def get(cls, id_):
205 205 if id_:
206 206 return cls.query().get(id_)
207 207
208 208 @classmethod
209 209 def get_or_404(cls, id_, pyramid_exc=False):
210 210 if pyramid_exc:
211 211 # NOTE(marcink): backward compat, once migration to pyramid
212 212 # this should only use pyramid exceptions
213 213 from pyramid.httpexceptions import HTTPNotFound
214 214 else:
215 215 from webob.exc import HTTPNotFound
216 216
217 217 try:
218 218 id_ = int(id_)
219 219 except (TypeError, ValueError):
220 220 raise HTTPNotFound
221 221
222 222 res = cls.query().get(id_)
223 223 if not res:
224 224 raise HTTPNotFound
225 225 return res
226 226
227 227 @classmethod
228 228 def getAll(cls):
229 229 # deprecated and left for backward compatibility
230 230 return cls.get_all()
231 231
232 232 @classmethod
233 233 def get_all(cls):
234 234 return cls.query().all()
235 235
236 236 @classmethod
237 237 def delete(cls, id_):
238 238 obj = cls.query().get(id_)
239 239 Session().delete(obj)
240 240
241 241 @classmethod
242 242 def identity_cache(cls, session, attr_name, value):
243 243 exist_in_session = []
244 244 for (item_cls, pkey), instance in session.identity_map.items():
245 245 if cls == item_cls and getattr(instance, attr_name) == value:
246 246 exist_in_session.append(instance)
247 247 if exist_in_session:
248 248 if len(exist_in_session) == 1:
249 249 return exist_in_session[0]
250 250 log.exception(
251 251 'multiple objects with attr %s and '
252 252 'value %s found with same name: %r',
253 253 attr_name, value, exist_in_session)
254 254
255 255 def __repr__(self):
256 256 if hasattr(self, '__unicode__'):
257 257 # python repr needs to return str
258 258 try:
259 259 return safe_str(self.__unicode__())
260 260 except UnicodeDecodeError:
261 261 pass
262 262 return '<DB:%s>' % (self.__class__.__name__)
263 263
264 264
265 265 class RhodeCodeSetting(Base, BaseModel):
266 266 __tablename__ = 'rhodecode_settings'
267 267 __table_args__ = (
268 268 UniqueConstraint('app_settings_name'),
269 269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 271 )
272 272
273 273 SETTINGS_TYPES = {
274 274 'str': safe_str,
275 275 'int': safe_int,
276 276 'unicode': safe_unicode,
277 277 'bool': str2bool,
278 278 'list': functools.partial(aslist, sep=',')
279 279 }
280 280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 281 GLOBAL_CONF_KEY = 'app_settings'
282 282
283 283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287 287
288 288 def __init__(self, key='', val='', type='unicode'):
289 289 self.app_settings_name = key
290 290 self.app_settings_type = type
291 291 self.app_settings_value = val
292 292
293 293 @validates('_app_settings_value')
294 294 def validate_settings_value(self, key, val):
295 295 assert type(val) == unicode
296 296 return val
297 297
298 298 @hybrid_property
299 299 def app_settings_value(self):
300 300 v = self._app_settings_value
301 301 _type = self.app_settings_type
302 302 if _type:
303 303 _type = self.app_settings_type.split('.')[0]
304 304 # decode the encrypted value
305 305 if 'encrypted' in self.app_settings_type:
306 306 cipher = EncryptedTextValue()
307 307 v = safe_unicode(cipher.process_result_value(v, None))
308 308
309 309 converter = self.SETTINGS_TYPES.get(_type) or \
310 310 self.SETTINGS_TYPES['unicode']
311 311 return converter(v)
312 312
313 313 @app_settings_value.setter
314 314 def app_settings_value(self, val):
315 315 """
316 316 Setter that will always make sure we use unicode in app_settings_value
317 317
318 318 :param val:
319 319 """
320 320 val = safe_unicode(val)
321 321 # encode the encrypted value
322 322 if 'encrypted' in self.app_settings_type:
323 323 cipher = EncryptedTextValue()
324 324 val = safe_unicode(cipher.process_bind_param(val, None))
325 325 self._app_settings_value = val
326 326
327 327 @hybrid_property
328 328 def app_settings_type(self):
329 329 return self._app_settings_type
330 330
331 331 @app_settings_type.setter
332 332 def app_settings_type(self, val):
333 333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 334 raise Exception('type must be one of %s got %s'
335 335 % (self.SETTINGS_TYPES.keys(), val))
336 336 self._app_settings_type = val
337 337
338 338 def __unicode__(self):
339 339 return u"<%s('%s:%s[%s]')>" % (
340 340 self.__class__.__name__,
341 341 self.app_settings_name, self.app_settings_value,
342 342 self.app_settings_type
343 343 )
344 344
345 345
346 346 class RhodeCodeUi(Base, BaseModel):
347 347 __tablename__ = 'rhodecode_ui'
348 348 __table_args__ = (
349 349 UniqueConstraint('ui_key'),
350 350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 352 )
353 353
354 354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 355 # HG
356 356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 357 HOOK_PULL = 'outgoing.pull_logger'
358 358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 360 HOOK_PUSH = 'changegroup.push_logger'
361 361 HOOK_PUSH_KEY = 'pushkey.key_push'
362 362
363 363 # TODO: johbo: Unify way how hooks are configured for git and hg,
364 364 # git part is currently hardcoded.
365 365
366 366 # SVN PATTERNS
367 367 SVN_BRANCH_ID = 'vcs_svn_branch'
368 368 SVN_TAG_ID = 'vcs_svn_tag'
369 369
370 370 ui_id = Column(
371 371 "ui_id", Integer(), nullable=False, unique=True, default=None,
372 372 primary_key=True)
373 373 ui_section = Column(
374 374 "ui_section", String(255), nullable=True, unique=None, default=None)
375 375 ui_key = Column(
376 376 "ui_key", String(255), nullable=True, unique=None, default=None)
377 377 ui_value = Column(
378 378 "ui_value", String(255), nullable=True, unique=None, default=None)
379 379 ui_active = Column(
380 380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
381 381
382 382 def __repr__(self):
383 383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
384 384 self.ui_key, self.ui_value)
385 385
386 386
387 387 class RepoRhodeCodeSetting(Base, BaseModel):
388 388 __tablename__ = 'repo_rhodecode_settings'
389 389 __table_args__ = (
390 390 UniqueConstraint(
391 391 'app_settings_name', 'repository_id',
392 392 name='uq_repo_rhodecode_setting_name_repo_id'),
393 393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
394 394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
395 395 )
396 396
397 397 repository_id = Column(
398 398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
399 399 nullable=False)
400 400 app_settings_id = Column(
401 401 "app_settings_id", Integer(), nullable=False, unique=True,
402 402 default=None, primary_key=True)
403 403 app_settings_name = Column(
404 404 "app_settings_name", String(255), nullable=True, unique=None,
405 405 default=None)
406 406 _app_settings_value = Column(
407 407 "app_settings_value", String(4096), nullable=True, unique=None,
408 408 default=None)
409 409 _app_settings_type = Column(
410 410 "app_settings_type", String(255), nullable=True, unique=None,
411 411 default=None)
412 412
413 413 repository = relationship('Repository')
414 414
415 415 def __init__(self, repository_id, key='', val='', type='unicode'):
416 416 self.repository_id = repository_id
417 417 self.app_settings_name = key
418 418 self.app_settings_type = type
419 419 self.app_settings_value = val
420 420
421 421 @validates('_app_settings_value')
422 422 def validate_settings_value(self, key, val):
423 423 assert type(val) == unicode
424 424 return val
425 425
426 426 @hybrid_property
427 427 def app_settings_value(self):
428 428 v = self._app_settings_value
429 429 type_ = self.app_settings_type
430 430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
431 431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
432 432 return converter(v)
433 433
434 434 @app_settings_value.setter
435 435 def app_settings_value(self, val):
436 436 """
437 437 Setter that will always make sure we use unicode in app_settings_value
438 438
439 439 :param val:
440 440 """
441 441 self._app_settings_value = safe_unicode(val)
442 442
443 443 @hybrid_property
444 444 def app_settings_type(self):
445 445 return self._app_settings_type
446 446
447 447 @app_settings_type.setter
448 448 def app_settings_type(self, val):
449 449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
450 450 if val not in SETTINGS_TYPES:
451 451 raise Exception('type must be one of %s got %s'
452 452 % (SETTINGS_TYPES.keys(), val))
453 453 self._app_settings_type = val
454 454
455 455 def __unicode__(self):
456 456 return u"<%s('%s:%s:%s[%s]')>" % (
457 457 self.__class__.__name__, self.repository.repo_name,
458 458 self.app_settings_name, self.app_settings_value,
459 459 self.app_settings_type
460 460 )
461 461
462 462
463 463 class RepoRhodeCodeUi(Base, BaseModel):
464 464 __tablename__ = 'repo_rhodecode_ui'
465 465 __table_args__ = (
466 466 UniqueConstraint(
467 467 'repository_id', 'ui_section', 'ui_key',
468 468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
469 469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
470 470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
471 471 )
472 472
473 473 repository_id = Column(
474 474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
475 475 nullable=False)
476 476 ui_id = Column(
477 477 "ui_id", Integer(), nullable=False, unique=True, default=None,
478 478 primary_key=True)
479 479 ui_section = Column(
480 480 "ui_section", String(255), nullable=True, unique=None, default=None)
481 481 ui_key = Column(
482 482 "ui_key", String(255), nullable=True, unique=None, default=None)
483 483 ui_value = Column(
484 484 "ui_value", String(255), nullable=True, unique=None, default=None)
485 485 ui_active = Column(
486 486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
487 487
488 488 repository = relationship('Repository')
489 489
490 490 def __repr__(self):
491 491 return '<%s[%s:%s]%s=>%s]>' % (
492 492 self.__class__.__name__, self.repository.repo_name,
493 493 self.ui_section, self.ui_key, self.ui_value)
494 494
495 495
496 496 class User(Base, BaseModel):
497 497 __tablename__ = 'users'
498 498 __table_args__ = (
499 499 UniqueConstraint('username'), UniqueConstraint('email'),
500 500 Index('u_username_idx', 'username'),
501 501 Index('u_email_idx', 'email'),
502 502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
503 503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
504 504 )
505 505 DEFAULT_USER = 'default'
506 506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
507 507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
508 508
509 509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
510 510 username = Column("username", String(255), nullable=True, unique=None, default=None)
511 511 password = Column("password", String(255), nullable=True, unique=None, default=None)
512 512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
513 513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
514 514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
515 515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
516 516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
517 517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
518 518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
519 519
520 520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
521 521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
522 522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
523 523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
525 525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
526 526
527 527 user_log = relationship('UserLog')
528 528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
529 529
530 530 repositories = relationship('Repository')
531 531 repository_groups = relationship('RepoGroup')
532 532 user_groups = relationship('UserGroup')
533 533
534 534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
535 535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
536 536
537 537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
538 538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
539 539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
540 540
541 541 group_member = relationship('UserGroupMember', cascade='all')
542 542
543 543 notifications = relationship('UserNotification', cascade='all')
544 544 # notifications assigned to this user
545 545 user_created_notifications = relationship('Notification', cascade='all')
546 546 # comments created by this user
547 547 user_comments = relationship('ChangesetComment', cascade='all')
548 548 # user profile extra info
549 549 user_emails = relationship('UserEmailMap', cascade='all')
550 550 user_ip_map = relationship('UserIpMap', cascade='all')
551 551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
552 552 # gists
553 553 user_gists = relationship('Gist', cascade='all')
554 554 # user pull requests
555 555 user_pull_requests = relationship('PullRequest', cascade='all')
556 556 # external identities
557 557 extenal_identities = relationship(
558 558 'ExternalIdentity',
559 559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
560 560 cascade='all')
561 561
562 562 def __unicode__(self):
563 563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
564 564 self.user_id, self.username)
565 565
566 566 @hybrid_property
567 567 def email(self):
568 568 return self._email
569 569
570 570 @email.setter
571 571 def email(self, val):
572 572 self._email = val.lower() if val else None
573 573
574 574 @hybrid_property
575 575 def api_key(self):
576 576 """
577 577 Fetch if exist an auth-token with role ALL connected to this user
578 578 """
579 579 user_auth_token = UserApiKeys.query()\
580 580 .filter(UserApiKeys.user_id == self.user_id)\
581 581 .filter(or_(UserApiKeys.expires == -1,
582 582 UserApiKeys.expires >= time.time()))\
583 583 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
584 584 if user_auth_token:
585 585 user_auth_token = user_auth_token.api_key
586 586
587 587 return user_auth_token
588 588
589 589 @api_key.setter
590 590 def api_key(self, val):
591 591 # don't allow to set API key this is deprecated for now
592 592 self._api_key = None
593 593
594 594 @property
595 595 def firstname(self):
596 596 # alias for future
597 597 return self.name
598 598
599 599 @property
600 600 def emails(self):
601 601 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
602 602 return [self.email] + [x.email for x in other]
603 603
604 604 @property
605 605 def auth_tokens(self):
606 606 return [x.api_key for x in self.extra_auth_tokens]
607 607
608 608 @property
609 609 def extra_auth_tokens(self):
610 610 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
611 611
612 612 @property
613 613 def feed_token(self):
614 614 return self.get_feed_token()
615 615
616 616 def get_feed_token(self):
617 617 feed_tokens = UserApiKeys.query()\
618 618 .filter(UserApiKeys.user == self)\
619 619 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
620 620 .all()
621 621 if feed_tokens:
622 622 return feed_tokens[0].api_key
623 623 return 'NO_FEED_TOKEN_AVAILABLE'
624 624
625 625 @classmethod
626 626 def extra_valid_auth_tokens(cls, user, role=None):
627 627 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
628 628 .filter(or_(UserApiKeys.expires == -1,
629 629 UserApiKeys.expires >= time.time()))
630 630 if role:
631 631 tokens = tokens.filter(or_(UserApiKeys.role == role,
632 632 UserApiKeys.role == UserApiKeys.ROLE_ALL))
633 633 return tokens.all()
634 634
635 635 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
636 636 from rhodecode.lib import auth
637 637
638 638 log.debug('Trying to authenticate user: %s via auth-token, '
639 639 'and roles: %s', self, roles)
640 640
641 641 if not auth_token:
642 642 return False
643 643
644 644 crypto_backend = auth.crypto_backend()
645 645
646 646 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
647 647 tokens_q = UserApiKeys.query()\
648 648 .filter(UserApiKeys.user_id == self.user_id)\
649 649 .filter(or_(UserApiKeys.expires == -1,
650 650 UserApiKeys.expires >= time.time()))
651 651
652 652 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
653 653
654 654 plain_tokens = []
655 655 hash_tokens = []
656 656
657 657 for token in tokens_q.all():
658 658 # verify scope first
659 659 if token.repo_id:
660 660 # token has a scope, we need to verify it
661 661 if scope_repo_id != token.repo_id:
662 662 log.debug(
663 663 'Scope mismatch: token has a set repo scope: %s, '
664 664 'and calling scope is:%s, skipping further checks',
665 665 token.repo, scope_repo_id)
666 666 # token has a scope, and it doesn't match, skip token
667 667 continue
668 668
669 669 if token.api_key.startswith(crypto_backend.ENC_PREF):
670 670 hash_tokens.append(token.api_key)
671 671 else:
672 672 plain_tokens.append(token.api_key)
673 673
674 674 is_plain_match = auth_token in plain_tokens
675 675 if is_plain_match:
676 676 return True
677 677
678 678 for hashed in hash_tokens:
679 679 # TODO(marcink): this is expensive to calculate, but most secure
680 680 match = crypto_backend.hash_check(auth_token, hashed)
681 681 if match:
682 682 return True
683 683
684 684 return False
685 685
686 686 @property
687 687 def ip_addresses(self):
688 688 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
689 689 return [x.ip_addr for x in ret]
690 690
691 691 @property
692 692 def username_and_name(self):
693 693 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
694 694
695 695 @property
696 696 def username_or_name_or_email(self):
697 697 full_name = self.full_name if self.full_name is not ' ' else None
698 698 return self.username or full_name or self.email
699 699
700 700 @property
701 701 def full_name(self):
702 702 return '%s %s' % (self.firstname, self.lastname)
703 703
704 704 @property
705 705 def full_name_or_username(self):
706 706 return ('%s %s' % (self.firstname, self.lastname)
707 707 if (self.firstname and self.lastname) else self.username)
708 708
709 709 @property
710 710 def full_contact(self):
711 711 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
712 712
713 713 @property
714 714 def short_contact(self):
715 715 return '%s %s' % (self.firstname, self.lastname)
716 716
717 717 @property
718 718 def is_admin(self):
719 719 return self.admin
720 720
721 721 @property
722 722 def AuthUser(self):
723 723 """
724 724 Returns instance of AuthUser for this user
725 725 """
726 726 from rhodecode.lib.auth import AuthUser
727 727 return AuthUser(user_id=self.user_id, username=self.username)
728 728
729 729 @hybrid_property
730 730 def user_data(self):
731 731 if not self._user_data:
732 732 return {}
733 733
734 734 try:
735 735 return json.loads(self._user_data)
736 736 except TypeError:
737 737 return {}
738 738
739 739 @user_data.setter
740 740 def user_data(self, val):
741 741 if not isinstance(val, dict):
742 742 raise Exception('user_data must be dict, got %s' % type(val))
743 743 try:
744 744 self._user_data = json.dumps(val)
745 745 except Exception:
746 746 log.error(traceback.format_exc())
747 747
748 748 @classmethod
749 749 def get_by_username(cls, username, case_insensitive=False,
750 750 cache=False, identity_cache=False):
751 751 session = Session()
752 752
753 753 if case_insensitive:
754 754 q = cls.query().filter(
755 755 func.lower(cls.username) == func.lower(username))
756 756 else:
757 757 q = cls.query().filter(cls.username == username)
758 758
759 759 if cache:
760 760 if identity_cache:
761 761 val = cls.identity_cache(session, 'username', username)
762 762 if val:
763 763 return val
764 764 else:
765 765 cache_key = "get_user_by_name_%s" % _hash_key(username)
766 766 q = q.options(
767 767 FromCache("sql_cache_short", cache_key))
768 768
769 769 return q.scalar()
770 770
771 771 @classmethod
772 772 def get_by_auth_token(cls, auth_token, cache=False):
773 773 q = UserApiKeys.query()\
774 774 .filter(UserApiKeys.api_key == auth_token)\
775 775 .filter(or_(UserApiKeys.expires == -1,
776 776 UserApiKeys.expires >= time.time()))
777 777 if cache:
778 778 q = q.options(
779 779 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
780 780
781 781 match = q.first()
782 782 if match:
783 783 return match.user
784 784
785 785 @classmethod
786 786 def get_by_email(cls, email, case_insensitive=False, cache=False):
787 787
788 788 if case_insensitive:
789 789 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
790 790
791 791 else:
792 792 q = cls.query().filter(cls.email == email)
793 793
794 794 email_key = _hash_key(email)
795 795 if cache:
796 796 q = q.options(
797 797 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
798 798
799 799 ret = q.scalar()
800 800 if ret is None:
801 801 q = UserEmailMap.query()
802 802 # try fetching in alternate email map
803 803 if case_insensitive:
804 804 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
805 805 else:
806 806 q = q.filter(UserEmailMap.email == email)
807 807 q = q.options(joinedload(UserEmailMap.user))
808 808 if cache:
809 809 q = q.options(
810 810 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
811 811 ret = getattr(q.scalar(), 'user', None)
812 812
813 813 return ret
814 814
815 815 @classmethod
816 816 def get_from_cs_author(cls, author):
817 817 """
818 818 Tries to get User objects out of commit author string
819 819
820 820 :param author:
821 821 """
822 822 from rhodecode.lib.helpers import email, author_name
823 823 # Valid email in the attribute passed, see if they're in the system
824 824 _email = email(author)
825 825 if _email:
826 826 user = cls.get_by_email(_email, case_insensitive=True)
827 827 if user:
828 828 return user
829 829 # Maybe we can match by username?
830 830 _author = author_name(author)
831 831 user = cls.get_by_username(_author, case_insensitive=True)
832 832 if user:
833 833 return user
834 834
835 835 def update_userdata(self, **kwargs):
836 836 usr = self
837 837 old = usr.user_data
838 838 old.update(**kwargs)
839 839 usr.user_data = old
840 840 Session().add(usr)
841 841 log.debug('updated userdata with ', kwargs)
842 842
843 843 def update_lastlogin(self):
844 844 """Update user lastlogin"""
845 845 self.last_login = datetime.datetime.now()
846 846 Session().add(self)
847 847 log.debug('updated user %s lastlogin', self.username)
848 848
849 849 def update_lastactivity(self):
850 850 """Update user lastactivity"""
851 851 self.last_activity = datetime.datetime.now()
852 852 Session().add(self)
853 853 log.debug('updated user %s lastactivity', self.username)
854 854
855 855 def update_password(self, new_password):
856 856 from rhodecode.lib.auth import get_crypt_password
857 857
858 858 self.password = get_crypt_password(new_password)
859 859 Session().add(self)
860 860
861 861 @classmethod
862 862 def get_first_super_admin(cls):
863 863 user = User.query().filter(User.admin == true()).first()
864 864 if user is None:
865 865 raise Exception('FATAL: Missing administrative account!')
866 866 return user
867 867
868 868 @classmethod
869 869 def get_all_super_admins(cls):
870 870 """
871 871 Returns all admin accounts sorted by username
872 872 """
873 873 return User.query().filter(User.admin == true())\
874 874 .order_by(User.username.asc()).all()
875 875
876 876 @classmethod
877 def get_default_user(cls, cache=False):
877 def get_default_user(cls, cache=False, refresh=False):
878 878 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
879 879 if user is None:
880 880 raise Exception('FATAL: Missing default account!')
881 if refresh:
882 # The default user might be based on outdated state which
883 # has been loaded from the cache.
884 # A call to refresh() ensures that the
885 # latest state from the database is used.
886 Session().refresh(user)
881 887 return user
882 888
883 889 def _get_default_perms(self, user, suffix=''):
884 890 from rhodecode.model.permission import PermissionModel
885 891 return PermissionModel().get_default_perms(user.user_perms, suffix)
886 892
887 893 def get_default_perms(self, suffix=''):
888 894 return self._get_default_perms(self, suffix)
889 895
890 896 def get_api_data(self, include_secrets=False, details='full'):
891 897 """
892 898 Common function for generating user related data for API
893 899
894 900 :param include_secrets: By default secrets in the API data will be replaced
895 901 by a placeholder value to prevent exposing this data by accident. In case
896 902 this data shall be exposed, set this flag to ``True``.
897 903
898 904 :param details: details can be 'basic|full' basic gives only a subset of
899 905 the available user information that includes user_id, name and emails.
900 906 """
901 907 user = self
902 908 user_data = self.user_data
903 909 data = {
904 910 'user_id': user.user_id,
905 911 'username': user.username,
906 912 'firstname': user.name,
907 913 'lastname': user.lastname,
908 914 'email': user.email,
909 915 'emails': user.emails,
910 916 }
911 917 if details == 'basic':
912 918 return data
913 919
914 920 api_key_length = 40
915 921 api_key_replacement = '*' * api_key_length
916 922
917 923 extras = {
918 924 'api_keys': [api_key_replacement],
919 925 'auth_tokens': [api_key_replacement],
920 926 'active': user.active,
921 927 'admin': user.admin,
922 928 'extern_type': user.extern_type,
923 929 'extern_name': user.extern_name,
924 930 'last_login': user.last_login,
925 931 'last_activity': user.last_activity,
926 932 'ip_addresses': user.ip_addresses,
927 933 'language': user_data.get('language')
928 934 }
929 935 data.update(extras)
930 936
931 937 if include_secrets:
932 938 data['api_keys'] = user.auth_tokens
933 939 data['auth_tokens'] = user.extra_auth_tokens
934 940 return data
935 941
936 942 def __json__(self):
937 943 data = {
938 944 'full_name': self.full_name,
939 945 'full_name_or_username': self.full_name_or_username,
940 946 'short_contact': self.short_contact,
941 947 'full_contact': self.full_contact,
942 948 }
943 949 data.update(self.get_api_data())
944 950 return data
945 951
946 952
947 953 class UserApiKeys(Base, BaseModel):
948 954 __tablename__ = 'user_api_keys'
949 955 __table_args__ = (
950 956 Index('uak_api_key_idx', 'api_key'),
951 957 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
952 958 UniqueConstraint('api_key'),
953 959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
954 960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
955 961 )
956 962 __mapper_args__ = {}
957 963
958 964 # ApiKey role
959 965 ROLE_ALL = 'token_role_all'
960 966 ROLE_HTTP = 'token_role_http'
961 967 ROLE_VCS = 'token_role_vcs'
962 968 ROLE_API = 'token_role_api'
963 969 ROLE_FEED = 'token_role_feed'
964 970 ROLE_PASSWORD_RESET = 'token_password_reset'
965 971
966 972 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
967 973
968 974 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 975 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
970 976 api_key = Column("api_key", String(255), nullable=False, unique=True)
971 977 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
972 978 expires = Column('expires', Float(53), nullable=False)
973 979 role = Column('role', String(255), nullable=True)
974 980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
975 981
976 982 # scope columns
977 983 repo_id = Column(
978 984 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
979 985 nullable=True, unique=None, default=None)
980 986 repo = relationship('Repository', lazy='joined')
981 987
982 988 repo_group_id = Column(
983 989 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
984 990 nullable=True, unique=None, default=None)
985 991 repo_group = relationship('RepoGroup', lazy='joined')
986 992
987 993 user = relationship('User', lazy='joined')
988 994
989 995 def __unicode__(self):
990 996 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
991 997
992 998 def __json__(self):
993 999 data = {
994 1000 'auth_token': self.api_key,
995 1001 'role': self.role,
996 1002 'scope': self.scope_humanized,
997 1003 'expired': self.expired
998 1004 }
999 1005 return data
1000 1006
1001 1007 @property
1002 1008 def expired(self):
1003 1009 if self.expires == -1:
1004 1010 return False
1005 1011 return time.time() > self.expires
1006 1012
1007 1013 @classmethod
1008 1014 def _get_role_name(cls, role):
1009 1015 return {
1010 1016 cls.ROLE_ALL: _('all'),
1011 1017 cls.ROLE_HTTP: _('http/web interface'),
1012 1018 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1013 1019 cls.ROLE_API: _('api calls'),
1014 1020 cls.ROLE_FEED: _('feed access'),
1015 1021 }.get(role, role)
1016 1022
1017 1023 @property
1018 1024 def role_humanized(self):
1019 1025 return self._get_role_name(self.role)
1020 1026
1021 1027 def _get_scope(self):
1022 1028 if self.repo:
1023 1029 return repr(self.repo)
1024 1030 if self.repo_group:
1025 1031 return repr(self.repo_group) + ' (recursive)'
1026 1032 return 'global'
1027 1033
1028 1034 @property
1029 1035 def scope_humanized(self):
1030 1036 return self._get_scope()
1031 1037
1032 1038
1033 1039 class UserEmailMap(Base, BaseModel):
1034 1040 __tablename__ = 'user_email_map'
1035 1041 __table_args__ = (
1036 1042 Index('uem_email_idx', 'email'),
1037 1043 UniqueConstraint('email'),
1038 1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1039 1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1040 1046 )
1041 1047 __mapper_args__ = {}
1042 1048
1043 1049 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1045 1051 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1046 1052 user = relationship('User', lazy='joined')
1047 1053
1048 1054 @validates('_email')
1049 1055 def validate_email(self, key, email):
1050 1056 # check if this email is not main one
1051 1057 main_email = Session().query(User).filter(User.email == email).scalar()
1052 1058 if main_email is not None:
1053 1059 raise AttributeError('email %s is present is user table' % email)
1054 1060 return email
1055 1061
1056 1062 @hybrid_property
1057 1063 def email(self):
1058 1064 return self._email
1059 1065
1060 1066 @email.setter
1061 1067 def email(self, val):
1062 1068 self._email = val.lower() if val else None
1063 1069
1064 1070
1065 1071 class UserIpMap(Base, BaseModel):
1066 1072 __tablename__ = 'user_ip_map'
1067 1073 __table_args__ = (
1068 1074 UniqueConstraint('user_id', 'ip_addr'),
1069 1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1070 1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1071 1077 )
1072 1078 __mapper_args__ = {}
1073 1079
1074 1080 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1076 1082 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1077 1083 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1078 1084 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1079 1085 user = relationship('User', lazy='joined')
1080 1086
1081 1087 @classmethod
1082 1088 def _get_ip_range(cls, ip_addr):
1083 1089 net = ipaddress.ip_network(ip_addr, strict=False)
1084 1090 return [str(net.network_address), str(net.broadcast_address)]
1085 1091
1086 1092 def __json__(self):
1087 1093 return {
1088 1094 'ip_addr': self.ip_addr,
1089 1095 'ip_range': self._get_ip_range(self.ip_addr),
1090 1096 }
1091 1097
1092 1098 def __unicode__(self):
1093 1099 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1094 1100 self.user_id, self.ip_addr)
1095 1101
1096 1102
1097 1103 class UserLog(Base, BaseModel):
1098 1104 __tablename__ = 'user_logs'
1099 1105 __table_args__ = (
1100 1106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1101 1107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1102 1108 )
1103 1109 VERSION_1 = 'v1'
1104 1110 VERSION_2 = 'v2'
1105 1111 VERSIONS = [VERSION_1, VERSION_2]
1106 1112
1107 1113 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1108 1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1109 1115 username = Column("username", String(255), nullable=True, unique=None, default=None)
1110 1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1111 1117 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1112 1118 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1113 1119 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1114 1120 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1115 1121
1116 1122 version = Column("version", String(255), nullable=True, default=VERSION_1)
1117 1123 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1118 1124 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1119 1125
1120 1126 def __unicode__(self):
1121 1127 return u"<%s('id:%s:%s')>" % (
1122 1128 self.__class__.__name__, self.repository_name, self.action)
1123 1129
1124 1130 def __json__(self):
1125 1131 return {
1126 1132 'user_id': self.user_id,
1127 1133 'username': self.username,
1128 1134 'repository_id': self.repository_id,
1129 1135 'repository_name': self.repository_name,
1130 1136 'user_ip': self.user_ip,
1131 1137 'action_date': self.action_date,
1132 1138 'action': self.action,
1133 1139 }
1134 1140
1135 1141 @property
1136 1142 def action_as_day(self):
1137 1143 return datetime.date(*self.action_date.timetuple()[:3])
1138 1144
1139 1145 user = relationship('User')
1140 1146 repository = relationship('Repository', cascade='')
1141 1147
1142 1148
1143 1149 class UserGroup(Base, BaseModel):
1144 1150 __tablename__ = 'users_groups'
1145 1151 __table_args__ = (
1146 1152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1147 1153 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1148 1154 )
1149 1155
1150 1156 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1151 1157 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1152 1158 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1153 1159 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1154 1160 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1155 1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1156 1162 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1157 1163 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1158 1164
1159 1165 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1160 1166 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1161 1167 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1162 1168 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1163 1169 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1164 1170 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1165 1171
1166 1172 user = relationship('User')
1167 1173
1168 1174 @hybrid_property
1169 1175 def group_data(self):
1170 1176 if not self._group_data:
1171 1177 return {}
1172 1178
1173 1179 try:
1174 1180 return json.loads(self._group_data)
1175 1181 except TypeError:
1176 1182 return {}
1177 1183
1178 1184 @group_data.setter
1179 1185 def group_data(self, val):
1180 1186 try:
1181 1187 self._group_data = json.dumps(val)
1182 1188 except Exception:
1183 1189 log.error(traceback.format_exc())
1184 1190
1185 1191 def __unicode__(self):
1186 1192 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1187 1193 self.users_group_id,
1188 1194 self.users_group_name)
1189 1195
1190 1196 @classmethod
1191 1197 def get_by_group_name(cls, group_name, cache=False,
1192 1198 case_insensitive=False):
1193 1199 if case_insensitive:
1194 1200 q = cls.query().filter(func.lower(cls.users_group_name) ==
1195 1201 func.lower(group_name))
1196 1202
1197 1203 else:
1198 1204 q = cls.query().filter(cls.users_group_name == group_name)
1199 1205 if cache:
1200 1206 q = q.options(
1201 1207 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1202 1208 return q.scalar()
1203 1209
1204 1210 @classmethod
1205 1211 def get(cls, user_group_id, cache=False):
1206 1212 user_group = cls.query()
1207 1213 if cache:
1208 1214 user_group = user_group.options(
1209 1215 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1210 1216 return user_group.get(user_group_id)
1211 1217
1212 1218 def permissions(self, with_admins=True, with_owner=True):
1213 1219 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1214 1220 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1215 1221 joinedload(UserUserGroupToPerm.user),
1216 1222 joinedload(UserUserGroupToPerm.permission),)
1217 1223
1218 1224 # get owners and admins and permissions. We do a trick of re-writing
1219 1225 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1220 1226 # has a global reference and changing one object propagates to all
1221 1227 # others. This means if admin is also an owner admin_row that change
1222 1228 # would propagate to both objects
1223 1229 perm_rows = []
1224 1230 for _usr in q.all():
1225 1231 usr = AttributeDict(_usr.user.get_dict())
1226 1232 usr.permission = _usr.permission.permission_name
1227 1233 perm_rows.append(usr)
1228 1234
1229 1235 # filter the perm rows by 'default' first and then sort them by
1230 1236 # admin,write,read,none permissions sorted again alphabetically in
1231 1237 # each group
1232 1238 perm_rows = sorted(perm_rows, key=display_sort)
1233 1239
1234 1240 _admin_perm = 'usergroup.admin'
1235 1241 owner_row = []
1236 1242 if with_owner:
1237 1243 usr = AttributeDict(self.user.get_dict())
1238 1244 usr.owner_row = True
1239 1245 usr.permission = _admin_perm
1240 1246 owner_row.append(usr)
1241 1247
1242 1248 super_admin_rows = []
1243 1249 if with_admins:
1244 1250 for usr in User.get_all_super_admins():
1245 1251 # if this admin is also owner, don't double the record
1246 1252 if usr.user_id == owner_row[0].user_id:
1247 1253 owner_row[0].admin_row = True
1248 1254 else:
1249 1255 usr = AttributeDict(usr.get_dict())
1250 1256 usr.admin_row = True
1251 1257 usr.permission = _admin_perm
1252 1258 super_admin_rows.append(usr)
1253 1259
1254 1260 return super_admin_rows + owner_row + perm_rows
1255 1261
1256 1262 def permission_user_groups(self):
1257 1263 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1258 1264 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1259 1265 joinedload(UserGroupUserGroupToPerm.target_user_group),
1260 1266 joinedload(UserGroupUserGroupToPerm.permission),)
1261 1267
1262 1268 perm_rows = []
1263 1269 for _user_group in q.all():
1264 1270 usr = AttributeDict(_user_group.user_group.get_dict())
1265 1271 usr.permission = _user_group.permission.permission_name
1266 1272 perm_rows.append(usr)
1267 1273
1268 1274 return perm_rows
1269 1275
1270 1276 def _get_default_perms(self, user_group, suffix=''):
1271 1277 from rhodecode.model.permission import PermissionModel
1272 1278 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1273 1279
1274 1280 def get_default_perms(self, suffix=''):
1275 1281 return self._get_default_perms(self, suffix)
1276 1282
1277 1283 def get_api_data(self, with_group_members=True, include_secrets=False):
1278 1284 """
1279 1285 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1280 1286 basically forwarded.
1281 1287
1282 1288 """
1283 1289 user_group = self
1284 1290 data = {
1285 1291 'users_group_id': user_group.users_group_id,
1286 1292 'group_name': user_group.users_group_name,
1287 1293 'group_description': user_group.user_group_description,
1288 1294 'active': user_group.users_group_active,
1289 1295 'owner': user_group.user.username,
1290 1296 'owner_email': user_group.user.email,
1291 1297 }
1292 1298
1293 1299 if with_group_members:
1294 1300 users = []
1295 1301 for user in user_group.members:
1296 1302 user = user.user
1297 1303 users.append(user.get_api_data(include_secrets=include_secrets))
1298 1304 data['users'] = users
1299 1305
1300 1306 return data
1301 1307
1302 1308
1303 1309 class UserGroupMember(Base, BaseModel):
1304 1310 __tablename__ = 'users_groups_members'
1305 1311 __table_args__ = (
1306 1312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1307 1313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1308 1314 )
1309 1315
1310 1316 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1312 1318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1313 1319
1314 1320 user = relationship('User', lazy='joined')
1315 1321 users_group = relationship('UserGroup')
1316 1322
1317 1323 def __init__(self, gr_id='', u_id=''):
1318 1324 self.users_group_id = gr_id
1319 1325 self.user_id = u_id
1320 1326
1321 1327
1322 1328 class RepositoryField(Base, BaseModel):
1323 1329 __tablename__ = 'repositories_fields'
1324 1330 __table_args__ = (
1325 1331 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1326 1332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1327 1333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1328 1334 )
1329 1335 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1330 1336
1331 1337 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 1338 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1333 1339 field_key = Column("field_key", String(250))
1334 1340 field_label = Column("field_label", String(1024), nullable=False)
1335 1341 field_value = Column("field_value", String(10000), nullable=False)
1336 1342 field_desc = Column("field_desc", String(1024), nullable=False)
1337 1343 field_type = Column("field_type", String(255), nullable=False, unique=None)
1338 1344 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1339 1345
1340 1346 repository = relationship('Repository')
1341 1347
1342 1348 @property
1343 1349 def field_key_prefixed(self):
1344 1350 return 'ex_%s' % self.field_key
1345 1351
1346 1352 @classmethod
1347 1353 def un_prefix_key(cls, key):
1348 1354 if key.startswith(cls.PREFIX):
1349 1355 return key[len(cls.PREFIX):]
1350 1356 return key
1351 1357
1352 1358 @classmethod
1353 1359 def get_by_key_name(cls, key, repo):
1354 1360 row = cls.query()\
1355 1361 .filter(cls.repository == repo)\
1356 1362 .filter(cls.field_key == key).scalar()
1357 1363 return row
1358 1364
1359 1365
1360 1366 class Repository(Base, BaseModel):
1361 1367 __tablename__ = 'repositories'
1362 1368 __table_args__ = (
1363 1369 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1364 1370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1365 1371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1366 1372 )
1367 1373 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1368 1374 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1369 1375
1370 1376 STATE_CREATED = 'repo_state_created'
1371 1377 STATE_PENDING = 'repo_state_pending'
1372 1378 STATE_ERROR = 'repo_state_error'
1373 1379
1374 1380 LOCK_AUTOMATIC = 'lock_auto'
1375 1381 LOCK_API = 'lock_api'
1376 1382 LOCK_WEB = 'lock_web'
1377 1383 LOCK_PULL = 'lock_pull'
1378 1384
1379 1385 NAME_SEP = URL_SEP
1380 1386
1381 1387 repo_id = Column(
1382 1388 "repo_id", Integer(), nullable=False, unique=True, default=None,
1383 1389 primary_key=True)
1384 1390 _repo_name = Column(
1385 1391 "repo_name", Text(), nullable=False, default=None)
1386 1392 _repo_name_hash = Column(
1387 1393 "repo_name_hash", String(255), nullable=False, unique=True)
1388 1394 repo_state = Column("repo_state", String(255), nullable=True)
1389 1395
1390 1396 clone_uri = Column(
1391 1397 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1392 1398 default=None)
1393 1399 repo_type = Column(
1394 1400 "repo_type", String(255), nullable=False, unique=False, default=None)
1395 1401 user_id = Column(
1396 1402 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1397 1403 unique=False, default=None)
1398 1404 private = Column(
1399 1405 "private", Boolean(), nullable=True, unique=None, default=None)
1400 1406 enable_statistics = Column(
1401 1407 "statistics", Boolean(), nullable=True, unique=None, default=True)
1402 1408 enable_downloads = Column(
1403 1409 "downloads", Boolean(), nullable=True, unique=None, default=True)
1404 1410 description = Column(
1405 1411 "description", String(10000), nullable=True, unique=None, default=None)
1406 1412 created_on = Column(
1407 1413 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1408 1414 default=datetime.datetime.now)
1409 1415 updated_on = Column(
1410 1416 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1411 1417 default=datetime.datetime.now)
1412 1418 _landing_revision = Column(
1413 1419 "landing_revision", String(255), nullable=False, unique=False,
1414 1420 default=None)
1415 1421 enable_locking = Column(
1416 1422 "enable_locking", Boolean(), nullable=False, unique=None,
1417 1423 default=False)
1418 1424 _locked = Column(
1419 1425 "locked", String(255), nullable=True, unique=False, default=None)
1420 1426 _changeset_cache = Column(
1421 1427 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1422 1428
1423 1429 fork_id = Column(
1424 1430 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1425 1431 nullable=True, unique=False, default=None)
1426 1432 group_id = Column(
1427 1433 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1428 1434 unique=False, default=None)
1429 1435
1430 1436 user = relationship('User', lazy='joined')
1431 1437 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1432 1438 group = relationship('RepoGroup', lazy='joined')
1433 1439 repo_to_perm = relationship(
1434 1440 'UserRepoToPerm', cascade='all',
1435 1441 order_by='UserRepoToPerm.repo_to_perm_id')
1436 1442 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1437 1443 stats = relationship('Statistics', cascade='all', uselist=False)
1438 1444
1439 1445 followers = relationship(
1440 1446 'UserFollowing',
1441 1447 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1442 1448 cascade='all')
1443 1449 extra_fields = relationship(
1444 1450 'RepositoryField', cascade="all, delete, delete-orphan")
1445 1451 logs = relationship('UserLog')
1446 1452 comments = relationship(
1447 1453 'ChangesetComment', cascade="all, delete, delete-orphan")
1448 1454 pull_requests_source = relationship(
1449 1455 'PullRequest',
1450 1456 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1451 1457 cascade="all, delete, delete-orphan")
1452 1458 pull_requests_target = relationship(
1453 1459 'PullRequest',
1454 1460 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1455 1461 cascade="all, delete, delete-orphan")
1456 1462 ui = relationship('RepoRhodeCodeUi', cascade="all")
1457 1463 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1458 1464 integrations = relationship('Integration',
1459 1465 cascade="all, delete, delete-orphan")
1460 1466
1461 1467 def __unicode__(self):
1462 1468 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1463 1469 safe_unicode(self.repo_name))
1464 1470
1465 1471 @hybrid_property
1466 1472 def landing_rev(self):
1467 1473 # always should return [rev_type, rev]
1468 1474 if self._landing_revision:
1469 1475 _rev_info = self._landing_revision.split(':')
1470 1476 if len(_rev_info) < 2:
1471 1477 _rev_info.insert(0, 'rev')
1472 1478 return [_rev_info[0], _rev_info[1]]
1473 1479 return [None, None]
1474 1480
1475 1481 @landing_rev.setter
1476 1482 def landing_rev(self, val):
1477 1483 if ':' not in val:
1478 1484 raise ValueError('value must be delimited with `:` and consist '
1479 1485 'of <rev_type>:<rev>, got %s instead' % val)
1480 1486 self._landing_revision = val
1481 1487
1482 1488 @hybrid_property
1483 1489 def locked(self):
1484 1490 if self._locked:
1485 1491 user_id, timelocked, reason = self._locked.split(':')
1486 1492 lock_values = int(user_id), timelocked, reason
1487 1493 else:
1488 1494 lock_values = [None, None, None]
1489 1495 return lock_values
1490 1496
1491 1497 @locked.setter
1492 1498 def locked(self, val):
1493 1499 if val and isinstance(val, (list, tuple)):
1494 1500 self._locked = ':'.join(map(str, val))
1495 1501 else:
1496 1502 self._locked = None
1497 1503
1498 1504 @hybrid_property
1499 1505 def changeset_cache(self):
1500 1506 from rhodecode.lib.vcs.backends.base import EmptyCommit
1501 1507 dummy = EmptyCommit().__json__()
1502 1508 if not self._changeset_cache:
1503 1509 return dummy
1504 1510 try:
1505 1511 return json.loads(self._changeset_cache)
1506 1512 except TypeError:
1507 1513 return dummy
1508 1514 except Exception:
1509 1515 log.error(traceback.format_exc())
1510 1516 return dummy
1511 1517
1512 1518 @changeset_cache.setter
1513 1519 def changeset_cache(self, val):
1514 1520 try:
1515 1521 self._changeset_cache = json.dumps(val)
1516 1522 except Exception:
1517 1523 log.error(traceback.format_exc())
1518 1524
1519 1525 @hybrid_property
1520 1526 def repo_name(self):
1521 1527 return self._repo_name
1522 1528
1523 1529 @repo_name.setter
1524 1530 def repo_name(self, value):
1525 1531 self._repo_name = value
1526 1532 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1527 1533
1528 1534 @classmethod
1529 1535 def normalize_repo_name(cls, repo_name):
1530 1536 """
1531 1537 Normalizes os specific repo_name to the format internally stored inside
1532 1538 database using URL_SEP
1533 1539
1534 1540 :param cls:
1535 1541 :param repo_name:
1536 1542 """
1537 1543 return cls.NAME_SEP.join(repo_name.split(os.sep))
1538 1544
1539 1545 @classmethod
1540 1546 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1541 1547 session = Session()
1542 1548 q = session.query(cls).filter(cls.repo_name == repo_name)
1543 1549
1544 1550 if cache:
1545 1551 if identity_cache:
1546 1552 val = cls.identity_cache(session, 'repo_name', repo_name)
1547 1553 if val:
1548 1554 return val
1549 1555 else:
1550 1556 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1551 1557 q = q.options(
1552 1558 FromCache("sql_cache_short", cache_key))
1553 1559
1554 1560 return q.scalar()
1555 1561
1556 1562 @classmethod
1557 1563 def get_by_full_path(cls, repo_full_path):
1558 1564 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1559 1565 repo_name = cls.normalize_repo_name(repo_name)
1560 1566 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1561 1567
1562 1568 @classmethod
1563 1569 def get_repo_forks(cls, repo_id):
1564 1570 return cls.query().filter(Repository.fork_id == repo_id)
1565 1571
1566 1572 @classmethod
1567 1573 def base_path(cls):
1568 1574 """
1569 1575 Returns base path when all repos are stored
1570 1576
1571 1577 :param cls:
1572 1578 """
1573 1579 q = Session().query(RhodeCodeUi)\
1574 1580 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1575 1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1576 1582 return q.one().ui_value
1577 1583
1578 1584 @classmethod
1579 1585 def is_valid(cls, repo_name):
1580 1586 """
1581 1587 returns True if given repo name is a valid filesystem repository
1582 1588
1583 1589 :param cls:
1584 1590 :param repo_name:
1585 1591 """
1586 1592 from rhodecode.lib.utils import is_valid_repo
1587 1593
1588 1594 return is_valid_repo(repo_name, cls.base_path())
1589 1595
1590 1596 @classmethod
1591 1597 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1592 1598 case_insensitive=True):
1593 1599 q = Repository.query()
1594 1600
1595 1601 if not isinstance(user_id, Optional):
1596 1602 q = q.filter(Repository.user_id == user_id)
1597 1603
1598 1604 if not isinstance(group_id, Optional):
1599 1605 q = q.filter(Repository.group_id == group_id)
1600 1606
1601 1607 if case_insensitive:
1602 1608 q = q.order_by(func.lower(Repository.repo_name))
1603 1609 else:
1604 1610 q = q.order_by(Repository.repo_name)
1605 1611 return q.all()
1606 1612
1607 1613 @property
1608 1614 def forks(self):
1609 1615 """
1610 1616 Return forks of this repo
1611 1617 """
1612 1618 return Repository.get_repo_forks(self.repo_id)
1613 1619
1614 1620 @property
1615 1621 def parent(self):
1616 1622 """
1617 1623 Returns fork parent
1618 1624 """
1619 1625 return self.fork
1620 1626
1621 1627 @property
1622 1628 def just_name(self):
1623 1629 return self.repo_name.split(self.NAME_SEP)[-1]
1624 1630
1625 1631 @property
1626 1632 def groups_with_parents(self):
1627 1633 groups = []
1628 1634 if self.group is None:
1629 1635 return groups
1630 1636
1631 1637 cur_gr = self.group
1632 1638 groups.insert(0, cur_gr)
1633 1639 while 1:
1634 1640 gr = getattr(cur_gr, 'parent_group', None)
1635 1641 cur_gr = cur_gr.parent_group
1636 1642 if gr is None:
1637 1643 break
1638 1644 groups.insert(0, gr)
1639 1645
1640 1646 return groups
1641 1647
1642 1648 @property
1643 1649 def groups_and_repo(self):
1644 1650 return self.groups_with_parents, self
1645 1651
1646 1652 @LazyProperty
1647 1653 def repo_path(self):
1648 1654 """
1649 1655 Returns base full path for that repository means where it actually
1650 1656 exists on a filesystem
1651 1657 """
1652 1658 q = Session().query(RhodeCodeUi).filter(
1653 1659 RhodeCodeUi.ui_key == self.NAME_SEP)
1654 1660 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1655 1661 return q.one().ui_value
1656 1662
1657 1663 @property
1658 1664 def repo_full_path(self):
1659 1665 p = [self.repo_path]
1660 1666 # we need to split the name by / since this is how we store the
1661 1667 # names in the database, but that eventually needs to be converted
1662 1668 # into a valid system path
1663 1669 p += self.repo_name.split(self.NAME_SEP)
1664 1670 return os.path.join(*map(safe_unicode, p))
1665 1671
1666 1672 @property
1667 1673 def cache_keys(self):
1668 1674 """
1669 1675 Returns associated cache keys for that repo
1670 1676 """
1671 1677 return CacheKey.query()\
1672 1678 .filter(CacheKey.cache_args == self.repo_name)\
1673 1679 .order_by(CacheKey.cache_key)\
1674 1680 .all()
1675 1681
1676 1682 def get_new_name(self, repo_name):
1677 1683 """
1678 1684 returns new full repository name based on assigned group and new new
1679 1685
1680 1686 :param group_name:
1681 1687 """
1682 1688 path_prefix = self.group.full_path_splitted if self.group else []
1683 1689 return self.NAME_SEP.join(path_prefix + [repo_name])
1684 1690
1685 1691 @property
1686 1692 def _config(self):
1687 1693 """
1688 1694 Returns db based config object.
1689 1695 """
1690 1696 from rhodecode.lib.utils import make_db_config
1691 1697 return make_db_config(clear_session=False, repo=self)
1692 1698
1693 1699 def permissions(self, with_admins=True, with_owner=True):
1694 1700 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1695 1701 q = q.options(joinedload(UserRepoToPerm.repository),
1696 1702 joinedload(UserRepoToPerm.user),
1697 1703 joinedload(UserRepoToPerm.permission),)
1698 1704
1699 1705 # get owners and admins and permissions. We do a trick of re-writing
1700 1706 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1701 1707 # has a global reference and changing one object propagates to all
1702 1708 # others. This means if admin is also an owner admin_row that change
1703 1709 # would propagate to both objects
1704 1710 perm_rows = []
1705 1711 for _usr in q.all():
1706 1712 usr = AttributeDict(_usr.user.get_dict())
1707 1713 usr.permission = _usr.permission.permission_name
1708 1714 perm_rows.append(usr)
1709 1715
1710 1716 # filter the perm rows by 'default' first and then sort them by
1711 1717 # admin,write,read,none permissions sorted again alphabetically in
1712 1718 # each group
1713 1719 perm_rows = sorted(perm_rows, key=display_sort)
1714 1720
1715 1721 _admin_perm = 'repository.admin'
1716 1722 owner_row = []
1717 1723 if with_owner:
1718 1724 usr = AttributeDict(self.user.get_dict())
1719 1725 usr.owner_row = True
1720 1726 usr.permission = _admin_perm
1721 1727 owner_row.append(usr)
1722 1728
1723 1729 super_admin_rows = []
1724 1730 if with_admins:
1725 1731 for usr in User.get_all_super_admins():
1726 1732 # if this admin is also owner, don't double the record
1727 1733 if usr.user_id == owner_row[0].user_id:
1728 1734 owner_row[0].admin_row = True
1729 1735 else:
1730 1736 usr = AttributeDict(usr.get_dict())
1731 1737 usr.admin_row = True
1732 1738 usr.permission = _admin_perm
1733 1739 super_admin_rows.append(usr)
1734 1740
1735 1741 return super_admin_rows + owner_row + perm_rows
1736 1742
1737 1743 def permission_user_groups(self):
1738 1744 q = UserGroupRepoToPerm.query().filter(
1739 1745 UserGroupRepoToPerm.repository == self)
1740 1746 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1741 1747 joinedload(UserGroupRepoToPerm.users_group),
1742 1748 joinedload(UserGroupRepoToPerm.permission),)
1743 1749
1744 1750 perm_rows = []
1745 1751 for _user_group in q.all():
1746 1752 usr = AttributeDict(_user_group.users_group.get_dict())
1747 1753 usr.permission = _user_group.permission.permission_name
1748 1754 perm_rows.append(usr)
1749 1755
1750 1756 return perm_rows
1751 1757
1752 1758 def get_api_data(self, include_secrets=False):
1753 1759 """
1754 1760 Common function for generating repo api data
1755 1761
1756 1762 :param include_secrets: See :meth:`User.get_api_data`.
1757 1763
1758 1764 """
1759 1765 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1760 1766 # move this methods on models level.
1761 1767 from rhodecode.model.settings import SettingsModel
1762 1768
1763 1769 repo = self
1764 1770 _user_id, _time, _reason = self.locked
1765 1771
1766 1772 data = {
1767 1773 'repo_id': repo.repo_id,
1768 1774 'repo_name': repo.repo_name,
1769 1775 'repo_type': repo.repo_type,
1770 1776 'clone_uri': repo.clone_uri or '',
1771 1777 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1772 1778 'private': repo.private,
1773 1779 'created_on': repo.created_on,
1774 1780 'description': repo.description,
1775 1781 'landing_rev': repo.landing_rev,
1776 1782 'owner': repo.user.username,
1777 1783 'fork_of': repo.fork.repo_name if repo.fork else None,
1778 1784 'enable_statistics': repo.enable_statistics,
1779 1785 'enable_locking': repo.enable_locking,
1780 1786 'enable_downloads': repo.enable_downloads,
1781 1787 'last_changeset': repo.changeset_cache,
1782 1788 'locked_by': User.get(_user_id).get_api_data(
1783 1789 include_secrets=include_secrets) if _user_id else None,
1784 1790 'locked_date': time_to_datetime(_time) if _time else None,
1785 1791 'lock_reason': _reason if _reason else None,
1786 1792 }
1787 1793
1788 1794 # TODO: mikhail: should be per-repo settings here
1789 1795 rc_config = SettingsModel().get_all_settings()
1790 1796 repository_fields = str2bool(
1791 1797 rc_config.get('rhodecode_repository_fields'))
1792 1798 if repository_fields:
1793 1799 for f in self.extra_fields:
1794 1800 data[f.field_key_prefixed] = f.field_value
1795 1801
1796 1802 return data
1797 1803
1798 1804 @classmethod
1799 1805 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1800 1806 if not lock_time:
1801 1807 lock_time = time.time()
1802 1808 if not lock_reason:
1803 1809 lock_reason = cls.LOCK_AUTOMATIC
1804 1810 repo.locked = [user_id, lock_time, lock_reason]
1805 1811 Session().add(repo)
1806 1812 Session().commit()
1807 1813
1808 1814 @classmethod
1809 1815 def unlock(cls, repo):
1810 1816 repo.locked = None
1811 1817 Session().add(repo)
1812 1818 Session().commit()
1813 1819
1814 1820 @classmethod
1815 1821 def getlock(cls, repo):
1816 1822 return repo.locked
1817 1823
1818 1824 def is_user_lock(self, user_id):
1819 1825 if self.lock[0]:
1820 1826 lock_user_id = safe_int(self.lock[0])
1821 1827 user_id = safe_int(user_id)
1822 1828 # both are ints, and they are equal
1823 1829 return all([lock_user_id, user_id]) and lock_user_id == user_id
1824 1830
1825 1831 return False
1826 1832
1827 1833 def get_locking_state(self, action, user_id, only_when_enabled=True):
1828 1834 """
1829 1835 Checks locking on this repository, if locking is enabled and lock is
1830 1836 present returns a tuple of make_lock, locked, locked_by.
1831 1837 make_lock can have 3 states None (do nothing) True, make lock
1832 1838 False release lock, This value is later propagated to hooks, which
1833 1839 do the locking. Think about this as signals passed to hooks what to do.
1834 1840
1835 1841 """
1836 1842 # TODO: johbo: This is part of the business logic and should be moved
1837 1843 # into the RepositoryModel.
1838 1844
1839 1845 if action not in ('push', 'pull'):
1840 1846 raise ValueError("Invalid action value: %s" % repr(action))
1841 1847
1842 1848 # defines if locked error should be thrown to user
1843 1849 currently_locked = False
1844 1850 # defines if new lock should be made, tri-state
1845 1851 make_lock = None
1846 1852 repo = self
1847 1853 user = User.get(user_id)
1848 1854
1849 1855 lock_info = repo.locked
1850 1856
1851 1857 if repo and (repo.enable_locking or not only_when_enabled):
1852 1858 if action == 'push':
1853 1859 # check if it's already locked !, if it is compare users
1854 1860 locked_by_user_id = lock_info[0]
1855 1861 if user.user_id == locked_by_user_id:
1856 1862 log.debug(
1857 1863 'Got `push` action from user %s, now unlocking', user)
1858 1864 # unlock if we have push from user who locked
1859 1865 make_lock = False
1860 1866 else:
1861 1867 # we're not the same user who locked, ban with
1862 1868 # code defined in settings (default is 423 HTTP Locked) !
1863 1869 log.debug('Repo %s is currently locked by %s', repo, user)
1864 1870 currently_locked = True
1865 1871 elif action == 'pull':
1866 1872 # [0] user [1] date
1867 1873 if lock_info[0] and lock_info[1]:
1868 1874 log.debug('Repo %s is currently locked by %s', repo, user)
1869 1875 currently_locked = True
1870 1876 else:
1871 1877 log.debug('Setting lock on repo %s by %s', repo, user)
1872 1878 make_lock = True
1873 1879
1874 1880 else:
1875 1881 log.debug('Repository %s do not have locking enabled', repo)
1876 1882
1877 1883 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1878 1884 make_lock, currently_locked, lock_info)
1879 1885
1880 1886 from rhodecode.lib.auth import HasRepoPermissionAny
1881 1887 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1882 1888 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1883 1889 # if we don't have at least write permission we cannot make a lock
1884 1890 log.debug('lock state reset back to FALSE due to lack '
1885 1891 'of at least read permission')
1886 1892 make_lock = False
1887 1893
1888 1894 return make_lock, currently_locked, lock_info
1889 1895
1890 1896 @property
1891 1897 def last_db_change(self):
1892 1898 return self.updated_on
1893 1899
1894 1900 @property
1895 1901 def clone_uri_hidden(self):
1896 1902 clone_uri = self.clone_uri
1897 1903 if clone_uri:
1898 1904 import urlobject
1899 1905 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1900 1906 if url_obj.password:
1901 1907 clone_uri = url_obj.with_password('*****')
1902 1908 return clone_uri
1903 1909
1904 1910 def clone_url(self, **override):
1905 1911 qualified_home_url = url('home', qualified=True)
1906 1912
1907 1913 uri_tmpl = None
1908 1914 if 'with_id' in override:
1909 1915 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1910 1916 del override['with_id']
1911 1917
1912 1918 if 'uri_tmpl' in override:
1913 1919 uri_tmpl = override['uri_tmpl']
1914 1920 del override['uri_tmpl']
1915 1921
1916 1922 # we didn't override our tmpl from **overrides
1917 1923 if not uri_tmpl:
1918 1924 uri_tmpl = self.DEFAULT_CLONE_URI
1919 1925 try:
1920 1926 from pylons import tmpl_context as c
1921 1927 uri_tmpl = c.clone_uri_tmpl
1922 1928 except Exception:
1923 1929 # in any case if we call this outside of request context,
1924 1930 # ie, not having tmpl_context set up
1925 1931 pass
1926 1932
1927 1933 return get_clone_url(uri_tmpl=uri_tmpl,
1928 1934 qualifed_home_url=qualified_home_url,
1929 1935 repo_name=self.repo_name,
1930 1936 repo_id=self.repo_id, **override)
1931 1937
1932 1938 def set_state(self, state):
1933 1939 self.repo_state = state
1934 1940 Session().add(self)
1935 1941 #==========================================================================
1936 1942 # SCM PROPERTIES
1937 1943 #==========================================================================
1938 1944
1939 1945 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1940 1946 return get_commit_safe(
1941 1947 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1942 1948
1943 1949 def get_changeset(self, rev=None, pre_load=None):
1944 1950 warnings.warn("Use get_commit", DeprecationWarning)
1945 1951 commit_id = None
1946 1952 commit_idx = None
1947 1953 if isinstance(rev, basestring):
1948 1954 commit_id = rev
1949 1955 else:
1950 1956 commit_idx = rev
1951 1957 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1952 1958 pre_load=pre_load)
1953 1959
1954 1960 def get_landing_commit(self):
1955 1961 """
1956 1962 Returns landing commit, or if that doesn't exist returns the tip
1957 1963 """
1958 1964 _rev_type, _rev = self.landing_rev
1959 1965 commit = self.get_commit(_rev)
1960 1966 if isinstance(commit, EmptyCommit):
1961 1967 return self.get_commit()
1962 1968 return commit
1963 1969
1964 1970 def update_commit_cache(self, cs_cache=None, config=None):
1965 1971 """
1966 1972 Update cache of last changeset for repository, keys should be::
1967 1973
1968 1974 short_id
1969 1975 raw_id
1970 1976 revision
1971 1977 parents
1972 1978 message
1973 1979 date
1974 1980 author
1975 1981
1976 1982 :param cs_cache:
1977 1983 """
1978 1984 from rhodecode.lib.vcs.backends.base import BaseChangeset
1979 1985 if cs_cache is None:
1980 1986 # use no-cache version here
1981 1987 scm_repo = self.scm_instance(cache=False, config=config)
1982 1988 if scm_repo:
1983 1989 cs_cache = scm_repo.get_commit(
1984 1990 pre_load=["author", "date", "message", "parents"])
1985 1991 else:
1986 1992 cs_cache = EmptyCommit()
1987 1993
1988 1994 if isinstance(cs_cache, BaseChangeset):
1989 1995 cs_cache = cs_cache.__json__()
1990 1996
1991 1997 def is_outdated(new_cs_cache):
1992 1998 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1993 1999 new_cs_cache['revision'] != self.changeset_cache['revision']):
1994 2000 return True
1995 2001 return False
1996 2002
1997 2003 # check if we have maybe already latest cached revision
1998 2004 if is_outdated(cs_cache) or not self.changeset_cache:
1999 2005 _default = datetime.datetime.fromtimestamp(0)
2000 2006 last_change = cs_cache.get('date') or _default
2001 2007 log.debug('updated repo %s with new cs cache %s',
2002 2008 self.repo_name, cs_cache)
2003 2009 self.updated_on = last_change
2004 2010 self.changeset_cache = cs_cache
2005 2011 Session().add(self)
2006 2012 Session().commit()
2007 2013 else:
2008 2014 log.debug('Skipping update_commit_cache for repo:`%s` '
2009 2015 'commit already with latest changes', self.repo_name)
2010 2016
2011 2017 @property
2012 2018 def tip(self):
2013 2019 return self.get_commit('tip')
2014 2020
2015 2021 @property
2016 2022 def author(self):
2017 2023 return self.tip.author
2018 2024
2019 2025 @property
2020 2026 def last_change(self):
2021 2027 return self.scm_instance().last_change
2022 2028
2023 2029 def get_comments(self, revisions=None):
2024 2030 """
2025 2031 Returns comments for this repository grouped by revisions
2026 2032
2027 2033 :param revisions: filter query by revisions only
2028 2034 """
2029 2035 cmts = ChangesetComment.query()\
2030 2036 .filter(ChangesetComment.repo == self)
2031 2037 if revisions:
2032 2038 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2033 2039 grouped = collections.defaultdict(list)
2034 2040 for cmt in cmts.all():
2035 2041 grouped[cmt.revision].append(cmt)
2036 2042 return grouped
2037 2043
2038 2044 def statuses(self, revisions=None):
2039 2045 """
2040 2046 Returns statuses for this repository
2041 2047
2042 2048 :param revisions: list of revisions to get statuses for
2043 2049 """
2044 2050 statuses = ChangesetStatus.query()\
2045 2051 .filter(ChangesetStatus.repo == self)\
2046 2052 .filter(ChangesetStatus.version == 0)
2047 2053
2048 2054 if revisions:
2049 2055 # Try doing the filtering in chunks to avoid hitting limits
2050 2056 size = 500
2051 2057 status_results = []
2052 2058 for chunk in xrange(0, len(revisions), size):
2053 2059 status_results += statuses.filter(
2054 2060 ChangesetStatus.revision.in_(
2055 2061 revisions[chunk: chunk+size])
2056 2062 ).all()
2057 2063 else:
2058 2064 status_results = statuses.all()
2059 2065
2060 2066 grouped = {}
2061 2067
2062 2068 # maybe we have open new pullrequest without a status?
2063 2069 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2064 2070 status_lbl = ChangesetStatus.get_status_lbl(stat)
2065 2071 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2066 2072 for rev in pr.revisions:
2067 2073 pr_id = pr.pull_request_id
2068 2074 pr_repo = pr.target_repo.repo_name
2069 2075 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2070 2076
2071 2077 for stat in status_results:
2072 2078 pr_id = pr_repo = None
2073 2079 if stat.pull_request:
2074 2080 pr_id = stat.pull_request.pull_request_id
2075 2081 pr_repo = stat.pull_request.target_repo.repo_name
2076 2082 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2077 2083 pr_id, pr_repo]
2078 2084 return grouped
2079 2085
2080 2086 # ==========================================================================
2081 2087 # SCM CACHE INSTANCE
2082 2088 # ==========================================================================
2083 2089
2084 2090 def scm_instance(self, **kwargs):
2085 2091 import rhodecode
2086 2092
2087 2093 # Passing a config will not hit the cache currently only used
2088 2094 # for repo2dbmapper
2089 2095 config = kwargs.pop('config', None)
2090 2096 cache = kwargs.pop('cache', None)
2091 2097 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2092 2098 # if cache is NOT defined use default global, else we have a full
2093 2099 # control over cache behaviour
2094 2100 if cache is None and full_cache and not config:
2095 2101 return self._get_instance_cached()
2096 2102 return self._get_instance(cache=bool(cache), config=config)
2097 2103
2098 2104 def _get_instance_cached(self):
2099 2105 @cache_region('long_term')
2100 2106 def _get_repo(cache_key):
2101 2107 return self._get_instance()
2102 2108
2103 2109 invalidator_context = CacheKey.repo_context_cache(
2104 2110 _get_repo, self.repo_name, None, thread_scoped=True)
2105 2111
2106 2112 with invalidator_context as context:
2107 2113 context.invalidate()
2108 2114 repo = context.compute()
2109 2115
2110 2116 return repo
2111 2117
2112 2118 def _get_instance(self, cache=True, config=None):
2113 2119 config = config or self._config
2114 2120 custom_wire = {
2115 2121 'cache': cache # controls the vcs.remote cache
2116 2122 }
2117 2123 repo = get_vcs_instance(
2118 2124 repo_path=safe_str(self.repo_full_path),
2119 2125 config=config,
2120 2126 with_wire=custom_wire,
2121 2127 create=False,
2122 2128 _vcs_alias=self.repo_type)
2123 2129
2124 2130 return repo
2125 2131
2126 2132 def __json__(self):
2127 2133 return {'landing_rev': self.landing_rev}
2128 2134
2129 2135 def get_dict(self):
2130 2136
2131 2137 # Since we transformed `repo_name` to a hybrid property, we need to
2132 2138 # keep compatibility with the code which uses `repo_name` field.
2133 2139
2134 2140 result = super(Repository, self).get_dict()
2135 2141 result['repo_name'] = result.pop('_repo_name', None)
2136 2142 return result
2137 2143
2138 2144
2139 2145 class RepoGroup(Base, BaseModel):
2140 2146 __tablename__ = 'groups'
2141 2147 __table_args__ = (
2142 2148 UniqueConstraint('group_name', 'group_parent_id'),
2143 2149 CheckConstraint('group_id != group_parent_id'),
2144 2150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2145 2151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2146 2152 )
2147 2153 __mapper_args__ = {'order_by': 'group_name'}
2148 2154
2149 2155 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2150 2156
2151 2157 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2152 2158 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2153 2159 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2154 2160 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2155 2161 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2156 2162 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2157 2163 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2158 2164 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2159 2165
2160 2166 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2161 2167 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2162 2168 parent_group = relationship('RepoGroup', remote_side=group_id)
2163 2169 user = relationship('User')
2164 2170 integrations = relationship('Integration',
2165 2171 cascade="all, delete, delete-orphan")
2166 2172
2167 2173 def __init__(self, group_name='', parent_group=None):
2168 2174 self.group_name = group_name
2169 2175 self.parent_group = parent_group
2170 2176
2171 2177 def __unicode__(self):
2172 2178 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2173 2179 self.group_name)
2174 2180
2175 2181 @classmethod
2176 2182 def _generate_choice(cls, repo_group):
2177 2183 from webhelpers.html import literal as _literal
2178 2184 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2179 2185 return repo_group.group_id, _name(repo_group.full_path_splitted)
2180 2186
2181 2187 @classmethod
2182 2188 def groups_choices(cls, groups=None, show_empty_group=True):
2183 2189 if not groups:
2184 2190 groups = cls.query().all()
2185 2191
2186 2192 repo_groups = []
2187 2193 if show_empty_group:
2188 2194 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2189 2195
2190 2196 repo_groups.extend([cls._generate_choice(x) for x in groups])
2191 2197
2192 2198 repo_groups = sorted(
2193 2199 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2194 2200 return repo_groups
2195 2201
2196 2202 @classmethod
2197 2203 def url_sep(cls):
2198 2204 return URL_SEP
2199 2205
2200 2206 @classmethod
2201 2207 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2202 2208 if case_insensitive:
2203 2209 gr = cls.query().filter(func.lower(cls.group_name)
2204 2210 == func.lower(group_name))
2205 2211 else:
2206 2212 gr = cls.query().filter(cls.group_name == group_name)
2207 2213 if cache:
2208 2214 name_key = _hash_key(group_name)
2209 2215 gr = gr.options(
2210 2216 FromCache("sql_cache_short", "get_group_%s" % name_key))
2211 2217 return gr.scalar()
2212 2218
2213 2219 @classmethod
2214 2220 def get_user_personal_repo_group(cls, user_id):
2215 2221 user = User.get(user_id)
2216 2222 if user.username == User.DEFAULT_USER:
2217 2223 return None
2218 2224
2219 2225 return cls.query()\
2220 2226 .filter(cls.personal == true()) \
2221 2227 .filter(cls.user == user).scalar()
2222 2228
2223 2229 @classmethod
2224 2230 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2225 2231 case_insensitive=True):
2226 2232 q = RepoGroup.query()
2227 2233
2228 2234 if not isinstance(user_id, Optional):
2229 2235 q = q.filter(RepoGroup.user_id == user_id)
2230 2236
2231 2237 if not isinstance(group_id, Optional):
2232 2238 q = q.filter(RepoGroup.group_parent_id == group_id)
2233 2239
2234 2240 if case_insensitive:
2235 2241 q = q.order_by(func.lower(RepoGroup.group_name))
2236 2242 else:
2237 2243 q = q.order_by(RepoGroup.group_name)
2238 2244 return q.all()
2239 2245
2240 2246 @property
2241 2247 def parents(self):
2242 2248 parents_recursion_limit = 10
2243 2249 groups = []
2244 2250 if self.parent_group is None:
2245 2251 return groups
2246 2252 cur_gr = self.parent_group
2247 2253 groups.insert(0, cur_gr)
2248 2254 cnt = 0
2249 2255 while 1:
2250 2256 cnt += 1
2251 2257 gr = getattr(cur_gr, 'parent_group', None)
2252 2258 cur_gr = cur_gr.parent_group
2253 2259 if gr is None:
2254 2260 break
2255 2261 if cnt == parents_recursion_limit:
2256 2262 # this will prevent accidental infinit loops
2257 2263 log.error(('more than %s parents found for group %s, stopping '
2258 2264 'recursive parent fetching' % (parents_recursion_limit, self)))
2259 2265 break
2260 2266
2261 2267 groups.insert(0, gr)
2262 2268 return groups
2263 2269
2264 2270 @property
2265 2271 def children(self):
2266 2272 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2267 2273
2268 2274 @property
2269 2275 def name(self):
2270 2276 return self.group_name.split(RepoGroup.url_sep())[-1]
2271 2277
2272 2278 @property
2273 2279 def full_path(self):
2274 2280 return self.group_name
2275 2281
2276 2282 @property
2277 2283 def full_path_splitted(self):
2278 2284 return self.group_name.split(RepoGroup.url_sep())
2279 2285
2280 2286 @property
2281 2287 def repositories(self):
2282 2288 return Repository.query()\
2283 2289 .filter(Repository.group == self)\
2284 2290 .order_by(Repository.repo_name)
2285 2291
2286 2292 @property
2287 2293 def repositories_recursive_count(self):
2288 2294 cnt = self.repositories.count()
2289 2295
2290 2296 def children_count(group):
2291 2297 cnt = 0
2292 2298 for child in group.children:
2293 2299 cnt += child.repositories.count()
2294 2300 cnt += children_count(child)
2295 2301 return cnt
2296 2302
2297 2303 return cnt + children_count(self)
2298 2304
2299 2305 def _recursive_objects(self, include_repos=True):
2300 2306 all_ = []
2301 2307
2302 2308 def _get_members(root_gr):
2303 2309 if include_repos:
2304 2310 for r in root_gr.repositories:
2305 2311 all_.append(r)
2306 2312 childs = root_gr.children.all()
2307 2313 if childs:
2308 2314 for gr in childs:
2309 2315 all_.append(gr)
2310 2316 _get_members(gr)
2311 2317
2312 2318 _get_members(self)
2313 2319 return [self] + all_
2314 2320
2315 2321 def recursive_groups_and_repos(self):
2316 2322 """
2317 2323 Recursive return all groups, with repositories in those groups
2318 2324 """
2319 2325 return self._recursive_objects()
2320 2326
2321 2327 def recursive_groups(self):
2322 2328 """
2323 2329 Returns all children groups for this group including children of children
2324 2330 """
2325 2331 return self._recursive_objects(include_repos=False)
2326 2332
2327 2333 def get_new_name(self, group_name):
2328 2334 """
2329 2335 returns new full group name based on parent and new name
2330 2336
2331 2337 :param group_name:
2332 2338 """
2333 2339 path_prefix = (self.parent_group.full_path_splitted if
2334 2340 self.parent_group else [])
2335 2341 return RepoGroup.url_sep().join(path_prefix + [group_name])
2336 2342
2337 2343 def permissions(self, with_admins=True, with_owner=True):
2338 2344 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2339 2345 q = q.options(joinedload(UserRepoGroupToPerm.group),
2340 2346 joinedload(UserRepoGroupToPerm.user),
2341 2347 joinedload(UserRepoGroupToPerm.permission),)
2342 2348
2343 2349 # get owners and admins and permissions. We do a trick of re-writing
2344 2350 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2345 2351 # has a global reference and changing one object propagates to all
2346 2352 # others. This means if admin is also an owner admin_row that change
2347 2353 # would propagate to both objects
2348 2354 perm_rows = []
2349 2355 for _usr in q.all():
2350 2356 usr = AttributeDict(_usr.user.get_dict())
2351 2357 usr.permission = _usr.permission.permission_name
2352 2358 perm_rows.append(usr)
2353 2359
2354 2360 # filter the perm rows by 'default' first and then sort them by
2355 2361 # admin,write,read,none permissions sorted again alphabetically in
2356 2362 # each group
2357 2363 perm_rows = sorted(perm_rows, key=display_sort)
2358 2364
2359 2365 _admin_perm = 'group.admin'
2360 2366 owner_row = []
2361 2367 if with_owner:
2362 2368 usr = AttributeDict(self.user.get_dict())
2363 2369 usr.owner_row = True
2364 2370 usr.permission = _admin_perm
2365 2371 owner_row.append(usr)
2366 2372
2367 2373 super_admin_rows = []
2368 2374 if with_admins:
2369 2375 for usr in User.get_all_super_admins():
2370 2376 # if this admin is also owner, don't double the record
2371 2377 if usr.user_id == owner_row[0].user_id:
2372 2378 owner_row[0].admin_row = True
2373 2379 else:
2374 2380 usr = AttributeDict(usr.get_dict())
2375 2381 usr.admin_row = True
2376 2382 usr.permission = _admin_perm
2377 2383 super_admin_rows.append(usr)
2378 2384
2379 2385 return super_admin_rows + owner_row + perm_rows
2380 2386
2381 2387 def permission_user_groups(self):
2382 2388 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2383 2389 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2384 2390 joinedload(UserGroupRepoGroupToPerm.users_group),
2385 2391 joinedload(UserGroupRepoGroupToPerm.permission),)
2386 2392
2387 2393 perm_rows = []
2388 2394 for _user_group in q.all():
2389 2395 usr = AttributeDict(_user_group.users_group.get_dict())
2390 2396 usr.permission = _user_group.permission.permission_name
2391 2397 perm_rows.append(usr)
2392 2398
2393 2399 return perm_rows
2394 2400
2395 2401 def get_api_data(self):
2396 2402 """
2397 2403 Common function for generating api data
2398 2404
2399 2405 """
2400 2406 group = self
2401 2407 data = {
2402 2408 'group_id': group.group_id,
2403 2409 'group_name': group.group_name,
2404 2410 'group_description': group.group_description,
2405 2411 'parent_group': group.parent_group.group_name if group.parent_group else None,
2406 2412 'repositories': [x.repo_name for x in group.repositories],
2407 2413 'owner': group.user.username,
2408 2414 }
2409 2415 return data
2410 2416
2411 2417
2412 2418 class Permission(Base, BaseModel):
2413 2419 __tablename__ = 'permissions'
2414 2420 __table_args__ = (
2415 2421 Index('p_perm_name_idx', 'permission_name'),
2416 2422 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2417 2423 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2418 2424 )
2419 2425 PERMS = [
2420 2426 ('hg.admin', _('RhodeCode Super Administrator')),
2421 2427
2422 2428 ('repository.none', _('Repository no access')),
2423 2429 ('repository.read', _('Repository read access')),
2424 2430 ('repository.write', _('Repository write access')),
2425 2431 ('repository.admin', _('Repository admin access')),
2426 2432
2427 2433 ('group.none', _('Repository group no access')),
2428 2434 ('group.read', _('Repository group read access')),
2429 2435 ('group.write', _('Repository group write access')),
2430 2436 ('group.admin', _('Repository group admin access')),
2431 2437
2432 2438 ('usergroup.none', _('User group no access')),
2433 2439 ('usergroup.read', _('User group read access')),
2434 2440 ('usergroup.write', _('User group write access')),
2435 2441 ('usergroup.admin', _('User group admin access')),
2436 2442
2437 2443 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2438 2444 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2439 2445
2440 2446 ('hg.usergroup.create.false', _('User Group creation disabled')),
2441 2447 ('hg.usergroup.create.true', _('User Group creation enabled')),
2442 2448
2443 2449 ('hg.create.none', _('Repository creation disabled')),
2444 2450 ('hg.create.repository', _('Repository creation enabled')),
2445 2451 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2446 2452 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2447 2453
2448 2454 ('hg.fork.none', _('Repository forking disabled')),
2449 2455 ('hg.fork.repository', _('Repository forking enabled')),
2450 2456
2451 2457 ('hg.register.none', _('Registration disabled')),
2452 2458 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2453 2459 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2454 2460
2455 2461 ('hg.password_reset.enabled', _('Password reset enabled')),
2456 2462 ('hg.password_reset.hidden', _('Password reset hidden')),
2457 2463 ('hg.password_reset.disabled', _('Password reset disabled')),
2458 2464
2459 2465 ('hg.extern_activate.manual', _('Manual activation of external account')),
2460 2466 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2461 2467
2462 2468 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2463 2469 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2464 2470 ]
2465 2471
2466 2472 # definition of system default permissions for DEFAULT user
2467 2473 DEFAULT_USER_PERMISSIONS = [
2468 2474 'repository.read',
2469 2475 'group.read',
2470 2476 'usergroup.read',
2471 2477 'hg.create.repository',
2472 2478 'hg.repogroup.create.false',
2473 2479 'hg.usergroup.create.false',
2474 2480 'hg.create.write_on_repogroup.true',
2475 2481 'hg.fork.repository',
2476 2482 'hg.register.manual_activate',
2477 2483 'hg.password_reset.enabled',
2478 2484 'hg.extern_activate.auto',
2479 2485 'hg.inherit_default_perms.true',
2480 2486 ]
2481 2487
2482 2488 # defines which permissions are more important higher the more important
2483 2489 # Weight defines which permissions are more important.
2484 2490 # The higher number the more important.
2485 2491 PERM_WEIGHTS = {
2486 2492 'repository.none': 0,
2487 2493 'repository.read': 1,
2488 2494 'repository.write': 3,
2489 2495 'repository.admin': 4,
2490 2496
2491 2497 'group.none': 0,
2492 2498 'group.read': 1,
2493 2499 'group.write': 3,
2494 2500 'group.admin': 4,
2495 2501
2496 2502 'usergroup.none': 0,
2497 2503 'usergroup.read': 1,
2498 2504 'usergroup.write': 3,
2499 2505 'usergroup.admin': 4,
2500 2506
2501 2507 'hg.repogroup.create.false': 0,
2502 2508 'hg.repogroup.create.true': 1,
2503 2509
2504 2510 'hg.usergroup.create.false': 0,
2505 2511 'hg.usergroup.create.true': 1,
2506 2512
2507 2513 'hg.fork.none': 0,
2508 2514 'hg.fork.repository': 1,
2509 2515 'hg.create.none': 0,
2510 2516 'hg.create.repository': 1
2511 2517 }
2512 2518
2513 2519 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2514 2520 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2515 2521 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2516 2522
2517 2523 def __unicode__(self):
2518 2524 return u"<%s('%s:%s')>" % (
2519 2525 self.__class__.__name__, self.permission_id, self.permission_name
2520 2526 )
2521 2527
2522 2528 @classmethod
2523 2529 def get_by_key(cls, key):
2524 2530 return cls.query().filter(cls.permission_name == key).scalar()
2525 2531
2526 2532 @classmethod
2527 2533 def get_default_repo_perms(cls, user_id, repo_id=None):
2528 2534 q = Session().query(UserRepoToPerm, Repository, Permission)\
2529 2535 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2530 2536 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2531 2537 .filter(UserRepoToPerm.user_id == user_id)
2532 2538 if repo_id:
2533 2539 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2534 2540 return q.all()
2535 2541
2536 2542 @classmethod
2537 2543 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2538 2544 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2539 2545 .join(
2540 2546 Permission,
2541 2547 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2542 2548 .join(
2543 2549 Repository,
2544 2550 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2545 2551 .join(
2546 2552 UserGroup,
2547 2553 UserGroupRepoToPerm.users_group_id ==
2548 2554 UserGroup.users_group_id)\
2549 2555 .join(
2550 2556 UserGroupMember,
2551 2557 UserGroupRepoToPerm.users_group_id ==
2552 2558 UserGroupMember.users_group_id)\
2553 2559 .filter(
2554 2560 UserGroupMember.user_id == user_id,
2555 2561 UserGroup.users_group_active == true())
2556 2562 if repo_id:
2557 2563 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2558 2564 return q.all()
2559 2565
2560 2566 @classmethod
2561 2567 def get_default_group_perms(cls, user_id, repo_group_id=None):
2562 2568 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2563 2569 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2564 2570 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2565 2571 .filter(UserRepoGroupToPerm.user_id == user_id)
2566 2572 if repo_group_id:
2567 2573 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2568 2574 return q.all()
2569 2575
2570 2576 @classmethod
2571 2577 def get_default_group_perms_from_user_group(
2572 2578 cls, user_id, repo_group_id=None):
2573 2579 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2574 2580 .join(
2575 2581 Permission,
2576 2582 UserGroupRepoGroupToPerm.permission_id ==
2577 2583 Permission.permission_id)\
2578 2584 .join(
2579 2585 RepoGroup,
2580 2586 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2581 2587 .join(
2582 2588 UserGroup,
2583 2589 UserGroupRepoGroupToPerm.users_group_id ==
2584 2590 UserGroup.users_group_id)\
2585 2591 .join(
2586 2592 UserGroupMember,
2587 2593 UserGroupRepoGroupToPerm.users_group_id ==
2588 2594 UserGroupMember.users_group_id)\
2589 2595 .filter(
2590 2596 UserGroupMember.user_id == user_id,
2591 2597 UserGroup.users_group_active == true())
2592 2598 if repo_group_id:
2593 2599 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2594 2600 return q.all()
2595 2601
2596 2602 @classmethod
2597 2603 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2598 2604 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2599 2605 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2600 2606 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2601 2607 .filter(UserUserGroupToPerm.user_id == user_id)
2602 2608 if user_group_id:
2603 2609 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2604 2610 return q.all()
2605 2611
2606 2612 @classmethod
2607 2613 def get_default_user_group_perms_from_user_group(
2608 2614 cls, user_id, user_group_id=None):
2609 2615 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2610 2616 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2611 2617 .join(
2612 2618 Permission,
2613 2619 UserGroupUserGroupToPerm.permission_id ==
2614 2620 Permission.permission_id)\
2615 2621 .join(
2616 2622 TargetUserGroup,
2617 2623 UserGroupUserGroupToPerm.target_user_group_id ==
2618 2624 TargetUserGroup.users_group_id)\
2619 2625 .join(
2620 2626 UserGroup,
2621 2627 UserGroupUserGroupToPerm.user_group_id ==
2622 2628 UserGroup.users_group_id)\
2623 2629 .join(
2624 2630 UserGroupMember,
2625 2631 UserGroupUserGroupToPerm.user_group_id ==
2626 2632 UserGroupMember.users_group_id)\
2627 2633 .filter(
2628 2634 UserGroupMember.user_id == user_id,
2629 2635 UserGroup.users_group_active == true())
2630 2636 if user_group_id:
2631 2637 q = q.filter(
2632 2638 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2633 2639
2634 2640 return q.all()
2635 2641
2636 2642
2637 2643 class UserRepoToPerm(Base, BaseModel):
2638 2644 __tablename__ = 'repo_to_perm'
2639 2645 __table_args__ = (
2640 2646 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2641 2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 2649 )
2644 2650 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 2651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2646 2652 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2647 2653 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2648 2654
2649 2655 user = relationship('User')
2650 2656 repository = relationship('Repository')
2651 2657 permission = relationship('Permission')
2652 2658
2653 2659 @classmethod
2654 2660 def create(cls, user, repository, permission):
2655 2661 n = cls()
2656 2662 n.user = user
2657 2663 n.repository = repository
2658 2664 n.permission = permission
2659 2665 Session().add(n)
2660 2666 return n
2661 2667
2662 2668 def __unicode__(self):
2663 2669 return u'<%s => %s >' % (self.user, self.repository)
2664 2670
2665 2671
2666 2672 class UserUserGroupToPerm(Base, BaseModel):
2667 2673 __tablename__ = 'user_user_group_to_perm'
2668 2674 __table_args__ = (
2669 2675 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2670 2676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2671 2677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2672 2678 )
2673 2679 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2674 2680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2675 2681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2676 2682 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2677 2683
2678 2684 user = relationship('User')
2679 2685 user_group = relationship('UserGroup')
2680 2686 permission = relationship('Permission')
2681 2687
2682 2688 @classmethod
2683 2689 def create(cls, user, user_group, permission):
2684 2690 n = cls()
2685 2691 n.user = user
2686 2692 n.user_group = user_group
2687 2693 n.permission = permission
2688 2694 Session().add(n)
2689 2695 return n
2690 2696
2691 2697 def __unicode__(self):
2692 2698 return u'<%s => %s >' % (self.user, self.user_group)
2693 2699
2694 2700
2695 2701 class UserToPerm(Base, BaseModel):
2696 2702 __tablename__ = 'user_to_perm'
2697 2703 __table_args__ = (
2698 2704 UniqueConstraint('user_id', 'permission_id'),
2699 2705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2700 2706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2701 2707 )
2702 2708 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2703 2709 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2704 2710 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2705 2711
2706 2712 user = relationship('User')
2707 2713 permission = relationship('Permission', lazy='joined')
2708 2714
2709 2715 def __unicode__(self):
2710 2716 return u'<%s => %s >' % (self.user, self.permission)
2711 2717
2712 2718
2713 2719 class UserGroupRepoToPerm(Base, BaseModel):
2714 2720 __tablename__ = 'users_group_repo_to_perm'
2715 2721 __table_args__ = (
2716 2722 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2717 2723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2718 2724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2719 2725 )
2720 2726 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2721 2727 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2722 2728 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2723 2729 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2724 2730
2725 2731 users_group = relationship('UserGroup')
2726 2732 permission = relationship('Permission')
2727 2733 repository = relationship('Repository')
2728 2734
2729 2735 @classmethod
2730 2736 def create(cls, users_group, repository, permission):
2731 2737 n = cls()
2732 2738 n.users_group = users_group
2733 2739 n.repository = repository
2734 2740 n.permission = permission
2735 2741 Session().add(n)
2736 2742 return n
2737 2743
2738 2744 def __unicode__(self):
2739 2745 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2740 2746
2741 2747
2742 2748 class UserGroupUserGroupToPerm(Base, BaseModel):
2743 2749 __tablename__ = 'user_group_user_group_to_perm'
2744 2750 __table_args__ = (
2745 2751 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2746 2752 CheckConstraint('target_user_group_id != user_group_id'),
2747 2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2748 2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2749 2755 )
2750 2756 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)
2751 2757 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2752 2758 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2753 2759 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2754 2760
2755 2761 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2756 2762 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2757 2763 permission = relationship('Permission')
2758 2764
2759 2765 @classmethod
2760 2766 def create(cls, target_user_group, user_group, permission):
2761 2767 n = cls()
2762 2768 n.target_user_group = target_user_group
2763 2769 n.user_group = user_group
2764 2770 n.permission = permission
2765 2771 Session().add(n)
2766 2772 return n
2767 2773
2768 2774 def __unicode__(self):
2769 2775 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2770 2776
2771 2777
2772 2778 class UserGroupToPerm(Base, BaseModel):
2773 2779 __tablename__ = 'users_group_to_perm'
2774 2780 __table_args__ = (
2775 2781 UniqueConstraint('users_group_id', 'permission_id',),
2776 2782 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2777 2783 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2778 2784 )
2779 2785 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2780 2786 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2781 2787 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2782 2788
2783 2789 users_group = relationship('UserGroup')
2784 2790 permission = relationship('Permission')
2785 2791
2786 2792
2787 2793 class UserRepoGroupToPerm(Base, BaseModel):
2788 2794 __tablename__ = 'user_repo_group_to_perm'
2789 2795 __table_args__ = (
2790 2796 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2791 2797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2792 2798 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2793 2799 )
2794 2800
2795 2801 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2796 2802 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2797 2803 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2798 2804 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2799 2805
2800 2806 user = relationship('User')
2801 2807 group = relationship('RepoGroup')
2802 2808 permission = relationship('Permission')
2803 2809
2804 2810 @classmethod
2805 2811 def create(cls, user, repository_group, permission):
2806 2812 n = cls()
2807 2813 n.user = user
2808 2814 n.group = repository_group
2809 2815 n.permission = permission
2810 2816 Session().add(n)
2811 2817 return n
2812 2818
2813 2819
2814 2820 class UserGroupRepoGroupToPerm(Base, BaseModel):
2815 2821 __tablename__ = 'users_group_repo_group_to_perm'
2816 2822 __table_args__ = (
2817 2823 UniqueConstraint('users_group_id', 'group_id'),
2818 2824 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2819 2825 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2820 2826 )
2821 2827
2822 2828 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)
2823 2829 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2824 2830 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2825 2831 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2826 2832
2827 2833 users_group = relationship('UserGroup')
2828 2834 permission = relationship('Permission')
2829 2835 group = relationship('RepoGroup')
2830 2836
2831 2837 @classmethod
2832 2838 def create(cls, user_group, repository_group, permission):
2833 2839 n = cls()
2834 2840 n.users_group = user_group
2835 2841 n.group = repository_group
2836 2842 n.permission = permission
2837 2843 Session().add(n)
2838 2844 return n
2839 2845
2840 2846 def __unicode__(self):
2841 2847 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2842 2848
2843 2849
2844 2850 class Statistics(Base, BaseModel):
2845 2851 __tablename__ = 'statistics'
2846 2852 __table_args__ = (
2847 2853 UniqueConstraint('repository_id'),
2848 2854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2849 2855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2850 2856 )
2851 2857 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2852 2858 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2853 2859 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2854 2860 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2855 2861 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2856 2862 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2857 2863
2858 2864 repository = relationship('Repository', single_parent=True)
2859 2865
2860 2866
2861 2867 class UserFollowing(Base, BaseModel):
2862 2868 __tablename__ = 'user_followings'
2863 2869 __table_args__ = (
2864 2870 UniqueConstraint('user_id', 'follows_repository_id'),
2865 2871 UniqueConstraint('user_id', 'follows_user_id'),
2866 2872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2867 2873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2868 2874 )
2869 2875
2870 2876 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2871 2877 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2872 2878 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2873 2879 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2874 2880 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2875 2881
2876 2882 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2877 2883
2878 2884 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2879 2885 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2880 2886
2881 2887 @classmethod
2882 2888 def get_repo_followers(cls, repo_id):
2883 2889 return cls.query().filter(cls.follows_repo_id == repo_id)
2884 2890
2885 2891
2886 2892 class CacheKey(Base, BaseModel):
2887 2893 __tablename__ = 'cache_invalidation'
2888 2894 __table_args__ = (
2889 2895 UniqueConstraint('cache_key'),
2890 2896 Index('key_idx', 'cache_key'),
2891 2897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2892 2898 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2893 2899 )
2894 2900 CACHE_TYPE_ATOM = 'ATOM'
2895 2901 CACHE_TYPE_RSS = 'RSS'
2896 2902 CACHE_TYPE_README = 'README'
2897 2903
2898 2904 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2899 2905 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2900 2906 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2901 2907 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2902 2908
2903 2909 def __init__(self, cache_key, cache_args=''):
2904 2910 self.cache_key = cache_key
2905 2911 self.cache_args = cache_args
2906 2912 self.cache_active = False
2907 2913
2908 2914 def __unicode__(self):
2909 2915 return u"<%s('%s:%s[%s]')>" % (
2910 2916 self.__class__.__name__,
2911 2917 self.cache_id, self.cache_key, self.cache_active)
2912 2918
2913 2919 def _cache_key_partition(self):
2914 2920 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2915 2921 return prefix, repo_name, suffix
2916 2922
2917 2923 def get_prefix(self):
2918 2924 """
2919 2925 Try to extract prefix from existing cache key. The key could consist
2920 2926 of prefix, repo_name, suffix
2921 2927 """
2922 2928 # this returns prefix, repo_name, suffix
2923 2929 return self._cache_key_partition()[0]
2924 2930
2925 2931 def get_suffix(self):
2926 2932 """
2927 2933 get suffix that might have been used in _get_cache_key to
2928 2934 generate self.cache_key. Only used for informational purposes
2929 2935 in repo_edit.mako.
2930 2936 """
2931 2937 # prefix, repo_name, suffix
2932 2938 return self._cache_key_partition()[2]
2933 2939
2934 2940 @classmethod
2935 2941 def delete_all_cache(cls):
2936 2942 """
2937 2943 Delete all cache keys from database.
2938 2944 Should only be run when all instances are down and all entries
2939 2945 thus stale.
2940 2946 """
2941 2947 cls.query().delete()
2942 2948 Session().commit()
2943 2949
2944 2950 @classmethod
2945 2951 def get_cache_key(cls, repo_name, cache_type):
2946 2952 """
2947 2953
2948 2954 Generate a cache key for this process of RhodeCode instance.
2949 2955 Prefix most likely will be process id or maybe explicitly set
2950 2956 instance_id from .ini file.
2951 2957 """
2952 2958 import rhodecode
2953 2959 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2954 2960
2955 2961 repo_as_unicode = safe_unicode(repo_name)
2956 2962 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2957 2963 if cache_type else repo_as_unicode
2958 2964
2959 2965 return u'{}{}'.format(prefix, key)
2960 2966
2961 2967 @classmethod
2962 2968 def set_invalidate(cls, repo_name, delete=False):
2963 2969 """
2964 2970 Mark all caches of a repo as invalid in the database.
2965 2971 """
2966 2972
2967 2973 try:
2968 2974 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2969 2975 if delete:
2970 2976 log.debug('cache objects deleted for repo %s',
2971 2977 safe_str(repo_name))
2972 2978 qry.delete()
2973 2979 else:
2974 2980 log.debug('cache objects marked as invalid for repo %s',
2975 2981 safe_str(repo_name))
2976 2982 qry.update({"cache_active": False})
2977 2983
2978 2984 Session().commit()
2979 2985 except Exception:
2980 2986 log.exception(
2981 2987 'Cache key invalidation failed for repository %s',
2982 2988 safe_str(repo_name))
2983 2989 Session().rollback()
2984 2990
2985 2991 @classmethod
2986 2992 def get_active_cache(cls, cache_key):
2987 2993 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2988 2994 if inv_obj:
2989 2995 return inv_obj
2990 2996 return None
2991 2997
2992 2998 @classmethod
2993 2999 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2994 3000 thread_scoped=False):
2995 3001 """
2996 3002 @cache_region('long_term')
2997 3003 def _heavy_calculation(cache_key):
2998 3004 return 'result'
2999 3005
3000 3006 cache_context = CacheKey.repo_context_cache(
3001 3007 _heavy_calculation, repo_name, cache_type)
3002 3008
3003 3009 with cache_context as context:
3004 3010 context.invalidate()
3005 3011 computed = context.compute()
3006 3012
3007 3013 assert computed == 'result'
3008 3014 """
3009 3015 from rhodecode.lib import caches
3010 3016 return caches.InvalidationContext(
3011 3017 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3012 3018
3013 3019
3014 3020 class ChangesetComment(Base, BaseModel):
3015 3021 __tablename__ = 'changeset_comments'
3016 3022 __table_args__ = (
3017 3023 Index('cc_revision_idx', 'revision'),
3018 3024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3019 3025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3020 3026 )
3021 3027
3022 3028 COMMENT_OUTDATED = u'comment_outdated'
3023 3029 COMMENT_TYPE_NOTE = u'note'
3024 3030 COMMENT_TYPE_TODO = u'todo'
3025 3031 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3026 3032
3027 3033 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3028 3034 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3029 3035 revision = Column('revision', String(40), nullable=True)
3030 3036 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3031 3037 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3032 3038 line_no = Column('line_no', Unicode(10), nullable=True)
3033 3039 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3034 3040 f_path = Column('f_path', Unicode(1000), nullable=True)
3035 3041 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3036 3042 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3037 3043 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3038 3044 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3039 3045 renderer = Column('renderer', Unicode(64), nullable=True)
3040 3046 display_state = Column('display_state', Unicode(128), nullable=True)
3041 3047
3042 3048 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3043 3049 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3044 3050 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3045 3051 author = relationship('User', lazy='joined')
3046 3052 repo = relationship('Repository')
3047 3053 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3048 3054 pull_request = relationship('PullRequest', lazy='joined')
3049 3055 pull_request_version = relationship('PullRequestVersion')
3050 3056
3051 3057 @classmethod
3052 3058 def get_users(cls, revision=None, pull_request_id=None):
3053 3059 """
3054 3060 Returns user associated with this ChangesetComment. ie those
3055 3061 who actually commented
3056 3062
3057 3063 :param cls:
3058 3064 :param revision:
3059 3065 """
3060 3066 q = Session().query(User)\
3061 3067 .join(ChangesetComment.author)
3062 3068 if revision:
3063 3069 q = q.filter(cls.revision == revision)
3064 3070 elif pull_request_id:
3065 3071 q = q.filter(cls.pull_request_id == pull_request_id)
3066 3072 return q.all()
3067 3073
3068 3074 @classmethod
3069 3075 def get_index_from_version(cls, pr_version, versions):
3070 3076 num_versions = [x.pull_request_version_id for x in versions]
3071 3077 try:
3072 3078 return num_versions.index(pr_version) +1
3073 3079 except (IndexError, ValueError):
3074 3080 return
3075 3081
3076 3082 @property
3077 3083 def outdated(self):
3078 3084 return self.display_state == self.COMMENT_OUTDATED
3079 3085
3080 3086 def outdated_at_version(self, version):
3081 3087 """
3082 3088 Checks if comment is outdated for given pull request version
3083 3089 """
3084 3090 return self.outdated and self.pull_request_version_id != version
3085 3091
3086 3092 def older_than_version(self, version):
3087 3093 """
3088 3094 Checks if comment is made from previous version than given
3089 3095 """
3090 3096 if version is None:
3091 3097 return self.pull_request_version_id is not None
3092 3098
3093 3099 return self.pull_request_version_id < version
3094 3100
3095 3101 @property
3096 3102 def resolved(self):
3097 3103 return self.resolved_by[0] if self.resolved_by else None
3098 3104
3099 3105 @property
3100 3106 def is_todo(self):
3101 3107 return self.comment_type == self.COMMENT_TYPE_TODO
3102 3108
3103 3109 def get_index_version(self, versions):
3104 3110 return self.get_index_from_version(
3105 3111 self.pull_request_version_id, versions)
3106 3112
3107 3113 def __repr__(self):
3108 3114 if self.comment_id:
3109 3115 return '<DB:Comment #%s>' % self.comment_id
3110 3116 else:
3111 3117 return '<DB:Comment at %#x>' % id(self)
3112 3118
3113 3119
3114 3120 class ChangesetStatus(Base, BaseModel):
3115 3121 __tablename__ = 'changeset_statuses'
3116 3122 __table_args__ = (
3117 3123 Index('cs_revision_idx', 'revision'),
3118 3124 Index('cs_version_idx', 'version'),
3119 3125 UniqueConstraint('repo_id', 'revision', 'version'),
3120 3126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3121 3127 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3122 3128 )
3123 3129 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3124 3130 STATUS_APPROVED = 'approved'
3125 3131 STATUS_REJECTED = 'rejected'
3126 3132 STATUS_UNDER_REVIEW = 'under_review'
3127 3133
3128 3134 STATUSES = [
3129 3135 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3130 3136 (STATUS_APPROVED, _("Approved")),
3131 3137 (STATUS_REJECTED, _("Rejected")),
3132 3138 (STATUS_UNDER_REVIEW, _("Under Review")),
3133 3139 ]
3134 3140
3135 3141 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3136 3142 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3137 3143 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3138 3144 revision = Column('revision', String(40), nullable=False)
3139 3145 status = Column('status', String(128), nullable=False, default=DEFAULT)
3140 3146 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3141 3147 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3142 3148 version = Column('version', Integer(), nullable=False, default=0)
3143 3149 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3144 3150
3145 3151 author = relationship('User', lazy='joined')
3146 3152 repo = relationship('Repository')
3147 3153 comment = relationship('ChangesetComment', lazy='joined')
3148 3154 pull_request = relationship('PullRequest', lazy='joined')
3149 3155
3150 3156 def __unicode__(self):
3151 3157 return u"<%s('%s[v%s]:%s')>" % (
3152 3158 self.__class__.__name__,
3153 3159 self.status, self.version, self.author
3154 3160 )
3155 3161
3156 3162 @classmethod
3157 3163 def get_status_lbl(cls, value):
3158 3164 return dict(cls.STATUSES).get(value)
3159 3165
3160 3166 @property
3161 3167 def status_lbl(self):
3162 3168 return ChangesetStatus.get_status_lbl(self.status)
3163 3169
3164 3170
3165 3171 class _PullRequestBase(BaseModel):
3166 3172 """
3167 3173 Common attributes of pull request and version entries.
3168 3174 """
3169 3175
3170 3176 # .status values
3171 3177 STATUS_NEW = u'new'
3172 3178 STATUS_OPEN = u'open'
3173 3179 STATUS_CLOSED = u'closed'
3174 3180
3175 3181 title = Column('title', Unicode(255), nullable=True)
3176 3182 description = Column(
3177 3183 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3178 3184 nullable=True)
3179 3185 # new/open/closed status of pull request (not approve/reject/etc)
3180 3186 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3181 3187 created_on = Column(
3182 3188 'created_on', DateTime(timezone=False), nullable=False,
3183 3189 default=datetime.datetime.now)
3184 3190 updated_on = Column(
3185 3191 'updated_on', DateTime(timezone=False), nullable=False,
3186 3192 default=datetime.datetime.now)
3187 3193
3188 3194 @declared_attr
3189 3195 def user_id(cls):
3190 3196 return Column(
3191 3197 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3192 3198 unique=None)
3193 3199
3194 3200 # 500 revisions max
3195 3201 _revisions = Column(
3196 3202 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3197 3203
3198 3204 @declared_attr
3199 3205 def source_repo_id(cls):
3200 3206 # TODO: dan: rename column to source_repo_id
3201 3207 return Column(
3202 3208 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3203 3209 nullable=False)
3204 3210
3205 3211 source_ref = Column('org_ref', Unicode(255), nullable=False)
3206 3212
3207 3213 @declared_attr
3208 3214 def target_repo_id(cls):
3209 3215 # TODO: dan: rename column to target_repo_id
3210 3216 return Column(
3211 3217 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3212 3218 nullable=False)
3213 3219
3214 3220 target_ref = Column('other_ref', Unicode(255), nullable=False)
3215 3221 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3216 3222
3217 3223 # TODO: dan: rename column to last_merge_source_rev
3218 3224 _last_merge_source_rev = Column(
3219 3225 'last_merge_org_rev', String(40), nullable=True)
3220 3226 # TODO: dan: rename column to last_merge_target_rev
3221 3227 _last_merge_target_rev = Column(
3222 3228 'last_merge_other_rev', String(40), nullable=True)
3223 3229 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3224 3230 merge_rev = Column('merge_rev', String(40), nullable=True)
3225 3231
3226 3232 @hybrid_property
3227 3233 def revisions(self):
3228 3234 return self._revisions.split(':') if self._revisions else []
3229 3235
3230 3236 @revisions.setter
3231 3237 def revisions(self, val):
3232 3238 self._revisions = ':'.join(val)
3233 3239
3234 3240 @declared_attr
3235 3241 def author(cls):
3236 3242 return relationship('User', lazy='joined')
3237 3243
3238 3244 @declared_attr
3239 3245 def source_repo(cls):
3240 3246 return relationship(
3241 3247 'Repository',
3242 3248 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3243 3249
3244 3250 @property
3245 3251 def source_ref_parts(self):
3246 3252 return self.unicode_to_reference(self.source_ref)
3247 3253
3248 3254 @declared_attr
3249 3255 def target_repo(cls):
3250 3256 return relationship(
3251 3257 'Repository',
3252 3258 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3253 3259
3254 3260 @property
3255 3261 def target_ref_parts(self):
3256 3262 return self.unicode_to_reference(self.target_ref)
3257 3263
3258 3264 @property
3259 3265 def shadow_merge_ref(self):
3260 3266 return self.unicode_to_reference(self._shadow_merge_ref)
3261 3267
3262 3268 @shadow_merge_ref.setter
3263 3269 def shadow_merge_ref(self, ref):
3264 3270 self._shadow_merge_ref = self.reference_to_unicode(ref)
3265 3271
3266 3272 def unicode_to_reference(self, raw):
3267 3273 """
3268 3274 Convert a unicode (or string) to a reference object.
3269 3275 If unicode evaluates to False it returns None.
3270 3276 """
3271 3277 if raw:
3272 3278 refs = raw.split(':')
3273 3279 return Reference(*refs)
3274 3280 else:
3275 3281 return None
3276 3282
3277 3283 def reference_to_unicode(self, ref):
3278 3284 """
3279 3285 Convert a reference object to unicode.
3280 3286 If reference is None it returns None.
3281 3287 """
3282 3288 if ref:
3283 3289 return u':'.join(ref)
3284 3290 else:
3285 3291 return None
3286 3292
3287 3293 def get_api_data(self):
3288 3294 from rhodecode.model.pull_request import PullRequestModel
3289 3295 pull_request = self
3290 3296 merge_status = PullRequestModel().merge_status(pull_request)
3291 3297
3292 3298 pull_request_url = url(
3293 3299 'pullrequest_show', repo_name=self.target_repo.repo_name,
3294 3300 pull_request_id=self.pull_request_id, qualified=True)
3295 3301
3296 3302 merge_data = {
3297 3303 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3298 3304 'reference': (
3299 3305 pull_request.shadow_merge_ref._asdict()
3300 3306 if pull_request.shadow_merge_ref else None),
3301 3307 }
3302 3308
3303 3309 data = {
3304 3310 'pull_request_id': pull_request.pull_request_id,
3305 3311 'url': pull_request_url,
3306 3312 'title': pull_request.title,
3307 3313 'description': pull_request.description,
3308 3314 'status': pull_request.status,
3309 3315 'created_on': pull_request.created_on,
3310 3316 'updated_on': pull_request.updated_on,
3311 3317 'commit_ids': pull_request.revisions,
3312 3318 'review_status': pull_request.calculated_review_status(),
3313 3319 'mergeable': {
3314 3320 'status': merge_status[0],
3315 3321 'message': unicode(merge_status[1]),
3316 3322 },
3317 3323 'source': {
3318 3324 'clone_url': pull_request.source_repo.clone_url(),
3319 3325 'repository': pull_request.source_repo.repo_name,
3320 3326 'reference': {
3321 3327 'name': pull_request.source_ref_parts.name,
3322 3328 'type': pull_request.source_ref_parts.type,
3323 3329 'commit_id': pull_request.source_ref_parts.commit_id,
3324 3330 },
3325 3331 },
3326 3332 'target': {
3327 3333 'clone_url': pull_request.target_repo.clone_url(),
3328 3334 'repository': pull_request.target_repo.repo_name,
3329 3335 'reference': {
3330 3336 'name': pull_request.target_ref_parts.name,
3331 3337 'type': pull_request.target_ref_parts.type,
3332 3338 'commit_id': pull_request.target_ref_parts.commit_id,
3333 3339 },
3334 3340 },
3335 3341 'merge': merge_data,
3336 3342 'author': pull_request.author.get_api_data(include_secrets=False,
3337 3343 details='basic'),
3338 3344 'reviewers': [
3339 3345 {
3340 3346 'user': reviewer.get_api_data(include_secrets=False,
3341 3347 details='basic'),
3342 3348 'reasons': reasons,
3343 3349 'review_status': st[0][1].status if st else 'not_reviewed',
3344 3350 }
3345 3351 for reviewer, reasons, st in pull_request.reviewers_statuses()
3346 3352 ]
3347 3353 }
3348 3354
3349 3355 return data
3350 3356
3351 3357
3352 3358 class PullRequest(Base, _PullRequestBase):
3353 3359 __tablename__ = 'pull_requests'
3354 3360 __table_args__ = (
3355 3361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3356 3362 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3357 3363 )
3358 3364
3359 3365 pull_request_id = Column(
3360 3366 'pull_request_id', Integer(), nullable=False, primary_key=True)
3361 3367
3362 3368 def __repr__(self):
3363 3369 if self.pull_request_id:
3364 3370 return '<DB:PullRequest #%s>' % self.pull_request_id
3365 3371 else:
3366 3372 return '<DB:PullRequest at %#x>' % id(self)
3367 3373
3368 3374 reviewers = relationship('PullRequestReviewers',
3369 3375 cascade="all, delete, delete-orphan")
3370 3376 statuses = relationship('ChangesetStatus')
3371 3377 comments = relationship('ChangesetComment',
3372 3378 cascade="all, delete, delete-orphan")
3373 3379 versions = relationship('PullRequestVersion',
3374 3380 cascade="all, delete, delete-orphan",
3375 3381 lazy='dynamic')
3376 3382
3377 3383 @classmethod
3378 3384 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3379 3385 internal_methods=None):
3380 3386
3381 3387 class PullRequestDisplay(object):
3382 3388 """
3383 3389 Special object wrapper for showing PullRequest data via Versions
3384 3390 It mimics PR object as close as possible. This is read only object
3385 3391 just for display
3386 3392 """
3387 3393
3388 3394 def __init__(self, attrs, internal=None):
3389 3395 self.attrs = attrs
3390 3396 # internal have priority over the given ones via attrs
3391 3397 self.internal = internal or ['versions']
3392 3398
3393 3399 def __getattr__(self, item):
3394 3400 if item in self.internal:
3395 3401 return getattr(self, item)
3396 3402 try:
3397 3403 return self.attrs[item]
3398 3404 except KeyError:
3399 3405 raise AttributeError(
3400 3406 '%s object has no attribute %s' % (self, item))
3401 3407
3402 3408 def __repr__(self):
3403 3409 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3404 3410
3405 3411 def versions(self):
3406 3412 return pull_request_obj.versions.order_by(
3407 3413 PullRequestVersion.pull_request_version_id).all()
3408 3414
3409 3415 def is_closed(self):
3410 3416 return pull_request_obj.is_closed()
3411 3417
3412 3418 @property
3413 3419 def pull_request_version_id(self):
3414 3420 return getattr(pull_request_obj, 'pull_request_version_id', None)
3415 3421
3416 3422 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3417 3423
3418 3424 attrs.author = StrictAttributeDict(
3419 3425 pull_request_obj.author.get_api_data())
3420 3426 if pull_request_obj.target_repo:
3421 3427 attrs.target_repo = StrictAttributeDict(
3422 3428 pull_request_obj.target_repo.get_api_data())
3423 3429 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3424 3430
3425 3431 if pull_request_obj.source_repo:
3426 3432 attrs.source_repo = StrictAttributeDict(
3427 3433 pull_request_obj.source_repo.get_api_data())
3428 3434 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3429 3435
3430 3436 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3431 3437 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3432 3438 attrs.revisions = pull_request_obj.revisions
3433 3439
3434 3440 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3435 3441
3436 3442 return PullRequestDisplay(attrs, internal=internal_methods)
3437 3443
3438 3444 def is_closed(self):
3439 3445 return self.status == self.STATUS_CLOSED
3440 3446
3441 3447 def __json__(self):
3442 3448 return {
3443 3449 'revisions': self.revisions,
3444 3450 }
3445 3451
3446 3452 def calculated_review_status(self):
3447 3453 from rhodecode.model.changeset_status import ChangesetStatusModel
3448 3454 return ChangesetStatusModel().calculated_review_status(self)
3449 3455
3450 3456 def reviewers_statuses(self):
3451 3457 from rhodecode.model.changeset_status import ChangesetStatusModel
3452 3458 return ChangesetStatusModel().reviewers_statuses(self)
3453 3459
3454 3460 @property
3455 3461 def workspace_id(self):
3456 3462 from rhodecode.model.pull_request import PullRequestModel
3457 3463 return PullRequestModel()._workspace_id(self)
3458 3464
3459 3465 def get_shadow_repo(self):
3460 3466 workspace_id = self.workspace_id
3461 3467 vcs_obj = self.target_repo.scm_instance()
3462 3468 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3463 3469 workspace_id)
3464 3470 return vcs_obj._get_shadow_instance(shadow_repository_path)
3465 3471
3466 3472
3467 3473 class PullRequestVersion(Base, _PullRequestBase):
3468 3474 __tablename__ = 'pull_request_versions'
3469 3475 __table_args__ = (
3470 3476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3471 3477 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3472 3478 )
3473 3479
3474 3480 pull_request_version_id = Column(
3475 3481 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3476 3482 pull_request_id = Column(
3477 3483 'pull_request_id', Integer(),
3478 3484 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3479 3485 pull_request = relationship('PullRequest')
3480 3486
3481 3487 def __repr__(self):
3482 3488 if self.pull_request_version_id:
3483 3489 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3484 3490 else:
3485 3491 return '<DB:PullRequestVersion at %#x>' % id(self)
3486 3492
3487 3493 @property
3488 3494 def reviewers(self):
3489 3495 return self.pull_request.reviewers
3490 3496
3491 3497 @property
3492 3498 def versions(self):
3493 3499 return self.pull_request.versions
3494 3500
3495 3501 def is_closed(self):
3496 3502 # calculate from original
3497 3503 return self.pull_request.status == self.STATUS_CLOSED
3498 3504
3499 3505 def calculated_review_status(self):
3500 3506 return self.pull_request.calculated_review_status()
3501 3507
3502 3508 def reviewers_statuses(self):
3503 3509 return self.pull_request.reviewers_statuses()
3504 3510
3505 3511
3506 3512 class PullRequestReviewers(Base, BaseModel):
3507 3513 __tablename__ = 'pull_request_reviewers'
3508 3514 __table_args__ = (
3509 3515 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3510 3516 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3511 3517 )
3512 3518
3513 3519 def __init__(self, user=None, pull_request=None, reasons=None):
3514 3520 self.user = user
3515 3521 self.pull_request = pull_request
3516 3522 self.reasons = reasons or []
3517 3523
3518 3524 @hybrid_property
3519 3525 def reasons(self):
3520 3526 if not self._reasons:
3521 3527 return []
3522 3528 return self._reasons
3523 3529
3524 3530 @reasons.setter
3525 3531 def reasons(self, val):
3526 3532 val = val or []
3527 3533 if any(not isinstance(x, basestring) for x in val):
3528 3534 raise Exception('invalid reasons type, must be list of strings')
3529 3535 self._reasons = val
3530 3536
3531 3537 pull_requests_reviewers_id = Column(
3532 3538 'pull_requests_reviewers_id', Integer(), nullable=False,
3533 3539 primary_key=True)
3534 3540 pull_request_id = Column(
3535 3541 "pull_request_id", Integer(),
3536 3542 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3537 3543 user_id = Column(
3538 3544 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3539 3545 _reasons = Column(
3540 3546 'reason', MutationList.as_mutable(
3541 3547 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3542 3548
3543 3549 user = relationship('User')
3544 3550 pull_request = relationship('PullRequest')
3545 3551
3546 3552
3547 3553 class Notification(Base, BaseModel):
3548 3554 __tablename__ = 'notifications'
3549 3555 __table_args__ = (
3550 3556 Index('notification_type_idx', 'type'),
3551 3557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3552 3558 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3553 3559 )
3554 3560
3555 3561 TYPE_CHANGESET_COMMENT = u'cs_comment'
3556 3562 TYPE_MESSAGE = u'message'
3557 3563 TYPE_MENTION = u'mention'
3558 3564 TYPE_REGISTRATION = u'registration'
3559 3565 TYPE_PULL_REQUEST = u'pull_request'
3560 3566 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3561 3567
3562 3568 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3563 3569 subject = Column('subject', Unicode(512), nullable=True)
3564 3570 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3565 3571 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3566 3572 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3567 3573 type_ = Column('type', Unicode(255))
3568 3574
3569 3575 created_by_user = relationship('User')
3570 3576 notifications_to_users = relationship('UserNotification', lazy='joined',
3571 3577 cascade="all, delete, delete-orphan")
3572 3578
3573 3579 @property
3574 3580 def recipients(self):
3575 3581 return [x.user for x in UserNotification.query()\
3576 3582 .filter(UserNotification.notification == self)\
3577 3583 .order_by(UserNotification.user_id.asc()).all()]
3578 3584
3579 3585 @classmethod
3580 3586 def create(cls, created_by, subject, body, recipients, type_=None):
3581 3587 if type_ is None:
3582 3588 type_ = Notification.TYPE_MESSAGE
3583 3589
3584 3590 notification = cls()
3585 3591 notification.created_by_user = created_by
3586 3592 notification.subject = subject
3587 3593 notification.body = body
3588 3594 notification.type_ = type_
3589 3595 notification.created_on = datetime.datetime.now()
3590 3596
3591 3597 for u in recipients:
3592 3598 assoc = UserNotification()
3593 3599 assoc.notification = notification
3594 3600
3595 3601 # if created_by is inside recipients mark his notification
3596 3602 # as read
3597 3603 if u.user_id == created_by.user_id:
3598 3604 assoc.read = True
3599 3605
3600 3606 u.notifications.append(assoc)
3601 3607 Session().add(notification)
3602 3608
3603 3609 return notification
3604 3610
3605 3611 @property
3606 3612 def description(self):
3607 3613 from rhodecode.model.notification import NotificationModel
3608 3614 return NotificationModel().make_description(self)
3609 3615
3610 3616
3611 3617 class UserNotification(Base, BaseModel):
3612 3618 __tablename__ = 'user_to_notification'
3613 3619 __table_args__ = (
3614 3620 UniqueConstraint('user_id', 'notification_id'),
3615 3621 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3616 3622 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3617 3623 )
3618 3624 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3619 3625 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3620 3626 read = Column('read', Boolean, default=False)
3621 3627 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3622 3628
3623 3629 user = relationship('User', lazy="joined")
3624 3630 notification = relationship('Notification', lazy="joined",
3625 3631 order_by=lambda: Notification.created_on.desc(),)
3626 3632
3627 3633 def mark_as_read(self):
3628 3634 self.read = True
3629 3635 Session().add(self)
3630 3636
3631 3637
3632 3638 class Gist(Base, BaseModel):
3633 3639 __tablename__ = 'gists'
3634 3640 __table_args__ = (
3635 3641 Index('g_gist_access_id_idx', 'gist_access_id'),
3636 3642 Index('g_created_on_idx', 'created_on'),
3637 3643 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3638 3644 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3639 3645 )
3640 3646 GIST_PUBLIC = u'public'
3641 3647 GIST_PRIVATE = u'private'
3642 3648 DEFAULT_FILENAME = u'gistfile1.txt'
3643 3649
3644 3650 ACL_LEVEL_PUBLIC = u'acl_public'
3645 3651 ACL_LEVEL_PRIVATE = u'acl_private'
3646 3652
3647 3653 gist_id = Column('gist_id', Integer(), primary_key=True)
3648 3654 gist_access_id = Column('gist_access_id', Unicode(250))
3649 3655 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3650 3656 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3651 3657 gist_expires = Column('gist_expires', Float(53), nullable=False)
3652 3658 gist_type = Column('gist_type', Unicode(128), nullable=False)
3653 3659 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3654 3660 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3655 3661 acl_level = Column('acl_level', Unicode(128), nullable=True)
3656 3662
3657 3663 owner = relationship('User')
3658 3664
3659 3665 def __repr__(self):
3660 3666 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3661 3667
3662 3668 @classmethod
3663 3669 def get_or_404(cls, id_, pyramid_exc=False):
3664 3670
3665 3671 if pyramid_exc:
3666 3672 from pyramid.httpexceptions import HTTPNotFound
3667 3673 else:
3668 3674 from webob.exc import HTTPNotFound
3669 3675
3670 3676 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3671 3677 if not res:
3672 3678 raise HTTPNotFound
3673 3679 return res
3674 3680
3675 3681 @classmethod
3676 3682 def get_by_access_id(cls, gist_access_id):
3677 3683 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3678 3684
3679 3685 def gist_url(self):
3680 3686 import rhodecode
3681 3687 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3682 3688 if alias_url:
3683 3689 return alias_url.replace('{gistid}', self.gist_access_id)
3684 3690
3685 3691 return url('gist', gist_id=self.gist_access_id, qualified=True)
3686 3692
3687 3693 @classmethod
3688 3694 def base_path(cls):
3689 3695 """
3690 3696 Returns base path when all gists are stored
3691 3697
3692 3698 :param cls:
3693 3699 """
3694 3700 from rhodecode.model.gist import GIST_STORE_LOC
3695 3701 q = Session().query(RhodeCodeUi)\
3696 3702 .filter(RhodeCodeUi.ui_key == URL_SEP)
3697 3703 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3698 3704 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3699 3705
3700 3706 def get_api_data(self):
3701 3707 """
3702 3708 Common function for generating gist related data for API
3703 3709 """
3704 3710 gist = self
3705 3711 data = {
3706 3712 'gist_id': gist.gist_id,
3707 3713 'type': gist.gist_type,
3708 3714 'access_id': gist.gist_access_id,
3709 3715 'description': gist.gist_description,
3710 3716 'url': gist.gist_url(),
3711 3717 'expires': gist.gist_expires,
3712 3718 'created_on': gist.created_on,
3713 3719 'modified_at': gist.modified_at,
3714 3720 'content': None,
3715 3721 'acl_level': gist.acl_level,
3716 3722 }
3717 3723 return data
3718 3724
3719 3725 def __json__(self):
3720 3726 data = dict(
3721 3727 )
3722 3728 data.update(self.get_api_data())
3723 3729 return data
3724 3730 # SCM functions
3725 3731
3726 3732 def scm_instance(self, **kwargs):
3727 3733 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3728 3734 return get_vcs_instance(
3729 3735 repo_path=safe_str(full_repo_path), create=False)
3730 3736
3731 3737
3732 3738 class ExternalIdentity(Base, BaseModel):
3733 3739 __tablename__ = 'external_identities'
3734 3740 __table_args__ = (
3735 3741 Index('local_user_id_idx', 'local_user_id'),
3736 3742 Index('external_id_idx', 'external_id'),
3737 3743 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3738 3744 'mysql_charset': 'utf8'})
3739 3745
3740 3746 external_id = Column('external_id', Unicode(255), default=u'',
3741 3747 primary_key=True)
3742 3748 external_username = Column('external_username', Unicode(1024), default=u'')
3743 3749 local_user_id = Column('local_user_id', Integer(),
3744 3750 ForeignKey('users.user_id'), primary_key=True)
3745 3751 provider_name = Column('provider_name', Unicode(255), default=u'',
3746 3752 primary_key=True)
3747 3753 access_token = Column('access_token', String(1024), default=u'')
3748 3754 alt_token = Column('alt_token', String(1024), default=u'')
3749 3755 token_secret = Column('token_secret', String(1024), default=u'')
3750 3756
3751 3757 @classmethod
3752 3758 def by_external_id_and_provider(cls, external_id, provider_name,
3753 3759 local_user_id=None):
3754 3760 """
3755 3761 Returns ExternalIdentity instance based on search params
3756 3762
3757 3763 :param external_id:
3758 3764 :param provider_name:
3759 3765 :return: ExternalIdentity
3760 3766 """
3761 3767 query = cls.query()
3762 3768 query = query.filter(cls.external_id == external_id)
3763 3769 query = query.filter(cls.provider_name == provider_name)
3764 3770 if local_user_id:
3765 3771 query = query.filter(cls.local_user_id == local_user_id)
3766 3772 return query.first()
3767 3773
3768 3774 @classmethod
3769 3775 def user_by_external_id_and_provider(cls, external_id, provider_name):
3770 3776 """
3771 3777 Returns User instance based on search params
3772 3778
3773 3779 :param external_id:
3774 3780 :param provider_name:
3775 3781 :return: User
3776 3782 """
3777 3783 query = User.query()
3778 3784 query = query.filter(cls.external_id == external_id)
3779 3785 query = query.filter(cls.provider_name == provider_name)
3780 3786 query = query.filter(User.user_id == cls.local_user_id)
3781 3787 return query.first()
3782 3788
3783 3789 @classmethod
3784 3790 def by_local_user_id(cls, local_user_id):
3785 3791 """
3786 3792 Returns all tokens for user
3787 3793
3788 3794 :param local_user_id:
3789 3795 :return: ExternalIdentity
3790 3796 """
3791 3797 query = cls.query()
3792 3798 query = query.filter(cls.local_user_id == local_user_id)
3793 3799 return query
3794 3800
3795 3801
3796 3802 class Integration(Base, BaseModel):
3797 3803 __tablename__ = 'integrations'
3798 3804 __table_args__ = (
3799 3805 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 3806 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3801 3807 )
3802 3808
3803 3809 integration_id = Column('integration_id', Integer(), primary_key=True)
3804 3810 integration_type = Column('integration_type', String(255))
3805 3811 enabled = Column('enabled', Boolean(), nullable=False)
3806 3812 name = Column('name', String(255), nullable=False)
3807 3813 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3808 3814 default=False)
3809 3815
3810 3816 settings = Column(
3811 3817 'settings_json', MutationObj.as_mutable(
3812 3818 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3813 3819 repo_id = Column(
3814 3820 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3815 3821 nullable=True, unique=None, default=None)
3816 3822 repo = relationship('Repository', lazy='joined')
3817 3823
3818 3824 repo_group_id = Column(
3819 3825 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3820 3826 nullable=True, unique=None, default=None)
3821 3827 repo_group = relationship('RepoGroup', lazy='joined')
3822 3828
3823 3829 @property
3824 3830 def scope(self):
3825 3831 if self.repo:
3826 3832 return repr(self.repo)
3827 3833 if self.repo_group:
3828 3834 if self.child_repos_only:
3829 3835 return repr(self.repo_group) + ' (child repos only)'
3830 3836 else:
3831 3837 return repr(self.repo_group) + ' (recursive)'
3832 3838 if self.child_repos_only:
3833 3839 return 'root_repos'
3834 3840 return 'global'
3835 3841
3836 3842 def __repr__(self):
3837 3843 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3838 3844
3839 3845
3840 3846 class RepoReviewRuleUser(Base, BaseModel):
3841 3847 __tablename__ = 'repo_review_rules_users'
3842 3848 __table_args__ = (
3843 3849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3844 3850 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3845 3851 )
3846 3852 repo_review_rule_user_id = Column(
3847 3853 'repo_review_rule_user_id', Integer(), primary_key=True)
3848 3854 repo_review_rule_id = Column("repo_review_rule_id",
3849 3855 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3850 3856 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3851 3857 nullable=False)
3852 3858 user = relationship('User')
3853 3859
3854 3860
3855 3861 class RepoReviewRuleUserGroup(Base, BaseModel):
3856 3862 __tablename__ = 'repo_review_rules_users_groups'
3857 3863 __table_args__ = (
3858 3864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3859 3865 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3860 3866 )
3861 3867 repo_review_rule_users_group_id = Column(
3862 3868 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3863 3869 repo_review_rule_id = Column("repo_review_rule_id",
3864 3870 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3865 3871 users_group_id = Column("users_group_id", Integer(),
3866 3872 ForeignKey('users_groups.users_group_id'), nullable=False)
3867 3873 users_group = relationship('UserGroup')
3868 3874
3869 3875
3870 3876 class RepoReviewRule(Base, BaseModel):
3871 3877 __tablename__ = 'repo_review_rules'
3872 3878 __table_args__ = (
3873 3879 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3874 3880 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3875 3881 )
3876 3882
3877 3883 repo_review_rule_id = Column(
3878 3884 'repo_review_rule_id', Integer(), primary_key=True)
3879 3885 repo_id = Column(
3880 3886 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3881 3887 repo = relationship('Repository', backref='review_rules')
3882 3888
3883 3889 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3884 3890 default=u'*') # glob
3885 3891 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3886 3892 default=u'*') # glob
3887 3893
3888 3894 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3889 3895 nullable=False, default=False)
3890 3896 rule_users = relationship('RepoReviewRuleUser')
3891 3897 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3892 3898
3893 3899 @hybrid_property
3894 3900 def branch_pattern(self):
3895 3901 return self._branch_pattern or '*'
3896 3902
3897 3903 def _validate_glob(self, value):
3898 3904 re.compile('^' + glob2re(value) + '$')
3899 3905
3900 3906 @branch_pattern.setter
3901 3907 def branch_pattern(self, value):
3902 3908 self._validate_glob(value)
3903 3909 self._branch_pattern = value or '*'
3904 3910
3905 3911 @hybrid_property
3906 3912 def file_pattern(self):
3907 3913 return self._file_pattern or '*'
3908 3914
3909 3915 @file_pattern.setter
3910 3916 def file_pattern(self, value):
3911 3917 self._validate_glob(value)
3912 3918 self._file_pattern = value or '*'
3913 3919
3914 3920 def matches(self, branch, files_changed):
3915 3921 """
3916 3922 Check if this review rule matches a branch/files in a pull request
3917 3923
3918 3924 :param branch: branch name for the commit
3919 3925 :param files_changed: list of file paths changed in the pull request
3920 3926 """
3921 3927
3922 3928 branch = branch or ''
3923 3929 files_changed = files_changed or []
3924 3930
3925 3931 branch_matches = True
3926 3932 if branch:
3927 3933 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3928 3934 branch_matches = bool(branch_regex.search(branch))
3929 3935
3930 3936 files_matches = True
3931 3937 if self.file_pattern != '*':
3932 3938 files_matches = False
3933 3939 file_regex = re.compile(glob2re(self.file_pattern))
3934 3940 for filename in files_changed:
3935 3941 if file_regex.search(filename):
3936 3942 files_matches = True
3937 3943 break
3938 3944
3939 3945 return branch_matches and files_matches
3940 3946
3941 3947 @property
3942 3948 def review_users(self):
3943 3949 """ Returns the users which this rule applies to """
3944 3950
3945 3951 users = set()
3946 3952 users |= set([
3947 3953 rule_user.user for rule_user in self.rule_users
3948 3954 if rule_user.user.active])
3949 3955 users |= set(
3950 3956 member.user
3951 3957 for rule_user_group in self.rule_user_groups
3952 3958 for member in rule_user_group.users_group.members
3953 3959 if member.user.active
3954 3960 )
3955 3961 return users
3956 3962
3957 3963 def __repr__(self):
3958 3964 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3959 3965 self.repo_review_rule_id, self.repo)
3960 3966
3961 3967
3962 3968 class DbMigrateVersion(Base, BaseModel):
3963 3969 __tablename__ = 'db_migrate_version'
3964 3970 __table_args__ = (
3965 3971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3966 3972 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3967 3973 )
3968 3974 repository_id = Column('repository_id', String(250), primary_key=True)
3969 3975 repository_path = Column('repository_path', Text)
3970 3976 version = Column('version', Integer)
3971 3977
3972 3978
3973 3979 class DbSession(Base, BaseModel):
3974 3980 __tablename__ = 'db_session'
3975 3981 __table_args__ = (
3976 3982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3977 3983 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3978 3984 )
3979 3985
3980 3986 def __repr__(self):
3981 3987 return '<DB:DbSession({})>'.format(self.id)
3982 3988
3983 3989 id = Column('id', Integer())
3984 3990 namespace = Column('namespace', String(255), primary_key=True)
3985 3991 accessed = Column('accessed', DateTime, nullable=False)
3986 3992 created = Column('created', DateTime, nullable=False)
3987 3993 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now