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