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