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