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