##// END OF EJS Templates
caches: cleanup code...
super-admin -
r5009:4102d6ca default
parent child Browse files
Show More
@@ -1,5827 +1,5830 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case, null
44 from sqlalchemy.sql.expression import true, false, case, null
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, lazyload, joinedload, class_mapper, validates, aliased)
47 relationship, lazyload, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers2.text import remove_formatting
54 from webhelpers2.text import remove_formatting
55
55
56 from rhodecode.translation import _
56 from rhodecode.translation import _
57 from rhodecode.lib.vcs import get_vcs_instance, VCSError
57 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs.backends.base import (
58 from rhodecode.lib.vcs.backends.base import (
59 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
59 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib import ext_json
66 from rhodecode.lib import ext_json
67 from rhodecode.lib import enc_utils
67 from rhodecode.lib import enc_utils
68 from rhodecode.lib.ext_json import json
68 from rhodecode.lib.ext_json import json
69 from rhodecode.lib.caching_query import FromCache
69 from rhodecode.lib.caching_query import FromCache
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = ''
84 ENCRYPTION_KEY = ''
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 extra_sort_num = '1' # default
106 extra_sort_num = '1' # default
107
107
108 # NOTE(dan): inactive duplicates goes last
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
111 return prefix + extra_sort_num + obj.username
112
112
113
113
114 def display_user_group_sort(obj):
114 def display_user_group_sort(obj):
115 """
115 """
116 Sort function used to sort permissions in .permissions() function of
116 Sort function used to sort permissions in .permissions() function of
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 of all other resources
118 of all other resources
119 """
119 """
120
120
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 return prefix + obj.users_group_name
122 return prefix + obj.users_group_name
123
123
124
124
125 def _hash_key(k):
125 def _hash_key(k):
126 return sha1_safe(k)
126 return sha1_safe(k)
127
127
128
128
129 def in_filter_generator(qry, items, limit=500):
129 def in_filter_generator(qry, items, limit=500):
130 """
130 """
131 Splits IN() into multiple with OR
131 Splits IN() into multiple with OR
132 e.g.::
132 e.g.::
133 cnt = Repository.query().filter(
133 cnt = Repository.query().filter(
134 or_(
134 or_(
135 *in_filter_generator(Repository.repo_id, range(100000))
135 *in_filter_generator(Repository.repo_id, range(100000))
136 )).count()
136 )).count()
137 """
137 """
138 if not items:
138 if not items:
139 # empty list will cause empty query which might cause security issues
139 # empty list will cause empty query which might cause security issues
140 # this can lead to hidden unpleasant results
140 # this can lead to hidden unpleasant results
141 items = [-1]
141 items = [-1]
142
142
143 parts = []
143 parts = []
144 for chunk in range(0, len(items), limit):
144 for chunk in range(0, len(items), limit):
145 parts.append(
145 parts.append(
146 qry.in_(items[chunk: chunk + limit])
146 qry.in_(items[chunk: chunk + limit])
147 )
147 )
148
148
149 return parts
149 return parts
150
150
151
151
152 base_table_args = {
152 base_table_args = {
153 'extend_existing': True,
153 'extend_existing': True,
154 'mysql_engine': 'InnoDB',
154 'mysql_engine': 'InnoDB',
155 'mysql_charset': 'utf8',
155 'mysql_charset': 'utf8',
156 'sqlite_autoincrement': True
156 'sqlite_autoincrement': True
157 }
157 }
158
158
159
159
160 class EncryptedTextValue(TypeDecorator):
160 class EncryptedTextValue(TypeDecorator):
161 """
161 """
162 Special column for encrypted long text data, use like::
162 Special column for encrypted long text data, use like::
163
163
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165
165
166 This column is intelligent so if value is in unencrypted form it return
166 This column is intelligent so if value is in unencrypted form it return
167 unencrypted form, but on save it always encrypts
167 unencrypted form, but on save it always encrypts
168 """
168 """
169 impl = Text
169 impl = Text
170
170
171 def process_bind_param(self, value, dialect):
171 def process_bind_param(self, value, dialect):
172 """
172 """
173 Setter for storing value
173 Setter for storing value
174 """
174 """
175 import rhodecode
175 import rhodecode
176 if not value:
176 if not value:
177 return value
177 return value
178
178
179 # protect against double encrypting if values is already encrypted
179 # protect against double encrypting if values is already encrypted
180 if value.startswith('enc$aes$') \
180 if value.startswith('enc$aes$') \
181 or value.startswith('enc$aes_hmac$') \
181 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc2$'):
182 or value.startswith('enc2$'):
183 raise ValueError('value needs to be in unencrypted format, '
183 raise ValueError('value needs to be in unencrypted format, '
184 'ie. not starting with enc$ or enc2$')
184 'ie. not starting with enc$ or enc2$')
185
185
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 return enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
187 return enc_utils.encrypt_value(value, enc_key=ENCRYPTION_KEY, algo=algo)
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199
199
200 return enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
200 return enc_utils.decrypt_value(value, enc_key=ENCRYPTION_KEY, strict_mode=enc_strict_mode)
201
201
202
202
203 class BaseModel(object):
203 class BaseModel(object):
204 """
204 """
205 Base Model for all classes
205 Base Model for all classes
206 """
206 """
207
207
208 @classmethod
208 @classmethod
209 def _get_keys(cls):
209 def _get_keys(cls):
210 """return column names for this model """
210 """return column names for this model """
211 return class_mapper(cls).c.keys()
211 return class_mapper(cls).c.keys()
212
212
213 def get_dict(self):
213 def get_dict(self):
214 """
214 """
215 return dict with keys and values corresponding
215 return dict with keys and values corresponding
216 to this model data """
216 to this model data """
217
217
218 d = {}
218 d = {}
219 for k in self._get_keys():
219 for k in self._get_keys():
220 d[k] = getattr(self, k)
220 d[k] = getattr(self, k)
221
221
222 # also use __json__() if present to get additional fields
222 # also use __json__() if present to get additional fields
223 _json_attr = getattr(self, '__json__', None)
223 _json_attr = getattr(self, '__json__', None)
224 if _json_attr:
224 if _json_attr:
225 # update with attributes from __json__
225 # update with attributes from __json__
226 if callable(_json_attr):
226 if callable(_json_attr):
227 _json_attr = _json_attr()
227 _json_attr = _json_attr()
228 for k, val in _json_attr.items():
228 for k, val in _json_attr.items():
229 d[k] = val
229 d[k] = val
230 return d
230 return d
231
231
232 def get_appstruct(self):
232 def get_appstruct(self):
233 """return list with keys and values tuples corresponding
233 """return list with keys and values tuples corresponding
234 to this model data """
234 to this model data """
235
235
236 lst = []
236 lst = []
237 for k in self._get_keys():
237 for k in self._get_keys():
238 lst.append((k, getattr(self, k),))
238 lst.append((k, getattr(self, k),))
239 return lst
239 return lst
240
240
241 def populate_obj(self, populate_dict):
241 def populate_obj(self, populate_dict):
242 """populate model with data from given populate_dict"""
242 """populate model with data from given populate_dict"""
243
243
244 for k in self._get_keys():
244 for k in self._get_keys():
245 if k in populate_dict:
245 if k in populate_dict:
246 setattr(self, k, populate_dict[k])
246 setattr(self, k, populate_dict[k])
247
247
248 @classmethod
248 @classmethod
249 def query(cls):
249 def query(cls):
250 return Session().query(cls)
250 return Session().query(cls)
251
251
252 @classmethod
252 @classmethod
253 def get(cls, id_):
253 def get(cls, id_):
254 if id_:
254 if id_:
255 return cls.query().get(id_)
255 return cls.query().get(id_)
256
256
257 @classmethod
257 @classmethod
258 def get_or_404(cls, id_):
258 def get_or_404(cls, id_):
259 from pyramid.httpexceptions import HTTPNotFound
259 from pyramid.httpexceptions import HTTPNotFound
260
260
261 try:
261 try:
262 id_ = int(id_)
262 id_ = int(id_)
263 except (TypeError, ValueError):
263 except (TypeError, ValueError):
264 raise HTTPNotFound()
264 raise HTTPNotFound()
265
265
266 res = cls.query().get(id_)
266 res = cls.query().get(id_)
267 if not res:
267 if not res:
268 raise HTTPNotFound()
268 raise HTTPNotFound()
269 return res
269 return res
270
270
271 @classmethod
271 @classmethod
272 def getAll(cls):
272 def getAll(cls):
273 # deprecated and left for backward compatibility
273 # deprecated and left for backward compatibility
274 return cls.get_all()
274 return cls.get_all()
275
275
276 @classmethod
276 @classmethod
277 def get_all(cls):
277 def get_all(cls):
278 return cls.query().all()
278 return cls.query().all()
279
279
280 @classmethod
280 @classmethod
281 def delete(cls, id_):
281 def delete(cls, id_):
282 obj = cls.query().get(id_)
282 obj = cls.query().get(id_)
283 Session().delete(obj)
283 Session().delete(obj)
284
284
285 @classmethod
285 @classmethod
286 def identity_cache(cls, session, attr_name, value):
286 def identity_cache(cls, session, attr_name, value):
287 exist_in_session = []
287 exist_in_session = []
288 for (item_cls, pkey), instance in session.identity_map.items():
288 for (item_cls, pkey), instance in session.identity_map.items():
289 if cls == item_cls and getattr(instance, attr_name) == value:
289 if cls == item_cls and getattr(instance, attr_name) == value:
290 exist_in_session.append(instance)
290 exist_in_session.append(instance)
291 if exist_in_session:
291 if exist_in_session:
292 if len(exist_in_session) == 1:
292 if len(exist_in_session) == 1:
293 return exist_in_session[0]
293 return exist_in_session[0]
294 log.exception(
294 log.exception(
295 'multiple objects with attr %s and '
295 'multiple objects with attr %s and '
296 'value %s found with same name: %r',
296 'value %s found with same name: %r',
297 attr_name, value, exist_in_session)
297 attr_name, value, exist_in_session)
298
298
299 def __repr__(self):
299 def __repr__(self):
300 if hasattr(self, '__str__'):
300 if hasattr(self, '__str__'):
301 # python repr needs to return str
301 # python repr needs to return str
302 try:
302 try:
303 return self.__str__()
303 return self.__str__()
304 except UnicodeDecodeError:
304 except UnicodeDecodeError:
305 pass
305 pass
306 return f'<DB:{self.__class__.__name__}>'
306 return f'<DB:{self.__class__.__name__}>'
307
307
308
308
309 class RhodeCodeSetting(Base, BaseModel):
309 class RhodeCodeSetting(Base, BaseModel):
310 __tablename__ = 'rhodecode_settings'
310 __tablename__ = 'rhodecode_settings'
311 __table_args__ = (
311 __table_args__ = (
312 UniqueConstraint('app_settings_name'),
312 UniqueConstraint('app_settings_name'),
313 base_table_args
313 base_table_args
314 )
314 )
315
315
316 SETTINGS_TYPES = {
316 SETTINGS_TYPES = {
317 'str': safe_str,
317 'str': safe_str,
318 'int': safe_int,
318 'int': safe_int,
319 'unicode': safe_unicode,
319 'unicode': safe_unicode,
320 'bool': str2bool,
320 'bool': str2bool,
321 'list': functools.partial(aslist, sep=',')
321 'list': functools.partial(aslist, sep=',')
322 }
322 }
323 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
323 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
324 GLOBAL_CONF_KEY = 'app_settings'
324 GLOBAL_CONF_KEY = 'app_settings'
325
325
326 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
327 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
328 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
328 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
329 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
329 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
330
330
331 def __init__(self, key='', val='', type='unicode'):
331 def __init__(self, key='', val='', type='unicode'):
332 self.app_settings_name = key
332 self.app_settings_name = key
333 self.app_settings_type = type
333 self.app_settings_type = type
334 self.app_settings_value = val
334 self.app_settings_value = val
335
335
336 @validates('_app_settings_value')
336 @validates('_app_settings_value')
337 def validate_settings_value(self, key, val):
337 def validate_settings_value(self, key, val):
338 assert type(val) == str
338 assert type(val) == str
339 return val
339 return val
340
340
341 @hybrid_property
341 @hybrid_property
342 def app_settings_value(self):
342 def app_settings_value(self):
343 v = self._app_settings_value
343 v = self._app_settings_value
344 _type = self.app_settings_type
344 _type = self.app_settings_type
345 if _type:
345 if _type:
346 _type = self.app_settings_type.split('.')[0]
346 _type = self.app_settings_type.split('.')[0]
347 # decode the encrypted value
347 # decode the encrypted value
348 if 'encrypted' in self.app_settings_type:
348 if 'encrypted' in self.app_settings_type:
349 cipher = EncryptedTextValue()
349 cipher = EncryptedTextValue()
350 v = safe_str(cipher.process_result_value(v, None))
350 v = safe_str(cipher.process_result_value(v, None))
351
351
352 converter = self.SETTINGS_TYPES.get(_type) or \
352 converter = self.SETTINGS_TYPES.get(_type) or \
353 self.SETTINGS_TYPES['unicode']
353 self.SETTINGS_TYPES['unicode']
354 return converter(v)
354 return converter(v)
355
355
356 @app_settings_value.setter
356 @app_settings_value.setter
357 def app_settings_value(self, val):
357 def app_settings_value(self, val):
358 """
358 """
359 Setter that will always make sure we use unicode in app_settings_value
359 Setter that will always make sure we use unicode in app_settings_value
360
360
361 :param val:
361 :param val:
362 """
362 """
363 val = safe_unicode(val)
363 val = safe_unicode(val)
364 # encode the encrypted value
364 # encode the encrypted value
365 if 'encrypted' in self.app_settings_type:
365 if 'encrypted' in self.app_settings_type:
366 cipher = EncryptedTextValue()
366 cipher = EncryptedTextValue()
367 val = safe_str(cipher.process_bind_param(val, None))
367 val = safe_str(cipher.process_bind_param(val, None))
368 self._app_settings_value = val
368 self._app_settings_value = val
369
369
370 @hybrid_property
370 @hybrid_property
371 def app_settings_type(self):
371 def app_settings_type(self):
372 return self._app_settings_type
372 return self._app_settings_type
373
373
374 @app_settings_type.setter
374 @app_settings_type.setter
375 def app_settings_type(self, val):
375 def app_settings_type(self, val):
376 if val.split('.')[0] not in self.SETTINGS_TYPES:
376 if val.split('.')[0] not in self.SETTINGS_TYPES:
377 raise Exception('type must be one of %s got %s'
377 raise Exception('type must be one of %s got %s'
378 % (self.SETTINGS_TYPES.keys(), val))
378 % (self.SETTINGS_TYPES.keys(), val))
379 self._app_settings_type = val
379 self._app_settings_type = val
380
380
381 @classmethod
381 @classmethod
382 def get_by_prefix(cls, prefix):
382 def get_by_prefix(cls, prefix):
383 return RhodeCodeSetting.query()\
383 return RhodeCodeSetting.query()\
384 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
384 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
385 .all()
385 .all()
386
386
387 def __str__(self):
387 def __str__(self):
388 return "<%s('%s:%s[%s]')>" % (
388 return "<%s('%s:%s[%s]')>" % (
389 self.__class__.__name__,
389 self.__class__.__name__,
390 self.app_settings_name, self.app_settings_value,
390 self.app_settings_name, self.app_settings_value,
391 self.app_settings_type
391 self.app_settings_type
392 )
392 )
393
393
394
394
395 class RhodeCodeUi(Base, BaseModel):
395 class RhodeCodeUi(Base, BaseModel):
396 __tablename__ = 'rhodecode_ui'
396 __tablename__ = 'rhodecode_ui'
397 __table_args__ = (
397 __table_args__ = (
398 UniqueConstraint('ui_key'),
398 UniqueConstraint('ui_key'),
399 base_table_args
399 base_table_args
400 )
400 )
401
401
402 HOOK_REPO_SIZE = 'changegroup.repo_size'
402 HOOK_REPO_SIZE = 'changegroup.repo_size'
403 # HG
403 # HG
404 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
404 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
405 HOOK_PULL = 'outgoing.pull_logger'
405 HOOK_PULL = 'outgoing.pull_logger'
406 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
406 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
407 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
407 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
408 HOOK_PUSH = 'changegroup.push_logger'
408 HOOK_PUSH = 'changegroup.push_logger'
409 HOOK_PUSH_KEY = 'pushkey.key_push'
409 HOOK_PUSH_KEY = 'pushkey.key_push'
410
410
411 HOOKS_BUILTIN = [
411 HOOKS_BUILTIN = [
412 HOOK_PRE_PULL,
412 HOOK_PRE_PULL,
413 HOOK_PULL,
413 HOOK_PULL,
414 HOOK_PRE_PUSH,
414 HOOK_PRE_PUSH,
415 HOOK_PRETX_PUSH,
415 HOOK_PRETX_PUSH,
416 HOOK_PUSH,
416 HOOK_PUSH,
417 HOOK_PUSH_KEY,
417 HOOK_PUSH_KEY,
418 ]
418 ]
419
419
420 # TODO: johbo: Unify way how hooks are configured for git and hg,
420 # TODO: johbo: Unify way how hooks are configured for git and hg,
421 # git part is currently hardcoded.
421 # git part is currently hardcoded.
422
422
423 # SVN PATTERNS
423 # SVN PATTERNS
424 SVN_BRANCH_ID = 'vcs_svn_branch'
424 SVN_BRANCH_ID = 'vcs_svn_branch'
425 SVN_TAG_ID = 'vcs_svn_tag'
425 SVN_TAG_ID = 'vcs_svn_tag'
426
426
427 ui_id = Column(
427 ui_id = Column(
428 "ui_id", Integer(), nullable=False, unique=True, default=None,
428 "ui_id", Integer(), nullable=False, unique=True, default=None,
429 primary_key=True)
429 primary_key=True)
430 ui_section = Column(
430 ui_section = Column(
431 "ui_section", String(255), nullable=True, unique=None, default=None)
431 "ui_section", String(255), nullable=True, unique=None, default=None)
432 ui_key = Column(
432 ui_key = Column(
433 "ui_key", String(255), nullable=True, unique=None, default=None)
433 "ui_key", String(255), nullable=True, unique=None, default=None)
434 ui_value = Column(
434 ui_value = Column(
435 "ui_value", String(255), nullable=True, unique=None, default=None)
435 "ui_value", String(255), nullable=True, unique=None, default=None)
436 ui_active = Column(
436 ui_active = Column(
437 "ui_active", Boolean(), nullable=True, unique=None, default=True)
437 "ui_active", Boolean(), nullable=True, unique=None, default=True)
438
438
439 def __repr__(self):
439 def __repr__(self):
440 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
440 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
441 self.ui_key, self.ui_value)
441 self.ui_key, self.ui_value)
442
442
443
443
444 class RepoRhodeCodeSetting(Base, BaseModel):
444 class RepoRhodeCodeSetting(Base, BaseModel):
445 __tablename__ = 'repo_rhodecode_settings'
445 __tablename__ = 'repo_rhodecode_settings'
446 __table_args__ = (
446 __table_args__ = (
447 UniqueConstraint(
447 UniqueConstraint(
448 'app_settings_name', 'repository_id',
448 'app_settings_name', 'repository_id',
449 name='uq_repo_rhodecode_setting_name_repo_id'),
449 name='uq_repo_rhodecode_setting_name_repo_id'),
450 base_table_args
450 base_table_args
451 )
451 )
452
452
453 repository_id = Column(
453 repository_id = Column(
454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
455 nullable=False)
455 nullable=False)
456 app_settings_id = Column(
456 app_settings_id = Column(
457 "app_settings_id", Integer(), nullable=False, unique=True,
457 "app_settings_id", Integer(), nullable=False, unique=True,
458 default=None, primary_key=True)
458 default=None, primary_key=True)
459 app_settings_name = Column(
459 app_settings_name = Column(
460 "app_settings_name", String(255), nullable=True, unique=None,
460 "app_settings_name", String(255), nullable=True, unique=None,
461 default=None)
461 default=None)
462 _app_settings_value = Column(
462 _app_settings_value = Column(
463 "app_settings_value", String(4096), nullable=True, unique=None,
463 "app_settings_value", String(4096), nullable=True, unique=None,
464 default=None)
464 default=None)
465 _app_settings_type = Column(
465 _app_settings_type = Column(
466 "app_settings_type", String(255), nullable=True, unique=None,
466 "app_settings_type", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468
468
469 repository = relationship('Repository')
469 repository = relationship('Repository')
470
470
471 def __init__(self, repository_id, key='', val='', type='unicode'):
471 def __init__(self, repository_id, key='', val='', type='unicode'):
472 self.repository_id = repository_id
472 self.repository_id = repository_id
473 self.app_settings_name = key
473 self.app_settings_name = key
474 self.app_settings_type = type
474 self.app_settings_type = type
475 self.app_settings_value = val
475 self.app_settings_value = val
476
476
477 @validates('_app_settings_value')
477 @validates('_app_settings_value')
478 def validate_settings_value(self, key, val):
478 def validate_settings_value(self, key, val):
479 assert type(val) == str
479 assert type(val) == str
480 return val
480 return val
481
481
482 @hybrid_property
482 @hybrid_property
483 def app_settings_value(self):
483 def app_settings_value(self):
484 v = self._app_settings_value
484 v = self._app_settings_value
485 type_ = self.app_settings_type
485 type_ = self.app_settings_type
486 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
487 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
487 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
488 return converter(v)
488 return converter(v)
489
489
490 @app_settings_value.setter
490 @app_settings_value.setter
491 def app_settings_value(self, val):
491 def app_settings_value(self, val):
492 """
492 """
493 Setter that will always make sure we use unicode in app_settings_value
493 Setter that will always make sure we use unicode in app_settings_value
494
494
495 :param val:
495 :param val:
496 """
496 """
497 self._app_settings_value = safe_unicode(val)
497 self._app_settings_value = safe_unicode(val)
498
498
499 @hybrid_property
499 @hybrid_property
500 def app_settings_type(self):
500 def app_settings_type(self):
501 return self._app_settings_type
501 return self._app_settings_type
502
502
503 @app_settings_type.setter
503 @app_settings_type.setter
504 def app_settings_type(self, val):
504 def app_settings_type(self, val):
505 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
505 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
506 if val not in SETTINGS_TYPES:
506 if val not in SETTINGS_TYPES:
507 raise Exception('type must be one of %s got %s'
507 raise Exception('type must be one of %s got %s'
508 % (SETTINGS_TYPES.keys(), val))
508 % (SETTINGS_TYPES.keys(), val))
509 self._app_settings_type = val
509 self._app_settings_type = val
510
510
511 def __unicode__(self):
511 def __unicode__(self):
512 return u"<%s('%s:%s:%s[%s]')>" % (
512 return u"<%s('%s:%s:%s[%s]')>" % (
513 self.__class__.__name__, self.repository.repo_name,
513 self.__class__.__name__, self.repository.repo_name,
514 self.app_settings_name, self.app_settings_value,
514 self.app_settings_name, self.app_settings_value,
515 self.app_settings_type
515 self.app_settings_type
516 )
516 )
517
517
518
518
519 class RepoRhodeCodeUi(Base, BaseModel):
519 class RepoRhodeCodeUi(Base, BaseModel):
520 __tablename__ = 'repo_rhodecode_ui'
520 __tablename__ = 'repo_rhodecode_ui'
521 __table_args__ = (
521 __table_args__ = (
522 UniqueConstraint(
522 UniqueConstraint(
523 'repository_id', 'ui_section', 'ui_key',
523 'repository_id', 'ui_section', 'ui_key',
524 name='uq_repo_rhodecode_ui_repository_id_section_key'),
524 name='uq_repo_rhodecode_ui_repository_id_section_key'),
525 base_table_args
525 base_table_args
526 )
526 )
527
527
528 repository_id = Column(
528 repository_id = Column(
529 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
529 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
530 nullable=False)
530 nullable=False)
531 ui_id = Column(
531 ui_id = Column(
532 "ui_id", Integer(), nullable=False, unique=True, default=None,
532 "ui_id", Integer(), nullable=False, unique=True, default=None,
533 primary_key=True)
533 primary_key=True)
534 ui_section = Column(
534 ui_section = Column(
535 "ui_section", String(255), nullable=True, unique=None, default=None)
535 "ui_section", String(255), nullable=True, unique=None, default=None)
536 ui_key = Column(
536 ui_key = Column(
537 "ui_key", String(255), nullable=True, unique=None, default=None)
537 "ui_key", String(255), nullable=True, unique=None, default=None)
538 ui_value = Column(
538 ui_value = Column(
539 "ui_value", String(255), nullable=True, unique=None, default=None)
539 "ui_value", String(255), nullable=True, unique=None, default=None)
540 ui_active = Column(
540 ui_active = Column(
541 "ui_active", Boolean(), nullable=True, unique=None, default=True)
541 "ui_active", Boolean(), nullable=True, unique=None, default=True)
542
542
543 repository = relationship('Repository')
543 repository = relationship('Repository')
544
544
545 def __repr__(self):
545 def __repr__(self):
546 return '<%s[%s:%s]%s=>%s]>' % (
546 return '<%s[%s:%s]%s=>%s]>' % (
547 self.__class__.__name__, self.repository.repo_name,
547 self.__class__.__name__, self.repository.repo_name,
548 self.ui_section, self.ui_key, self.ui_value)
548 self.ui_section, self.ui_key, self.ui_value)
549
549
550
550
551 class User(Base, BaseModel):
551 class User(Base, BaseModel):
552 __tablename__ = 'users'
552 __tablename__ = 'users'
553 __table_args__ = (
553 __table_args__ = (
554 UniqueConstraint('username'), UniqueConstraint('email'),
554 UniqueConstraint('username'), UniqueConstraint('email'),
555 Index('u_username_idx', 'username'),
555 Index('u_username_idx', 'username'),
556 Index('u_email_idx', 'email'),
556 Index('u_email_idx', 'email'),
557 base_table_args
557 base_table_args
558 )
558 )
559
559
560 DEFAULT_USER = 'default'
560 DEFAULT_USER = 'default'
561 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
561 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
562 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
562 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
563
563
564 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
564 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
565 username = Column("username", String(255), nullable=True, unique=None, default=None)
565 username = Column("username", String(255), nullable=True, unique=None, default=None)
566 password = Column("password", String(255), nullable=True, unique=None, default=None)
566 password = Column("password", String(255), nullable=True, unique=None, default=None)
567 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
567 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
568 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
568 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
569 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
569 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
570 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
570 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
571 _email = Column("email", String(255), nullable=True, unique=None, default=None)
571 _email = Column("email", String(255), nullable=True, unique=None, default=None)
572 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
572 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
573 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
573 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
574 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
574 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
575
575
576 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
576 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
577 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
577 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
578 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
578 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
579 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
579 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
581 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
581 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
582
582
583 user_log = relationship('UserLog')
583 user_log = relationship('UserLog')
584 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
584 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
585
585
586 repositories = relationship('Repository')
586 repositories = relationship('Repository')
587 repository_groups = relationship('RepoGroup')
587 repository_groups = relationship('RepoGroup')
588 user_groups = relationship('UserGroup')
588 user_groups = relationship('UserGroup')
589
589
590 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
590 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
591 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
591 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
592
592
593 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
593 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
594 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
594 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
595 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
595 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
596
596
597 group_member = relationship('UserGroupMember', cascade='all')
597 group_member = relationship('UserGroupMember', cascade='all')
598
598
599 notifications = relationship('UserNotification', cascade='all')
599 notifications = relationship('UserNotification', cascade='all')
600 # notifications assigned to this user
600 # notifications assigned to this user
601 user_created_notifications = relationship('Notification', cascade='all')
601 user_created_notifications = relationship('Notification', cascade='all')
602 # comments created by this user
602 # comments created by this user
603 user_comments = relationship('ChangesetComment', cascade='all')
603 user_comments = relationship('ChangesetComment', cascade='all')
604 # user profile extra info
604 # user profile extra info
605 user_emails = relationship('UserEmailMap', cascade='all')
605 user_emails = relationship('UserEmailMap', cascade='all')
606 user_ip_map = relationship('UserIpMap', cascade='all')
606 user_ip_map = relationship('UserIpMap', cascade='all')
607 user_auth_tokens = relationship('UserApiKeys', cascade='all')
607 user_auth_tokens = relationship('UserApiKeys', cascade='all')
608 user_ssh_keys = relationship('UserSshKeys', cascade='all')
608 user_ssh_keys = relationship('UserSshKeys', cascade='all')
609
609
610 # gists
610 # gists
611 user_gists = relationship('Gist', cascade='all')
611 user_gists = relationship('Gist', cascade='all')
612 # user pull requests
612 # user pull requests
613 user_pull_requests = relationship('PullRequest', cascade='all')
613 user_pull_requests = relationship('PullRequest', cascade='all')
614
614
615 # external identities
615 # external identities
616 external_identities = relationship(
616 external_identities = relationship(
617 'ExternalIdentity',
617 'ExternalIdentity',
618 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
618 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
619 cascade='all')
619 cascade='all')
620 # review rules
620 # review rules
621 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
621 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
622
622
623 # artifacts owned
623 # artifacts owned
624 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
624 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
625
625
626 # no cascade, set NULL
626 # no cascade, set NULL
627 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
627 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
628
628
629 def __unicode__(self):
629 def __unicode__(self):
630 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
630 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
631 self.user_id, self.username)
631 self.user_id, self.username)
632
632
633 @hybrid_property
633 @hybrid_property
634 def email(self):
634 def email(self):
635 return self._email
635 return self._email
636
636
637 @email.setter
637 @email.setter
638 def email(self, val):
638 def email(self, val):
639 self._email = val.lower() if val else None
639 self._email = val.lower() if val else None
640
640
641 @hybrid_property
641 @hybrid_property
642 def first_name(self):
642 def first_name(self):
643 from rhodecode.lib import helpers as h
643 from rhodecode.lib import helpers as h
644 if self.name:
644 if self.name:
645 return h.escape(self.name)
645 return h.escape(self.name)
646 return self.name
646 return self.name
647
647
648 @hybrid_property
648 @hybrid_property
649 def last_name(self):
649 def last_name(self):
650 from rhodecode.lib import helpers as h
650 from rhodecode.lib import helpers as h
651 if self.lastname:
651 if self.lastname:
652 return h.escape(self.lastname)
652 return h.escape(self.lastname)
653 return self.lastname
653 return self.lastname
654
654
655 @hybrid_property
655 @hybrid_property
656 def api_key(self):
656 def api_key(self):
657 """
657 """
658 Fetch if exist an auth-token with role ALL connected to this user
658 Fetch if exist an auth-token with role ALL connected to this user
659 """
659 """
660 user_auth_token = UserApiKeys.query()\
660 user_auth_token = UserApiKeys.query()\
661 .filter(UserApiKeys.user_id == self.user_id)\
661 .filter(UserApiKeys.user_id == self.user_id)\
662 .filter(or_(UserApiKeys.expires == -1,
662 .filter(or_(UserApiKeys.expires == -1,
663 UserApiKeys.expires >= time.time()))\
663 UserApiKeys.expires >= time.time()))\
664 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
664 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
665 if user_auth_token:
665 if user_auth_token:
666 user_auth_token = user_auth_token.api_key
666 user_auth_token = user_auth_token.api_key
667
667
668 return user_auth_token
668 return user_auth_token
669
669
670 @api_key.setter
670 @api_key.setter
671 def api_key(self, val):
671 def api_key(self, val):
672 # don't allow to set API key this is deprecated for now
672 # don't allow to set API key this is deprecated for now
673 self._api_key = None
673 self._api_key = None
674
674
675 @property
675 @property
676 def reviewer_pull_requests(self):
676 def reviewer_pull_requests(self):
677 return PullRequestReviewers.query() \
677 return PullRequestReviewers.query() \
678 .options(joinedload(PullRequestReviewers.pull_request)) \
678 .options(joinedload(PullRequestReviewers.pull_request)) \
679 .filter(PullRequestReviewers.user_id == self.user_id) \
679 .filter(PullRequestReviewers.user_id == self.user_id) \
680 .all()
680 .all()
681
681
682 @property
682 @property
683 def firstname(self):
683 def firstname(self):
684 # alias for future
684 # alias for future
685 return self.name
685 return self.name
686
686
687 @property
687 @property
688 def emails(self):
688 def emails(self):
689 other = UserEmailMap.query()\
689 other = UserEmailMap.query()\
690 .filter(UserEmailMap.user == self) \
690 .filter(UserEmailMap.user == self) \
691 .order_by(UserEmailMap.email_id.asc()) \
691 .order_by(UserEmailMap.email_id.asc()) \
692 .all()
692 .all()
693 return [self.email] + [x.email for x in other]
693 return [self.email] + [x.email for x in other]
694
694
695 def emails_cached(self):
695 def emails_cached(self):
696 emails = UserEmailMap.query()\
696 emails = []
697 .filter(UserEmailMap.user == self) \
697 if self.user_id != self.get_default_user_id():
698 .order_by(UserEmailMap.email_id.asc())
698 emails = UserEmailMap.query()\
699
699 .filter(UserEmailMap.user == self) \
700 emails = emails.options(
700 .order_by(UserEmailMap.email_id.asc())
701 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
701
702 )
702 emails = emails.options(
703 FromCache("sql_cache_short", f"get_user_{self.user_id}_emails")
704 )
703
705
704 return [self.email] + [x.email for x in emails]
706 return [self.email] + [x.email for x in emails]
705
707
706 @property
708 @property
707 def auth_tokens(self):
709 def auth_tokens(self):
708 auth_tokens = self.get_auth_tokens()
710 auth_tokens = self.get_auth_tokens()
709 return [x.api_key for x in auth_tokens]
711 return [x.api_key for x in auth_tokens]
710
712
711 def get_auth_tokens(self):
713 def get_auth_tokens(self):
712 return UserApiKeys.query()\
714 return UserApiKeys.query()\
713 .filter(UserApiKeys.user == self)\
715 .filter(UserApiKeys.user == self)\
714 .order_by(UserApiKeys.user_api_key_id.asc())\
716 .order_by(UserApiKeys.user_api_key_id.asc())\
715 .all()
717 .all()
716
718
717 @LazyProperty
719 @LazyProperty
718 def feed_token(self):
720 def feed_token(self):
719 return self.get_feed_token()
721 return self.get_feed_token()
720
722
721 def get_feed_token(self, cache=True):
723 def get_feed_token(self, cache=True):
722 feed_tokens = UserApiKeys.query()\
724 feed_tokens = UserApiKeys.query()\
723 .filter(UserApiKeys.user == self)\
725 .filter(UserApiKeys.user == self)\
724 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
726 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
725 if cache:
727 if cache:
726 feed_tokens = feed_tokens.options(
728 feed_tokens = feed_tokens.options(
727 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
729 FromCache("sql_cache_short", f"get_user_feed_token_{self.user_id}"))
728
730
729 feed_tokens = feed_tokens.all()
731 feed_tokens = feed_tokens.all()
730 if feed_tokens:
732 if feed_tokens:
731 return feed_tokens[0].api_key
733 return feed_tokens[0].api_key
732 return 'NO_FEED_TOKEN_AVAILABLE'
734 return 'NO_FEED_TOKEN_AVAILABLE'
733
735
734 @LazyProperty
736 @LazyProperty
735 def artifact_token(self):
737 def artifact_token(self):
736 return self.get_artifact_token()
738 return self.get_artifact_token()
737
739
738 def get_artifact_token(self, cache=True):
740 def get_artifact_token(self, cache=True):
739 artifacts_tokens = UserApiKeys.query()\
741 artifacts_tokens = UserApiKeys.query()\
740 .filter(UserApiKeys.user == self) \
742 .filter(UserApiKeys.user == self) \
741 .filter(or_(UserApiKeys.expires == -1,
743 .filter(or_(UserApiKeys.expires == -1,
742 UserApiKeys.expires >= time.time())) \
744 UserApiKeys.expires >= time.time())) \
743 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
745 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
744
746
745 if cache:
747 if cache:
746 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
747 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", f"get_user_artifact_token_{self.user_id}"))
748
750
749 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
750 if artifacts_tokens:
752 if artifacts_tokens:
751 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
752 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
753
755
754 def get_or_create_artifact_token(self):
756 def get_or_create_artifact_token(self):
755 artifacts_tokens = UserApiKeys.query()\
757 artifacts_tokens = UserApiKeys.query()\
756 .filter(UserApiKeys.user == self) \
758 .filter(UserApiKeys.user == self) \
757 .filter(or_(UserApiKeys.expires == -1,
759 .filter(or_(UserApiKeys.expires == -1,
758 UserApiKeys.expires >= time.time())) \
760 UserApiKeys.expires >= time.time())) \
759 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
761 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
760
762
761 artifacts_tokens = artifacts_tokens.all()
763 artifacts_tokens = artifacts_tokens.all()
762 if artifacts_tokens:
764 if artifacts_tokens:
763 return artifacts_tokens[0].api_key
765 return artifacts_tokens[0].api_key
764 else:
766 else:
765 from rhodecode.model.auth_token import AuthTokenModel
767 from rhodecode.model.auth_token import AuthTokenModel
766 artifact_token = AuthTokenModel().create(
768 artifact_token = AuthTokenModel().create(
767 self, 'auto-generated-artifact-token',
769 self, 'auto-generated-artifact-token',
768 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
770 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
769 Session.commit()
771 Session.commit()
770 return artifact_token.api_key
772 return artifact_token.api_key
771
773
772 @classmethod
774 @classmethod
773 def get(cls, user_id, cache=False):
775 def get(cls, user_id, cache=False):
774 if not user_id:
776 if not user_id:
775 return
777 return
776
778
777 user = cls.query()
779 user = cls.query()
778 if cache:
780 if cache:
779 user = user.options(
781 user = user.options(
780 FromCache("sql_cache_short", "get_users_%s" % user_id))
782 FromCache("sql_cache_short", f"get_users_{user_id}"))
781 return user.get(user_id)
783 return user.get(user_id)
782
784
783 @classmethod
785 @classmethod
784 def extra_valid_auth_tokens(cls, user, role=None):
786 def extra_valid_auth_tokens(cls, user, role=None):
785 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
787 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
786 .filter(or_(UserApiKeys.expires == -1,
788 .filter(or_(UserApiKeys.expires == -1,
787 UserApiKeys.expires >= time.time()))
789 UserApiKeys.expires >= time.time()))
788 if role:
790 if role:
789 tokens = tokens.filter(or_(UserApiKeys.role == role,
791 tokens = tokens.filter(or_(UserApiKeys.role == role,
790 UserApiKeys.role == UserApiKeys.ROLE_ALL))
792 UserApiKeys.role == UserApiKeys.ROLE_ALL))
791 return tokens.all()
793 return tokens.all()
792
794
793 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
795 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
794 from rhodecode.lib import auth
796 from rhodecode.lib import auth
795
797
796 log.debug('Trying to authenticate user: %s via auth-token, '
798 log.debug('Trying to authenticate user: %s via auth-token, '
797 'and roles: %s', self, roles)
799 'and roles: %s', self, roles)
798
800
799 if not auth_token:
801 if not auth_token:
800 return False
802 return False
801
803
802 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
804 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
803 tokens_q = UserApiKeys.query()\
805 tokens_q = UserApiKeys.query()\
804 .filter(UserApiKeys.user_id == self.user_id)\
806 .filter(UserApiKeys.user_id == self.user_id)\
805 .filter(or_(UserApiKeys.expires == -1,
807 .filter(or_(UserApiKeys.expires == -1,
806 UserApiKeys.expires >= time.time()))
808 UserApiKeys.expires >= time.time()))
807
809
808 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
810 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
809
811
810 crypto_backend = auth.crypto_backend()
812 crypto_backend = auth.crypto_backend()
811 enc_token_map = {}
813 enc_token_map = {}
812 plain_token_map = {}
814 plain_token_map = {}
813 for token in tokens_q:
815 for token in tokens_q:
814 if token.api_key.startswith(crypto_backend.ENC_PREF):
816 if token.api_key.startswith(crypto_backend.ENC_PREF):
815 enc_token_map[token.api_key] = token
817 enc_token_map[token.api_key] = token
816 else:
818 else:
817 plain_token_map[token.api_key] = token
819 plain_token_map[token.api_key] = token
818 log.debug(
820 log.debug(
819 'Found %s plain and %s encrypted tokens to check for authentication for this user',
821 'Found %s plain and %s encrypted tokens to check for authentication for this user',
820 len(plain_token_map), len(enc_token_map))
822 len(plain_token_map), len(enc_token_map))
821
823
822 # plain token match comes first
824 # plain token match comes first
823 match = plain_token_map.get(auth_token)
825 match = plain_token_map.get(auth_token)
824
826
825 # check encrypted tokens now
827 # check encrypted tokens now
826 if not match:
828 if not match:
827 for token_hash, token in enc_token_map.items():
829 for token_hash, token in enc_token_map.items():
828 # NOTE(marcink): this is expensive to calculate, but most secure
830 # NOTE(marcink): this is expensive to calculate, but most secure
829 if crypto_backend.hash_check(auth_token, token_hash):
831 if crypto_backend.hash_check(auth_token, token_hash):
830 match = token
832 match = token
831 break
833 break
832
834
833 if match:
835 if match:
834 log.debug('Found matching token %s', match)
836 log.debug('Found matching token %s', match)
835 if match.repo_id:
837 if match.repo_id:
836 log.debug('Found scope, checking for scope match of token %s', match)
838 log.debug('Found scope, checking for scope match of token %s', match)
837 if match.repo_id == scope_repo_id:
839 if match.repo_id == scope_repo_id:
838 return True
840 return True
839 else:
841 else:
840 log.debug(
842 log.debug(
841 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
843 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
842 'and calling scope is:%s, skipping further checks',
844 'and calling scope is:%s, skipping further checks',
843 match.repo, scope_repo_id)
845 match.repo, scope_repo_id)
844 return False
846 return False
845 else:
847 else:
846 return True
848 return True
847
849
848 return False
850 return False
849
851
850 @property
852 @property
851 def ip_addresses(self):
853 def ip_addresses(self):
852 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
854 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
853 return [x.ip_addr for x in ret]
855 return [x.ip_addr for x in ret]
854
856
855 @property
857 @property
856 def username_and_name(self):
858 def username_and_name(self):
857 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
859 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
858
860
859 @property
861 @property
860 def username_or_name_or_email(self):
862 def username_or_name_or_email(self):
861 full_name = self.full_name if self.full_name is not ' ' else None
863 full_name = self.full_name if self.full_name != ' ' else None
862 return self.username or full_name or self.email
864 return self.username or full_name or self.email
863
865
864 @property
866 @property
865 def full_name(self):
867 def full_name(self):
866 return '%s %s' % (self.first_name, self.last_name)
868 return '%s %s' % (self.first_name, self.last_name)
867
869
868 @property
870 @property
869 def full_name_or_username(self):
871 def full_name_or_username(self):
870 return ('%s %s' % (self.first_name, self.last_name)
872 return ('%s %s' % (self.first_name, self.last_name)
871 if (self.first_name and self.last_name) else self.username)
873 if (self.first_name and self.last_name) else self.username)
872
874
873 @property
875 @property
874 def full_contact(self):
876 def full_contact(self):
875 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
877 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
876
878
877 @property
879 @property
878 def short_contact(self):
880 def short_contact(self):
879 return '%s %s' % (self.first_name, self.last_name)
881 return '%s %s' % (self.first_name, self.last_name)
880
882
881 @property
883 @property
882 def is_admin(self):
884 def is_admin(self):
883 return self.admin
885 return self.admin
884
886
885 @property
887 @property
886 def language(self):
888 def language(self):
887 return self.user_data.get('language')
889 return self.user_data.get('language')
888
890
889 def AuthUser(self, **kwargs):
891 def AuthUser(self, **kwargs):
890 """
892 """
891 Returns instance of AuthUser for this user
893 Returns instance of AuthUser for this user
892 """
894 """
893 from rhodecode.lib.auth import AuthUser
895 from rhodecode.lib.auth import AuthUser
894 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
896 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
895
897
896 @hybrid_property
898 @hybrid_property
897 def user_data(self):
899 def user_data(self):
898 if not self._user_data:
900 if not self._user_data:
899 return {}
901 return {}
900
902
901 try:
903 try:
902 return json.loads(self._user_data) or {}
904 return json.loads(self._user_data) or {}
903 except TypeError:
905 except TypeError:
904 return {}
906 return {}
905
907
906 @user_data.setter
908 @user_data.setter
907 def user_data(self, val):
909 def user_data(self, val):
908 if not isinstance(val, dict):
910 if not isinstance(val, dict):
909 raise Exception('user_data must be dict, got %s' % type(val))
911 raise Exception('user_data must be dict, got %s' % type(val))
910 try:
912 try:
911 self._user_data = json.dumps(val)
913 self._user_data = json.dumps(val)
912 except Exception:
914 except Exception:
913 log.error(traceback.format_exc())
915 log.error(traceback.format_exc())
914
916
915 @classmethod
917 @classmethod
916 def get_by_username(cls, username, case_insensitive=False,
918 def get_by_username(cls, username, case_insensitive=False,
917 cache=False, identity_cache=False):
919 cache=False, identity_cache=False):
918 session = Session()
920 session = Session()
919
921
920 if case_insensitive:
922 if case_insensitive:
921 q = cls.query().filter(
923 q = cls.query().filter(
922 func.lower(cls.username) == func.lower(username))
924 func.lower(cls.username) == func.lower(username))
923 else:
925 else:
924 q = cls.query().filter(cls.username == username)
926 q = cls.query().filter(cls.username == username)
925
927
926 if cache:
928 if cache:
927 if identity_cache:
929 if identity_cache:
928 val = cls.identity_cache(session, 'username', username)
930 val = cls.identity_cache(session, 'username', username)
929 if val:
931 if val:
930 return val
932 return val
931 else:
933 else:
932 cache_key = "get_user_by_name_%s" % _hash_key(username)
934 cache_key = "get_user_by_name_%s" % _hash_key(username)
933 q = q.options(
935 q = q.options(
934 FromCache("sql_cache_short", cache_key))
936 FromCache("sql_cache_short", cache_key))
935
937
936 return q.scalar()
938 return q.scalar()
937
939
938 @classmethod
940 @classmethod
939 def get_by_auth_token(cls, auth_token, cache=False):
941 def get_by_auth_token(cls, auth_token, cache=False):
940 q = UserApiKeys.query()\
942 q = UserApiKeys.query()\
941 .filter(UserApiKeys.api_key == auth_token)\
943 .filter(UserApiKeys.api_key == auth_token)\
942 .filter(or_(UserApiKeys.expires == -1,
944 .filter(or_(UserApiKeys.expires == -1,
943 UserApiKeys.expires >= time.time()))
945 UserApiKeys.expires >= time.time()))
944 if cache:
946 if cache:
945 q = q.options(
947 q = q.options(
946 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
948 FromCache("sql_cache_short", f"get_auth_token_{auth_token}"))
947
949
948 match = q.first()
950 match = q.first()
949 if match:
951 if match:
950 return match.user
952 return match.user
951
953
952 @classmethod
954 @classmethod
953 def get_by_email(cls, email, case_insensitive=False, cache=False):
955 def get_by_email(cls, email, case_insensitive=False, cache=False):
954
956
955 if case_insensitive:
957 if case_insensitive:
956 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
958 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
957
959
958 else:
960 else:
959 q = cls.query().filter(cls.email == email)
961 q = cls.query().filter(cls.email == email)
960
962
961 email_key = _hash_key(email)
963 email_key = _hash_key(email)
962 if cache:
964 if cache:
963 q = q.options(
965 q = q.options(
964 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
966 FromCache("sql_cache_short", f"get_email_key_{email_key}"))
965
967
966 ret = q.scalar()
968 ret = q.scalar()
967 if ret is None:
969 if ret is None:
968 q = UserEmailMap.query()
970 q = UserEmailMap.query()
969 # try fetching in alternate email map
971 # try fetching in alternate email map
970 if case_insensitive:
972 if case_insensitive:
971 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
973 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
972 else:
974 else:
973 q = q.filter(UserEmailMap.email == email)
975 q = q.filter(UserEmailMap.email == email)
974 q = q.options(joinedload(UserEmailMap.user))
976 q = q.options(joinedload(UserEmailMap.user))
975 if cache:
977 if cache:
976 q = q.options(
978 q = q.options(
977 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
979 FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
978 ret = getattr(q.scalar(), 'user', None)
980 ret = getattr(q.scalar(), 'user', None)
979
981
980 return ret
982 return ret
981
983
982 @classmethod
984 @classmethod
983 def get_from_cs_author(cls, author):
985 def get_from_cs_author(cls, author):
984 """
986 """
985 Tries to get User objects out of commit author string
987 Tries to get User objects out of commit author string
986
988
987 :param author:
989 :param author:
988 """
990 """
989 from rhodecode.lib.helpers import email, author_name
991 from rhodecode.lib.helpers import email, author_name
990 # Valid email in the attribute passed, see if they're in the system
992 # Valid email in the attribute passed, see if they're in the system
991 _email = email(author)
993 _email = email(author)
992 if _email:
994 if _email:
993 user = cls.get_by_email(_email, case_insensitive=True)
995 user = cls.get_by_email(_email, case_insensitive=True)
994 if user:
996 if user:
995 return user
997 return user
996 # Maybe we can match by username?
998 # Maybe we can match by username?
997 _author = author_name(author)
999 _author = author_name(author)
998 user = cls.get_by_username(_author, case_insensitive=True)
1000 user = cls.get_by_username(_author, case_insensitive=True)
999 if user:
1001 if user:
1000 return user
1002 return user
1001
1003
1002 def update_userdata(self, **kwargs):
1004 def update_userdata(self, **kwargs):
1003 usr = self
1005 usr = self
1004 old = usr.user_data
1006 old = usr.user_data
1005 old.update(**kwargs)
1007 old.update(**kwargs)
1006 usr.user_data = old
1008 usr.user_data = old
1007 Session().add(usr)
1009 Session().add(usr)
1008 log.debug('updated userdata with %s', kwargs)
1010 log.debug('updated userdata with %s', kwargs)
1009
1011
1010 def update_lastlogin(self):
1012 def update_lastlogin(self):
1011 """Update user lastlogin"""
1013 """Update user lastlogin"""
1012 self.last_login = datetime.datetime.now()
1014 self.last_login = datetime.datetime.now()
1013 Session().add(self)
1015 Session().add(self)
1014 log.debug('updated user %s lastlogin', self.username)
1016 log.debug('updated user %s lastlogin', self.username)
1015
1017
1016 def update_password(self, new_password):
1018 def update_password(self, new_password):
1017 from rhodecode.lib.auth import get_crypt_password
1019 from rhodecode.lib.auth import get_crypt_password
1018
1020
1019 self.password = get_crypt_password(new_password)
1021 self.password = get_crypt_password(new_password)
1020 Session().add(self)
1022 Session().add(self)
1021
1023
1022 @classmethod
1024 @classmethod
1023 def get_first_super_admin(cls):
1025 def get_first_super_admin(cls):
1024 user = User.query()\
1026 user = User.query()\
1025 .filter(User.admin == true()) \
1027 .filter(User.admin == true()) \
1026 .order_by(User.user_id.asc()) \
1028 .order_by(User.user_id.asc()) \
1027 .first()
1029 .first()
1028
1030
1029 if user is None:
1031 if user is None:
1030 raise Exception('FATAL: Missing administrative account!')
1032 raise Exception('FATAL: Missing administrative account!')
1031 return user
1033 return user
1032
1034
1033 @classmethod
1035 @classmethod
1034 def get_all_super_admins(cls, only_active=False):
1036 def get_all_super_admins(cls, only_active=False):
1035 """
1037 """
1036 Returns all admin accounts sorted by username
1038 Returns all admin accounts sorted by username
1037 """
1039 """
1038 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1040 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1039 if only_active:
1041 if only_active:
1040 qry = qry.filter(User.active == true())
1042 qry = qry.filter(User.active == true())
1041 return qry.all()
1043 return qry.all()
1042
1044
1043 @classmethod
1045 @classmethod
1044 def get_all_user_ids(cls, only_active=True):
1046 def get_all_user_ids(cls, only_active=True):
1045 """
1047 """
1046 Returns all users IDs
1048 Returns all users IDs
1047 """
1049 """
1048 qry = Session().query(User.user_id)
1050 qry = Session().query(User.user_id)
1049
1051
1050 if only_active:
1052 if only_active:
1051 qry = qry.filter(User.active == true())
1053 qry = qry.filter(User.active == true())
1052 return [x.user_id for x in qry]
1054 return [x.user_id for x in qry]
1053
1055
1054 @classmethod
1056 @classmethod
1055 def get_default_user(cls, cache=False, refresh=False):
1057 def get_default_user(cls, cache=False, refresh=False):
1056 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1058 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1057 if user is None:
1059 if user is None:
1058 raise Exception('FATAL: Missing default account!')
1060 raise Exception('FATAL: Missing default account!')
1059 if refresh:
1061 if refresh:
1060 # The default user might be based on outdated state which
1062 # The default user might be based on outdated state which
1061 # has been loaded from the cache.
1063 # has been loaded from the cache.
1062 # A call to refresh() ensures that the
1064 # A call to refresh() ensures that the
1063 # latest state from the database is used.
1065 # latest state from the database is used.
1064 Session().refresh(user)
1066 Session().refresh(user)
1065 return user
1067 return user
1066
1068
1067 @classmethod
1069 @classmethod
1068 def get_default_user_id(cls):
1070 def get_default_user_id(cls):
1069 import rhodecode
1071 import rhodecode
1070 return rhodecode.CONFIG['default_user_id']
1072 return rhodecode.CONFIG['default_user_id']
1071
1073
1072 def _get_default_perms(self, user, suffix=''):
1074 def _get_default_perms(self, user, suffix=''):
1073 from rhodecode.model.permission import PermissionModel
1075 from rhodecode.model.permission import PermissionModel
1074 return PermissionModel().get_default_perms(user.user_perms, suffix)
1076 return PermissionModel().get_default_perms(user.user_perms, suffix)
1075
1077
1076 def get_default_perms(self, suffix=''):
1078 def get_default_perms(self, suffix=''):
1077 return self._get_default_perms(self, suffix)
1079 return self._get_default_perms(self, suffix)
1078
1080
1079 def get_api_data(self, include_secrets=False, details='full'):
1081 def get_api_data(self, include_secrets=False, details='full'):
1080 """
1082 """
1081 Common function for generating user related data for API
1083 Common function for generating user related data for API
1082
1084
1083 :param include_secrets: By default secrets in the API data will be replaced
1085 :param include_secrets: By default secrets in the API data will be replaced
1084 by a placeholder value to prevent exposing this data by accident. In case
1086 by a placeholder value to prevent exposing this data by accident. In case
1085 this data shall be exposed, set this flag to ``True``.
1087 this data shall be exposed, set this flag to ``True``.
1086
1088
1087 :param details: details can be 'basic|full' basic gives only a subset of
1089 :param details: details can be 'basic|full' basic gives only a subset of
1088 the available user information that includes user_id, name and emails.
1090 the available user information that includes user_id, name and emails.
1089 """
1091 """
1090 user = self
1092 user = self
1091 user_data = self.user_data
1093 user_data = self.user_data
1092 data = {
1094 data = {
1093 'user_id': user.user_id,
1095 'user_id': user.user_id,
1094 'username': user.username,
1096 'username': user.username,
1095 'firstname': user.name,
1097 'firstname': user.name,
1096 'lastname': user.lastname,
1098 'lastname': user.lastname,
1097 'description': user.description,
1099 'description': user.description,
1098 'email': user.email,
1100 'email': user.email,
1099 'emails': user.emails,
1101 'emails': user.emails,
1100 }
1102 }
1101 if details == 'basic':
1103 if details == 'basic':
1102 return data
1104 return data
1103
1105
1104 auth_token_length = 40
1106 auth_token_length = 40
1105 auth_token_replacement = '*' * auth_token_length
1107 auth_token_replacement = '*' * auth_token_length
1106
1108
1107 extras = {
1109 extras = {
1108 'auth_tokens': [auth_token_replacement],
1110 'auth_tokens': [auth_token_replacement],
1109 'active': user.active,
1111 'active': user.active,
1110 'admin': user.admin,
1112 'admin': user.admin,
1111 'extern_type': user.extern_type,
1113 'extern_type': user.extern_type,
1112 'extern_name': user.extern_name,
1114 'extern_name': user.extern_name,
1113 'last_login': user.last_login,
1115 'last_login': user.last_login,
1114 'last_activity': user.last_activity,
1116 'last_activity': user.last_activity,
1115 'ip_addresses': user.ip_addresses,
1117 'ip_addresses': user.ip_addresses,
1116 'language': user_data.get('language')
1118 'language': user_data.get('language')
1117 }
1119 }
1118 data.update(extras)
1120 data.update(extras)
1119
1121
1120 if include_secrets:
1122 if include_secrets:
1121 data['auth_tokens'] = user.auth_tokens
1123 data['auth_tokens'] = user.auth_tokens
1122 return data
1124 return data
1123
1125
1124 def __json__(self):
1126 def __json__(self):
1125 data = {
1127 data = {
1126 'full_name': self.full_name,
1128 'full_name': self.full_name,
1127 'full_name_or_username': self.full_name_or_username,
1129 'full_name_or_username': self.full_name_or_username,
1128 'short_contact': self.short_contact,
1130 'short_contact': self.short_contact,
1129 'full_contact': self.full_contact,
1131 'full_contact': self.full_contact,
1130 }
1132 }
1131 data.update(self.get_api_data())
1133 data.update(self.get_api_data())
1132 return data
1134 return data
1133
1135
1134
1136
1135 class UserApiKeys(Base, BaseModel):
1137 class UserApiKeys(Base, BaseModel):
1136 __tablename__ = 'user_api_keys'
1138 __tablename__ = 'user_api_keys'
1137 __table_args__ = (
1139 __table_args__ = (
1138 Index('uak_api_key_idx', 'api_key'),
1140 Index('uak_api_key_idx', 'api_key'),
1139 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1141 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1140 base_table_args
1142 base_table_args
1141 )
1143 )
1142 __mapper_args__ = {}
1144 __mapper_args__ = {}
1143
1145
1144 # ApiKey role
1146 # ApiKey role
1145 ROLE_ALL = 'token_role_all'
1147 ROLE_ALL = 'token_role_all'
1146 ROLE_VCS = 'token_role_vcs'
1148 ROLE_VCS = 'token_role_vcs'
1147 ROLE_API = 'token_role_api'
1149 ROLE_API = 'token_role_api'
1148 ROLE_HTTP = 'token_role_http'
1150 ROLE_HTTP = 'token_role_http'
1149 ROLE_FEED = 'token_role_feed'
1151 ROLE_FEED = 'token_role_feed'
1150 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1152 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1151 # The last one is ignored in the list as we only
1153 # The last one is ignored in the list as we only
1152 # use it for one action, and cannot be created by users
1154 # use it for one action, and cannot be created by users
1153 ROLE_PASSWORD_RESET = 'token_password_reset'
1155 ROLE_PASSWORD_RESET = 'token_password_reset'
1154
1156
1155 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1157 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1156
1158
1157 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1159 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1158 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1160 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1159 api_key = Column("api_key", String(255), nullable=False, unique=True)
1161 api_key = Column("api_key", String(255), nullable=False, unique=True)
1160 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1162 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1161 expires = Column('expires', Float(53), nullable=False)
1163 expires = Column('expires', Float(53), nullable=False)
1162 role = Column('role', String(255), nullable=True)
1164 role = Column('role', String(255), nullable=True)
1163 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1165 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1164
1166
1165 # scope columns
1167 # scope columns
1166 repo_id = Column(
1168 repo_id = Column(
1167 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1169 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1168 nullable=True, unique=None, default=None)
1170 nullable=True, unique=None, default=None)
1169 repo = relationship('Repository', lazy='joined')
1171 repo = relationship('Repository', lazy='joined')
1170
1172
1171 repo_group_id = Column(
1173 repo_group_id = Column(
1172 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1174 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1173 nullable=True, unique=None, default=None)
1175 nullable=True, unique=None, default=None)
1174 repo_group = relationship('RepoGroup', lazy='joined')
1176 repo_group = relationship('RepoGroup', lazy='joined')
1175
1177
1176 user = relationship('User', lazy='joined')
1178 user = relationship('User', lazy='joined')
1177
1179
1178 def __unicode__(self):
1180 def __unicode__(self):
1179 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1181 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1180
1182
1181 def __json__(self):
1183 def __json__(self):
1182 data = {
1184 data = {
1183 'auth_token': self.api_key,
1185 'auth_token': self.api_key,
1184 'role': self.role,
1186 'role': self.role,
1185 'scope': self.scope_humanized,
1187 'scope': self.scope_humanized,
1186 'expired': self.expired
1188 'expired': self.expired
1187 }
1189 }
1188 return data
1190 return data
1189
1191
1190 def get_api_data(self, include_secrets=False):
1192 def get_api_data(self, include_secrets=False):
1191 data = self.__json__()
1193 data = self.__json__()
1192 if include_secrets:
1194 if include_secrets:
1193 return data
1195 return data
1194 else:
1196 else:
1195 data['auth_token'] = self.token_obfuscated
1197 data['auth_token'] = self.token_obfuscated
1196 return data
1198 return data
1197
1199
1198 @hybrid_property
1200 @hybrid_property
1199 def description_safe(self):
1201 def description_safe(self):
1200 from rhodecode.lib import helpers as h
1202 from rhodecode.lib import helpers as h
1201 return h.escape(self.description)
1203 return h.escape(self.description)
1202
1204
1203 @property
1205 @property
1204 def expired(self):
1206 def expired(self):
1205 if self.expires == -1:
1207 if self.expires == -1:
1206 return False
1208 return False
1207 return time.time() > self.expires
1209 return time.time() > self.expires
1208
1210
1209 @classmethod
1211 @classmethod
1210 def _get_role_name(cls, role):
1212 def _get_role_name(cls, role):
1211 return {
1213 return {
1212 cls.ROLE_ALL: _('all'),
1214 cls.ROLE_ALL: _('all'),
1213 cls.ROLE_HTTP: _('http/web interface'),
1215 cls.ROLE_HTTP: _('http/web interface'),
1214 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1216 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1215 cls.ROLE_API: _('api calls'),
1217 cls.ROLE_API: _('api calls'),
1216 cls.ROLE_FEED: _('feed access'),
1218 cls.ROLE_FEED: _('feed access'),
1217 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1219 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1218 }.get(role, role)
1220 }.get(role, role)
1219
1221
1220 @classmethod
1222 @classmethod
1221 def _get_role_description(cls, role):
1223 def _get_role_description(cls, role):
1222 return {
1224 return {
1223 cls.ROLE_ALL: _('Token for all actions.'),
1225 cls.ROLE_ALL: _('Token for all actions.'),
1224 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1226 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1225 'login using `api_access_controllers_whitelist` functionality.'),
1227 'login using `api_access_controllers_whitelist` functionality.'),
1226 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1228 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1227 'Requires auth_token authentication plugin to be active. <br/>'
1229 'Requires auth_token authentication plugin to be active. <br/>'
1228 'Such Token should be used then instead of a password to '
1230 'Such Token should be used then instead of a password to '
1229 'interact with a repository, and additionally can be '
1231 'interact with a repository, and additionally can be '
1230 'limited to single repository using repo scope.'),
1232 'limited to single repository using repo scope.'),
1231 cls.ROLE_API: _('Token limited to api calls.'),
1233 cls.ROLE_API: _('Token limited to api calls.'),
1232 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1234 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1233 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1235 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1234 }.get(role, role)
1236 }.get(role, role)
1235
1237
1236 @property
1238 @property
1237 def role_humanized(self):
1239 def role_humanized(self):
1238 return self._get_role_name(self.role)
1240 return self._get_role_name(self.role)
1239
1241
1240 def _get_scope(self):
1242 def _get_scope(self):
1241 if self.repo:
1243 if self.repo:
1242 return 'Repository: {}'.format(self.repo.repo_name)
1244 return 'Repository: {}'.format(self.repo.repo_name)
1243 if self.repo_group:
1245 if self.repo_group:
1244 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1246 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1245 return 'Global'
1247 return 'Global'
1246
1248
1247 @property
1249 @property
1248 def scope_humanized(self):
1250 def scope_humanized(self):
1249 return self._get_scope()
1251 return self._get_scope()
1250
1252
1251 @property
1253 @property
1252 def token_obfuscated(self):
1254 def token_obfuscated(self):
1253 if self.api_key:
1255 if self.api_key:
1254 return self.api_key[:4] + "****"
1256 return self.api_key[:4] + "****"
1255
1257
1256
1258
1257 class UserEmailMap(Base, BaseModel):
1259 class UserEmailMap(Base, BaseModel):
1258 __tablename__ = 'user_email_map'
1260 __tablename__ = 'user_email_map'
1259 __table_args__ = (
1261 __table_args__ = (
1260 Index('uem_email_idx', 'email'),
1262 Index('uem_email_idx', 'email'),
1261 UniqueConstraint('email'),
1263 UniqueConstraint('email'),
1262 base_table_args
1264 base_table_args
1263 )
1265 )
1264 __mapper_args__ = {}
1266 __mapper_args__ = {}
1265
1267
1266 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1268 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1269 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1268 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1270 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1269 user = relationship('User', lazy='joined')
1271 user = relationship('User', lazy='joined')
1270
1272
1271 @validates('_email')
1273 @validates('_email')
1272 def validate_email(self, key, email):
1274 def validate_email(self, key, email):
1273 # check if this email is not main one
1275 # check if this email is not main one
1274 main_email = Session().query(User).filter(User.email == email).scalar()
1276 main_email = Session().query(User).filter(User.email == email).scalar()
1275 if main_email is not None:
1277 if main_email is not None:
1276 raise AttributeError('email %s is present is user table' % email)
1278 raise AttributeError('email %s is present is user table' % email)
1277 return email
1279 return email
1278
1280
1279 @hybrid_property
1281 @hybrid_property
1280 def email(self):
1282 def email(self):
1281 return self._email
1283 return self._email
1282
1284
1283 @email.setter
1285 @email.setter
1284 def email(self, val):
1286 def email(self, val):
1285 self._email = val.lower() if val else None
1287 self._email = val.lower() if val else None
1286
1288
1287
1289
1288 class UserIpMap(Base, BaseModel):
1290 class UserIpMap(Base, BaseModel):
1289 __tablename__ = 'user_ip_map'
1291 __tablename__ = 'user_ip_map'
1290 __table_args__ = (
1292 __table_args__ = (
1291 UniqueConstraint('user_id', 'ip_addr'),
1293 UniqueConstraint('user_id', 'ip_addr'),
1292 base_table_args
1294 base_table_args
1293 )
1295 )
1294 __mapper_args__ = {}
1296 __mapper_args__ = {}
1295
1297
1296 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1297 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1298 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1300 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1299 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1301 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1300 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1302 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1301 user = relationship('User', lazy='joined')
1303 user = relationship('User', lazy='joined')
1302
1304
1303 @hybrid_property
1305 @hybrid_property
1304 def description_safe(self):
1306 def description_safe(self):
1305 from rhodecode.lib import helpers as h
1307 from rhodecode.lib import helpers as h
1306 return h.escape(self.description)
1308 return h.escape(self.description)
1307
1309
1308 @classmethod
1310 @classmethod
1309 def _get_ip_range(cls, ip_addr):
1311 def _get_ip_range(cls, ip_addr):
1310 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1312 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1311 return [str(net.network_address), str(net.broadcast_address)]
1313 return [str(net.network_address), str(net.broadcast_address)]
1312
1314
1313 def __json__(self):
1315 def __json__(self):
1314 return {
1316 return {
1315 'ip_addr': self.ip_addr,
1317 'ip_addr': self.ip_addr,
1316 'ip_range': self._get_ip_range(self.ip_addr),
1318 'ip_range': self._get_ip_range(self.ip_addr),
1317 }
1319 }
1318
1320
1319 def __unicode__(self):
1321 def __unicode__(self):
1320 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1322 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1321 self.user_id, self.ip_addr)
1323 self.user_id, self.ip_addr)
1322
1324
1323
1325
1324 class UserSshKeys(Base, BaseModel):
1326 class UserSshKeys(Base, BaseModel):
1325 __tablename__ = 'user_ssh_keys'
1327 __tablename__ = 'user_ssh_keys'
1326 __table_args__ = (
1328 __table_args__ = (
1327 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1329 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1328
1330
1329 UniqueConstraint('ssh_key_fingerprint'),
1331 UniqueConstraint('ssh_key_fingerprint'),
1330
1332
1331 base_table_args
1333 base_table_args
1332 )
1334 )
1333 __mapper_args__ = {}
1335 __mapper_args__ = {}
1334
1336
1335 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1337 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1336 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1338 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1337 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1339 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1338
1340
1339 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1341 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1340
1342
1341 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1343 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1342 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1344 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1343 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1345 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1344
1346
1345 user = relationship('User', lazy='joined')
1347 user = relationship('User', lazy='joined')
1346
1348
1347 def __json__(self):
1349 def __json__(self):
1348 data = {
1350 data = {
1349 'ssh_fingerprint': self.ssh_key_fingerprint,
1351 'ssh_fingerprint': self.ssh_key_fingerprint,
1350 'description': self.description,
1352 'description': self.description,
1351 'created_on': self.created_on
1353 'created_on': self.created_on
1352 }
1354 }
1353 return data
1355 return data
1354
1356
1355 def get_api_data(self):
1357 def get_api_data(self):
1356 data = self.__json__()
1358 data = self.__json__()
1357 return data
1359 return data
1358
1360
1359
1361
1360 class UserLog(Base, BaseModel):
1362 class UserLog(Base, BaseModel):
1361 __tablename__ = 'user_logs'
1363 __tablename__ = 'user_logs'
1362 __table_args__ = (
1364 __table_args__ = (
1363 base_table_args,
1365 base_table_args,
1364 )
1366 )
1365
1367
1366 VERSION_1 = 'v1'
1368 VERSION_1 = 'v1'
1367 VERSION_2 = 'v2'
1369 VERSION_2 = 'v2'
1368 VERSIONS = [VERSION_1, VERSION_2]
1370 VERSIONS = [VERSION_1, VERSION_2]
1369
1371
1370 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1372 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1372 username = Column("username", String(255), nullable=True, unique=None, default=None)
1374 username = Column("username", String(255), nullable=True, unique=None, default=None)
1373 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1375 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1374 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1376 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1375 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1377 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1376 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1378 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1377 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1379 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1378
1380
1379 version = Column("version", String(255), nullable=True, default=VERSION_1)
1381 version = Column("version", String(255), nullable=True, default=VERSION_1)
1380 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1382 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1381 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1383 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1382
1384
1383 def __unicode__(self):
1385 def __unicode__(self):
1384 return u"<%s('id:%s:%s')>" % (
1386 return u"<%s('id:%s:%s')>" % (
1385 self.__class__.__name__, self.repository_name, self.action)
1387 self.__class__.__name__, self.repository_name, self.action)
1386
1388
1387 def __json__(self):
1389 def __json__(self):
1388 return {
1390 return {
1389 'user_id': self.user_id,
1391 'user_id': self.user_id,
1390 'username': self.username,
1392 'username': self.username,
1391 'repository_id': self.repository_id,
1393 'repository_id': self.repository_id,
1392 'repository_name': self.repository_name,
1394 'repository_name': self.repository_name,
1393 'user_ip': self.user_ip,
1395 'user_ip': self.user_ip,
1394 'action_date': self.action_date,
1396 'action_date': self.action_date,
1395 'action': self.action,
1397 'action': self.action,
1396 }
1398 }
1397
1399
1398 @hybrid_property
1400 @hybrid_property
1399 def entry_id(self):
1401 def entry_id(self):
1400 return self.user_log_id
1402 return self.user_log_id
1401
1403
1402 @property
1404 @property
1403 def action_as_day(self):
1405 def action_as_day(self):
1404 return datetime.date(*self.action_date.timetuple()[:3])
1406 return datetime.date(*self.action_date.timetuple()[:3])
1405
1407
1406 user = relationship('User')
1408 user = relationship('User')
1407 repository = relationship('Repository', cascade='')
1409 repository = relationship('Repository', cascade='')
1408
1410
1409
1411
1410 class UserGroup(Base, BaseModel):
1412 class UserGroup(Base, BaseModel):
1411 __tablename__ = 'users_groups'
1413 __tablename__ = 'users_groups'
1412 __table_args__ = (
1414 __table_args__ = (
1413 base_table_args,
1415 base_table_args,
1414 )
1416 )
1415
1417
1416 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1418 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1417 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1419 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1418 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1420 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1419 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1421 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1420 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1422 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1421 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1423 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1422 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1424 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1423 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1425 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1424
1426
1425 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1427 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1426 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1428 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1427 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1429 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1428 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1430 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1429 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1431 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1430 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1432 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1431
1433
1432 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1434 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1433 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1435 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1434
1436
1435 @classmethod
1437 @classmethod
1436 def _load_group_data(cls, column):
1438 def _load_group_data(cls, column):
1437 if not column:
1439 if not column:
1438 return {}
1440 return {}
1439
1441
1440 try:
1442 try:
1441 return json.loads(column) or {}
1443 return json.loads(column) or {}
1442 except TypeError:
1444 except TypeError:
1443 return {}
1445 return {}
1444
1446
1445 @hybrid_property
1447 @hybrid_property
1446 def description_safe(self):
1448 def description_safe(self):
1447 from rhodecode.lib import helpers as h
1449 from rhodecode.lib import helpers as h
1448 return h.escape(self.user_group_description)
1450 return h.escape(self.user_group_description)
1449
1451
1450 @hybrid_property
1452 @hybrid_property
1451 def group_data(self):
1453 def group_data(self):
1452 return self._load_group_data(self._group_data)
1454 return self._load_group_data(self._group_data)
1453
1455
1454 @group_data.expression
1456 @group_data.expression
1455 def group_data(self, **kwargs):
1457 def group_data(self, **kwargs):
1456 return self._group_data
1458 return self._group_data
1457
1459
1458 @group_data.setter
1460 @group_data.setter
1459 def group_data(self, val):
1461 def group_data(self, val):
1460 try:
1462 try:
1461 self._group_data = json.dumps(val)
1463 self._group_data = json.dumps(val)
1462 except Exception:
1464 except Exception:
1463 log.error(traceback.format_exc())
1465 log.error(traceback.format_exc())
1464
1466
1465 @classmethod
1467 @classmethod
1466 def _load_sync(cls, group_data):
1468 def _load_sync(cls, group_data):
1467 if group_data:
1469 if group_data:
1468 return group_data.get('extern_type')
1470 return group_data.get('extern_type')
1469
1471
1470 @property
1472 @property
1471 def sync(self):
1473 def sync(self):
1472 return self._load_sync(self.group_data)
1474 return self._load_sync(self.group_data)
1473
1475
1474 def __unicode__(self):
1476 def __unicode__(self):
1475 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1477 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1476 self.users_group_id,
1478 self.users_group_id,
1477 self.users_group_name)
1479 self.users_group_name)
1478
1480
1479 @classmethod
1481 @classmethod
1480 def get_by_group_name(cls, group_name, cache=False,
1482 def get_by_group_name(cls, group_name, cache=False,
1481 case_insensitive=False):
1483 case_insensitive=False):
1482 if case_insensitive:
1484 if case_insensitive:
1483 q = cls.query().filter(func.lower(cls.users_group_name) ==
1485 q = cls.query().filter(func.lower(cls.users_group_name) ==
1484 func.lower(group_name))
1486 func.lower(group_name))
1485
1487
1486 else:
1488 else:
1487 q = cls.query().filter(cls.users_group_name == group_name)
1489 q = cls.query().filter(cls.users_group_name == group_name)
1488 if cache:
1490 if cache:
1491 name_key = _hash_key(group_name)
1489 q = q.options(
1492 q = q.options(
1490 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1493 FromCache("sql_cache_short", f"get_group_{name_key}"))
1491 return q.scalar()
1494 return q.scalar()
1492
1495
1493 @classmethod
1496 @classmethod
1494 def get(cls, user_group_id, cache=False):
1497 def get(cls, user_group_id, cache=False):
1495 if not user_group_id:
1498 if not user_group_id:
1496 return
1499 return
1497
1500
1498 user_group = cls.query()
1501 user_group = cls.query()
1499 if cache:
1502 if cache:
1500 user_group = user_group.options(
1503 user_group = user_group.options(
1501 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1504 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1502 return user_group.get(user_group_id)
1505 return user_group.get(user_group_id)
1503
1506
1504 def permissions(self, with_admins=True, with_owner=True,
1507 def permissions(self, with_admins=True, with_owner=True,
1505 expand_from_user_groups=False):
1508 expand_from_user_groups=False):
1506 """
1509 """
1507 Permissions for user groups
1510 Permissions for user groups
1508 """
1511 """
1509 _admin_perm = 'usergroup.admin'
1512 _admin_perm = 'usergroup.admin'
1510
1513
1511 owner_row = []
1514 owner_row = []
1512 if with_owner:
1515 if with_owner:
1513 usr = AttributeDict(self.user.get_dict())
1516 usr = AttributeDict(self.user.get_dict())
1514 usr.owner_row = True
1517 usr.owner_row = True
1515 usr.permission = _admin_perm
1518 usr.permission = _admin_perm
1516 owner_row.append(usr)
1519 owner_row.append(usr)
1517
1520
1518 super_admin_ids = []
1521 super_admin_ids = []
1519 super_admin_rows = []
1522 super_admin_rows = []
1520 if with_admins:
1523 if with_admins:
1521 for usr in User.get_all_super_admins():
1524 for usr in User.get_all_super_admins():
1522 super_admin_ids.append(usr.user_id)
1525 super_admin_ids.append(usr.user_id)
1523 # if this admin is also owner, don't double the record
1526 # if this admin is also owner, don't double the record
1524 if usr.user_id == owner_row[0].user_id:
1527 if usr.user_id == owner_row[0].user_id:
1525 owner_row[0].admin_row = True
1528 owner_row[0].admin_row = True
1526 else:
1529 else:
1527 usr = AttributeDict(usr.get_dict())
1530 usr = AttributeDict(usr.get_dict())
1528 usr.admin_row = True
1531 usr.admin_row = True
1529 usr.permission = _admin_perm
1532 usr.permission = _admin_perm
1530 super_admin_rows.append(usr)
1533 super_admin_rows.append(usr)
1531
1534
1532 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1535 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1533 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1536 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1534 joinedload(UserUserGroupToPerm.user),
1537 joinedload(UserUserGroupToPerm.user),
1535 joinedload(UserUserGroupToPerm.permission),)
1538 joinedload(UserUserGroupToPerm.permission),)
1536
1539
1537 # get owners and admins and permissions. We do a trick of re-writing
1540 # get owners and admins and permissions. We do a trick of re-writing
1538 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1541 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1539 # has a global reference and changing one object propagates to all
1542 # has a global reference and changing one object propagates to all
1540 # others. This means if admin is also an owner admin_row that change
1543 # others. This means if admin is also an owner admin_row that change
1541 # would propagate to both objects
1544 # would propagate to both objects
1542 perm_rows = []
1545 perm_rows = []
1543 for _usr in q.all():
1546 for _usr in q.all():
1544 usr = AttributeDict(_usr.user.get_dict())
1547 usr = AttributeDict(_usr.user.get_dict())
1545 # if this user is also owner/admin, mark as duplicate record
1548 # if this user is also owner/admin, mark as duplicate record
1546 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1549 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1547 usr.duplicate_perm = True
1550 usr.duplicate_perm = True
1548 usr.permission = _usr.permission.permission_name
1551 usr.permission = _usr.permission.permission_name
1549 perm_rows.append(usr)
1552 perm_rows.append(usr)
1550
1553
1551 # filter the perm rows by 'default' first and then sort them by
1554 # filter the perm rows by 'default' first and then sort them by
1552 # admin,write,read,none permissions sorted again alphabetically in
1555 # admin,write,read,none permissions sorted again alphabetically in
1553 # each group
1556 # each group
1554 perm_rows = sorted(perm_rows, key=display_user_sort)
1557 perm_rows = sorted(perm_rows, key=display_user_sort)
1555
1558
1556 user_groups_rows = []
1559 user_groups_rows = []
1557 if expand_from_user_groups:
1560 if expand_from_user_groups:
1558 for ug in self.permission_user_groups(with_members=True):
1561 for ug in self.permission_user_groups(with_members=True):
1559 for user_data in ug.members:
1562 for user_data in ug.members:
1560 user_groups_rows.append(user_data)
1563 user_groups_rows.append(user_data)
1561
1564
1562 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1565 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1563
1566
1564 def permission_user_groups(self, with_members=False):
1567 def permission_user_groups(self, with_members=False):
1565 q = UserGroupUserGroupToPerm.query()\
1568 q = UserGroupUserGroupToPerm.query()\
1566 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1569 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1567 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1570 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1568 joinedload(UserGroupUserGroupToPerm.target_user_group),
1571 joinedload(UserGroupUserGroupToPerm.target_user_group),
1569 joinedload(UserGroupUserGroupToPerm.permission),)
1572 joinedload(UserGroupUserGroupToPerm.permission),)
1570
1573
1571 perm_rows = []
1574 perm_rows = []
1572 for _user_group in q.all():
1575 for _user_group in q.all():
1573 entry = AttributeDict(_user_group.user_group.get_dict())
1576 entry = AttributeDict(_user_group.user_group.get_dict())
1574 entry.permission = _user_group.permission.permission_name
1577 entry.permission = _user_group.permission.permission_name
1575 if with_members:
1578 if with_members:
1576 entry.members = [x.user.get_dict()
1579 entry.members = [x.user.get_dict()
1577 for x in _user_group.user_group.members]
1580 for x in _user_group.user_group.members]
1578 perm_rows.append(entry)
1581 perm_rows.append(entry)
1579
1582
1580 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1583 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1581 return perm_rows
1584 return perm_rows
1582
1585
1583 def _get_default_perms(self, user_group, suffix=''):
1586 def _get_default_perms(self, user_group, suffix=''):
1584 from rhodecode.model.permission import PermissionModel
1587 from rhodecode.model.permission import PermissionModel
1585 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1588 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1586
1589
1587 def get_default_perms(self, suffix=''):
1590 def get_default_perms(self, suffix=''):
1588 return self._get_default_perms(self, suffix)
1591 return self._get_default_perms(self, suffix)
1589
1592
1590 def get_api_data(self, with_group_members=True, include_secrets=False):
1593 def get_api_data(self, with_group_members=True, include_secrets=False):
1591 """
1594 """
1592 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1595 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1593 basically forwarded.
1596 basically forwarded.
1594
1597
1595 """
1598 """
1596 user_group = self
1599 user_group = self
1597 data = {
1600 data = {
1598 'users_group_id': user_group.users_group_id,
1601 'users_group_id': user_group.users_group_id,
1599 'group_name': user_group.users_group_name,
1602 'group_name': user_group.users_group_name,
1600 'group_description': user_group.user_group_description,
1603 'group_description': user_group.user_group_description,
1601 'active': user_group.users_group_active,
1604 'active': user_group.users_group_active,
1602 'owner': user_group.user.username,
1605 'owner': user_group.user.username,
1603 'sync': user_group.sync,
1606 'sync': user_group.sync,
1604 'owner_email': user_group.user.email,
1607 'owner_email': user_group.user.email,
1605 }
1608 }
1606
1609
1607 if with_group_members:
1610 if with_group_members:
1608 users = []
1611 users = []
1609 for user in user_group.members:
1612 for user in user_group.members:
1610 user = user.user
1613 user = user.user
1611 users.append(user.get_api_data(include_secrets=include_secrets))
1614 users.append(user.get_api_data(include_secrets=include_secrets))
1612 data['users'] = users
1615 data['users'] = users
1613
1616
1614 return data
1617 return data
1615
1618
1616
1619
1617 class UserGroupMember(Base, BaseModel):
1620 class UserGroupMember(Base, BaseModel):
1618 __tablename__ = 'users_groups_members'
1621 __tablename__ = 'users_groups_members'
1619 __table_args__ = (
1622 __table_args__ = (
1620 base_table_args,
1623 base_table_args,
1621 )
1624 )
1622
1625
1623 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1626 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1624 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1627 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1625 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1628 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1626
1629
1627 user = relationship('User', lazy='joined')
1630 user = relationship('User', lazy='joined')
1628 users_group = relationship('UserGroup')
1631 users_group = relationship('UserGroup')
1629
1632
1630 def __init__(self, gr_id='', u_id=''):
1633 def __init__(self, gr_id='', u_id=''):
1631 self.users_group_id = gr_id
1634 self.users_group_id = gr_id
1632 self.user_id = u_id
1635 self.user_id = u_id
1633
1636
1634
1637
1635 class RepositoryField(Base, BaseModel):
1638 class RepositoryField(Base, BaseModel):
1636 __tablename__ = 'repositories_fields'
1639 __tablename__ = 'repositories_fields'
1637 __table_args__ = (
1640 __table_args__ = (
1638 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1641 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1639 base_table_args,
1642 base_table_args,
1640 )
1643 )
1641
1644
1642 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1645 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1643
1646
1644 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1647 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1645 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1648 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1646 field_key = Column("field_key", String(250))
1649 field_key = Column("field_key", String(250))
1647 field_label = Column("field_label", String(1024), nullable=False)
1650 field_label = Column("field_label", String(1024), nullable=False)
1648 field_value = Column("field_value", String(10000), nullable=False)
1651 field_value = Column("field_value", String(10000), nullable=False)
1649 field_desc = Column("field_desc", String(1024), nullable=False)
1652 field_desc = Column("field_desc", String(1024), nullable=False)
1650 field_type = Column("field_type", String(255), nullable=False, unique=None)
1653 field_type = Column("field_type", String(255), nullable=False, unique=None)
1651 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1654 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1652
1655
1653 repository = relationship('Repository')
1656 repository = relationship('Repository')
1654
1657
1655 @property
1658 @property
1656 def field_key_prefixed(self):
1659 def field_key_prefixed(self):
1657 return 'ex_%s' % self.field_key
1660 return 'ex_%s' % self.field_key
1658
1661
1659 @classmethod
1662 @classmethod
1660 def un_prefix_key(cls, key):
1663 def un_prefix_key(cls, key):
1661 if key.startswith(cls.PREFIX):
1664 if key.startswith(cls.PREFIX):
1662 return key[len(cls.PREFIX):]
1665 return key[len(cls.PREFIX):]
1663 return key
1666 return key
1664
1667
1665 @classmethod
1668 @classmethod
1666 def get_by_key_name(cls, key, repo):
1669 def get_by_key_name(cls, key, repo):
1667 row = cls.query()\
1670 row = cls.query()\
1668 .filter(cls.repository == repo)\
1671 .filter(cls.repository == repo)\
1669 .filter(cls.field_key == key).scalar()
1672 .filter(cls.field_key == key).scalar()
1670 return row
1673 return row
1671
1674
1672
1675
1673 class Repository(Base, BaseModel):
1676 class Repository(Base, BaseModel):
1674 __tablename__ = 'repositories'
1677 __tablename__ = 'repositories'
1675 __table_args__ = (
1678 __table_args__ = (
1676 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1679 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1677 base_table_args,
1680 base_table_args,
1678 )
1681 )
1679 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1682 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1680 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1683 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1681 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1684 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1682
1685
1683 STATE_CREATED = 'repo_state_created'
1686 STATE_CREATED = 'repo_state_created'
1684 STATE_PENDING = 'repo_state_pending'
1687 STATE_PENDING = 'repo_state_pending'
1685 STATE_ERROR = 'repo_state_error'
1688 STATE_ERROR = 'repo_state_error'
1686
1689
1687 LOCK_AUTOMATIC = 'lock_auto'
1690 LOCK_AUTOMATIC = 'lock_auto'
1688 LOCK_API = 'lock_api'
1691 LOCK_API = 'lock_api'
1689 LOCK_WEB = 'lock_web'
1692 LOCK_WEB = 'lock_web'
1690 LOCK_PULL = 'lock_pull'
1693 LOCK_PULL = 'lock_pull'
1691
1694
1692 NAME_SEP = URL_SEP
1695 NAME_SEP = URL_SEP
1693
1696
1694 repo_id = Column(
1697 repo_id = Column(
1695 "repo_id", Integer(), nullable=False, unique=True, default=None,
1698 "repo_id", Integer(), nullable=False, unique=True, default=None,
1696 primary_key=True)
1699 primary_key=True)
1697 _repo_name = Column(
1700 _repo_name = Column(
1698 "repo_name", Text(), nullable=False, default=None)
1701 "repo_name", Text(), nullable=False, default=None)
1699 repo_name_hash = Column(
1702 repo_name_hash = Column(
1700 "repo_name_hash", String(255), nullable=False, unique=True)
1703 "repo_name_hash", String(255), nullable=False, unique=True)
1701 repo_state = Column("repo_state", String(255), nullable=True)
1704 repo_state = Column("repo_state", String(255), nullable=True)
1702
1705
1703 clone_uri = Column(
1706 clone_uri = Column(
1704 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1707 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1705 default=None)
1708 default=None)
1706 push_uri = Column(
1709 push_uri = Column(
1707 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1710 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1708 default=None)
1711 default=None)
1709 repo_type = Column(
1712 repo_type = Column(
1710 "repo_type", String(255), nullable=False, unique=False, default=None)
1713 "repo_type", String(255), nullable=False, unique=False, default=None)
1711 user_id = Column(
1714 user_id = Column(
1712 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1715 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1713 unique=False, default=None)
1716 unique=False, default=None)
1714 private = Column(
1717 private = Column(
1715 "private", Boolean(), nullable=True, unique=None, default=None)
1718 "private", Boolean(), nullable=True, unique=None, default=None)
1716 archived = Column(
1719 archived = Column(
1717 "archived", Boolean(), nullable=True, unique=None, default=None)
1720 "archived", Boolean(), nullable=True, unique=None, default=None)
1718 enable_statistics = Column(
1721 enable_statistics = Column(
1719 "statistics", Boolean(), nullable=True, unique=None, default=True)
1722 "statistics", Boolean(), nullable=True, unique=None, default=True)
1720 enable_downloads = Column(
1723 enable_downloads = Column(
1721 "downloads", Boolean(), nullable=True, unique=None, default=True)
1724 "downloads", Boolean(), nullable=True, unique=None, default=True)
1722 description = Column(
1725 description = Column(
1723 "description", String(10000), nullable=True, unique=None, default=None)
1726 "description", String(10000), nullable=True, unique=None, default=None)
1724 created_on = Column(
1727 created_on = Column(
1725 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1728 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1726 default=datetime.datetime.now)
1729 default=datetime.datetime.now)
1727 updated_on = Column(
1730 updated_on = Column(
1728 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1731 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1729 default=datetime.datetime.now)
1732 default=datetime.datetime.now)
1730 _landing_revision = Column(
1733 _landing_revision = Column(
1731 "landing_revision", String(255), nullable=False, unique=False,
1734 "landing_revision", String(255), nullable=False, unique=False,
1732 default=None)
1735 default=None)
1733 enable_locking = Column(
1736 enable_locking = Column(
1734 "enable_locking", Boolean(), nullable=False, unique=None,
1737 "enable_locking", Boolean(), nullable=False, unique=None,
1735 default=False)
1738 default=False)
1736 _locked = Column(
1739 _locked = Column(
1737 "locked", String(255), nullable=True, unique=False, default=None)
1740 "locked", String(255), nullable=True, unique=False, default=None)
1738 _changeset_cache = Column(
1741 _changeset_cache = Column(
1739 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1742 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1740
1743
1741 fork_id = Column(
1744 fork_id = Column(
1742 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1745 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1743 nullable=True, unique=False, default=None)
1746 nullable=True, unique=False, default=None)
1744 group_id = Column(
1747 group_id = Column(
1745 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1748 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1746 unique=False, default=None)
1749 unique=False, default=None)
1747
1750
1748 user = relationship('User', lazy='joined')
1751 user = relationship('User', lazy='joined')
1749 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1752 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1750 group = relationship('RepoGroup', lazy='joined')
1753 group = relationship('RepoGroup', lazy='joined')
1751 repo_to_perm = relationship(
1754 repo_to_perm = relationship(
1752 'UserRepoToPerm', cascade='all',
1755 'UserRepoToPerm', cascade='all',
1753 order_by='UserRepoToPerm.repo_to_perm_id')
1756 order_by='UserRepoToPerm.repo_to_perm_id')
1754 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1757 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1755 stats = relationship('Statistics', cascade='all', uselist=False)
1758 stats = relationship('Statistics', cascade='all', uselist=False)
1756
1759
1757 followers = relationship(
1760 followers = relationship(
1758 'UserFollowing',
1761 'UserFollowing',
1759 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1762 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1760 cascade='all')
1763 cascade='all')
1761 extra_fields = relationship(
1764 extra_fields = relationship(
1762 'RepositoryField', cascade="all, delete-orphan")
1765 'RepositoryField', cascade="all, delete-orphan")
1763 logs = relationship('UserLog')
1766 logs = relationship('UserLog')
1764 comments = relationship(
1767 comments = relationship(
1765 'ChangesetComment', cascade="all, delete-orphan")
1768 'ChangesetComment', cascade="all, delete-orphan")
1766 pull_requests_source = relationship(
1769 pull_requests_source = relationship(
1767 'PullRequest',
1770 'PullRequest',
1768 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1771 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1769 cascade="all, delete-orphan")
1772 cascade="all, delete-orphan")
1770 pull_requests_target = relationship(
1773 pull_requests_target = relationship(
1771 'PullRequest',
1774 'PullRequest',
1772 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1775 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1773 cascade="all, delete-orphan")
1776 cascade="all, delete-orphan")
1774 ui = relationship('RepoRhodeCodeUi', cascade="all")
1777 ui = relationship('RepoRhodeCodeUi', cascade="all")
1775 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1778 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1776 integrations = relationship('Integration', cascade="all, delete-orphan")
1779 integrations = relationship('Integration', cascade="all, delete-orphan")
1777
1780
1778 scoped_tokens = relationship('UserApiKeys', cascade="all")
1781 scoped_tokens = relationship('UserApiKeys', cascade="all")
1779
1782
1780 # no cascade, set NULL
1783 # no cascade, set NULL
1781 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1784 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1782
1785
1783 def __unicode__(self):
1786 def __unicode__(self):
1784 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1787 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1785 safe_unicode(self.repo_name))
1788 safe_unicode(self.repo_name))
1786
1789
1787 @hybrid_property
1790 @hybrid_property
1788 def description_safe(self):
1791 def description_safe(self):
1789 from rhodecode.lib import helpers as h
1792 from rhodecode.lib import helpers as h
1790 return h.escape(self.description)
1793 return h.escape(self.description)
1791
1794
1792 @hybrid_property
1795 @hybrid_property
1793 def landing_rev(self):
1796 def landing_rev(self):
1794 # always should return [rev_type, rev], e.g ['branch', 'master']
1797 # always should return [rev_type, rev], e.g ['branch', 'master']
1795 if self._landing_revision:
1798 if self._landing_revision:
1796 _rev_info = self._landing_revision.split(':')
1799 _rev_info = self._landing_revision.split(':')
1797 if len(_rev_info) < 2:
1800 if len(_rev_info) < 2:
1798 _rev_info.insert(0, 'rev')
1801 _rev_info.insert(0, 'rev')
1799 return [_rev_info[0], _rev_info[1]]
1802 return [_rev_info[0], _rev_info[1]]
1800 return [None, None]
1803 return [None, None]
1801
1804
1802 @property
1805 @property
1803 def landing_ref_type(self):
1806 def landing_ref_type(self):
1804 return self.landing_rev[0]
1807 return self.landing_rev[0]
1805
1808
1806 @property
1809 @property
1807 def landing_ref_name(self):
1810 def landing_ref_name(self):
1808 return self.landing_rev[1]
1811 return self.landing_rev[1]
1809
1812
1810 @landing_rev.setter
1813 @landing_rev.setter
1811 def landing_rev(self, val):
1814 def landing_rev(self, val):
1812 if ':' not in val:
1815 if ':' not in val:
1813 raise ValueError('value must be delimited with `:` and consist '
1816 raise ValueError('value must be delimited with `:` and consist '
1814 'of <rev_type>:<rev>, got %s instead' % val)
1817 'of <rev_type>:<rev>, got %s instead' % val)
1815 self._landing_revision = val
1818 self._landing_revision = val
1816
1819
1817 @hybrid_property
1820 @hybrid_property
1818 def locked(self):
1821 def locked(self):
1819 if self._locked:
1822 if self._locked:
1820 user_id, timelocked, reason = self._locked.split(':')
1823 user_id, timelocked, reason = self._locked.split(':')
1821 lock_values = int(user_id), timelocked, reason
1824 lock_values = int(user_id), timelocked, reason
1822 else:
1825 else:
1823 lock_values = [None, None, None]
1826 lock_values = [None, None, None]
1824 return lock_values
1827 return lock_values
1825
1828
1826 @locked.setter
1829 @locked.setter
1827 def locked(self, val):
1830 def locked(self, val):
1828 if val and isinstance(val, (list, tuple)):
1831 if val and isinstance(val, (list, tuple)):
1829 self._locked = ':'.join(map(str, val))
1832 self._locked = ':'.join(map(str, val))
1830 else:
1833 else:
1831 self._locked = None
1834 self._locked = None
1832
1835
1833 @classmethod
1836 @classmethod
1834 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1837 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1835 from rhodecode.lib.vcs.backends.base import EmptyCommit
1838 from rhodecode.lib.vcs.backends.base import EmptyCommit
1836 dummy = EmptyCommit().__json__()
1839 dummy = EmptyCommit().__json__()
1837 if not changeset_cache_raw:
1840 if not changeset_cache_raw:
1838 dummy['source_repo_id'] = repo_id
1841 dummy['source_repo_id'] = repo_id
1839 return json.loads(json.dumps(dummy))
1842 return json.loads(json.dumps(dummy))
1840
1843
1841 try:
1844 try:
1842 return json.loads(changeset_cache_raw)
1845 return json.loads(changeset_cache_raw)
1843 except TypeError:
1846 except TypeError:
1844 return dummy
1847 return dummy
1845 except Exception:
1848 except Exception:
1846 log.error(traceback.format_exc())
1849 log.error(traceback.format_exc())
1847 return dummy
1850 return dummy
1848
1851
1849 @hybrid_property
1852 @hybrid_property
1850 def changeset_cache(self):
1853 def changeset_cache(self):
1851 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1854 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1852
1855
1853 @changeset_cache.setter
1856 @changeset_cache.setter
1854 def changeset_cache(self, val):
1857 def changeset_cache(self, val):
1855 try:
1858 try:
1856 self._changeset_cache = json.dumps(val)
1859 self._changeset_cache = json.dumps(val)
1857 except Exception:
1860 except Exception:
1858 log.error(traceback.format_exc())
1861 log.error(traceback.format_exc())
1859
1862
1860 @hybrid_property
1863 @hybrid_property
1861 def repo_name(self):
1864 def repo_name(self):
1862 return self._repo_name
1865 return self._repo_name
1863
1866
1864 @repo_name.setter
1867 @repo_name.setter
1865 def repo_name(self, value):
1868 def repo_name(self, value):
1866 self._repo_name = value
1869 self._repo_name = value
1867 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1870 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1868
1871
1869 @classmethod
1872 @classmethod
1870 def normalize_repo_name(cls, repo_name):
1873 def normalize_repo_name(cls, repo_name):
1871 """
1874 """
1872 Normalizes os specific repo_name to the format internally stored inside
1875 Normalizes os specific repo_name to the format internally stored inside
1873 database using URL_SEP
1876 database using URL_SEP
1874
1877
1875 :param cls:
1878 :param cls:
1876 :param repo_name:
1879 :param repo_name:
1877 """
1880 """
1878 return cls.NAME_SEP.join(repo_name.split(os.sep))
1881 return cls.NAME_SEP.join(repo_name.split(os.sep))
1879
1882
1880 @classmethod
1883 @classmethod
1881 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1884 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1882 session = Session()
1885 session = Session()
1883 q = session.query(cls).filter(cls.repo_name == repo_name)
1886 q = session.query(cls).filter(cls.repo_name == repo_name)
1884
1887
1885 if cache:
1888 if cache:
1886 if identity_cache:
1889 if identity_cache:
1887 val = cls.identity_cache(session, 'repo_name', repo_name)
1890 val = cls.identity_cache(session, 'repo_name', repo_name)
1888 if val:
1891 if val:
1889 return val
1892 return val
1890 else:
1893 else:
1891 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1894 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1892 q = q.options(
1895 q = q.options(
1893 FromCache("sql_cache_short", cache_key))
1896 FromCache("sql_cache_short", cache_key))
1894
1897
1895 return q.scalar()
1898 return q.scalar()
1896
1899
1897 @classmethod
1900 @classmethod
1898 def get_by_id_or_repo_name(cls, repoid):
1901 def get_by_id_or_repo_name(cls, repoid):
1899 if isinstance(repoid, int):
1902 if isinstance(repoid, int):
1900 try:
1903 try:
1901 repo = cls.get(repoid)
1904 repo = cls.get(repoid)
1902 except ValueError:
1905 except ValueError:
1903 repo = None
1906 repo = None
1904 else:
1907 else:
1905 repo = cls.get_by_repo_name(repoid)
1908 repo = cls.get_by_repo_name(repoid)
1906 return repo
1909 return repo
1907
1910
1908 @classmethod
1911 @classmethod
1909 def get_by_full_path(cls, repo_full_path):
1912 def get_by_full_path(cls, repo_full_path):
1910 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1913 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1911 repo_name = cls.normalize_repo_name(repo_name)
1914 repo_name = cls.normalize_repo_name(repo_name)
1912 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1915 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1913
1916
1914 @classmethod
1917 @classmethod
1915 def get_repo_forks(cls, repo_id):
1918 def get_repo_forks(cls, repo_id):
1916 return cls.query().filter(Repository.fork_id == repo_id)
1919 return cls.query().filter(Repository.fork_id == repo_id)
1917
1920
1918 @classmethod
1921 @classmethod
1919 def base_path(cls):
1922 def base_path(cls):
1920 """
1923 """
1921 Returns base path when all repos are stored
1924 Returns base path when all repos are stored
1922
1925
1923 :param cls:
1926 :param cls:
1924 """
1927 """
1925 q = Session().query(RhodeCodeUi)\
1928 q = Session().query(RhodeCodeUi)\
1926 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1929 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1927 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1930 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1928 return q.one().ui_value
1931 return q.one().ui_value
1929
1932
1930 @classmethod
1933 @classmethod
1931 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1934 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1932 case_insensitive=True, archived=False):
1935 case_insensitive=True, archived=False):
1933 q = Repository.query()
1936 q = Repository.query()
1934
1937
1935 if not archived:
1938 if not archived:
1936 q = q.filter(Repository.archived.isnot(true()))
1939 q = q.filter(Repository.archived.isnot(true()))
1937
1940
1938 if not isinstance(user_id, Optional):
1941 if not isinstance(user_id, Optional):
1939 q = q.filter(Repository.user_id == user_id)
1942 q = q.filter(Repository.user_id == user_id)
1940
1943
1941 if not isinstance(group_id, Optional):
1944 if not isinstance(group_id, Optional):
1942 q = q.filter(Repository.group_id == group_id)
1945 q = q.filter(Repository.group_id == group_id)
1943
1946
1944 if case_insensitive:
1947 if case_insensitive:
1945 q = q.order_by(func.lower(Repository.repo_name))
1948 q = q.order_by(func.lower(Repository.repo_name))
1946 else:
1949 else:
1947 q = q.order_by(Repository.repo_name)
1950 q = q.order_by(Repository.repo_name)
1948
1951
1949 return q.all()
1952 return q.all()
1950
1953
1951 @property
1954 @property
1952 def repo_uid(self):
1955 def repo_uid(self):
1953 return '_{}'.format(self.repo_id)
1956 return '_{}'.format(self.repo_id)
1954
1957
1955 @property
1958 @property
1956 def forks(self):
1959 def forks(self):
1957 """
1960 """
1958 Return forks of this repo
1961 Return forks of this repo
1959 """
1962 """
1960 return Repository.get_repo_forks(self.repo_id)
1963 return Repository.get_repo_forks(self.repo_id)
1961
1964
1962 @property
1965 @property
1963 def parent(self):
1966 def parent(self):
1964 """
1967 """
1965 Returns fork parent
1968 Returns fork parent
1966 """
1969 """
1967 return self.fork
1970 return self.fork
1968
1971
1969 @property
1972 @property
1970 def just_name(self):
1973 def just_name(self):
1971 return self.repo_name.split(self.NAME_SEP)[-1]
1974 return self.repo_name.split(self.NAME_SEP)[-1]
1972
1975
1973 @property
1976 @property
1974 def groups_with_parents(self):
1977 def groups_with_parents(self):
1975 groups = []
1978 groups = []
1976 if self.group is None:
1979 if self.group is None:
1977 return groups
1980 return groups
1978
1981
1979 cur_gr = self.group
1982 cur_gr = self.group
1980 groups.insert(0, cur_gr)
1983 groups.insert(0, cur_gr)
1981 while 1:
1984 while 1:
1982 gr = getattr(cur_gr, 'parent_group', None)
1985 gr = getattr(cur_gr, 'parent_group', None)
1983 cur_gr = cur_gr.parent_group
1986 cur_gr = cur_gr.parent_group
1984 if gr is None:
1987 if gr is None:
1985 break
1988 break
1986 groups.insert(0, gr)
1989 groups.insert(0, gr)
1987
1990
1988 return groups
1991 return groups
1989
1992
1990 @property
1993 @property
1991 def groups_and_repo(self):
1994 def groups_and_repo(self):
1992 return self.groups_with_parents, self
1995 return self.groups_with_parents, self
1993
1996
1994 @LazyProperty
1997 @LazyProperty
1995 def repo_path(self):
1998 def repo_path(self):
1996 """
1999 """
1997 Returns base full path for that repository means where it actually
2000 Returns base full path for that repository means where it actually
1998 exists on a filesystem
2001 exists on a filesystem
1999 """
2002 """
2000 q = Session().query(RhodeCodeUi).filter(
2003 q = Session().query(RhodeCodeUi).filter(
2001 RhodeCodeUi.ui_key == self.NAME_SEP)
2004 RhodeCodeUi.ui_key == self.NAME_SEP)
2002 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2005 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2003 return q.one().ui_value
2006 return q.one().ui_value
2004
2007
2005 @property
2008 @property
2006 def repo_full_path(self):
2009 def repo_full_path(self):
2007 p = [self.repo_path]
2010 p = [self.repo_path]
2008 # we need to split the name by / since this is how we store the
2011 # we need to split the name by / since this is how we store the
2009 # names in the database, but that eventually needs to be converted
2012 # names in the database, but that eventually needs to be converted
2010 # into a valid system path
2013 # into a valid system path
2011 p += self.repo_name.split(self.NAME_SEP)
2014 p += self.repo_name.split(self.NAME_SEP)
2012 return os.path.join(*map(safe_unicode, p))
2015 return os.path.join(*map(safe_unicode, p))
2013
2016
2014 @property
2017 @property
2015 def cache_keys(self):
2018 def cache_keys(self):
2016 """
2019 """
2017 Returns associated cache keys for that repo
2020 Returns associated cache keys for that repo
2018 """
2021 """
2019 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2022 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2020 repo_id=self.repo_id)
2023 repo_id=self.repo_id)
2021 return CacheKey.query()\
2024 return CacheKey.query()\
2022 .filter(CacheKey.cache_args == invalidation_namespace)\
2025 .filter(CacheKey.cache_args == invalidation_namespace)\
2023 .order_by(CacheKey.cache_key)\
2026 .order_by(CacheKey.cache_key)\
2024 .all()
2027 .all()
2025
2028
2026 @property
2029 @property
2027 def cached_diffs_relative_dir(self):
2030 def cached_diffs_relative_dir(self):
2028 """
2031 """
2029 Return a relative to the repository store path of cached diffs
2032 Return a relative to the repository store path of cached diffs
2030 used for safe display for users, who shouldn't know the absolute store
2033 used for safe display for users, who shouldn't know the absolute store
2031 path
2034 path
2032 """
2035 """
2033 return os.path.join(
2036 return os.path.join(
2034 os.path.dirname(self.repo_name),
2037 os.path.dirname(self.repo_name),
2035 self.cached_diffs_dir.split(os.path.sep)[-1])
2038 self.cached_diffs_dir.split(os.path.sep)[-1])
2036
2039
2037 @property
2040 @property
2038 def cached_diffs_dir(self):
2041 def cached_diffs_dir(self):
2039 path = self.repo_full_path
2042 path = self.repo_full_path
2040 return os.path.join(
2043 return os.path.join(
2041 os.path.dirname(path),
2044 os.path.dirname(path),
2042 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2045 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2043
2046
2044 def cached_diffs(self):
2047 def cached_diffs(self):
2045 diff_cache_dir = self.cached_diffs_dir
2048 diff_cache_dir = self.cached_diffs_dir
2046 if os.path.isdir(diff_cache_dir):
2049 if os.path.isdir(diff_cache_dir):
2047 return os.listdir(diff_cache_dir)
2050 return os.listdir(diff_cache_dir)
2048 return []
2051 return []
2049
2052
2050 def shadow_repos(self):
2053 def shadow_repos(self):
2051 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2054 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2052 return [
2055 return [
2053 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2056 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2054 if x.startswith(shadow_repos_pattern)]
2057 if x.startswith(shadow_repos_pattern)]
2055
2058
2056 def get_new_name(self, repo_name):
2059 def get_new_name(self, repo_name):
2057 """
2060 """
2058 returns new full repository name based on assigned group and new new
2061 returns new full repository name based on assigned group and new new
2059
2062
2060 :param group_name:
2063 :param group_name:
2061 """
2064 """
2062 path_prefix = self.group.full_path_splitted if self.group else []
2065 path_prefix = self.group.full_path_splitted if self.group else []
2063 return self.NAME_SEP.join(path_prefix + [repo_name])
2066 return self.NAME_SEP.join(path_prefix + [repo_name])
2064
2067
2065 @property
2068 @property
2066 def _config(self):
2069 def _config(self):
2067 """
2070 """
2068 Returns db based config object.
2071 Returns db based config object.
2069 """
2072 """
2070 from rhodecode.lib.utils import make_db_config
2073 from rhodecode.lib.utils import make_db_config
2071 return make_db_config(clear_session=False, repo=self)
2074 return make_db_config(clear_session=False, repo=self)
2072
2075
2073 def permissions(self, with_admins=True, with_owner=True,
2076 def permissions(self, with_admins=True, with_owner=True,
2074 expand_from_user_groups=False):
2077 expand_from_user_groups=False):
2075 """
2078 """
2076 Permissions for repositories
2079 Permissions for repositories
2077 """
2080 """
2078 _admin_perm = 'repository.admin'
2081 _admin_perm = 'repository.admin'
2079
2082
2080 owner_row = []
2083 owner_row = []
2081 if with_owner:
2084 if with_owner:
2082 usr = AttributeDict(self.user.get_dict())
2085 usr = AttributeDict(self.user.get_dict())
2083 usr.owner_row = True
2086 usr.owner_row = True
2084 usr.permission = _admin_perm
2087 usr.permission = _admin_perm
2085 usr.permission_id = None
2088 usr.permission_id = None
2086 owner_row.append(usr)
2089 owner_row.append(usr)
2087
2090
2088 super_admin_ids = []
2091 super_admin_ids = []
2089 super_admin_rows = []
2092 super_admin_rows = []
2090 if with_admins:
2093 if with_admins:
2091 for usr in User.get_all_super_admins():
2094 for usr in User.get_all_super_admins():
2092 super_admin_ids.append(usr.user_id)
2095 super_admin_ids.append(usr.user_id)
2093 # if this admin is also owner, don't double the record
2096 # if this admin is also owner, don't double the record
2094 if usr.user_id == owner_row[0].user_id:
2097 if usr.user_id == owner_row[0].user_id:
2095 owner_row[0].admin_row = True
2098 owner_row[0].admin_row = True
2096 else:
2099 else:
2097 usr = AttributeDict(usr.get_dict())
2100 usr = AttributeDict(usr.get_dict())
2098 usr.admin_row = True
2101 usr.admin_row = True
2099 usr.permission = _admin_perm
2102 usr.permission = _admin_perm
2100 usr.permission_id = None
2103 usr.permission_id = None
2101 super_admin_rows.append(usr)
2104 super_admin_rows.append(usr)
2102
2105
2103 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2106 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2104 q = q.options(joinedload(UserRepoToPerm.repository),
2107 q = q.options(joinedload(UserRepoToPerm.repository),
2105 joinedload(UserRepoToPerm.user),
2108 joinedload(UserRepoToPerm.user),
2106 joinedload(UserRepoToPerm.permission),)
2109 joinedload(UserRepoToPerm.permission),)
2107
2110
2108 # get owners and admins and permissions. We do a trick of re-writing
2111 # get owners and admins and permissions. We do a trick of re-writing
2109 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2112 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2110 # has a global reference and changing one object propagates to all
2113 # has a global reference and changing one object propagates to all
2111 # others. This means if admin is also an owner admin_row that change
2114 # others. This means if admin is also an owner admin_row that change
2112 # would propagate to both objects
2115 # would propagate to both objects
2113 perm_rows = []
2116 perm_rows = []
2114 for _usr in q.all():
2117 for _usr in q.all():
2115 usr = AttributeDict(_usr.user.get_dict())
2118 usr = AttributeDict(_usr.user.get_dict())
2116 # if this user is also owner/admin, mark as duplicate record
2119 # if this user is also owner/admin, mark as duplicate record
2117 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2120 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2118 usr.duplicate_perm = True
2121 usr.duplicate_perm = True
2119 # also check if this permission is maybe used by branch_permissions
2122 # also check if this permission is maybe used by branch_permissions
2120 if _usr.branch_perm_entry:
2123 if _usr.branch_perm_entry:
2121 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2124 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2122
2125
2123 usr.permission = _usr.permission.permission_name
2126 usr.permission = _usr.permission.permission_name
2124 usr.permission_id = _usr.repo_to_perm_id
2127 usr.permission_id = _usr.repo_to_perm_id
2125 perm_rows.append(usr)
2128 perm_rows.append(usr)
2126
2129
2127 # filter the perm rows by 'default' first and then sort them by
2130 # filter the perm rows by 'default' first and then sort them by
2128 # admin,write,read,none permissions sorted again alphabetically in
2131 # admin,write,read,none permissions sorted again alphabetically in
2129 # each group
2132 # each group
2130 perm_rows = sorted(perm_rows, key=display_user_sort)
2133 perm_rows = sorted(perm_rows, key=display_user_sort)
2131
2134
2132 user_groups_rows = []
2135 user_groups_rows = []
2133 if expand_from_user_groups:
2136 if expand_from_user_groups:
2134 for ug in self.permission_user_groups(with_members=True):
2137 for ug in self.permission_user_groups(with_members=True):
2135 for user_data in ug.members:
2138 for user_data in ug.members:
2136 user_groups_rows.append(user_data)
2139 user_groups_rows.append(user_data)
2137
2140
2138 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2141 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2139
2142
2140 def permission_user_groups(self, with_members=True):
2143 def permission_user_groups(self, with_members=True):
2141 q = UserGroupRepoToPerm.query()\
2144 q = UserGroupRepoToPerm.query()\
2142 .filter(UserGroupRepoToPerm.repository == self)
2145 .filter(UserGroupRepoToPerm.repository == self)
2143 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2146 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2144 joinedload(UserGroupRepoToPerm.users_group),
2147 joinedload(UserGroupRepoToPerm.users_group),
2145 joinedload(UserGroupRepoToPerm.permission),)
2148 joinedload(UserGroupRepoToPerm.permission),)
2146
2149
2147 perm_rows = []
2150 perm_rows = []
2148 for _user_group in q.all():
2151 for _user_group in q.all():
2149 entry = AttributeDict(_user_group.users_group.get_dict())
2152 entry = AttributeDict(_user_group.users_group.get_dict())
2150 entry.permission = _user_group.permission.permission_name
2153 entry.permission = _user_group.permission.permission_name
2151 if with_members:
2154 if with_members:
2152 entry.members = [x.user.get_dict()
2155 entry.members = [x.user.get_dict()
2153 for x in _user_group.users_group.members]
2156 for x in _user_group.users_group.members]
2154 perm_rows.append(entry)
2157 perm_rows.append(entry)
2155
2158
2156 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2159 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2157 return perm_rows
2160 return perm_rows
2158
2161
2159 def get_api_data(self, include_secrets=False):
2162 def get_api_data(self, include_secrets=False):
2160 """
2163 """
2161 Common function for generating repo api data
2164 Common function for generating repo api data
2162
2165
2163 :param include_secrets: See :meth:`User.get_api_data`.
2166 :param include_secrets: See :meth:`User.get_api_data`.
2164
2167
2165 """
2168 """
2166 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2169 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2167 # move this methods on models level.
2170 # move this methods on models level.
2168 from rhodecode.model.settings import SettingsModel
2171 from rhodecode.model.settings import SettingsModel
2169 from rhodecode.model.repo import RepoModel
2172 from rhodecode.model.repo import RepoModel
2170
2173
2171 repo = self
2174 repo = self
2172 _user_id, _time, _reason = self.locked
2175 _user_id, _time, _reason = self.locked
2173
2176
2174 data = {
2177 data = {
2175 'repo_id': repo.repo_id,
2178 'repo_id': repo.repo_id,
2176 'repo_name': repo.repo_name,
2179 'repo_name': repo.repo_name,
2177 'repo_type': repo.repo_type,
2180 'repo_type': repo.repo_type,
2178 'clone_uri': repo.clone_uri or '',
2181 'clone_uri': repo.clone_uri or '',
2179 'push_uri': repo.push_uri or '',
2182 'push_uri': repo.push_uri or '',
2180 'url': RepoModel().get_url(self),
2183 'url': RepoModel().get_url(self),
2181 'private': repo.private,
2184 'private': repo.private,
2182 'created_on': repo.created_on,
2185 'created_on': repo.created_on,
2183 'description': repo.description_safe,
2186 'description': repo.description_safe,
2184 'landing_rev': repo.landing_rev,
2187 'landing_rev': repo.landing_rev,
2185 'owner': repo.user.username,
2188 'owner': repo.user.username,
2186 'fork_of': repo.fork.repo_name if repo.fork else None,
2189 'fork_of': repo.fork.repo_name if repo.fork else None,
2187 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2190 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2188 'enable_statistics': repo.enable_statistics,
2191 'enable_statistics': repo.enable_statistics,
2189 'enable_locking': repo.enable_locking,
2192 'enable_locking': repo.enable_locking,
2190 'enable_downloads': repo.enable_downloads,
2193 'enable_downloads': repo.enable_downloads,
2191 'last_changeset': repo.changeset_cache,
2194 'last_changeset': repo.changeset_cache,
2192 'locked_by': User.get(_user_id).get_api_data(
2195 'locked_by': User.get(_user_id).get_api_data(
2193 include_secrets=include_secrets) if _user_id else None,
2196 include_secrets=include_secrets) if _user_id else None,
2194 'locked_date': time_to_datetime(_time) if _time else None,
2197 'locked_date': time_to_datetime(_time) if _time else None,
2195 'lock_reason': _reason if _reason else None,
2198 'lock_reason': _reason if _reason else None,
2196 }
2199 }
2197
2200
2198 # TODO: mikhail: should be per-repo settings here
2201 # TODO: mikhail: should be per-repo settings here
2199 rc_config = SettingsModel().get_all_settings()
2202 rc_config = SettingsModel().get_all_settings()
2200 repository_fields = str2bool(
2203 repository_fields = str2bool(
2201 rc_config.get('rhodecode_repository_fields'))
2204 rc_config.get('rhodecode_repository_fields'))
2202 if repository_fields:
2205 if repository_fields:
2203 for f in self.extra_fields:
2206 for f in self.extra_fields:
2204 data[f.field_key_prefixed] = f.field_value
2207 data[f.field_key_prefixed] = f.field_value
2205
2208
2206 return data
2209 return data
2207
2210
2208 @classmethod
2211 @classmethod
2209 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2212 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2210 if not lock_time:
2213 if not lock_time:
2211 lock_time = time.time()
2214 lock_time = time.time()
2212 if not lock_reason:
2215 if not lock_reason:
2213 lock_reason = cls.LOCK_AUTOMATIC
2216 lock_reason = cls.LOCK_AUTOMATIC
2214 repo.locked = [user_id, lock_time, lock_reason]
2217 repo.locked = [user_id, lock_time, lock_reason]
2215 Session().add(repo)
2218 Session().add(repo)
2216 Session().commit()
2219 Session().commit()
2217
2220
2218 @classmethod
2221 @classmethod
2219 def unlock(cls, repo):
2222 def unlock(cls, repo):
2220 repo.locked = None
2223 repo.locked = None
2221 Session().add(repo)
2224 Session().add(repo)
2222 Session().commit()
2225 Session().commit()
2223
2226
2224 @classmethod
2227 @classmethod
2225 def getlock(cls, repo):
2228 def getlock(cls, repo):
2226 return repo.locked
2229 return repo.locked
2227
2230
2228 def is_user_lock(self, user_id):
2231 def is_user_lock(self, user_id):
2229 if self.lock[0]:
2232 if self.lock[0]:
2230 lock_user_id = safe_int(self.lock[0])
2233 lock_user_id = safe_int(self.lock[0])
2231 user_id = safe_int(user_id)
2234 user_id = safe_int(user_id)
2232 # both are ints, and they are equal
2235 # both are ints, and they are equal
2233 return all([lock_user_id, user_id]) and lock_user_id == user_id
2236 return all([lock_user_id, user_id]) and lock_user_id == user_id
2234
2237
2235 return False
2238 return False
2236
2239
2237 def get_locking_state(self, action, user_id, only_when_enabled=True):
2240 def get_locking_state(self, action, user_id, only_when_enabled=True):
2238 """
2241 """
2239 Checks locking on this repository, if locking is enabled and lock is
2242 Checks locking on this repository, if locking is enabled and lock is
2240 present returns a tuple of make_lock, locked, locked_by.
2243 present returns a tuple of make_lock, locked, locked_by.
2241 make_lock can have 3 states None (do nothing) True, make lock
2244 make_lock can have 3 states None (do nothing) True, make lock
2242 False release lock, This value is later propagated to hooks, which
2245 False release lock, This value is later propagated to hooks, which
2243 do the locking. Think about this as signals passed to hooks what to do.
2246 do the locking. Think about this as signals passed to hooks what to do.
2244
2247
2245 """
2248 """
2246 # TODO: johbo: This is part of the business logic and should be moved
2249 # TODO: johbo: This is part of the business logic and should be moved
2247 # into the RepositoryModel.
2250 # into the RepositoryModel.
2248
2251
2249 if action not in ('push', 'pull'):
2252 if action not in ('push', 'pull'):
2250 raise ValueError("Invalid action value: %s" % repr(action))
2253 raise ValueError("Invalid action value: %s" % repr(action))
2251
2254
2252 # defines if locked error should be thrown to user
2255 # defines if locked error should be thrown to user
2253 currently_locked = False
2256 currently_locked = False
2254 # defines if new lock should be made, tri-state
2257 # defines if new lock should be made, tri-state
2255 make_lock = None
2258 make_lock = None
2256 repo = self
2259 repo = self
2257 user = User.get(user_id)
2260 user = User.get(user_id)
2258
2261
2259 lock_info = repo.locked
2262 lock_info = repo.locked
2260
2263
2261 if repo and (repo.enable_locking or not only_when_enabled):
2264 if repo and (repo.enable_locking or not only_when_enabled):
2262 if action == 'push':
2265 if action == 'push':
2263 # check if it's already locked !, if it is compare users
2266 # check if it's already locked !, if it is compare users
2264 locked_by_user_id = lock_info[0]
2267 locked_by_user_id = lock_info[0]
2265 if user.user_id == locked_by_user_id:
2268 if user.user_id == locked_by_user_id:
2266 log.debug(
2269 log.debug(
2267 'Got `push` action from user %s, now unlocking', user)
2270 'Got `push` action from user %s, now unlocking', user)
2268 # unlock if we have push from user who locked
2271 # unlock if we have push from user who locked
2269 make_lock = False
2272 make_lock = False
2270 else:
2273 else:
2271 # we're not the same user who locked, ban with
2274 # we're not the same user who locked, ban with
2272 # code defined in settings (default is 423 HTTP Locked) !
2275 # code defined in settings (default is 423 HTTP Locked) !
2273 log.debug('Repo %s is currently locked by %s', repo, user)
2276 log.debug('Repo %s is currently locked by %s', repo, user)
2274 currently_locked = True
2277 currently_locked = True
2275 elif action == 'pull':
2278 elif action == 'pull':
2276 # [0] user [1] date
2279 # [0] user [1] date
2277 if lock_info[0] and lock_info[1]:
2280 if lock_info[0] and lock_info[1]:
2278 log.debug('Repo %s is currently locked by %s', repo, user)
2281 log.debug('Repo %s is currently locked by %s', repo, user)
2279 currently_locked = True
2282 currently_locked = True
2280 else:
2283 else:
2281 log.debug('Setting lock on repo %s by %s', repo, user)
2284 log.debug('Setting lock on repo %s by %s', repo, user)
2282 make_lock = True
2285 make_lock = True
2283
2286
2284 else:
2287 else:
2285 log.debug('Repository %s do not have locking enabled', repo)
2288 log.debug('Repository %s do not have locking enabled', repo)
2286
2289
2287 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2290 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2288 make_lock, currently_locked, lock_info)
2291 make_lock, currently_locked, lock_info)
2289
2292
2290 from rhodecode.lib.auth import HasRepoPermissionAny
2293 from rhodecode.lib.auth import HasRepoPermissionAny
2291 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2294 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2292 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2295 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2293 # if we don't have at least write permission we cannot make a lock
2296 # if we don't have at least write permission we cannot make a lock
2294 log.debug('lock state reset back to FALSE due to lack '
2297 log.debug('lock state reset back to FALSE due to lack '
2295 'of at least read permission')
2298 'of at least read permission')
2296 make_lock = False
2299 make_lock = False
2297
2300
2298 return make_lock, currently_locked, lock_info
2301 return make_lock, currently_locked, lock_info
2299
2302
2300 @property
2303 @property
2301 def last_commit_cache_update_diff(self):
2304 def last_commit_cache_update_diff(self):
2302 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2305 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2303
2306
2304 @classmethod
2307 @classmethod
2305 def _load_commit_change(cls, last_commit_cache):
2308 def _load_commit_change(cls, last_commit_cache):
2306 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2309 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2307 empty_date = datetime.datetime.fromtimestamp(0)
2310 empty_date = datetime.datetime.fromtimestamp(0)
2308 date_latest = last_commit_cache.get('date', empty_date)
2311 date_latest = last_commit_cache.get('date', empty_date)
2309 try:
2312 try:
2310 return parse_datetime(date_latest)
2313 return parse_datetime(date_latest)
2311 except Exception:
2314 except Exception:
2312 return empty_date
2315 return empty_date
2313
2316
2314 @property
2317 @property
2315 def last_commit_change(self):
2318 def last_commit_change(self):
2316 return self._load_commit_change(self.changeset_cache)
2319 return self._load_commit_change(self.changeset_cache)
2317
2320
2318 @property
2321 @property
2319 def last_db_change(self):
2322 def last_db_change(self):
2320 return self.updated_on
2323 return self.updated_on
2321
2324
2322 @property
2325 @property
2323 def clone_uri_hidden(self):
2326 def clone_uri_hidden(self):
2324 clone_uri = self.clone_uri
2327 clone_uri = self.clone_uri
2325 if clone_uri:
2328 if clone_uri:
2326 import urlobject
2329 import urlobject
2327 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2330 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2328 if url_obj.password:
2331 if url_obj.password:
2329 clone_uri = url_obj.with_password('*****')
2332 clone_uri = url_obj.with_password('*****')
2330 return clone_uri
2333 return clone_uri
2331
2334
2332 @property
2335 @property
2333 def push_uri_hidden(self):
2336 def push_uri_hidden(self):
2334 push_uri = self.push_uri
2337 push_uri = self.push_uri
2335 if push_uri:
2338 if push_uri:
2336 import urlobject
2339 import urlobject
2337 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2340 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2338 if url_obj.password:
2341 if url_obj.password:
2339 push_uri = url_obj.with_password('*****')
2342 push_uri = url_obj.with_password('*****')
2340 return push_uri
2343 return push_uri
2341
2344
2342 def clone_url(self, **override):
2345 def clone_url(self, **override):
2343 from rhodecode.model.settings import SettingsModel
2346 from rhodecode.model.settings import SettingsModel
2344
2347
2345 uri_tmpl = None
2348 uri_tmpl = None
2346 if 'with_id' in override:
2349 if 'with_id' in override:
2347 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2350 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2348 del override['with_id']
2351 del override['with_id']
2349
2352
2350 if 'uri_tmpl' in override:
2353 if 'uri_tmpl' in override:
2351 uri_tmpl = override['uri_tmpl']
2354 uri_tmpl = override['uri_tmpl']
2352 del override['uri_tmpl']
2355 del override['uri_tmpl']
2353
2356
2354 ssh = False
2357 ssh = False
2355 if 'ssh' in override:
2358 if 'ssh' in override:
2356 ssh = True
2359 ssh = True
2357 del override['ssh']
2360 del override['ssh']
2358
2361
2359 # we didn't override our tmpl from **overrides
2362 # we didn't override our tmpl from **overrides
2360 request = get_current_request()
2363 request = get_current_request()
2361 if not uri_tmpl:
2364 if not uri_tmpl:
2362 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2365 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2363 rc_config = request.call_context.rc_config
2366 rc_config = request.call_context.rc_config
2364 else:
2367 else:
2365 rc_config = SettingsModel().get_all_settings(cache=True)
2368 rc_config = SettingsModel().get_all_settings(cache=True)
2366
2369
2367 if ssh:
2370 if ssh:
2368 uri_tmpl = rc_config.get(
2371 uri_tmpl = rc_config.get(
2369 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2372 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2370
2373
2371 else:
2374 else:
2372 uri_tmpl = rc_config.get(
2375 uri_tmpl = rc_config.get(
2373 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2376 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2374
2377
2375 return get_clone_url(request=request,
2378 return get_clone_url(request=request,
2376 uri_tmpl=uri_tmpl,
2379 uri_tmpl=uri_tmpl,
2377 repo_name=self.repo_name,
2380 repo_name=self.repo_name,
2378 repo_id=self.repo_id,
2381 repo_id=self.repo_id,
2379 repo_type=self.repo_type,
2382 repo_type=self.repo_type,
2380 **override)
2383 **override)
2381
2384
2382 def set_state(self, state):
2385 def set_state(self, state):
2383 self.repo_state = state
2386 self.repo_state = state
2384 Session().add(self)
2387 Session().add(self)
2385 #==========================================================================
2388 #==========================================================================
2386 # SCM PROPERTIES
2389 # SCM PROPERTIES
2387 #==========================================================================
2390 #==========================================================================
2388
2391
2389 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2392 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2390 return get_commit_safe(
2393 return get_commit_safe(
2391 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2394 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2392 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2395 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2393
2396
2394 def get_changeset(self, rev=None, pre_load=None):
2397 def get_changeset(self, rev=None, pre_load=None):
2395 warnings.warn("Use get_commit", DeprecationWarning)
2398 warnings.warn("Use get_commit", DeprecationWarning)
2396 commit_id = None
2399 commit_id = None
2397 commit_idx = None
2400 commit_idx = None
2398 if isinstance(rev, str):
2401 if isinstance(rev, str):
2399 commit_id = rev
2402 commit_id = rev
2400 else:
2403 else:
2401 commit_idx = rev
2404 commit_idx = rev
2402 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2405 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2403 pre_load=pre_load)
2406 pre_load=pre_load)
2404
2407
2405 def get_landing_commit(self):
2408 def get_landing_commit(self):
2406 """
2409 """
2407 Returns landing commit, or if that doesn't exist returns the tip
2410 Returns landing commit, or if that doesn't exist returns the tip
2408 """
2411 """
2409 _rev_type, _rev = self.landing_rev
2412 _rev_type, _rev = self.landing_rev
2410 commit = self.get_commit(_rev)
2413 commit = self.get_commit(_rev)
2411 if isinstance(commit, EmptyCommit):
2414 if isinstance(commit, EmptyCommit):
2412 return self.get_commit()
2415 return self.get_commit()
2413 return commit
2416 return commit
2414
2417
2415 def flush_commit_cache(self):
2418 def flush_commit_cache(self):
2416 self.update_commit_cache(cs_cache={'raw_id':'0'})
2419 self.update_commit_cache(cs_cache={'raw_id':'0'})
2417 self.update_commit_cache()
2420 self.update_commit_cache()
2418
2421
2419 def update_commit_cache(self, cs_cache=None, config=None):
2422 def update_commit_cache(self, cs_cache=None, config=None):
2420 """
2423 """
2421 Update cache of last commit for repository
2424 Update cache of last commit for repository
2422 cache_keys should be::
2425 cache_keys should be::
2423
2426
2424 source_repo_id
2427 source_repo_id
2425 short_id
2428 short_id
2426 raw_id
2429 raw_id
2427 revision
2430 revision
2428 parents
2431 parents
2429 message
2432 message
2430 date
2433 date
2431 author
2434 author
2432 updated_on
2435 updated_on
2433
2436
2434 """
2437 """
2435 from rhodecode.lib.vcs.backends.base import BaseChangeset
2438 from rhodecode.lib.vcs.backends.base import BaseChangeset
2436 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2439 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2437 empty_date = datetime.datetime.fromtimestamp(0)
2440 empty_date = datetime.datetime.fromtimestamp(0)
2438
2441
2439 if cs_cache is None:
2442 if cs_cache is None:
2440 # use no-cache version here
2443 # use no-cache version here
2441 try:
2444 try:
2442 scm_repo = self.scm_instance(cache=False, config=config)
2445 scm_repo = self.scm_instance(cache=False, config=config)
2443 except VCSError:
2446 except VCSError:
2444 scm_repo = None
2447 scm_repo = None
2445 empty = scm_repo is None or scm_repo.is_empty()
2448 empty = scm_repo is None or scm_repo.is_empty()
2446
2449
2447 if not empty:
2450 if not empty:
2448 cs_cache = scm_repo.get_commit(
2451 cs_cache = scm_repo.get_commit(
2449 pre_load=["author", "date", "message", "parents", "branch"])
2452 pre_load=["author", "date", "message", "parents", "branch"])
2450 else:
2453 else:
2451 cs_cache = EmptyCommit()
2454 cs_cache = EmptyCommit()
2452
2455
2453 if isinstance(cs_cache, BaseChangeset):
2456 if isinstance(cs_cache, BaseChangeset):
2454 cs_cache = cs_cache.__json__()
2457 cs_cache = cs_cache.__json__()
2455
2458
2456 def is_outdated(new_cs_cache):
2459 def is_outdated(new_cs_cache):
2457 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2460 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2458 new_cs_cache['revision'] != self.changeset_cache['revision']):
2461 new_cs_cache['revision'] != self.changeset_cache['revision']):
2459 return True
2462 return True
2460 return False
2463 return False
2461
2464
2462 # check if we have maybe already latest cached revision
2465 # check if we have maybe already latest cached revision
2463 if is_outdated(cs_cache) or not self.changeset_cache:
2466 if is_outdated(cs_cache) or not self.changeset_cache:
2464 _current_datetime = datetime.datetime.utcnow()
2467 _current_datetime = datetime.datetime.utcnow()
2465 last_change = cs_cache.get('date') or _current_datetime
2468 last_change = cs_cache.get('date') or _current_datetime
2466 # we check if last update is newer than the new value
2469 # we check if last update is newer than the new value
2467 # if yes, we use the current timestamp instead. Imagine you get
2470 # if yes, we use the current timestamp instead. Imagine you get
2468 # old commit pushed 1y ago, we'd set last update 1y to ago.
2471 # old commit pushed 1y ago, we'd set last update 1y to ago.
2469 last_change_timestamp = datetime_to_time(last_change)
2472 last_change_timestamp = datetime_to_time(last_change)
2470 current_timestamp = datetime_to_time(last_change)
2473 current_timestamp = datetime_to_time(last_change)
2471 if last_change_timestamp > current_timestamp and not empty:
2474 if last_change_timestamp > current_timestamp and not empty:
2472 cs_cache['date'] = _current_datetime
2475 cs_cache['date'] = _current_datetime
2473
2476
2474 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2477 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2475 cs_cache['updated_on'] = time.time()
2478 cs_cache['updated_on'] = time.time()
2476 self.changeset_cache = cs_cache
2479 self.changeset_cache = cs_cache
2477 self.updated_on = last_change
2480 self.updated_on = last_change
2478 Session().add(self)
2481 Session().add(self)
2479 Session().commit()
2482 Session().commit()
2480
2483
2481 else:
2484 else:
2482 if empty:
2485 if empty:
2483 cs_cache = EmptyCommit().__json__()
2486 cs_cache = EmptyCommit().__json__()
2484 else:
2487 else:
2485 cs_cache = self.changeset_cache
2488 cs_cache = self.changeset_cache
2486
2489
2487 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2490 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2488
2491
2489 cs_cache['updated_on'] = time.time()
2492 cs_cache['updated_on'] = time.time()
2490 self.changeset_cache = cs_cache
2493 self.changeset_cache = cs_cache
2491 self.updated_on = _date_latest
2494 self.updated_on = _date_latest
2492 Session().add(self)
2495 Session().add(self)
2493 Session().commit()
2496 Session().commit()
2494
2497
2495 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2498 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2496 self.repo_name, cs_cache, _date_latest)
2499 self.repo_name, cs_cache, _date_latest)
2497
2500
2498 @property
2501 @property
2499 def tip(self):
2502 def tip(self):
2500 return self.get_commit('tip')
2503 return self.get_commit('tip')
2501
2504
2502 @property
2505 @property
2503 def author(self):
2506 def author(self):
2504 return self.tip.author
2507 return self.tip.author
2505
2508
2506 @property
2509 @property
2507 def last_change(self):
2510 def last_change(self):
2508 return self.scm_instance().last_change
2511 return self.scm_instance().last_change
2509
2512
2510 def get_comments(self, revisions=None):
2513 def get_comments(self, revisions=None):
2511 """
2514 """
2512 Returns comments for this repository grouped by revisions
2515 Returns comments for this repository grouped by revisions
2513
2516
2514 :param revisions: filter query by revisions only
2517 :param revisions: filter query by revisions only
2515 """
2518 """
2516 cmts = ChangesetComment.query()\
2519 cmts = ChangesetComment.query()\
2517 .filter(ChangesetComment.repo == self)
2520 .filter(ChangesetComment.repo == self)
2518 if revisions:
2521 if revisions:
2519 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2522 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2520 grouped = collections.defaultdict(list)
2523 grouped = collections.defaultdict(list)
2521 for cmt in cmts.all():
2524 for cmt in cmts.all():
2522 grouped[cmt.revision].append(cmt)
2525 grouped[cmt.revision].append(cmt)
2523 return grouped
2526 return grouped
2524
2527
2525 def statuses(self, revisions=None):
2528 def statuses(self, revisions=None):
2526 """
2529 """
2527 Returns statuses for this repository
2530 Returns statuses for this repository
2528
2531
2529 :param revisions: list of revisions to get statuses for
2532 :param revisions: list of revisions to get statuses for
2530 """
2533 """
2531 statuses = ChangesetStatus.query()\
2534 statuses = ChangesetStatus.query()\
2532 .filter(ChangesetStatus.repo == self)\
2535 .filter(ChangesetStatus.repo == self)\
2533 .filter(ChangesetStatus.version == 0)
2536 .filter(ChangesetStatus.version == 0)
2534
2537
2535 if revisions:
2538 if revisions:
2536 # Try doing the filtering in chunks to avoid hitting limits
2539 # Try doing the filtering in chunks to avoid hitting limits
2537 size = 500
2540 size = 500
2538 status_results = []
2541 status_results = []
2539 for chunk in range(0, len(revisions), size):
2542 for chunk in range(0, len(revisions), size):
2540 status_results += statuses.filter(
2543 status_results += statuses.filter(
2541 ChangesetStatus.revision.in_(
2544 ChangesetStatus.revision.in_(
2542 revisions[chunk: chunk+size])
2545 revisions[chunk: chunk+size])
2543 ).all()
2546 ).all()
2544 else:
2547 else:
2545 status_results = statuses.all()
2548 status_results = statuses.all()
2546
2549
2547 grouped = {}
2550 grouped = {}
2548
2551
2549 # maybe we have open new pullrequest without a status?
2552 # maybe we have open new pullrequest without a status?
2550 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2553 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2551 status_lbl = ChangesetStatus.get_status_lbl(stat)
2554 status_lbl = ChangesetStatus.get_status_lbl(stat)
2552 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2555 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2553 for rev in pr.revisions:
2556 for rev in pr.revisions:
2554 pr_id = pr.pull_request_id
2557 pr_id = pr.pull_request_id
2555 pr_repo = pr.target_repo.repo_name
2558 pr_repo = pr.target_repo.repo_name
2556 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2559 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2557
2560
2558 for stat in status_results:
2561 for stat in status_results:
2559 pr_id = pr_repo = None
2562 pr_id = pr_repo = None
2560 if stat.pull_request:
2563 if stat.pull_request:
2561 pr_id = stat.pull_request.pull_request_id
2564 pr_id = stat.pull_request.pull_request_id
2562 pr_repo = stat.pull_request.target_repo.repo_name
2565 pr_repo = stat.pull_request.target_repo.repo_name
2563 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2566 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2564 pr_id, pr_repo]
2567 pr_id, pr_repo]
2565 return grouped
2568 return grouped
2566
2569
2567 # ==========================================================================
2570 # ==========================================================================
2568 # SCM CACHE INSTANCE
2571 # SCM CACHE INSTANCE
2569 # ==========================================================================
2572 # ==========================================================================
2570
2573
2571 def scm_instance(self, **kwargs):
2574 def scm_instance(self, **kwargs):
2572 import rhodecode
2575 import rhodecode
2573
2576
2574 # Passing a config will not hit the cache currently only used
2577 # Passing a config will not hit the cache currently only used
2575 # for repo2dbmapper
2578 # for repo2dbmapper
2576 config = kwargs.pop('config', None)
2579 config = kwargs.pop('config', None)
2577 cache = kwargs.pop('cache', None)
2580 cache = kwargs.pop('cache', None)
2578 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2581 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2579 if vcs_full_cache is not None:
2582 if vcs_full_cache is not None:
2580 # allows override global config
2583 # allows override global config
2581 full_cache = vcs_full_cache
2584 full_cache = vcs_full_cache
2582 else:
2585 else:
2583 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2586 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2584 # if cache is NOT defined use default global, else we have a full
2587 # if cache is NOT defined use default global, else we have a full
2585 # control over cache behaviour
2588 # control over cache behaviour
2586 if cache is None and full_cache and not config:
2589 if cache is None and full_cache and not config:
2587 log.debug('Initializing pure cached instance for %s', self.repo_path)
2590 log.debug('Initializing pure cached instance for %s', self.repo_path)
2588 return self._get_instance_cached()
2591 return self._get_instance_cached()
2589
2592
2590 # cache here is sent to the "vcs server"
2593 # cache here is sent to the "vcs server"
2591 return self._get_instance(cache=bool(cache), config=config)
2594 return self._get_instance(cache=bool(cache), config=config)
2592
2595
2593 def _get_instance_cached(self):
2596 def _get_instance_cached(self):
2594 from rhodecode.lib import rc_cache
2597 from rhodecode.lib import rc_cache
2595
2598
2596 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2599 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2597 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2600 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2598 repo_id=self.repo_id)
2601 repo_id=self.repo_id)
2599 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2602 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2600
2603
2601 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2604 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2602 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2605 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2603 return self._get_instance(repo_state_uid=_cache_state_uid)
2606 return self._get_instance(repo_state_uid=_cache_state_uid)
2604
2607
2605 # we must use thread scoped cache here,
2608 # we must use thread scoped cache here,
2606 # because each thread of gevent needs it's own not shared connection and cache
2609 # because each thread of gevent needs it's own not shared connection and cache
2607 # we also alter `args` so the cache key is individual for every green thread.
2610 # we also alter `args` so the cache key is individual for every green thread.
2608 inv_context_manager = rc_cache.InvalidationContext(
2611 inv_context_manager = rc_cache.InvalidationContext(
2609 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2612 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2610 thread_scoped=True)
2613 thread_scoped=True)
2611 with inv_context_manager as invalidation_context:
2614 with inv_context_manager as invalidation_context:
2612 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2615 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2613 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2616 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2614
2617
2615 # re-compute and store cache if we get invalidate signal
2618 # re-compute and store cache if we get invalidate signal
2616 if invalidation_context.should_invalidate():
2619 if invalidation_context.should_invalidate():
2617 instance = get_instance_cached.refresh(*args)
2620 instance = get_instance_cached.refresh(*args)
2618 else:
2621 else:
2619 instance = get_instance_cached(*args)
2622 instance = get_instance_cached(*args)
2620
2623
2621 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2624 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2622 return instance
2625 return instance
2623
2626
2624 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2627 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2625 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2628 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2626 self.repo_type, self.repo_path, cache)
2629 self.repo_type, self.repo_path, cache)
2627 config = config or self._config
2630 config = config or self._config
2628 custom_wire = {
2631 custom_wire = {
2629 'cache': cache, # controls the vcs.remote cache
2632 'cache': cache, # controls the vcs.remote cache
2630 'repo_state_uid': repo_state_uid
2633 'repo_state_uid': repo_state_uid
2631 }
2634 }
2632 repo = get_vcs_instance(
2635 repo = get_vcs_instance(
2633 repo_path=safe_str(self.repo_full_path),
2636 repo_path=safe_str(self.repo_full_path),
2634 config=config,
2637 config=config,
2635 with_wire=custom_wire,
2638 with_wire=custom_wire,
2636 create=False,
2639 create=False,
2637 _vcs_alias=self.repo_type)
2640 _vcs_alias=self.repo_type)
2638 if repo is not None:
2641 if repo is not None:
2639 repo.count() # cache rebuild
2642 repo.count() # cache rebuild
2640 return repo
2643 return repo
2641
2644
2642 def get_shadow_repository_path(self, workspace_id):
2645 def get_shadow_repository_path(self, workspace_id):
2643 from rhodecode.lib.vcs.backends.base import BaseRepository
2646 from rhodecode.lib.vcs.backends.base import BaseRepository
2644 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2647 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2645 self.repo_full_path, self.repo_id, workspace_id)
2648 self.repo_full_path, self.repo_id, workspace_id)
2646 return shadow_repo_path
2649 return shadow_repo_path
2647
2650
2648 def __json__(self):
2651 def __json__(self):
2649 return {'landing_rev': self.landing_rev}
2652 return {'landing_rev': self.landing_rev}
2650
2653
2651 def get_dict(self):
2654 def get_dict(self):
2652
2655
2653 # Since we transformed `repo_name` to a hybrid property, we need to
2656 # Since we transformed `repo_name` to a hybrid property, we need to
2654 # keep compatibility with the code which uses `repo_name` field.
2657 # keep compatibility with the code which uses `repo_name` field.
2655
2658
2656 result = super(Repository, self).get_dict()
2659 result = super(Repository, self).get_dict()
2657 result['repo_name'] = result.pop('_repo_name', None)
2660 result['repo_name'] = result.pop('_repo_name', None)
2658 return result
2661 return result
2659
2662
2660
2663
2661 class RepoGroup(Base, BaseModel):
2664 class RepoGroup(Base, BaseModel):
2662 __tablename__ = 'groups'
2665 __tablename__ = 'groups'
2663 __table_args__ = (
2666 __table_args__ = (
2664 UniqueConstraint('group_name', 'group_parent_id'),
2667 UniqueConstraint('group_name', 'group_parent_id'),
2665 base_table_args,
2668 base_table_args,
2666 )
2669 )
2667 __mapper_args__ = {
2670 __mapper_args__ = {
2668 #TODO: this is now depracated ?!
2671 #TODO: this is now depracated ?!
2669 # 'order_by': 'group_name'
2672 # 'order_by': 'group_name'
2670 }
2673 }
2671
2674
2672 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2675 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2673
2676
2674 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2677 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2675 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2678 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2676 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2679 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2677 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2680 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2678 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2681 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2679 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2682 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2683 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2681 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2684 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2682 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2685 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2683 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2686 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2684 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2687 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2685
2688
2686 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2689 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2687 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2690 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2688 parent_group = relationship('RepoGroup', remote_side=group_id)
2691 parent_group = relationship('RepoGroup', remote_side=group_id)
2689 user = relationship('User')
2692 user = relationship('User')
2690 integrations = relationship('Integration', cascade="all, delete-orphan")
2693 integrations = relationship('Integration', cascade="all, delete-orphan")
2691
2694
2692 # no cascade, set NULL
2695 # no cascade, set NULL
2693 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2696 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2694
2697
2695 def __init__(self, group_name='', parent_group=None):
2698 def __init__(self, group_name='', parent_group=None):
2696 self.group_name = group_name
2699 self.group_name = group_name
2697 self.parent_group = parent_group
2700 self.parent_group = parent_group
2698
2701
2699 def __unicode__(self):
2702 def __unicode__(self):
2700 return u"<%s('id:%s:%s')>" % (
2703 return u"<%s('id:%s:%s')>" % (
2701 self.__class__.__name__, self.group_id, self.group_name)
2704 self.__class__.__name__, self.group_id, self.group_name)
2702
2705
2703 @hybrid_property
2706 @hybrid_property
2704 def group_name(self):
2707 def group_name(self):
2705 return self._group_name
2708 return self._group_name
2706
2709
2707 @group_name.setter
2710 @group_name.setter
2708 def group_name(self, value):
2711 def group_name(self, value):
2709 self._group_name = value
2712 self._group_name = value
2710 self.group_name_hash = self.hash_repo_group_name(value)
2713 self.group_name_hash = self.hash_repo_group_name(value)
2711
2714
2712 @classmethod
2715 @classmethod
2713 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2716 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2714 from rhodecode.lib.vcs.backends.base import EmptyCommit
2717 from rhodecode.lib.vcs.backends.base import EmptyCommit
2715 dummy = EmptyCommit().__json__()
2718 dummy = EmptyCommit().__json__()
2716 if not changeset_cache_raw:
2719 if not changeset_cache_raw:
2717 dummy['source_repo_id'] = repo_id
2720 dummy['source_repo_id'] = repo_id
2718 return json.loads(json.dumps(dummy))
2721 return json.loads(json.dumps(dummy))
2719
2722
2720 try:
2723 try:
2721 return json.loads(changeset_cache_raw)
2724 return json.loads(changeset_cache_raw)
2722 except TypeError:
2725 except TypeError:
2723 return dummy
2726 return dummy
2724 except Exception:
2727 except Exception:
2725 log.error(traceback.format_exc())
2728 log.error(traceback.format_exc())
2726 return dummy
2729 return dummy
2727
2730
2728 @hybrid_property
2731 @hybrid_property
2729 def changeset_cache(self):
2732 def changeset_cache(self):
2730 return self._load_changeset_cache('', self._changeset_cache)
2733 return self._load_changeset_cache('', self._changeset_cache)
2731
2734
2732 @changeset_cache.setter
2735 @changeset_cache.setter
2733 def changeset_cache(self, val):
2736 def changeset_cache(self, val):
2734 try:
2737 try:
2735 self._changeset_cache = json.dumps(val)
2738 self._changeset_cache = json.dumps(val)
2736 except Exception:
2739 except Exception:
2737 log.error(traceback.format_exc())
2740 log.error(traceback.format_exc())
2738
2741
2739 @validates('group_parent_id')
2742 @validates('group_parent_id')
2740 def validate_group_parent_id(self, key, val):
2743 def validate_group_parent_id(self, key, val):
2741 """
2744 """
2742 Check cycle references for a parent group to self
2745 Check cycle references for a parent group to self
2743 """
2746 """
2744 if self.group_id and val:
2747 if self.group_id and val:
2745 assert val != self.group_id
2748 assert val != self.group_id
2746
2749
2747 return val
2750 return val
2748
2751
2749 @hybrid_property
2752 @hybrid_property
2750 def description_safe(self):
2753 def description_safe(self):
2751 from rhodecode.lib import helpers as h
2754 from rhodecode.lib import helpers as h
2752 return h.escape(self.group_description)
2755 return h.escape(self.group_description)
2753
2756
2754 @classmethod
2757 @classmethod
2755 def hash_repo_group_name(cls, repo_group_name):
2758 def hash_repo_group_name(cls, repo_group_name):
2756 val = remove_formatting(repo_group_name)
2759 val = remove_formatting(repo_group_name)
2757 val = safe_str(val).lower()
2760 val = safe_str(val).lower()
2758 chars = []
2761 chars = []
2759 for c in val:
2762 for c in val:
2760 if c not in string.ascii_letters:
2763 if c not in string.ascii_letters:
2761 c = str(ord(c))
2764 c = str(ord(c))
2762 chars.append(c)
2765 chars.append(c)
2763
2766
2764 return ''.join(chars)
2767 return ''.join(chars)
2765
2768
2766 @classmethod
2769 @classmethod
2767 def _generate_choice(cls, repo_group):
2770 def _generate_choice(cls, repo_group):
2768 from webhelpers2.html import literal as _literal
2771 from webhelpers2.html import literal as _literal
2769 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2772 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2770 return repo_group.group_id, _name(repo_group.full_path_splitted)
2773 return repo_group.group_id, _name(repo_group.full_path_splitted)
2771
2774
2772 @classmethod
2775 @classmethod
2773 def groups_choices(cls, groups=None, show_empty_group=True):
2776 def groups_choices(cls, groups=None, show_empty_group=True):
2774 if not groups:
2777 if not groups:
2775 groups = cls.query().all()
2778 groups = cls.query().all()
2776
2779
2777 repo_groups = []
2780 repo_groups = []
2778 if show_empty_group:
2781 if show_empty_group:
2779 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2782 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2780
2783
2781 repo_groups.extend([cls._generate_choice(x) for x in groups])
2784 repo_groups.extend([cls._generate_choice(x) for x in groups])
2782
2785
2783 repo_groups = sorted(
2786 repo_groups = sorted(
2784 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2787 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2785 return repo_groups
2788 return repo_groups
2786
2789
2787 @classmethod
2790 @classmethod
2788 def url_sep(cls):
2791 def url_sep(cls):
2789 return URL_SEP
2792 return URL_SEP
2790
2793
2791 @classmethod
2794 @classmethod
2792 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2795 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2793 if case_insensitive:
2796 if case_insensitive:
2794 gr = cls.query().filter(func.lower(cls.group_name)
2797 gr = cls.query().filter(func.lower(cls.group_name)
2795 == func.lower(group_name))
2798 == func.lower(group_name))
2796 else:
2799 else:
2797 gr = cls.query().filter(cls.group_name == group_name)
2800 gr = cls.query().filter(cls.group_name == group_name)
2798 if cache:
2801 if cache:
2799 name_key = _hash_key(group_name)
2802 name_key = _hash_key(group_name)
2800 gr = gr.options(
2803 gr = gr.options(
2801 FromCache("sql_cache_short", "get_group_%s" % name_key))
2804 FromCache("sql_cache_short", "get_group_%s" % name_key))
2802 return gr.scalar()
2805 return gr.scalar()
2803
2806
2804 @classmethod
2807 @classmethod
2805 def get_user_personal_repo_group(cls, user_id):
2808 def get_user_personal_repo_group(cls, user_id):
2806 user = User.get(user_id)
2809 user = User.get(user_id)
2807 if user.username == User.DEFAULT_USER:
2810 if user.username == User.DEFAULT_USER:
2808 return None
2811 return None
2809
2812
2810 return cls.query()\
2813 return cls.query()\
2811 .filter(cls.personal == true()) \
2814 .filter(cls.personal == true()) \
2812 .filter(cls.user == user) \
2815 .filter(cls.user == user) \
2813 .order_by(cls.group_id.asc()) \
2816 .order_by(cls.group_id.asc()) \
2814 .first()
2817 .first()
2815
2818
2816 @classmethod
2819 @classmethod
2817 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2820 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2818 case_insensitive=True):
2821 case_insensitive=True):
2819 q = RepoGroup.query()
2822 q = RepoGroup.query()
2820
2823
2821 if not isinstance(user_id, Optional):
2824 if not isinstance(user_id, Optional):
2822 q = q.filter(RepoGroup.user_id == user_id)
2825 q = q.filter(RepoGroup.user_id == user_id)
2823
2826
2824 if not isinstance(group_id, Optional):
2827 if not isinstance(group_id, Optional):
2825 q = q.filter(RepoGroup.group_parent_id == group_id)
2828 q = q.filter(RepoGroup.group_parent_id == group_id)
2826
2829
2827 if case_insensitive:
2830 if case_insensitive:
2828 q = q.order_by(func.lower(RepoGroup.group_name))
2831 q = q.order_by(func.lower(RepoGroup.group_name))
2829 else:
2832 else:
2830 q = q.order_by(RepoGroup.group_name)
2833 q = q.order_by(RepoGroup.group_name)
2831 return q.all()
2834 return q.all()
2832
2835
2833 @property
2836 @property
2834 def parents(self, parents_recursion_limit=10):
2837 def parents(self, parents_recursion_limit=10):
2835 groups = []
2838 groups = []
2836 if self.parent_group is None:
2839 if self.parent_group is None:
2837 return groups
2840 return groups
2838 cur_gr = self.parent_group
2841 cur_gr = self.parent_group
2839 groups.insert(0, cur_gr)
2842 groups.insert(0, cur_gr)
2840 cnt = 0
2843 cnt = 0
2841 while 1:
2844 while 1:
2842 cnt += 1
2845 cnt += 1
2843 gr = getattr(cur_gr, 'parent_group', None)
2846 gr = getattr(cur_gr, 'parent_group', None)
2844 cur_gr = cur_gr.parent_group
2847 cur_gr = cur_gr.parent_group
2845 if gr is None:
2848 if gr is None:
2846 break
2849 break
2847 if cnt == parents_recursion_limit:
2850 if cnt == parents_recursion_limit:
2848 # this will prevent accidental infinit loops
2851 # this will prevent accidental infinit loops
2849 log.error('more than %s parents found for group %s, stopping '
2852 log.error('more than %s parents found for group %s, stopping '
2850 'recursive parent fetching', parents_recursion_limit, self)
2853 'recursive parent fetching', parents_recursion_limit, self)
2851 break
2854 break
2852
2855
2853 groups.insert(0, gr)
2856 groups.insert(0, gr)
2854 return groups
2857 return groups
2855
2858
2856 @property
2859 @property
2857 def last_commit_cache_update_diff(self):
2860 def last_commit_cache_update_diff(self):
2858 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2861 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2859
2862
2860 @classmethod
2863 @classmethod
2861 def _load_commit_change(cls, last_commit_cache):
2864 def _load_commit_change(cls, last_commit_cache):
2862 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2865 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2863 empty_date = datetime.datetime.fromtimestamp(0)
2866 empty_date = datetime.datetime.fromtimestamp(0)
2864 date_latest = last_commit_cache.get('date', empty_date)
2867 date_latest = last_commit_cache.get('date', empty_date)
2865 try:
2868 try:
2866 return parse_datetime(date_latest)
2869 return parse_datetime(date_latest)
2867 except Exception:
2870 except Exception:
2868 return empty_date
2871 return empty_date
2869
2872
2870 @property
2873 @property
2871 def last_commit_change(self):
2874 def last_commit_change(self):
2872 return self._load_commit_change(self.changeset_cache)
2875 return self._load_commit_change(self.changeset_cache)
2873
2876
2874 @property
2877 @property
2875 def last_db_change(self):
2878 def last_db_change(self):
2876 return self.updated_on
2879 return self.updated_on
2877
2880
2878 @property
2881 @property
2879 def children(self):
2882 def children(self):
2880 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2883 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2881
2884
2882 @property
2885 @property
2883 def name(self):
2886 def name(self):
2884 return self.group_name.split(RepoGroup.url_sep())[-1]
2887 return self.group_name.split(RepoGroup.url_sep())[-1]
2885
2888
2886 @property
2889 @property
2887 def full_path(self):
2890 def full_path(self):
2888 return self.group_name
2891 return self.group_name
2889
2892
2890 @property
2893 @property
2891 def full_path_splitted(self):
2894 def full_path_splitted(self):
2892 return self.group_name.split(RepoGroup.url_sep())
2895 return self.group_name.split(RepoGroup.url_sep())
2893
2896
2894 @property
2897 @property
2895 def repositories(self):
2898 def repositories(self):
2896 return Repository.query()\
2899 return Repository.query()\
2897 .filter(Repository.group == self)\
2900 .filter(Repository.group == self)\
2898 .order_by(Repository.repo_name)
2901 .order_by(Repository.repo_name)
2899
2902
2900 @property
2903 @property
2901 def repositories_recursive_count(self):
2904 def repositories_recursive_count(self):
2902 cnt = self.repositories.count()
2905 cnt = self.repositories.count()
2903
2906
2904 def children_count(group):
2907 def children_count(group):
2905 cnt = 0
2908 cnt = 0
2906 for child in group.children:
2909 for child in group.children:
2907 cnt += child.repositories.count()
2910 cnt += child.repositories.count()
2908 cnt += children_count(child)
2911 cnt += children_count(child)
2909 return cnt
2912 return cnt
2910
2913
2911 return cnt + children_count(self)
2914 return cnt + children_count(self)
2912
2915
2913 def _recursive_objects(self, include_repos=True, include_groups=True):
2916 def _recursive_objects(self, include_repos=True, include_groups=True):
2914 all_ = []
2917 all_ = []
2915
2918
2916 def _get_members(root_gr):
2919 def _get_members(root_gr):
2917 if include_repos:
2920 if include_repos:
2918 for r in root_gr.repositories:
2921 for r in root_gr.repositories:
2919 all_.append(r)
2922 all_.append(r)
2920 childs = root_gr.children.all()
2923 childs = root_gr.children.all()
2921 if childs:
2924 if childs:
2922 for gr in childs:
2925 for gr in childs:
2923 if include_groups:
2926 if include_groups:
2924 all_.append(gr)
2927 all_.append(gr)
2925 _get_members(gr)
2928 _get_members(gr)
2926
2929
2927 root_group = []
2930 root_group = []
2928 if include_groups:
2931 if include_groups:
2929 root_group = [self]
2932 root_group = [self]
2930
2933
2931 _get_members(self)
2934 _get_members(self)
2932 return root_group + all_
2935 return root_group + all_
2933
2936
2934 def recursive_groups_and_repos(self):
2937 def recursive_groups_and_repos(self):
2935 """
2938 """
2936 Recursive return all groups, with repositories in those groups
2939 Recursive return all groups, with repositories in those groups
2937 """
2940 """
2938 return self._recursive_objects()
2941 return self._recursive_objects()
2939
2942
2940 def recursive_groups(self):
2943 def recursive_groups(self):
2941 """
2944 """
2942 Returns all children groups for this group including children of children
2945 Returns all children groups for this group including children of children
2943 """
2946 """
2944 return self._recursive_objects(include_repos=False)
2947 return self._recursive_objects(include_repos=False)
2945
2948
2946 def recursive_repos(self):
2949 def recursive_repos(self):
2947 """
2950 """
2948 Returns all children repositories for this group
2951 Returns all children repositories for this group
2949 """
2952 """
2950 return self._recursive_objects(include_groups=False)
2953 return self._recursive_objects(include_groups=False)
2951
2954
2952 def get_new_name(self, group_name):
2955 def get_new_name(self, group_name):
2953 """
2956 """
2954 returns new full group name based on parent and new name
2957 returns new full group name based on parent and new name
2955
2958
2956 :param group_name:
2959 :param group_name:
2957 """
2960 """
2958 path_prefix = (self.parent_group.full_path_splitted if
2961 path_prefix = (self.parent_group.full_path_splitted if
2959 self.parent_group else [])
2962 self.parent_group else [])
2960 return RepoGroup.url_sep().join(path_prefix + [group_name])
2963 return RepoGroup.url_sep().join(path_prefix + [group_name])
2961
2964
2962 def update_commit_cache(self, config=None):
2965 def update_commit_cache(self, config=None):
2963 """
2966 """
2964 Update cache of last commit for newest repository inside this repository group.
2967 Update cache of last commit for newest repository inside this repository group.
2965 cache_keys should be::
2968 cache_keys should be::
2966
2969
2967 source_repo_id
2970 source_repo_id
2968 short_id
2971 short_id
2969 raw_id
2972 raw_id
2970 revision
2973 revision
2971 parents
2974 parents
2972 message
2975 message
2973 date
2976 date
2974 author
2977 author
2975
2978
2976 """
2979 """
2977 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2980 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2978 empty_date = datetime.datetime.fromtimestamp(0)
2981 empty_date = datetime.datetime.fromtimestamp(0)
2979
2982
2980 def repo_groups_and_repos(root_gr):
2983 def repo_groups_and_repos(root_gr):
2981 for _repo in root_gr.repositories:
2984 for _repo in root_gr.repositories:
2982 yield _repo
2985 yield _repo
2983 for child_group in root_gr.children.all():
2986 for child_group in root_gr.children.all():
2984 yield child_group
2987 yield child_group
2985
2988
2986 latest_repo_cs_cache = {}
2989 latest_repo_cs_cache = {}
2987 for obj in repo_groups_and_repos(self):
2990 for obj in repo_groups_and_repos(self):
2988 repo_cs_cache = obj.changeset_cache
2991 repo_cs_cache = obj.changeset_cache
2989 date_latest = latest_repo_cs_cache.get('date', empty_date)
2992 date_latest = latest_repo_cs_cache.get('date', empty_date)
2990 date_current = repo_cs_cache.get('date', empty_date)
2993 date_current = repo_cs_cache.get('date', empty_date)
2991 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2994 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2992 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2995 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2993 latest_repo_cs_cache = repo_cs_cache
2996 latest_repo_cs_cache = repo_cs_cache
2994 if hasattr(obj, 'repo_id'):
2997 if hasattr(obj, 'repo_id'):
2995 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2998 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2996 else:
2999 else:
2997 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3000 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2998
3001
2999 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3002 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3000
3003
3001 latest_repo_cs_cache['updated_on'] = time.time()
3004 latest_repo_cs_cache['updated_on'] = time.time()
3002 self.changeset_cache = latest_repo_cs_cache
3005 self.changeset_cache = latest_repo_cs_cache
3003 self.updated_on = _date_latest
3006 self.updated_on = _date_latest
3004 Session().add(self)
3007 Session().add(self)
3005 Session().commit()
3008 Session().commit()
3006
3009
3007 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3010 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3008 self.group_name, latest_repo_cs_cache, _date_latest)
3011 self.group_name, latest_repo_cs_cache, _date_latest)
3009
3012
3010 def permissions(self, with_admins=True, with_owner=True,
3013 def permissions(self, with_admins=True, with_owner=True,
3011 expand_from_user_groups=False):
3014 expand_from_user_groups=False):
3012 """
3015 """
3013 Permissions for repository groups
3016 Permissions for repository groups
3014 """
3017 """
3015 _admin_perm = 'group.admin'
3018 _admin_perm = 'group.admin'
3016
3019
3017 owner_row = []
3020 owner_row = []
3018 if with_owner:
3021 if with_owner:
3019 usr = AttributeDict(self.user.get_dict())
3022 usr = AttributeDict(self.user.get_dict())
3020 usr.owner_row = True
3023 usr.owner_row = True
3021 usr.permission = _admin_perm
3024 usr.permission = _admin_perm
3022 owner_row.append(usr)
3025 owner_row.append(usr)
3023
3026
3024 super_admin_ids = []
3027 super_admin_ids = []
3025 super_admin_rows = []
3028 super_admin_rows = []
3026 if with_admins:
3029 if with_admins:
3027 for usr in User.get_all_super_admins():
3030 for usr in User.get_all_super_admins():
3028 super_admin_ids.append(usr.user_id)
3031 super_admin_ids.append(usr.user_id)
3029 # if this admin is also owner, don't double the record
3032 # if this admin is also owner, don't double the record
3030 if usr.user_id == owner_row[0].user_id:
3033 if usr.user_id == owner_row[0].user_id:
3031 owner_row[0].admin_row = True
3034 owner_row[0].admin_row = True
3032 else:
3035 else:
3033 usr = AttributeDict(usr.get_dict())
3036 usr = AttributeDict(usr.get_dict())
3034 usr.admin_row = True
3037 usr.admin_row = True
3035 usr.permission = _admin_perm
3038 usr.permission = _admin_perm
3036 super_admin_rows.append(usr)
3039 super_admin_rows.append(usr)
3037
3040
3038 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3041 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3039 q = q.options(joinedload(UserRepoGroupToPerm.group),
3042 q = q.options(joinedload(UserRepoGroupToPerm.group),
3040 joinedload(UserRepoGroupToPerm.user),
3043 joinedload(UserRepoGroupToPerm.user),
3041 joinedload(UserRepoGroupToPerm.permission),)
3044 joinedload(UserRepoGroupToPerm.permission),)
3042
3045
3043 # get owners and admins and permissions. We do a trick of re-writing
3046 # get owners and admins and permissions. We do a trick of re-writing
3044 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3045 # has a global reference and changing one object propagates to all
3048 # has a global reference and changing one object propagates to all
3046 # others. This means if admin is also an owner admin_row that change
3049 # others. This means if admin is also an owner admin_row that change
3047 # would propagate to both objects
3050 # would propagate to both objects
3048 perm_rows = []
3051 perm_rows = []
3049 for _usr in q.all():
3052 for _usr in q.all():
3050 usr = AttributeDict(_usr.user.get_dict())
3053 usr = AttributeDict(_usr.user.get_dict())
3051 # if this user is also owner/admin, mark as duplicate record
3054 # if this user is also owner/admin, mark as duplicate record
3052 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3053 usr.duplicate_perm = True
3056 usr.duplicate_perm = True
3054 usr.permission = _usr.permission.permission_name
3057 usr.permission = _usr.permission.permission_name
3055 perm_rows.append(usr)
3058 perm_rows.append(usr)
3056
3059
3057 # filter the perm rows by 'default' first and then sort them by
3060 # filter the perm rows by 'default' first and then sort them by
3058 # admin,write,read,none permissions sorted again alphabetically in
3061 # admin,write,read,none permissions sorted again alphabetically in
3059 # each group
3062 # each group
3060 perm_rows = sorted(perm_rows, key=display_user_sort)
3063 perm_rows = sorted(perm_rows, key=display_user_sort)
3061
3064
3062 user_groups_rows = []
3065 user_groups_rows = []
3063 if expand_from_user_groups:
3066 if expand_from_user_groups:
3064 for ug in self.permission_user_groups(with_members=True):
3067 for ug in self.permission_user_groups(with_members=True):
3065 for user_data in ug.members:
3068 for user_data in ug.members:
3066 user_groups_rows.append(user_data)
3069 user_groups_rows.append(user_data)
3067
3070
3068 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3071 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3069
3072
3070 def permission_user_groups(self, with_members=False):
3073 def permission_user_groups(self, with_members=False):
3071 q = UserGroupRepoGroupToPerm.query()\
3074 q = UserGroupRepoGroupToPerm.query()\
3072 .filter(UserGroupRepoGroupToPerm.group == self)
3075 .filter(UserGroupRepoGroupToPerm.group == self)
3073 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3076 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3074 joinedload(UserGroupRepoGroupToPerm.users_group),
3077 joinedload(UserGroupRepoGroupToPerm.users_group),
3075 joinedload(UserGroupRepoGroupToPerm.permission),)
3078 joinedload(UserGroupRepoGroupToPerm.permission),)
3076
3079
3077 perm_rows = []
3080 perm_rows = []
3078 for _user_group in q.all():
3081 for _user_group in q.all():
3079 entry = AttributeDict(_user_group.users_group.get_dict())
3082 entry = AttributeDict(_user_group.users_group.get_dict())
3080 entry.permission = _user_group.permission.permission_name
3083 entry.permission = _user_group.permission.permission_name
3081 if with_members:
3084 if with_members:
3082 entry.members = [x.user.get_dict()
3085 entry.members = [x.user.get_dict()
3083 for x in _user_group.users_group.members]
3086 for x in _user_group.users_group.members]
3084 perm_rows.append(entry)
3087 perm_rows.append(entry)
3085
3088
3086 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3089 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3087 return perm_rows
3090 return perm_rows
3088
3091
3089 def get_api_data(self):
3092 def get_api_data(self):
3090 """
3093 """
3091 Common function for generating api data
3094 Common function for generating api data
3092
3095
3093 """
3096 """
3094 group = self
3097 group = self
3095 data = {
3098 data = {
3096 'group_id': group.group_id,
3099 'group_id': group.group_id,
3097 'group_name': group.group_name,
3100 'group_name': group.group_name,
3098 'group_description': group.description_safe,
3101 'group_description': group.description_safe,
3099 'parent_group': group.parent_group.group_name if group.parent_group else None,
3102 'parent_group': group.parent_group.group_name if group.parent_group else None,
3100 'repositories': [x.repo_name for x in group.repositories],
3103 'repositories': [x.repo_name for x in group.repositories],
3101 'owner': group.user.username,
3104 'owner': group.user.username,
3102 }
3105 }
3103 return data
3106 return data
3104
3107
3105 def get_dict(self):
3108 def get_dict(self):
3106 # Since we transformed `group_name` to a hybrid property, we need to
3109 # Since we transformed `group_name` to a hybrid property, we need to
3107 # keep compatibility with the code which uses `group_name` field.
3110 # keep compatibility with the code which uses `group_name` field.
3108 result = super(RepoGroup, self).get_dict()
3111 result = super(RepoGroup, self).get_dict()
3109 result['group_name'] = result.pop('_group_name', None)
3112 result['group_name'] = result.pop('_group_name', None)
3110 return result
3113 return result
3111
3114
3112
3115
3113 class Permission(Base, BaseModel):
3116 class Permission(Base, BaseModel):
3114 __tablename__ = 'permissions'
3117 __tablename__ = 'permissions'
3115 __table_args__ = (
3118 __table_args__ = (
3116 Index('p_perm_name_idx', 'permission_name'),
3119 Index('p_perm_name_idx', 'permission_name'),
3117 base_table_args,
3120 base_table_args,
3118 )
3121 )
3119
3122
3120 PERMS = [
3123 PERMS = [
3121 ('hg.admin', _('RhodeCode Super Administrator')),
3124 ('hg.admin', _('RhodeCode Super Administrator')),
3122
3125
3123 ('repository.none', _('Repository no access')),
3126 ('repository.none', _('Repository no access')),
3124 ('repository.read', _('Repository read access')),
3127 ('repository.read', _('Repository read access')),
3125 ('repository.write', _('Repository write access')),
3128 ('repository.write', _('Repository write access')),
3126 ('repository.admin', _('Repository admin access')),
3129 ('repository.admin', _('Repository admin access')),
3127
3130
3128 ('group.none', _('Repository group no access')),
3131 ('group.none', _('Repository group no access')),
3129 ('group.read', _('Repository group read access')),
3132 ('group.read', _('Repository group read access')),
3130 ('group.write', _('Repository group write access')),
3133 ('group.write', _('Repository group write access')),
3131 ('group.admin', _('Repository group admin access')),
3134 ('group.admin', _('Repository group admin access')),
3132
3135
3133 ('usergroup.none', _('User group no access')),
3136 ('usergroup.none', _('User group no access')),
3134 ('usergroup.read', _('User group read access')),
3137 ('usergroup.read', _('User group read access')),
3135 ('usergroup.write', _('User group write access')),
3138 ('usergroup.write', _('User group write access')),
3136 ('usergroup.admin', _('User group admin access')),
3139 ('usergroup.admin', _('User group admin access')),
3137
3140
3138 ('branch.none', _('Branch no permissions')),
3141 ('branch.none', _('Branch no permissions')),
3139 ('branch.merge', _('Branch access by web merge')),
3142 ('branch.merge', _('Branch access by web merge')),
3140 ('branch.push', _('Branch access by push')),
3143 ('branch.push', _('Branch access by push')),
3141 ('branch.push_force', _('Branch access by push with force')),
3144 ('branch.push_force', _('Branch access by push with force')),
3142
3145
3143 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3146 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3144 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3147 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3145
3148
3146 ('hg.usergroup.create.false', _('User Group creation disabled')),
3149 ('hg.usergroup.create.false', _('User Group creation disabled')),
3147 ('hg.usergroup.create.true', _('User Group creation enabled')),
3150 ('hg.usergroup.create.true', _('User Group creation enabled')),
3148
3151
3149 ('hg.create.none', _('Repository creation disabled')),
3152 ('hg.create.none', _('Repository creation disabled')),
3150 ('hg.create.repository', _('Repository creation enabled')),
3153 ('hg.create.repository', _('Repository creation enabled')),
3151 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3154 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3152 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3155 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3153
3156
3154 ('hg.fork.none', _('Repository forking disabled')),
3157 ('hg.fork.none', _('Repository forking disabled')),
3155 ('hg.fork.repository', _('Repository forking enabled')),
3158 ('hg.fork.repository', _('Repository forking enabled')),
3156
3159
3157 ('hg.register.none', _('Registration disabled')),
3160 ('hg.register.none', _('Registration disabled')),
3158 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3161 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3159 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3162 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3160
3163
3161 ('hg.password_reset.enabled', _('Password reset enabled')),
3164 ('hg.password_reset.enabled', _('Password reset enabled')),
3162 ('hg.password_reset.hidden', _('Password reset hidden')),
3165 ('hg.password_reset.hidden', _('Password reset hidden')),
3163 ('hg.password_reset.disabled', _('Password reset disabled')),
3166 ('hg.password_reset.disabled', _('Password reset disabled')),
3164
3167
3165 ('hg.extern_activate.manual', _('Manual activation of external account')),
3168 ('hg.extern_activate.manual', _('Manual activation of external account')),
3166 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3169 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3167
3170
3168 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3171 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3169 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3172 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3170 ]
3173 ]
3171
3174
3172 # definition of system default permissions for DEFAULT user, created on
3175 # definition of system default permissions for DEFAULT user, created on
3173 # system setup
3176 # system setup
3174 DEFAULT_USER_PERMISSIONS = [
3177 DEFAULT_USER_PERMISSIONS = [
3175 # object perms
3178 # object perms
3176 'repository.read',
3179 'repository.read',
3177 'group.read',
3180 'group.read',
3178 'usergroup.read',
3181 'usergroup.read',
3179 # branch, for backward compat we need same value as before so forced pushed
3182 # branch, for backward compat we need same value as before so forced pushed
3180 'branch.push_force',
3183 'branch.push_force',
3181 # global
3184 # global
3182 'hg.create.repository',
3185 'hg.create.repository',
3183 'hg.repogroup.create.false',
3186 'hg.repogroup.create.false',
3184 'hg.usergroup.create.false',
3187 'hg.usergroup.create.false',
3185 'hg.create.write_on_repogroup.true',
3188 'hg.create.write_on_repogroup.true',
3186 'hg.fork.repository',
3189 'hg.fork.repository',
3187 'hg.register.manual_activate',
3190 'hg.register.manual_activate',
3188 'hg.password_reset.enabled',
3191 'hg.password_reset.enabled',
3189 'hg.extern_activate.auto',
3192 'hg.extern_activate.auto',
3190 'hg.inherit_default_perms.true',
3193 'hg.inherit_default_perms.true',
3191 ]
3194 ]
3192
3195
3193 # defines which permissions are more important higher the more important
3196 # defines which permissions are more important higher the more important
3194 # Weight defines which permissions are more important.
3197 # Weight defines which permissions are more important.
3195 # The higher number the more important.
3198 # The higher number the more important.
3196 PERM_WEIGHTS = {
3199 PERM_WEIGHTS = {
3197 'repository.none': 0,
3200 'repository.none': 0,
3198 'repository.read': 1,
3201 'repository.read': 1,
3199 'repository.write': 3,
3202 'repository.write': 3,
3200 'repository.admin': 4,
3203 'repository.admin': 4,
3201
3204
3202 'group.none': 0,
3205 'group.none': 0,
3203 'group.read': 1,
3206 'group.read': 1,
3204 'group.write': 3,
3207 'group.write': 3,
3205 'group.admin': 4,
3208 'group.admin': 4,
3206
3209
3207 'usergroup.none': 0,
3210 'usergroup.none': 0,
3208 'usergroup.read': 1,
3211 'usergroup.read': 1,
3209 'usergroup.write': 3,
3212 'usergroup.write': 3,
3210 'usergroup.admin': 4,
3213 'usergroup.admin': 4,
3211
3214
3212 'branch.none': 0,
3215 'branch.none': 0,
3213 'branch.merge': 1,
3216 'branch.merge': 1,
3214 'branch.push': 3,
3217 'branch.push': 3,
3215 'branch.push_force': 4,
3218 'branch.push_force': 4,
3216
3219
3217 'hg.repogroup.create.false': 0,
3220 'hg.repogroup.create.false': 0,
3218 'hg.repogroup.create.true': 1,
3221 'hg.repogroup.create.true': 1,
3219
3222
3220 'hg.usergroup.create.false': 0,
3223 'hg.usergroup.create.false': 0,
3221 'hg.usergroup.create.true': 1,
3224 'hg.usergroup.create.true': 1,
3222
3225
3223 'hg.fork.none': 0,
3226 'hg.fork.none': 0,
3224 'hg.fork.repository': 1,
3227 'hg.fork.repository': 1,
3225 'hg.create.none': 0,
3228 'hg.create.none': 0,
3226 'hg.create.repository': 1
3229 'hg.create.repository': 1
3227 }
3230 }
3228
3231
3229 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3232 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3230 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3233 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3231 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3234 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3232
3235
3233 def __unicode__(self):
3236 def __unicode__(self):
3234 return u"<%s('%s:%s')>" % (
3237 return u"<%s('%s:%s')>" % (
3235 self.__class__.__name__, self.permission_id, self.permission_name
3238 self.__class__.__name__, self.permission_id, self.permission_name
3236 )
3239 )
3237
3240
3238 @classmethod
3241 @classmethod
3239 def get_by_key(cls, key):
3242 def get_by_key(cls, key):
3240 return cls.query().filter(cls.permission_name == key).scalar()
3243 return cls.query().filter(cls.permission_name == key).scalar()
3241
3244
3242 @classmethod
3245 @classmethod
3243 def get_default_repo_perms(cls, user_id, repo_id=None):
3246 def get_default_repo_perms(cls, user_id, repo_id=None):
3244 q = Session().query(UserRepoToPerm, Repository, Permission)\
3247 q = Session().query(UserRepoToPerm, Repository, Permission)\
3245 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3248 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3246 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3249 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3247 .filter(UserRepoToPerm.user_id == user_id)
3250 .filter(UserRepoToPerm.user_id == user_id)
3248 if repo_id:
3251 if repo_id:
3249 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3252 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3250 return q.all()
3253 return q.all()
3251
3254
3252 @classmethod
3255 @classmethod
3253 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3256 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3254 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3257 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3255 .join(
3258 .join(
3256 Permission,
3259 Permission,
3257 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3260 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3258 .join(
3261 .join(
3259 UserRepoToPerm,
3262 UserRepoToPerm,
3260 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3263 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3261 .filter(UserRepoToPerm.user_id == user_id)
3264 .filter(UserRepoToPerm.user_id == user_id)
3262
3265
3263 if repo_id:
3266 if repo_id:
3264 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3267 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3265 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3268 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3266
3269
3267 @classmethod
3270 @classmethod
3268 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3271 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3269 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3272 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3270 .join(
3273 .join(
3271 Permission,
3274 Permission,
3272 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3275 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3273 .join(
3276 .join(
3274 Repository,
3277 Repository,
3275 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3278 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3276 .join(
3279 .join(
3277 UserGroup,
3280 UserGroup,
3278 UserGroupRepoToPerm.users_group_id ==
3281 UserGroupRepoToPerm.users_group_id ==
3279 UserGroup.users_group_id)\
3282 UserGroup.users_group_id)\
3280 .join(
3283 .join(
3281 UserGroupMember,
3284 UserGroupMember,
3282 UserGroupRepoToPerm.users_group_id ==
3285 UserGroupRepoToPerm.users_group_id ==
3283 UserGroupMember.users_group_id)\
3286 UserGroupMember.users_group_id)\
3284 .filter(
3287 .filter(
3285 UserGroupMember.user_id == user_id,
3288 UserGroupMember.user_id == user_id,
3286 UserGroup.users_group_active == true())
3289 UserGroup.users_group_active == true())
3287 if repo_id:
3290 if repo_id:
3288 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3291 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3289 return q.all()
3292 return q.all()
3290
3293
3291 @classmethod
3294 @classmethod
3292 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3295 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3293 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3296 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3294 .join(
3297 .join(
3295 Permission,
3298 Permission,
3296 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3299 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3297 .join(
3300 .join(
3298 UserGroupRepoToPerm,
3301 UserGroupRepoToPerm,
3299 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3302 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3300 .join(
3303 .join(
3301 UserGroup,
3304 UserGroup,
3302 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3305 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3303 .join(
3306 .join(
3304 UserGroupMember,
3307 UserGroupMember,
3305 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3308 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3306 .filter(
3309 .filter(
3307 UserGroupMember.user_id == user_id,
3310 UserGroupMember.user_id == user_id,
3308 UserGroup.users_group_active == true())
3311 UserGroup.users_group_active == true())
3309
3312
3310 if repo_id:
3313 if repo_id:
3311 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3314 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3312 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3315 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3313
3316
3314 @classmethod
3317 @classmethod
3315 def get_default_group_perms(cls, user_id, repo_group_id=None):
3318 def get_default_group_perms(cls, user_id, repo_group_id=None):
3316 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3319 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3317 .join(
3320 .join(
3318 Permission,
3321 Permission,
3319 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3322 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3320 .join(
3323 .join(
3321 RepoGroup,
3324 RepoGroup,
3322 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3325 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3323 .filter(UserRepoGroupToPerm.user_id == user_id)
3326 .filter(UserRepoGroupToPerm.user_id == user_id)
3324 if repo_group_id:
3327 if repo_group_id:
3325 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3328 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3326 return q.all()
3329 return q.all()
3327
3330
3328 @classmethod
3331 @classmethod
3329 def get_default_group_perms_from_user_group(
3332 def get_default_group_perms_from_user_group(
3330 cls, user_id, repo_group_id=None):
3333 cls, user_id, repo_group_id=None):
3331 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3334 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3332 .join(
3335 .join(
3333 Permission,
3336 Permission,
3334 UserGroupRepoGroupToPerm.permission_id ==
3337 UserGroupRepoGroupToPerm.permission_id ==
3335 Permission.permission_id)\
3338 Permission.permission_id)\
3336 .join(
3339 .join(
3337 RepoGroup,
3340 RepoGroup,
3338 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3341 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3339 .join(
3342 .join(
3340 UserGroup,
3343 UserGroup,
3341 UserGroupRepoGroupToPerm.users_group_id ==
3344 UserGroupRepoGroupToPerm.users_group_id ==
3342 UserGroup.users_group_id)\
3345 UserGroup.users_group_id)\
3343 .join(
3346 .join(
3344 UserGroupMember,
3347 UserGroupMember,
3345 UserGroupRepoGroupToPerm.users_group_id ==
3348 UserGroupRepoGroupToPerm.users_group_id ==
3346 UserGroupMember.users_group_id)\
3349 UserGroupMember.users_group_id)\
3347 .filter(
3350 .filter(
3348 UserGroupMember.user_id == user_id,
3351 UserGroupMember.user_id == user_id,
3349 UserGroup.users_group_active == true())
3352 UserGroup.users_group_active == true())
3350 if repo_group_id:
3353 if repo_group_id:
3351 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3354 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3352 return q.all()
3355 return q.all()
3353
3356
3354 @classmethod
3357 @classmethod
3355 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3358 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3356 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3359 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3357 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3360 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3358 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3361 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3359 .filter(UserUserGroupToPerm.user_id == user_id)
3362 .filter(UserUserGroupToPerm.user_id == user_id)
3360 if user_group_id:
3363 if user_group_id:
3361 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3364 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3362 return q.all()
3365 return q.all()
3363
3366
3364 @classmethod
3367 @classmethod
3365 def get_default_user_group_perms_from_user_group(
3368 def get_default_user_group_perms_from_user_group(
3366 cls, user_id, user_group_id=None):
3369 cls, user_id, user_group_id=None):
3367 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3370 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3368 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3371 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3369 .join(
3372 .join(
3370 Permission,
3373 Permission,
3371 UserGroupUserGroupToPerm.permission_id ==
3374 UserGroupUserGroupToPerm.permission_id ==
3372 Permission.permission_id)\
3375 Permission.permission_id)\
3373 .join(
3376 .join(
3374 TargetUserGroup,
3377 TargetUserGroup,
3375 UserGroupUserGroupToPerm.target_user_group_id ==
3378 UserGroupUserGroupToPerm.target_user_group_id ==
3376 TargetUserGroup.users_group_id)\
3379 TargetUserGroup.users_group_id)\
3377 .join(
3380 .join(
3378 UserGroup,
3381 UserGroup,
3379 UserGroupUserGroupToPerm.user_group_id ==
3382 UserGroupUserGroupToPerm.user_group_id ==
3380 UserGroup.users_group_id)\
3383 UserGroup.users_group_id)\
3381 .join(
3384 .join(
3382 UserGroupMember,
3385 UserGroupMember,
3383 UserGroupUserGroupToPerm.user_group_id ==
3386 UserGroupUserGroupToPerm.user_group_id ==
3384 UserGroupMember.users_group_id)\
3387 UserGroupMember.users_group_id)\
3385 .filter(
3388 .filter(
3386 UserGroupMember.user_id == user_id,
3389 UserGroupMember.user_id == user_id,
3387 UserGroup.users_group_active == true())
3390 UserGroup.users_group_active == true())
3388 if user_group_id:
3391 if user_group_id:
3389 q = q.filter(
3392 q = q.filter(
3390 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3393 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3391
3394
3392 return q.all()
3395 return q.all()
3393
3396
3394
3397
3395 class UserRepoToPerm(Base, BaseModel):
3398 class UserRepoToPerm(Base, BaseModel):
3396 __tablename__ = 'repo_to_perm'
3399 __tablename__ = 'repo_to_perm'
3397 __table_args__ = (
3400 __table_args__ = (
3398 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3401 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3399 base_table_args
3402 base_table_args
3400 )
3403 )
3401
3404
3402 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3405 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3406 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3407 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3408 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3406
3409
3407 user = relationship('User')
3410 user = relationship('User')
3408 repository = relationship('Repository')
3411 repository = relationship('Repository')
3409 permission = relationship('Permission')
3412 permission = relationship('Permission')
3410
3413
3411 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3414 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3412
3415
3413 @classmethod
3416 @classmethod
3414 def create(cls, user, repository, permission):
3417 def create(cls, user, repository, permission):
3415 n = cls()
3418 n = cls()
3416 n.user = user
3419 n.user = user
3417 n.repository = repository
3420 n.repository = repository
3418 n.permission = permission
3421 n.permission = permission
3419 Session().add(n)
3422 Session().add(n)
3420 return n
3423 return n
3421
3424
3422 def __unicode__(self):
3425 def __unicode__(self):
3423 return u'<%s => %s >' % (self.user, self.repository)
3426 return u'<%s => %s >' % (self.user, self.repository)
3424
3427
3425
3428
3426 class UserUserGroupToPerm(Base, BaseModel):
3429 class UserUserGroupToPerm(Base, BaseModel):
3427 __tablename__ = 'user_user_group_to_perm'
3430 __tablename__ = 'user_user_group_to_perm'
3428 __table_args__ = (
3431 __table_args__ = (
3429 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3432 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3430 base_table_args
3433 base_table_args
3431 )
3434 )
3432
3435
3433 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3436 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3434 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3438 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3436 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3439 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3437
3440
3438 user = relationship('User')
3441 user = relationship('User')
3439 user_group = relationship('UserGroup')
3442 user_group = relationship('UserGroup')
3440 permission = relationship('Permission')
3443 permission = relationship('Permission')
3441
3444
3442 @classmethod
3445 @classmethod
3443 def create(cls, user, user_group, permission):
3446 def create(cls, user, user_group, permission):
3444 n = cls()
3447 n = cls()
3445 n.user = user
3448 n.user = user
3446 n.user_group = user_group
3449 n.user_group = user_group
3447 n.permission = permission
3450 n.permission = permission
3448 Session().add(n)
3451 Session().add(n)
3449 return n
3452 return n
3450
3453
3451 def __unicode__(self):
3454 def __unicode__(self):
3452 return u'<%s => %s >' % (self.user, self.user_group)
3455 return u'<%s => %s >' % (self.user, self.user_group)
3453
3456
3454
3457
3455 class UserToPerm(Base, BaseModel):
3458 class UserToPerm(Base, BaseModel):
3456 __tablename__ = 'user_to_perm'
3459 __tablename__ = 'user_to_perm'
3457 __table_args__ = (
3460 __table_args__ = (
3458 UniqueConstraint('user_id', 'permission_id'),
3461 UniqueConstraint('user_id', 'permission_id'),
3459 base_table_args
3462 base_table_args
3460 )
3463 )
3461
3464
3462 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3465 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3467 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3465
3468
3466 user = relationship('User')
3469 user = relationship('User')
3467 permission = relationship('Permission', lazy='joined')
3470 permission = relationship('Permission', lazy='joined')
3468
3471
3469 def __unicode__(self):
3472 def __unicode__(self):
3470 return u'<%s => %s >' % (self.user, self.permission)
3473 return u'<%s => %s >' % (self.user, self.permission)
3471
3474
3472
3475
3473 class UserGroupRepoToPerm(Base, BaseModel):
3476 class UserGroupRepoToPerm(Base, BaseModel):
3474 __tablename__ = 'users_group_repo_to_perm'
3477 __tablename__ = 'users_group_repo_to_perm'
3475 __table_args__ = (
3478 __table_args__ = (
3476 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3479 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3477 base_table_args
3480 base_table_args
3478 )
3481 )
3479
3482
3480 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3483 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3481 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3484 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3482 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3485 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3483 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3486 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3484
3487
3485 users_group = relationship('UserGroup')
3488 users_group = relationship('UserGroup')
3486 permission = relationship('Permission')
3489 permission = relationship('Permission')
3487 repository = relationship('Repository')
3490 repository = relationship('Repository')
3488 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3491 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3489
3492
3490 @classmethod
3493 @classmethod
3491 def create(cls, users_group, repository, permission):
3494 def create(cls, users_group, repository, permission):
3492 n = cls()
3495 n = cls()
3493 n.users_group = users_group
3496 n.users_group = users_group
3494 n.repository = repository
3497 n.repository = repository
3495 n.permission = permission
3498 n.permission = permission
3496 Session().add(n)
3499 Session().add(n)
3497 return n
3500 return n
3498
3501
3499 def __unicode__(self):
3502 def __unicode__(self):
3500 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3503 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3501
3504
3502
3505
3503 class UserGroupUserGroupToPerm(Base, BaseModel):
3506 class UserGroupUserGroupToPerm(Base, BaseModel):
3504 __tablename__ = 'user_group_user_group_to_perm'
3507 __tablename__ = 'user_group_user_group_to_perm'
3505 __table_args__ = (
3508 __table_args__ = (
3506 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3509 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3507 CheckConstraint('target_user_group_id != user_group_id'),
3510 CheckConstraint('target_user_group_id != user_group_id'),
3508 base_table_args
3511 base_table_args
3509 )
3512 )
3510
3513
3511 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)
3514 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)
3512 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3515 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3513 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3516 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3514 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3517 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3515
3518
3516 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3519 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3517 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3520 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3518 permission = relationship('Permission')
3521 permission = relationship('Permission')
3519
3522
3520 @classmethod
3523 @classmethod
3521 def create(cls, target_user_group, user_group, permission):
3524 def create(cls, target_user_group, user_group, permission):
3522 n = cls()
3525 n = cls()
3523 n.target_user_group = target_user_group
3526 n.target_user_group = target_user_group
3524 n.user_group = user_group
3527 n.user_group = user_group
3525 n.permission = permission
3528 n.permission = permission
3526 Session().add(n)
3529 Session().add(n)
3527 return n
3530 return n
3528
3531
3529 def __unicode__(self):
3532 def __unicode__(self):
3530 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3533 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3531
3534
3532
3535
3533 class UserGroupToPerm(Base, BaseModel):
3536 class UserGroupToPerm(Base, BaseModel):
3534 __tablename__ = 'users_group_to_perm'
3537 __tablename__ = 'users_group_to_perm'
3535 __table_args__ = (
3538 __table_args__ = (
3536 UniqueConstraint('users_group_id', 'permission_id',),
3539 UniqueConstraint('users_group_id', 'permission_id',),
3537 base_table_args
3540 base_table_args
3538 )
3541 )
3539
3542
3540 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3543 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3541 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3544 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3542 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3545 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3543
3546
3544 users_group = relationship('UserGroup')
3547 users_group = relationship('UserGroup')
3545 permission = relationship('Permission')
3548 permission = relationship('Permission')
3546
3549
3547
3550
3548 class UserRepoGroupToPerm(Base, BaseModel):
3551 class UserRepoGroupToPerm(Base, BaseModel):
3549 __tablename__ = 'user_repo_group_to_perm'
3552 __tablename__ = 'user_repo_group_to_perm'
3550 __table_args__ = (
3553 __table_args__ = (
3551 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3554 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3552 base_table_args
3555 base_table_args
3553 )
3556 )
3554
3557
3555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3558 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3559 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3560 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3561 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3559
3562
3560 user = relationship('User')
3563 user = relationship('User')
3561 group = relationship('RepoGroup')
3564 group = relationship('RepoGroup')
3562 permission = relationship('Permission')
3565 permission = relationship('Permission')
3563
3566
3564 @classmethod
3567 @classmethod
3565 def create(cls, user, repository_group, permission):
3568 def create(cls, user, repository_group, permission):
3566 n = cls()
3569 n = cls()
3567 n.user = user
3570 n.user = user
3568 n.group = repository_group
3571 n.group = repository_group
3569 n.permission = permission
3572 n.permission = permission
3570 Session().add(n)
3573 Session().add(n)
3571 return n
3574 return n
3572
3575
3573
3576
3574 class UserGroupRepoGroupToPerm(Base, BaseModel):
3577 class UserGroupRepoGroupToPerm(Base, BaseModel):
3575 __tablename__ = 'users_group_repo_group_to_perm'
3578 __tablename__ = 'users_group_repo_group_to_perm'
3576 __table_args__ = (
3579 __table_args__ = (
3577 UniqueConstraint('users_group_id', 'group_id'),
3580 UniqueConstraint('users_group_id', 'group_id'),
3578 base_table_args
3581 base_table_args
3579 )
3582 )
3580
3583
3581 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)
3584 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)
3582 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3586 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3587 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3585
3588
3586 users_group = relationship('UserGroup')
3589 users_group = relationship('UserGroup')
3587 permission = relationship('Permission')
3590 permission = relationship('Permission')
3588 group = relationship('RepoGroup')
3591 group = relationship('RepoGroup')
3589
3592
3590 @classmethod
3593 @classmethod
3591 def create(cls, user_group, repository_group, permission):
3594 def create(cls, user_group, repository_group, permission):
3592 n = cls()
3595 n = cls()
3593 n.users_group = user_group
3596 n.users_group = user_group
3594 n.group = repository_group
3597 n.group = repository_group
3595 n.permission = permission
3598 n.permission = permission
3596 Session().add(n)
3599 Session().add(n)
3597 return n
3600 return n
3598
3601
3599 def __unicode__(self):
3602 def __unicode__(self):
3600 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3603 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3601
3604
3602
3605
3603 class Statistics(Base, BaseModel):
3606 class Statistics(Base, BaseModel):
3604 __tablename__ = 'statistics'
3607 __tablename__ = 'statistics'
3605 __table_args__ = (
3608 __table_args__ = (
3606 base_table_args
3609 base_table_args
3607 )
3610 )
3608
3611
3609 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3612 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3613 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3611 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3614 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3612 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3615 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3613 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3616 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3614 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3617 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3615
3618
3616 repository = relationship('Repository', single_parent=True)
3619 repository = relationship('Repository', single_parent=True)
3617
3620
3618
3621
3619 class UserFollowing(Base, BaseModel):
3622 class UserFollowing(Base, BaseModel):
3620 __tablename__ = 'user_followings'
3623 __tablename__ = 'user_followings'
3621 __table_args__ = (
3624 __table_args__ = (
3622 UniqueConstraint('user_id', 'follows_repository_id'),
3625 UniqueConstraint('user_id', 'follows_repository_id'),
3623 UniqueConstraint('user_id', 'follows_user_id'),
3626 UniqueConstraint('user_id', 'follows_user_id'),
3624 base_table_args
3627 base_table_args
3625 )
3628 )
3626
3629
3627 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3630 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3628 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3629 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3632 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3630 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3633 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3631 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3634 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3632
3635
3633 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3636 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3634
3637
3635 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3638 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3636 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3639 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3637
3640
3638 @classmethod
3641 @classmethod
3639 def get_repo_followers(cls, repo_id):
3642 def get_repo_followers(cls, repo_id):
3640 return cls.query().filter(cls.follows_repo_id == repo_id)
3643 return cls.query().filter(cls.follows_repo_id == repo_id)
3641
3644
3642
3645
3643 class CacheKey(Base, BaseModel):
3646 class CacheKey(Base, BaseModel):
3644 __tablename__ = 'cache_invalidation'
3647 __tablename__ = 'cache_invalidation'
3645 __table_args__ = (
3648 __table_args__ = (
3646 UniqueConstraint('cache_key'),
3649 UniqueConstraint('cache_key'),
3647 Index('key_idx', 'cache_key'),
3650 Index('key_idx', 'cache_key'),
3648 Index('cache_args_idx', 'cache_args'),
3651 Index('cache_args_idx', 'cache_args'),
3649 base_table_args,
3652 base_table_args,
3650 )
3653 )
3651
3654
3652 CACHE_TYPE_FEED = 'FEED'
3655 CACHE_TYPE_FEED = 'FEED'
3653
3656
3654 # namespaces used to register process/thread aware caches
3657 # namespaces used to register process/thread aware caches
3655 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3658 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3656
3659
3657 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3660 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3658 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3661 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3659 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3662 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3660 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3663 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3661 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3664 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3662
3665
3663 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3666 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3664 self.cache_key = cache_key
3667 self.cache_key = cache_key
3665 self.cache_args = cache_args
3668 self.cache_args = cache_args
3666 self.cache_active = False
3669 self.cache_active = False
3667 # first key should be same for all entries, since all workers should share it
3670 # first key should be same for all entries, since all workers should share it
3668 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3671 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3669
3672
3670 def __unicode__(self):
3673 def __unicode__(self):
3671 return u"<%s('%s:%s[%s]')>" % (
3674 return u"<%s('%s:%s[%s]')>" % (
3672 self.__class__.__name__,
3675 self.__class__.__name__,
3673 self.cache_id, self.cache_key, self.cache_active)
3676 self.cache_id, self.cache_key, self.cache_active)
3674
3677
3675 def _cache_key_partition(self):
3678 def _cache_key_partition(self):
3676 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3679 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3677 return prefix, repo_name, suffix
3680 return prefix, repo_name, suffix
3678
3681
3679 def get_prefix(self):
3682 def get_prefix(self):
3680 """
3683 """
3681 Try to extract prefix from existing cache key. The key could consist
3684 Try to extract prefix from existing cache key. The key could consist
3682 of prefix, repo_name, suffix
3685 of prefix, repo_name, suffix
3683 """
3686 """
3684 # this returns prefix, repo_name, suffix
3687 # this returns prefix, repo_name, suffix
3685 return self._cache_key_partition()[0]
3688 return self._cache_key_partition()[0]
3686
3689
3687 def get_suffix(self):
3690 def get_suffix(self):
3688 """
3691 """
3689 get suffix that might have been used in _get_cache_key to
3692 get suffix that might have been used in _get_cache_key to
3690 generate self.cache_key. Only used for informational purposes
3693 generate self.cache_key. Only used for informational purposes
3691 in repo_edit.mako.
3694 in repo_edit.mako.
3692 """
3695 """
3693 # prefix, repo_name, suffix
3696 # prefix, repo_name, suffix
3694 return self._cache_key_partition()[2]
3697 return self._cache_key_partition()[2]
3695
3698
3696 @classmethod
3699 @classmethod
3697 def generate_new_state_uid(cls, based_on=None):
3700 def generate_new_state_uid(cls, based_on=None):
3698 if based_on:
3701 if based_on:
3699 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3702 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3700 else:
3703 else:
3701 return str(uuid.uuid4())
3704 return str(uuid.uuid4())
3702
3705
3703 @classmethod
3706 @classmethod
3704 def delete_all_cache(cls):
3707 def delete_all_cache(cls):
3705 """
3708 """
3706 Delete all cache keys from database.
3709 Delete all cache keys from database.
3707 Should only be run when all instances are down and all entries
3710 Should only be run when all instances are down and all entries
3708 thus stale.
3711 thus stale.
3709 """
3712 """
3710 cls.query().delete()
3713 cls.query().delete()
3711 Session().commit()
3714 Session().commit()
3712
3715
3713 @classmethod
3716 @classmethod
3714 def set_invalidate(cls, cache_uid, delete=False):
3717 def set_invalidate(cls, cache_uid, delete=False):
3715 """
3718 """
3716 Mark all caches of a repo as invalid in the database.
3719 Mark all caches of a repo as invalid in the database.
3717 """
3720 """
3718
3721
3719 try:
3722 try:
3720 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3723 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3721 if delete:
3724 if delete:
3722 qry.delete()
3725 qry.delete()
3723 log.debug('cache objects deleted for cache args %s',
3726 log.debug('cache objects deleted for cache args %s',
3724 safe_str(cache_uid))
3727 safe_str(cache_uid))
3725 else:
3728 else:
3726 qry.update({"cache_active": False,
3729 qry.update({"cache_active": False,
3727 "cache_state_uid": cls.generate_new_state_uid()})
3730 "cache_state_uid": cls.generate_new_state_uid()})
3728 log.debug('cache objects marked as invalid for cache args %s',
3731 log.debug('cache objects marked as invalid for cache args %s',
3729 safe_str(cache_uid))
3732 safe_str(cache_uid))
3730
3733
3731 Session().commit()
3734 Session().commit()
3732 except Exception:
3735 except Exception:
3733 log.exception(
3736 log.exception(
3734 'Cache key invalidation failed for cache args %s',
3737 'Cache key invalidation failed for cache args %s',
3735 safe_str(cache_uid))
3738 safe_str(cache_uid))
3736 Session().rollback()
3739 Session().rollback()
3737
3740
3738 @classmethod
3741 @classmethod
3739 def get_active_cache(cls, cache_key):
3742 def get_active_cache(cls, cache_key):
3740 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3743 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3741 if inv_obj:
3744 if inv_obj:
3742 return inv_obj
3745 return inv_obj
3743 return None
3746 return None
3744
3747
3745 @classmethod
3748 @classmethod
3746 def get_namespace_map(cls, namespace):
3749 def get_namespace_map(cls, namespace):
3747 return {
3750 return {
3748 x.cache_key: x
3751 x.cache_key: x
3749 for x in cls.query().filter(cls.cache_args == namespace)}
3752 for x in cls.query().filter(cls.cache_args == namespace)}
3750
3753
3751
3754
3752 class ChangesetComment(Base, BaseModel):
3755 class ChangesetComment(Base, BaseModel):
3753 __tablename__ = 'changeset_comments'
3756 __tablename__ = 'changeset_comments'
3754 __table_args__ = (
3757 __table_args__ = (
3755 Index('cc_revision_idx', 'revision'),
3758 Index('cc_revision_idx', 'revision'),
3756 base_table_args,
3759 base_table_args,
3757 )
3760 )
3758
3761
3759 COMMENT_OUTDATED = u'comment_outdated'
3762 COMMENT_OUTDATED = u'comment_outdated'
3760 COMMENT_TYPE_NOTE = u'note'
3763 COMMENT_TYPE_NOTE = u'note'
3761 COMMENT_TYPE_TODO = u'todo'
3764 COMMENT_TYPE_TODO = u'todo'
3762 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3765 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3763
3766
3764 OP_IMMUTABLE = u'immutable'
3767 OP_IMMUTABLE = u'immutable'
3765 OP_CHANGEABLE = u'changeable'
3768 OP_CHANGEABLE = u'changeable'
3766
3769
3767 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3770 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3768 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3771 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3769 revision = Column('revision', String(40), nullable=True)
3772 revision = Column('revision', String(40), nullable=True)
3770 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3773 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3771 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3774 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3772 line_no = Column('line_no', Unicode(10), nullable=True)
3775 line_no = Column('line_no', Unicode(10), nullable=True)
3773 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3776 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3774 f_path = Column('f_path', Unicode(1000), nullable=True)
3777 f_path = Column('f_path', Unicode(1000), nullable=True)
3775 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3778 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3776 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3779 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3777 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3780 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3778 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3781 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3779 renderer = Column('renderer', Unicode(64), nullable=True)
3782 renderer = Column('renderer', Unicode(64), nullable=True)
3780 display_state = Column('display_state', Unicode(128), nullable=True)
3783 display_state = Column('display_state', Unicode(128), nullable=True)
3781 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3784 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3782 draft = Column('draft', Boolean(), nullable=True, default=False)
3785 draft = Column('draft', Boolean(), nullable=True, default=False)
3783
3786
3784 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3787 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3785 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3788 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3786
3789
3787 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3790 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3788 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3791 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3789
3792
3790 author = relationship('User', lazy='select')
3793 author = relationship('User', lazy='select')
3791 repo = relationship('Repository')
3794 repo = relationship('Repository')
3792 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
3795 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
3793 pull_request = relationship('PullRequest', lazy='select')
3796 pull_request = relationship('PullRequest', lazy='select')
3794 pull_request_version = relationship('PullRequestVersion', lazy='select')
3797 pull_request_version = relationship('PullRequestVersion', lazy='select')
3795 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
3798 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
3796
3799
3797 @classmethod
3800 @classmethod
3798 def get_users(cls, revision=None, pull_request_id=None):
3801 def get_users(cls, revision=None, pull_request_id=None):
3799 """
3802 """
3800 Returns user associated with this ChangesetComment. ie those
3803 Returns user associated with this ChangesetComment. ie those
3801 who actually commented
3804 who actually commented
3802
3805
3803 :param cls:
3806 :param cls:
3804 :param revision:
3807 :param revision:
3805 """
3808 """
3806 q = Session().query(User)\
3809 q = Session().query(User)\
3807 .join(ChangesetComment.author)
3810 .join(ChangesetComment.author)
3808 if revision:
3811 if revision:
3809 q = q.filter(cls.revision == revision)
3812 q = q.filter(cls.revision == revision)
3810 elif pull_request_id:
3813 elif pull_request_id:
3811 q = q.filter(cls.pull_request_id == pull_request_id)
3814 q = q.filter(cls.pull_request_id == pull_request_id)
3812 return q.all()
3815 return q.all()
3813
3816
3814 @classmethod
3817 @classmethod
3815 def get_index_from_version(cls, pr_version, versions=None, num_versions=None):
3818 def get_index_from_version(cls, pr_version, versions=None, num_versions=None):
3816
3819
3817 if versions is not None:
3820 if versions is not None:
3818 num_versions = [x.pull_request_version_id for x in versions]
3821 num_versions = [x.pull_request_version_id for x in versions]
3819
3822
3820 num_versions = num_versions or []
3823 num_versions = num_versions or []
3821 try:
3824 try:
3822 return num_versions.index(pr_version) + 1
3825 return num_versions.index(pr_version) + 1
3823 except (IndexError, ValueError):
3826 except (IndexError, ValueError):
3824 return
3827 return
3825
3828
3826 @property
3829 @property
3827 def outdated(self):
3830 def outdated(self):
3828 return self.display_state == self.COMMENT_OUTDATED
3831 return self.display_state == self.COMMENT_OUTDATED
3829
3832
3830 @property
3833 @property
3831 def outdated_js(self):
3834 def outdated_js(self):
3832 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3835 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3833
3836
3834 @property
3837 @property
3835 def immutable(self):
3838 def immutable(self):
3836 return self.immutable_state == self.OP_IMMUTABLE
3839 return self.immutable_state == self.OP_IMMUTABLE
3837
3840
3838 def outdated_at_version(self, version):
3841 def outdated_at_version(self, version):
3839 """
3842 """
3840 Checks if comment is outdated for given pull request version
3843 Checks if comment is outdated for given pull request version
3841 """
3844 """
3842 def version_check():
3845 def version_check():
3843 return self.pull_request_version_id and self.pull_request_version_id != version
3846 return self.pull_request_version_id and self.pull_request_version_id != version
3844
3847
3845 if self.is_inline:
3848 if self.is_inline:
3846 return self.outdated and version_check()
3849 return self.outdated and version_check()
3847 else:
3850 else:
3848 # general comments don't have .outdated set, also latest don't have a version
3851 # general comments don't have .outdated set, also latest don't have a version
3849 return version_check()
3852 return version_check()
3850
3853
3851 def outdated_at_version_js(self, version):
3854 def outdated_at_version_js(self, version):
3852 """
3855 """
3853 Checks if comment is outdated for given pull request version
3856 Checks if comment is outdated for given pull request version
3854 """
3857 """
3855 return json.dumps(self.outdated_at_version(version))
3858 return json.dumps(self.outdated_at_version(version))
3856
3859
3857 def older_than_version(self, version):
3860 def older_than_version(self, version):
3858 """
3861 """
3859 Checks if comment is made from previous version than given
3862 Checks if comment is made from previous version than given
3860 """
3863 """
3861 if version is None:
3864 if version is None:
3862 return self.pull_request_version != version
3865 return self.pull_request_version != version
3863
3866
3864 return self.pull_request_version < version
3867 return self.pull_request_version < version
3865
3868
3866 def older_than_version_js(self, version):
3869 def older_than_version_js(self, version):
3867 """
3870 """
3868 Checks if comment is made from previous version than given
3871 Checks if comment is made from previous version than given
3869 """
3872 """
3870 return json.dumps(self.older_than_version(version))
3873 return json.dumps(self.older_than_version(version))
3871
3874
3872 @property
3875 @property
3873 def commit_id(self):
3876 def commit_id(self):
3874 """New style naming to stop using .revision"""
3877 """New style naming to stop using .revision"""
3875 return self.revision
3878 return self.revision
3876
3879
3877 @property
3880 @property
3878 def resolved(self):
3881 def resolved(self):
3879 return self.resolved_by[0] if self.resolved_by else None
3882 return self.resolved_by[0] if self.resolved_by else None
3880
3883
3881 @property
3884 @property
3882 def is_todo(self):
3885 def is_todo(self):
3883 return self.comment_type == self.COMMENT_TYPE_TODO
3886 return self.comment_type == self.COMMENT_TYPE_TODO
3884
3887
3885 @property
3888 @property
3886 def is_inline(self):
3889 def is_inline(self):
3887 if self.line_no and self.f_path:
3890 if self.line_no and self.f_path:
3888 return True
3891 return True
3889 return False
3892 return False
3890
3893
3891 @property
3894 @property
3892 def last_version(self):
3895 def last_version(self):
3893 version = 0
3896 version = 0
3894 if self.history:
3897 if self.history:
3895 version = self.history[-1].version
3898 version = self.history[-1].version
3896 return version
3899 return version
3897
3900
3898 def get_index_version(self, versions):
3901 def get_index_version(self, versions):
3899 return self.get_index_from_version(
3902 return self.get_index_from_version(
3900 self.pull_request_version_id, versions)
3903 self.pull_request_version_id, versions)
3901
3904
3902 @property
3905 @property
3903 def review_status(self):
3906 def review_status(self):
3904 if self.status_change:
3907 if self.status_change:
3905 return self.status_change[0].status
3908 return self.status_change[0].status
3906
3909
3907 @property
3910 @property
3908 def review_status_lbl(self):
3911 def review_status_lbl(self):
3909 if self.status_change:
3912 if self.status_change:
3910 return self.status_change[0].status_lbl
3913 return self.status_change[0].status_lbl
3911
3914
3912 def __repr__(self):
3915 def __repr__(self):
3913 if self.comment_id:
3916 if self.comment_id:
3914 return '<DB:Comment #%s>' % self.comment_id
3917 return '<DB:Comment #%s>' % self.comment_id
3915 else:
3918 else:
3916 return '<DB:Comment at %#x>' % id(self)
3919 return '<DB:Comment at %#x>' % id(self)
3917
3920
3918 def get_api_data(self):
3921 def get_api_data(self):
3919 comment = self
3922 comment = self
3920
3923
3921 data = {
3924 data = {
3922 'comment_id': comment.comment_id,
3925 'comment_id': comment.comment_id,
3923 'comment_type': comment.comment_type,
3926 'comment_type': comment.comment_type,
3924 'comment_text': comment.text,
3927 'comment_text': comment.text,
3925 'comment_status': comment.status_change,
3928 'comment_status': comment.status_change,
3926 'comment_f_path': comment.f_path,
3929 'comment_f_path': comment.f_path,
3927 'comment_lineno': comment.line_no,
3930 'comment_lineno': comment.line_no,
3928 'comment_author': comment.author,
3931 'comment_author': comment.author,
3929 'comment_created_on': comment.created_on,
3932 'comment_created_on': comment.created_on,
3930 'comment_resolved_by': self.resolved,
3933 'comment_resolved_by': self.resolved,
3931 'comment_commit_id': comment.revision,
3934 'comment_commit_id': comment.revision,
3932 'comment_pull_request_id': comment.pull_request_id,
3935 'comment_pull_request_id': comment.pull_request_id,
3933 'comment_last_version': self.last_version
3936 'comment_last_version': self.last_version
3934 }
3937 }
3935 return data
3938 return data
3936
3939
3937 def __json__(self):
3940 def __json__(self):
3938 data = dict()
3941 data = dict()
3939 data.update(self.get_api_data())
3942 data.update(self.get_api_data())
3940 return data
3943 return data
3941
3944
3942
3945
3943 class ChangesetCommentHistory(Base, BaseModel):
3946 class ChangesetCommentHistory(Base, BaseModel):
3944 __tablename__ = 'changeset_comments_history'
3947 __tablename__ = 'changeset_comments_history'
3945 __table_args__ = (
3948 __table_args__ = (
3946 Index('cch_comment_id_idx', 'comment_id'),
3949 Index('cch_comment_id_idx', 'comment_id'),
3947 base_table_args,
3950 base_table_args,
3948 )
3951 )
3949
3952
3950 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3953 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3951 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3954 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3952 version = Column("version", Integer(), nullable=False, default=0)
3955 version = Column("version", Integer(), nullable=False, default=0)
3953 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3956 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3954 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3957 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3955 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3956 deleted = Column('deleted', Boolean(), default=False)
3959 deleted = Column('deleted', Boolean(), default=False)
3957
3960
3958 author = relationship('User', lazy='joined')
3961 author = relationship('User', lazy='joined')
3959 comment = relationship('ChangesetComment', cascade="all, delete")
3962 comment = relationship('ChangesetComment', cascade="all, delete")
3960
3963
3961 @classmethod
3964 @classmethod
3962 def get_version(cls, comment_id):
3965 def get_version(cls, comment_id):
3963 q = Session().query(ChangesetCommentHistory).filter(
3966 q = Session().query(ChangesetCommentHistory).filter(
3964 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3967 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3965 if q.count() == 0:
3968 if q.count() == 0:
3966 return 1
3969 return 1
3967 elif q.count() >= q[0].version:
3970 elif q.count() >= q[0].version:
3968 return q.count() + 1
3971 return q.count() + 1
3969 else:
3972 else:
3970 return q[0].version + 1
3973 return q[0].version + 1
3971
3974
3972
3975
3973 class ChangesetStatus(Base, BaseModel):
3976 class ChangesetStatus(Base, BaseModel):
3974 __tablename__ = 'changeset_statuses'
3977 __tablename__ = 'changeset_statuses'
3975 __table_args__ = (
3978 __table_args__ = (
3976 Index('cs_revision_idx', 'revision'),
3979 Index('cs_revision_idx', 'revision'),
3977 Index('cs_version_idx', 'version'),
3980 Index('cs_version_idx', 'version'),
3978 UniqueConstraint('repo_id', 'revision', 'version'),
3981 UniqueConstraint('repo_id', 'revision', 'version'),
3979 base_table_args
3982 base_table_args
3980 )
3983 )
3981
3984
3982 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3985 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3983 STATUS_APPROVED = 'approved'
3986 STATUS_APPROVED = 'approved'
3984 STATUS_REJECTED = 'rejected'
3987 STATUS_REJECTED = 'rejected'
3985 STATUS_UNDER_REVIEW = 'under_review'
3988 STATUS_UNDER_REVIEW = 'under_review'
3986 CheckConstraint,
3989 CheckConstraint,
3987 STATUSES = [
3990 STATUSES = [
3988 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3991 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3989 (STATUS_APPROVED, _("Approved")),
3992 (STATUS_APPROVED, _("Approved")),
3990 (STATUS_REJECTED, _("Rejected")),
3993 (STATUS_REJECTED, _("Rejected")),
3991 (STATUS_UNDER_REVIEW, _("Under Review")),
3994 (STATUS_UNDER_REVIEW, _("Under Review")),
3992 ]
3995 ]
3993
3996
3994 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3997 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3995 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3998 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3999 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3997 revision = Column('revision', String(40), nullable=False)
4000 revision = Column('revision', String(40), nullable=False)
3998 status = Column('status', String(128), nullable=False, default=DEFAULT)
4001 status = Column('status', String(128), nullable=False, default=DEFAULT)
3999 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4002 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4000 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4003 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4001 version = Column('version', Integer(), nullable=False, default=0)
4004 version = Column('version', Integer(), nullable=False, default=0)
4002 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4005 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4003
4006
4004 author = relationship('User', lazy='select')
4007 author = relationship('User', lazy='select')
4005 repo = relationship('Repository', lazy='select')
4008 repo = relationship('Repository', lazy='select')
4006 comment = relationship('ChangesetComment', lazy='select')
4009 comment = relationship('ChangesetComment', lazy='select')
4007 pull_request = relationship('PullRequest', lazy='select')
4010 pull_request = relationship('PullRequest', lazy='select')
4008
4011
4009 def __unicode__(self):
4012 def __unicode__(self):
4010 return u"<%s('%s[v%s]:%s')>" % (
4013 return u"<%s('%s[v%s]:%s')>" % (
4011 self.__class__.__name__,
4014 self.__class__.__name__,
4012 self.status, self.version, self.author
4015 self.status, self.version, self.author
4013 )
4016 )
4014
4017
4015 @classmethod
4018 @classmethod
4016 def get_status_lbl(cls, value):
4019 def get_status_lbl(cls, value):
4017 return dict(cls.STATUSES).get(value)
4020 return dict(cls.STATUSES).get(value)
4018
4021
4019 @property
4022 @property
4020 def status_lbl(self):
4023 def status_lbl(self):
4021 return ChangesetStatus.get_status_lbl(self.status)
4024 return ChangesetStatus.get_status_lbl(self.status)
4022
4025
4023 def get_api_data(self):
4026 def get_api_data(self):
4024 status = self
4027 status = self
4025 data = {
4028 data = {
4026 'status_id': status.changeset_status_id,
4029 'status_id': status.changeset_status_id,
4027 'status': status.status,
4030 'status': status.status,
4028 }
4031 }
4029 return data
4032 return data
4030
4033
4031 def __json__(self):
4034 def __json__(self):
4032 data = dict()
4035 data = dict()
4033 data.update(self.get_api_data())
4036 data.update(self.get_api_data())
4034 return data
4037 return data
4035
4038
4036
4039
4037 class _SetState(object):
4040 class _SetState(object):
4038 """
4041 """
4039 Context processor allowing changing state for sensitive operation such as
4042 Context processor allowing changing state for sensitive operation such as
4040 pull request update or merge
4043 pull request update or merge
4041 """
4044 """
4042
4045
4043 def __init__(self, pull_request, pr_state, back_state=None):
4046 def __init__(self, pull_request, pr_state, back_state=None):
4044 self._pr = pull_request
4047 self._pr = pull_request
4045 self._org_state = back_state or pull_request.pull_request_state
4048 self._org_state = back_state or pull_request.pull_request_state
4046 self._pr_state = pr_state
4049 self._pr_state = pr_state
4047 self._current_state = None
4050 self._current_state = None
4048
4051
4049 def __enter__(self):
4052 def __enter__(self):
4050 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4053 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4051 self._pr, self._pr_state)
4054 self._pr, self._pr_state)
4052 self.set_pr_state(self._pr_state)
4055 self.set_pr_state(self._pr_state)
4053 return self
4056 return self
4054
4057
4055 def __exit__(self, exc_type, exc_val, exc_tb):
4058 def __exit__(self, exc_type, exc_val, exc_tb):
4056 if exc_val is not None or exc_type is not None:
4059 if exc_val is not None or exc_type is not None:
4057 log.error(traceback.format_exc(exc_tb))
4060 log.error(traceback.format_exc(exc_tb))
4058 return None
4061 return None
4059
4062
4060 self.set_pr_state(self._org_state)
4063 self.set_pr_state(self._org_state)
4061 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4064 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4062 self._pr, self._org_state)
4065 self._pr, self._org_state)
4063
4066
4064 @property
4067 @property
4065 def state(self):
4068 def state(self):
4066 return self._current_state
4069 return self._current_state
4067
4070
4068 def set_pr_state(self, pr_state):
4071 def set_pr_state(self, pr_state):
4069 try:
4072 try:
4070 self._pr.pull_request_state = pr_state
4073 self._pr.pull_request_state = pr_state
4071 Session().add(self._pr)
4074 Session().add(self._pr)
4072 Session().commit()
4075 Session().commit()
4073 self._current_state = pr_state
4076 self._current_state = pr_state
4074 except Exception:
4077 except Exception:
4075 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4078 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4076 raise
4079 raise
4077
4080
4078
4081
4079 class _PullRequestBase(BaseModel):
4082 class _PullRequestBase(BaseModel):
4080 """
4083 """
4081 Common attributes of pull request and version entries.
4084 Common attributes of pull request and version entries.
4082 """
4085 """
4083
4086
4084 # .status values
4087 # .status values
4085 STATUS_NEW = u'new'
4088 STATUS_NEW = u'new'
4086 STATUS_OPEN = u'open'
4089 STATUS_OPEN = u'open'
4087 STATUS_CLOSED = u'closed'
4090 STATUS_CLOSED = u'closed'
4088
4091
4089 # available states
4092 # available states
4090 STATE_CREATING = u'creating'
4093 STATE_CREATING = u'creating'
4091 STATE_UPDATING = u'updating'
4094 STATE_UPDATING = u'updating'
4092 STATE_MERGING = u'merging'
4095 STATE_MERGING = u'merging'
4093 STATE_CREATED = u'created'
4096 STATE_CREATED = u'created'
4094
4097
4095 title = Column('title', Unicode(255), nullable=True)
4098 title = Column('title', Unicode(255), nullable=True)
4096 description = Column(
4099 description = Column(
4097 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4100 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4098 nullable=True)
4101 nullable=True)
4099 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4102 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4100
4103
4101 # new/open/closed status of pull request (not approve/reject/etc)
4104 # new/open/closed status of pull request (not approve/reject/etc)
4102 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4105 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4103 created_on = Column(
4106 created_on = Column(
4104 'created_on', DateTime(timezone=False), nullable=False,
4107 'created_on', DateTime(timezone=False), nullable=False,
4105 default=datetime.datetime.now)
4108 default=datetime.datetime.now)
4106 updated_on = Column(
4109 updated_on = Column(
4107 'updated_on', DateTime(timezone=False), nullable=False,
4110 'updated_on', DateTime(timezone=False), nullable=False,
4108 default=datetime.datetime.now)
4111 default=datetime.datetime.now)
4109
4112
4110 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4113 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4111
4114
4112 @declared_attr
4115 @declared_attr
4113 def user_id(cls):
4116 def user_id(cls):
4114 return Column(
4117 return Column(
4115 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4118 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4116 unique=None)
4119 unique=None)
4117
4120
4118 # 500 revisions max
4121 # 500 revisions max
4119 _revisions = Column(
4122 _revisions = Column(
4120 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4123 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4121
4124
4122 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4125 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4123
4126
4124 @declared_attr
4127 @declared_attr
4125 def source_repo_id(cls):
4128 def source_repo_id(cls):
4126 # TODO: dan: rename column to source_repo_id
4129 # TODO: dan: rename column to source_repo_id
4127 return Column(
4130 return Column(
4128 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4131 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4129 nullable=False)
4132 nullable=False)
4130
4133
4131 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4134 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4132
4135
4133 @hybrid_property
4136 @hybrid_property
4134 def source_ref(self):
4137 def source_ref(self):
4135 return self._source_ref
4138 return self._source_ref
4136
4139
4137 @source_ref.setter
4140 @source_ref.setter
4138 def source_ref(self, val):
4141 def source_ref(self, val):
4139 parts = (val or '').split(':')
4142 parts = (val or '').split(':')
4140 if len(parts) != 3:
4143 if len(parts) != 3:
4141 raise ValueError(
4144 raise ValueError(
4142 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4145 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4143 self._source_ref = safe_unicode(val)
4146 self._source_ref = safe_unicode(val)
4144
4147
4145 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4148 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4146
4149
4147 @hybrid_property
4150 @hybrid_property
4148 def target_ref(self):
4151 def target_ref(self):
4149 return self._target_ref
4152 return self._target_ref
4150
4153
4151 @target_ref.setter
4154 @target_ref.setter
4152 def target_ref(self, val):
4155 def target_ref(self, val):
4153 parts = (val or '').split(':')
4156 parts = (val or '').split(':')
4154 if len(parts) != 3:
4157 if len(parts) != 3:
4155 raise ValueError(
4158 raise ValueError(
4156 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4159 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4157 self._target_ref = safe_unicode(val)
4160 self._target_ref = safe_unicode(val)
4158
4161
4159 @declared_attr
4162 @declared_attr
4160 def target_repo_id(cls):
4163 def target_repo_id(cls):
4161 # TODO: dan: rename column to target_repo_id
4164 # TODO: dan: rename column to target_repo_id
4162 return Column(
4165 return Column(
4163 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4166 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4164 nullable=False)
4167 nullable=False)
4165
4168
4166 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4169 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4167
4170
4168 # TODO: dan: rename column to last_merge_source_rev
4171 # TODO: dan: rename column to last_merge_source_rev
4169 _last_merge_source_rev = Column(
4172 _last_merge_source_rev = Column(
4170 'last_merge_org_rev', String(40), nullable=True)
4173 'last_merge_org_rev', String(40), nullable=True)
4171 # TODO: dan: rename column to last_merge_target_rev
4174 # TODO: dan: rename column to last_merge_target_rev
4172 _last_merge_target_rev = Column(
4175 _last_merge_target_rev = Column(
4173 'last_merge_other_rev', String(40), nullable=True)
4176 'last_merge_other_rev', String(40), nullable=True)
4174 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4177 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4175 last_merge_metadata = Column(
4178 last_merge_metadata = Column(
4176 'last_merge_metadata', MutationObj.as_mutable(
4179 'last_merge_metadata', MutationObj.as_mutable(
4177 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4180 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4178
4181
4179 merge_rev = Column('merge_rev', String(40), nullable=True)
4182 merge_rev = Column('merge_rev', String(40), nullable=True)
4180
4183
4181 reviewer_data = Column(
4184 reviewer_data = Column(
4182 'reviewer_data_json', MutationObj.as_mutable(
4185 'reviewer_data_json', MutationObj.as_mutable(
4183 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4186 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4184
4187
4185 @property
4188 @property
4186 def reviewer_data_json(self):
4189 def reviewer_data_json(self):
4187 return json.dumps(self.reviewer_data)
4190 return json.dumps(self.reviewer_data)
4188
4191
4189 @property
4192 @property
4190 def last_merge_metadata_parsed(self):
4193 def last_merge_metadata_parsed(self):
4191 metadata = {}
4194 metadata = {}
4192 if not self.last_merge_metadata:
4195 if not self.last_merge_metadata:
4193 return metadata
4196 return metadata
4194
4197
4195 if hasattr(self.last_merge_metadata, 'de_coerce'):
4198 if hasattr(self.last_merge_metadata, 'de_coerce'):
4196 for k, v in self.last_merge_metadata.de_coerce().items():
4199 for k, v in self.last_merge_metadata.de_coerce().items():
4197 if k in ['target_ref', 'source_ref']:
4200 if k in ['target_ref', 'source_ref']:
4198 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4201 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4199 else:
4202 else:
4200 if hasattr(v, 'de_coerce'):
4203 if hasattr(v, 'de_coerce'):
4201 metadata[k] = v.de_coerce()
4204 metadata[k] = v.de_coerce()
4202 else:
4205 else:
4203 metadata[k] = v
4206 metadata[k] = v
4204 return metadata
4207 return metadata
4205
4208
4206 @property
4209 @property
4207 def work_in_progress(self):
4210 def work_in_progress(self):
4208 """checks if pull request is work in progress by checking the title"""
4211 """checks if pull request is work in progress by checking the title"""
4209 title = self.title.upper()
4212 title = self.title.upper()
4210 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4213 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4211 return True
4214 return True
4212 return False
4215 return False
4213
4216
4214 @property
4217 @property
4215 def title_safe(self):
4218 def title_safe(self):
4216 return self.title\
4219 return self.title\
4217 .replace('{', '{{')\
4220 .replace('{', '{{')\
4218 .replace('}', '}}')
4221 .replace('}', '}}')
4219
4222
4220 @hybrid_property
4223 @hybrid_property
4221 def description_safe(self):
4224 def description_safe(self):
4222 from rhodecode.lib import helpers as h
4225 from rhodecode.lib import helpers as h
4223 return h.escape(self.description)
4226 return h.escape(self.description)
4224
4227
4225 @hybrid_property
4228 @hybrid_property
4226 def revisions(self):
4229 def revisions(self):
4227 return self._revisions.split(':') if self._revisions else []
4230 return self._revisions.split(':') if self._revisions else []
4228
4231
4229 @revisions.setter
4232 @revisions.setter
4230 def revisions(self, val):
4233 def revisions(self, val):
4231 self._revisions = u':'.join(val)
4234 self._revisions = u':'.join(val)
4232
4235
4233 @hybrid_property
4236 @hybrid_property
4234 def last_merge_status(self):
4237 def last_merge_status(self):
4235 return safe_int(self._last_merge_status)
4238 return safe_int(self._last_merge_status)
4236
4239
4237 @last_merge_status.setter
4240 @last_merge_status.setter
4238 def last_merge_status(self, val):
4241 def last_merge_status(self, val):
4239 self._last_merge_status = val
4242 self._last_merge_status = val
4240
4243
4241 @declared_attr
4244 @declared_attr
4242 def author(cls):
4245 def author(cls):
4243 return relationship('User', lazy='joined')
4246 return relationship('User', lazy='joined')
4244
4247
4245 @declared_attr
4248 @declared_attr
4246 def source_repo(cls):
4249 def source_repo(cls):
4247 return relationship(
4250 return relationship(
4248 'Repository',
4251 'Repository',
4249 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4252 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4250
4253
4251 @property
4254 @property
4252 def source_ref_parts(self):
4255 def source_ref_parts(self):
4253 return self.unicode_to_reference(self.source_ref)
4256 return self.unicode_to_reference(self.source_ref)
4254
4257
4255 @declared_attr
4258 @declared_attr
4256 def target_repo(cls):
4259 def target_repo(cls):
4257 return relationship(
4260 return relationship(
4258 'Repository',
4261 'Repository',
4259 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4262 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4260
4263
4261 @property
4264 @property
4262 def target_ref_parts(self):
4265 def target_ref_parts(self):
4263 return self.unicode_to_reference(self.target_ref)
4266 return self.unicode_to_reference(self.target_ref)
4264
4267
4265 @property
4268 @property
4266 def shadow_merge_ref(self):
4269 def shadow_merge_ref(self):
4267 return self.unicode_to_reference(self._shadow_merge_ref)
4270 return self.unicode_to_reference(self._shadow_merge_ref)
4268
4271
4269 @shadow_merge_ref.setter
4272 @shadow_merge_ref.setter
4270 def shadow_merge_ref(self, ref):
4273 def shadow_merge_ref(self, ref):
4271 self._shadow_merge_ref = self.reference_to_unicode(ref)
4274 self._shadow_merge_ref = self.reference_to_unicode(ref)
4272
4275
4273 @staticmethod
4276 @staticmethod
4274 def unicode_to_reference(raw):
4277 def unicode_to_reference(raw):
4275 return unicode_to_reference(raw)
4278 return unicode_to_reference(raw)
4276
4279
4277 @staticmethod
4280 @staticmethod
4278 def reference_to_unicode(ref):
4281 def reference_to_unicode(ref):
4279 return reference_to_unicode(ref)
4282 return reference_to_unicode(ref)
4280
4283
4281 def get_api_data(self, with_merge_state=True):
4284 def get_api_data(self, with_merge_state=True):
4282 from rhodecode.model.pull_request import PullRequestModel
4285 from rhodecode.model.pull_request import PullRequestModel
4283
4286
4284 pull_request = self
4287 pull_request = self
4285 if with_merge_state:
4288 if with_merge_state:
4286 merge_response, merge_status, msg = \
4289 merge_response, merge_status, msg = \
4287 PullRequestModel().merge_status(pull_request)
4290 PullRequestModel().merge_status(pull_request)
4288 merge_state = {
4291 merge_state = {
4289 'status': merge_status,
4292 'status': merge_status,
4290 'message': safe_unicode(msg),
4293 'message': safe_unicode(msg),
4291 }
4294 }
4292 else:
4295 else:
4293 merge_state = {'status': 'not_available',
4296 merge_state = {'status': 'not_available',
4294 'message': 'not_available'}
4297 'message': 'not_available'}
4295
4298
4296 merge_data = {
4299 merge_data = {
4297 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4300 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4298 'reference': (
4301 'reference': (
4299 pull_request.shadow_merge_ref._asdict()
4302 pull_request.shadow_merge_ref._asdict()
4300 if pull_request.shadow_merge_ref else None),
4303 if pull_request.shadow_merge_ref else None),
4301 }
4304 }
4302
4305
4303 data = {
4306 data = {
4304 'pull_request_id': pull_request.pull_request_id,
4307 'pull_request_id': pull_request.pull_request_id,
4305 'url': PullRequestModel().get_url(pull_request),
4308 'url': PullRequestModel().get_url(pull_request),
4306 'title': pull_request.title,
4309 'title': pull_request.title,
4307 'description': pull_request.description,
4310 'description': pull_request.description,
4308 'status': pull_request.status,
4311 'status': pull_request.status,
4309 'state': pull_request.pull_request_state,
4312 'state': pull_request.pull_request_state,
4310 'created_on': pull_request.created_on,
4313 'created_on': pull_request.created_on,
4311 'updated_on': pull_request.updated_on,
4314 'updated_on': pull_request.updated_on,
4312 'commit_ids': pull_request.revisions,
4315 'commit_ids': pull_request.revisions,
4313 'review_status': pull_request.calculated_review_status(),
4316 'review_status': pull_request.calculated_review_status(),
4314 'mergeable': merge_state,
4317 'mergeable': merge_state,
4315 'source': {
4318 'source': {
4316 'clone_url': pull_request.source_repo.clone_url(),
4319 'clone_url': pull_request.source_repo.clone_url(),
4317 'repository': pull_request.source_repo.repo_name,
4320 'repository': pull_request.source_repo.repo_name,
4318 'reference': {
4321 'reference': {
4319 'name': pull_request.source_ref_parts.name,
4322 'name': pull_request.source_ref_parts.name,
4320 'type': pull_request.source_ref_parts.type,
4323 'type': pull_request.source_ref_parts.type,
4321 'commit_id': pull_request.source_ref_parts.commit_id,
4324 'commit_id': pull_request.source_ref_parts.commit_id,
4322 },
4325 },
4323 },
4326 },
4324 'target': {
4327 'target': {
4325 'clone_url': pull_request.target_repo.clone_url(),
4328 'clone_url': pull_request.target_repo.clone_url(),
4326 'repository': pull_request.target_repo.repo_name,
4329 'repository': pull_request.target_repo.repo_name,
4327 'reference': {
4330 'reference': {
4328 'name': pull_request.target_ref_parts.name,
4331 'name': pull_request.target_ref_parts.name,
4329 'type': pull_request.target_ref_parts.type,
4332 'type': pull_request.target_ref_parts.type,
4330 'commit_id': pull_request.target_ref_parts.commit_id,
4333 'commit_id': pull_request.target_ref_parts.commit_id,
4331 },
4334 },
4332 },
4335 },
4333 'merge': merge_data,
4336 'merge': merge_data,
4334 'author': pull_request.author.get_api_data(include_secrets=False,
4337 'author': pull_request.author.get_api_data(include_secrets=False,
4335 details='basic'),
4338 details='basic'),
4336 'reviewers': [
4339 'reviewers': [
4337 {
4340 {
4338 'user': reviewer.get_api_data(include_secrets=False,
4341 'user': reviewer.get_api_data(include_secrets=False,
4339 details='basic'),
4342 details='basic'),
4340 'reasons': reasons,
4343 'reasons': reasons,
4341 'review_status': st[0][1].status if st else 'not_reviewed',
4344 'review_status': st[0][1].status if st else 'not_reviewed',
4342 }
4345 }
4343 for obj, reviewer, reasons, mandatory, st in
4346 for obj, reviewer, reasons, mandatory, st in
4344 pull_request.reviewers_statuses()
4347 pull_request.reviewers_statuses()
4345 ]
4348 ]
4346 }
4349 }
4347
4350
4348 return data
4351 return data
4349
4352
4350 def set_state(self, pull_request_state, final_state=None):
4353 def set_state(self, pull_request_state, final_state=None):
4351 """
4354 """
4352 # goes from initial state to updating to initial state.
4355 # goes from initial state to updating to initial state.
4353 # initial state can be changed by specifying back_state=
4356 # initial state can be changed by specifying back_state=
4354 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4357 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4355 pull_request.merge()
4358 pull_request.merge()
4356
4359
4357 :param pull_request_state:
4360 :param pull_request_state:
4358 :param final_state:
4361 :param final_state:
4359
4362
4360 """
4363 """
4361
4364
4362 return _SetState(self, pull_request_state, back_state=final_state)
4365 return _SetState(self, pull_request_state, back_state=final_state)
4363
4366
4364
4367
4365 class PullRequest(Base, _PullRequestBase):
4368 class PullRequest(Base, _PullRequestBase):
4366 __tablename__ = 'pull_requests'
4369 __tablename__ = 'pull_requests'
4367 __table_args__ = (
4370 __table_args__ = (
4368 base_table_args,
4371 base_table_args,
4369 )
4372 )
4370 LATEST_VER = 'latest'
4373 LATEST_VER = 'latest'
4371
4374
4372 pull_request_id = Column(
4375 pull_request_id = Column(
4373 'pull_request_id', Integer(), nullable=False, primary_key=True)
4376 'pull_request_id', Integer(), nullable=False, primary_key=True)
4374
4377
4375 def __repr__(self):
4378 def __repr__(self):
4376 if self.pull_request_id:
4379 if self.pull_request_id:
4377 return '<DB:PullRequest #%s>' % self.pull_request_id
4380 return '<DB:PullRequest #%s>' % self.pull_request_id
4378 else:
4381 else:
4379 return '<DB:PullRequest at %#x>' % id(self)
4382 return '<DB:PullRequest at %#x>' % id(self)
4380
4383
4381 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4384 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4382 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4385 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4383 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4386 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4384 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4387 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4385 lazy='dynamic')
4388 lazy='dynamic')
4386
4389
4387 @classmethod
4390 @classmethod
4388 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4391 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4389 internal_methods=None):
4392 internal_methods=None):
4390
4393
4391 class PullRequestDisplay(object):
4394 class PullRequestDisplay(object):
4392 """
4395 """
4393 Special object wrapper for showing PullRequest data via Versions
4396 Special object wrapper for showing PullRequest data via Versions
4394 It mimics PR object as close as possible. This is read only object
4397 It mimics PR object as close as possible. This is read only object
4395 just for display
4398 just for display
4396 """
4399 """
4397
4400
4398 def __init__(self, attrs, internal=None):
4401 def __init__(self, attrs, internal=None):
4399 self.attrs = attrs
4402 self.attrs = attrs
4400 # internal have priority over the given ones via attrs
4403 # internal have priority over the given ones via attrs
4401 self.internal = internal or ['versions']
4404 self.internal = internal or ['versions']
4402
4405
4403 def __getattr__(self, item):
4406 def __getattr__(self, item):
4404 if item in self.internal:
4407 if item in self.internal:
4405 return getattr(self, item)
4408 return getattr(self, item)
4406 try:
4409 try:
4407 return self.attrs[item]
4410 return self.attrs[item]
4408 except KeyError:
4411 except KeyError:
4409 raise AttributeError(
4412 raise AttributeError(
4410 '%s object has no attribute %s' % (self, item))
4413 '%s object has no attribute %s' % (self, item))
4411
4414
4412 def __repr__(self):
4415 def __repr__(self):
4413 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4416 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4414
4417
4415 def versions(self):
4418 def versions(self):
4416 return pull_request_obj.versions.order_by(
4419 return pull_request_obj.versions.order_by(
4417 PullRequestVersion.pull_request_version_id).all()
4420 PullRequestVersion.pull_request_version_id).all()
4418
4421
4419 def is_closed(self):
4422 def is_closed(self):
4420 return pull_request_obj.is_closed()
4423 return pull_request_obj.is_closed()
4421
4424
4422 def is_state_changing(self):
4425 def is_state_changing(self):
4423 return pull_request_obj.is_state_changing()
4426 return pull_request_obj.is_state_changing()
4424
4427
4425 @property
4428 @property
4426 def pull_request_version_id(self):
4429 def pull_request_version_id(self):
4427 return getattr(pull_request_obj, 'pull_request_version_id', None)
4430 return getattr(pull_request_obj, 'pull_request_version_id', None)
4428
4431
4429 @property
4432 @property
4430 def pull_request_last_version(self):
4433 def pull_request_last_version(self):
4431 return pull_request_obj.pull_request_last_version
4434 return pull_request_obj.pull_request_last_version
4432
4435
4433 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4436 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4434
4437
4435 attrs.author = StrictAttributeDict(
4438 attrs.author = StrictAttributeDict(
4436 pull_request_obj.author.get_api_data())
4439 pull_request_obj.author.get_api_data())
4437 if pull_request_obj.target_repo:
4440 if pull_request_obj.target_repo:
4438 attrs.target_repo = StrictAttributeDict(
4441 attrs.target_repo = StrictAttributeDict(
4439 pull_request_obj.target_repo.get_api_data())
4442 pull_request_obj.target_repo.get_api_data())
4440 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4443 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4441
4444
4442 if pull_request_obj.source_repo:
4445 if pull_request_obj.source_repo:
4443 attrs.source_repo = StrictAttributeDict(
4446 attrs.source_repo = StrictAttributeDict(
4444 pull_request_obj.source_repo.get_api_data())
4447 pull_request_obj.source_repo.get_api_data())
4445 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4448 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4446
4449
4447 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4450 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4448 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4451 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4449 attrs.revisions = pull_request_obj.revisions
4452 attrs.revisions = pull_request_obj.revisions
4450 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4453 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4451 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4454 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4452 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4455 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4453 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4456 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4454
4457
4455 return PullRequestDisplay(attrs, internal=internal_methods)
4458 return PullRequestDisplay(attrs, internal=internal_methods)
4456
4459
4457 def is_closed(self):
4460 def is_closed(self):
4458 return self.status == self.STATUS_CLOSED
4461 return self.status == self.STATUS_CLOSED
4459
4462
4460 def is_state_changing(self):
4463 def is_state_changing(self):
4461 return self.pull_request_state != PullRequest.STATE_CREATED
4464 return self.pull_request_state != PullRequest.STATE_CREATED
4462
4465
4463 def __json__(self):
4466 def __json__(self):
4464 return {
4467 return {
4465 'revisions': self.revisions,
4468 'revisions': self.revisions,
4466 'versions': self.versions_count
4469 'versions': self.versions_count
4467 }
4470 }
4468
4471
4469 def calculated_review_status(self):
4472 def calculated_review_status(self):
4470 from rhodecode.model.changeset_status import ChangesetStatusModel
4473 from rhodecode.model.changeset_status import ChangesetStatusModel
4471 return ChangesetStatusModel().calculated_review_status(self)
4474 return ChangesetStatusModel().calculated_review_status(self)
4472
4475
4473 def reviewers_statuses(self, user=None):
4476 def reviewers_statuses(self, user=None):
4474 from rhodecode.model.changeset_status import ChangesetStatusModel
4477 from rhodecode.model.changeset_status import ChangesetStatusModel
4475 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4478 return ChangesetStatusModel().reviewers_statuses(self, user=user)
4476
4479
4477 def get_pull_request_reviewers(self, role=None):
4480 def get_pull_request_reviewers(self, role=None):
4478 qry = PullRequestReviewers.query()\
4481 qry = PullRequestReviewers.query()\
4479 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4482 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4480 if role:
4483 if role:
4481 qry = qry.filter(PullRequestReviewers.role == role)
4484 qry = qry.filter(PullRequestReviewers.role == role)
4482
4485
4483 return qry.all()
4486 return qry.all()
4484
4487
4485 @property
4488 @property
4486 def reviewers_count(self):
4489 def reviewers_count(self):
4487 qry = PullRequestReviewers.query()\
4490 qry = PullRequestReviewers.query()\
4488 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4491 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4489 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4492 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4490 return qry.count()
4493 return qry.count()
4491
4494
4492 @property
4495 @property
4493 def observers_count(self):
4496 def observers_count(self):
4494 qry = PullRequestReviewers.query()\
4497 qry = PullRequestReviewers.query()\
4495 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4498 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4496 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4499 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4497 return qry.count()
4500 return qry.count()
4498
4501
4499 def observers(self):
4502 def observers(self):
4500 qry = PullRequestReviewers.query()\
4503 qry = PullRequestReviewers.query()\
4501 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4504 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4502 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4505 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4503 .all()
4506 .all()
4504
4507
4505 for entry in qry:
4508 for entry in qry:
4506 yield entry, entry.user
4509 yield entry, entry.user
4507
4510
4508 @property
4511 @property
4509 def workspace_id(self):
4512 def workspace_id(self):
4510 from rhodecode.model.pull_request import PullRequestModel
4513 from rhodecode.model.pull_request import PullRequestModel
4511 return PullRequestModel()._workspace_id(self)
4514 return PullRequestModel()._workspace_id(self)
4512
4515
4513 def get_shadow_repo(self):
4516 def get_shadow_repo(self):
4514 workspace_id = self.workspace_id
4517 workspace_id = self.workspace_id
4515 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4518 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4516 if os.path.isdir(shadow_repository_path):
4519 if os.path.isdir(shadow_repository_path):
4517 vcs_obj = self.target_repo.scm_instance()
4520 vcs_obj = self.target_repo.scm_instance()
4518 return vcs_obj.get_shadow_instance(shadow_repository_path)
4521 return vcs_obj.get_shadow_instance(shadow_repository_path)
4519
4522
4520 @property
4523 @property
4521 def versions_count(self):
4524 def versions_count(self):
4522 """
4525 """
4523 return number of versions this PR have, e.g a PR that once been
4526 return number of versions this PR have, e.g a PR that once been
4524 updated will have 2 versions
4527 updated will have 2 versions
4525 """
4528 """
4526 return self.versions.count() + 1
4529 return self.versions.count() + 1
4527
4530
4528 @property
4531 @property
4529 def pull_request_last_version(self):
4532 def pull_request_last_version(self):
4530 return self.versions_count
4533 return self.versions_count
4531
4534
4532
4535
4533 class PullRequestVersion(Base, _PullRequestBase):
4536 class PullRequestVersion(Base, _PullRequestBase):
4534 __tablename__ = 'pull_request_versions'
4537 __tablename__ = 'pull_request_versions'
4535 __table_args__ = (
4538 __table_args__ = (
4536 base_table_args,
4539 base_table_args,
4537 )
4540 )
4538
4541
4539 pull_request_version_id = Column(
4542 pull_request_version_id = Column(
4540 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4543 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4541 pull_request_id = Column(
4544 pull_request_id = Column(
4542 'pull_request_id', Integer(),
4545 'pull_request_id', Integer(),
4543 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4546 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4544 pull_request = relationship('PullRequest')
4547 pull_request = relationship('PullRequest')
4545
4548
4546 def __repr__(self):
4549 def __repr__(self):
4547 if self.pull_request_version_id:
4550 if self.pull_request_version_id:
4548 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4551 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4549 else:
4552 else:
4550 return '<DB:PullRequestVersion at %#x>' % id(self)
4553 return '<DB:PullRequestVersion at %#x>' % id(self)
4551
4554
4552 @property
4555 @property
4553 def reviewers(self):
4556 def reviewers(self):
4554 return self.pull_request.reviewers
4557 return self.pull_request.reviewers
4555 @property
4558 @property
4556 def reviewers(self):
4559 def reviewers(self):
4557 return self.pull_request.reviewers
4560 return self.pull_request.reviewers
4558
4561
4559 @property
4562 @property
4560 def versions(self):
4563 def versions(self):
4561 return self.pull_request.versions
4564 return self.pull_request.versions
4562
4565
4563 def is_closed(self):
4566 def is_closed(self):
4564 # calculate from original
4567 # calculate from original
4565 return self.pull_request.status == self.STATUS_CLOSED
4568 return self.pull_request.status == self.STATUS_CLOSED
4566
4569
4567 def is_state_changing(self):
4570 def is_state_changing(self):
4568 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4571 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4569
4572
4570 def calculated_review_status(self):
4573 def calculated_review_status(self):
4571 return self.pull_request.calculated_review_status()
4574 return self.pull_request.calculated_review_status()
4572
4575
4573 def reviewers_statuses(self):
4576 def reviewers_statuses(self):
4574 return self.pull_request.reviewers_statuses()
4577 return self.pull_request.reviewers_statuses()
4575
4578
4576 def observers(self):
4579 def observers(self):
4577 return self.pull_request.observers()
4580 return self.pull_request.observers()
4578
4581
4579
4582
4580 class PullRequestReviewers(Base, BaseModel):
4583 class PullRequestReviewers(Base, BaseModel):
4581 __tablename__ = 'pull_request_reviewers'
4584 __tablename__ = 'pull_request_reviewers'
4582 __table_args__ = (
4585 __table_args__ = (
4583 base_table_args,
4586 base_table_args,
4584 )
4587 )
4585 ROLE_REVIEWER = u'reviewer'
4588 ROLE_REVIEWER = u'reviewer'
4586 ROLE_OBSERVER = u'observer'
4589 ROLE_OBSERVER = u'observer'
4587 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4590 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4588
4591
4589 @hybrid_property
4592 @hybrid_property
4590 def reasons(self):
4593 def reasons(self):
4591 if not self._reasons:
4594 if not self._reasons:
4592 return []
4595 return []
4593 return self._reasons
4596 return self._reasons
4594
4597
4595 @reasons.setter
4598 @reasons.setter
4596 def reasons(self, val):
4599 def reasons(self, val):
4597 val = val or []
4600 val = val or []
4598 if any(not isinstance(x, str) for x in val):
4601 if any(not isinstance(x, str) for x in val):
4599 raise Exception('invalid reasons type, must be list of strings')
4602 raise Exception('invalid reasons type, must be list of strings')
4600 self._reasons = val
4603 self._reasons = val
4601
4604
4602 pull_requests_reviewers_id = Column(
4605 pull_requests_reviewers_id = Column(
4603 'pull_requests_reviewers_id', Integer(), nullable=False,
4606 'pull_requests_reviewers_id', Integer(), nullable=False,
4604 primary_key=True)
4607 primary_key=True)
4605 pull_request_id = Column(
4608 pull_request_id = Column(
4606 "pull_request_id", Integer(),
4609 "pull_request_id", Integer(),
4607 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4610 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4608 user_id = Column(
4611 user_id = Column(
4609 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4612 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4610 _reasons = Column(
4613 _reasons = Column(
4611 'reason', MutationList.as_mutable(
4614 'reason', MutationList.as_mutable(
4612 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4615 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4613
4616
4614 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4617 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4615 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4618 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4616
4619
4617 user = relationship('User')
4620 user = relationship('User')
4618 pull_request = relationship('PullRequest')
4621 pull_request = relationship('PullRequest')
4619
4622
4620 rule_data = Column(
4623 rule_data = Column(
4621 'rule_data_json',
4624 'rule_data_json',
4622 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4625 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4623
4626
4624 def rule_user_group_data(self):
4627 def rule_user_group_data(self):
4625 """
4628 """
4626 Returns the voting user group rule data for this reviewer
4629 Returns the voting user group rule data for this reviewer
4627 """
4630 """
4628
4631
4629 if self.rule_data and 'vote_rule' in self.rule_data:
4632 if self.rule_data and 'vote_rule' in self.rule_data:
4630 user_group_data = {}
4633 user_group_data = {}
4631 if 'rule_user_group_entry_id' in self.rule_data:
4634 if 'rule_user_group_entry_id' in self.rule_data:
4632 # means a group with voting rules !
4635 # means a group with voting rules !
4633 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4636 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4634 user_group_data['name'] = self.rule_data['rule_name']
4637 user_group_data['name'] = self.rule_data['rule_name']
4635 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4638 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4636
4639
4637 return user_group_data
4640 return user_group_data
4638
4641
4639 @classmethod
4642 @classmethod
4640 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4643 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4641 qry = PullRequestReviewers.query()\
4644 qry = PullRequestReviewers.query()\
4642 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4645 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4643 if role:
4646 if role:
4644 qry = qry.filter(PullRequestReviewers.role == role)
4647 qry = qry.filter(PullRequestReviewers.role == role)
4645
4648
4646 return qry.all()
4649 return qry.all()
4647
4650
4648 def __unicode__(self):
4651 def __unicode__(self):
4649 return u"<%s('id:%s')>" % (self.__class__.__name__,
4652 return u"<%s('id:%s')>" % (self.__class__.__name__,
4650 self.pull_requests_reviewers_id)
4653 self.pull_requests_reviewers_id)
4651
4654
4652
4655
4653 class Notification(Base, BaseModel):
4656 class Notification(Base, BaseModel):
4654 __tablename__ = 'notifications'
4657 __tablename__ = 'notifications'
4655 __table_args__ = (
4658 __table_args__ = (
4656 Index('notification_type_idx', 'type'),
4659 Index('notification_type_idx', 'type'),
4657 base_table_args,
4660 base_table_args,
4658 )
4661 )
4659
4662
4660 TYPE_CHANGESET_COMMENT = u'cs_comment'
4663 TYPE_CHANGESET_COMMENT = u'cs_comment'
4661 TYPE_MESSAGE = u'message'
4664 TYPE_MESSAGE = u'message'
4662 TYPE_MENTION = u'mention'
4665 TYPE_MENTION = u'mention'
4663 TYPE_REGISTRATION = u'registration'
4666 TYPE_REGISTRATION = u'registration'
4664 TYPE_PULL_REQUEST = u'pull_request'
4667 TYPE_PULL_REQUEST = u'pull_request'
4665 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4668 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4666 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4669 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4667
4670
4668 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4671 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4669 subject = Column('subject', Unicode(512), nullable=True)
4672 subject = Column('subject', Unicode(512), nullable=True)
4670 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4673 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4671 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4674 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4672 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4675 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4673 type_ = Column('type', Unicode(255))
4676 type_ = Column('type', Unicode(255))
4674
4677
4675 created_by_user = relationship('User')
4678 created_by_user = relationship('User')
4676 notifications_to_users = relationship('UserNotification', lazy='joined',
4679 notifications_to_users = relationship('UserNotification', lazy='joined',
4677 cascade="all, delete-orphan")
4680 cascade="all, delete-orphan")
4678
4681
4679 @property
4682 @property
4680 def recipients(self):
4683 def recipients(self):
4681 return [x.user for x in UserNotification.query()\
4684 return [x.user for x in UserNotification.query()\
4682 .filter(UserNotification.notification == self)\
4685 .filter(UserNotification.notification == self)\
4683 .order_by(UserNotification.user_id.asc()).all()]
4686 .order_by(UserNotification.user_id.asc()).all()]
4684
4687
4685 @classmethod
4688 @classmethod
4686 def create(cls, created_by, subject, body, recipients, type_=None):
4689 def create(cls, created_by, subject, body, recipients, type_=None):
4687 if type_ is None:
4690 if type_ is None:
4688 type_ = Notification.TYPE_MESSAGE
4691 type_ = Notification.TYPE_MESSAGE
4689
4692
4690 notification = cls()
4693 notification = cls()
4691 notification.created_by_user = created_by
4694 notification.created_by_user = created_by
4692 notification.subject = subject
4695 notification.subject = subject
4693 notification.body = body
4696 notification.body = body
4694 notification.type_ = type_
4697 notification.type_ = type_
4695 notification.created_on = datetime.datetime.now()
4698 notification.created_on = datetime.datetime.now()
4696
4699
4697 # For each recipient link the created notification to his account
4700 # For each recipient link the created notification to his account
4698 for u in recipients:
4701 for u in recipients:
4699 assoc = UserNotification()
4702 assoc = UserNotification()
4700 assoc.user_id = u.user_id
4703 assoc.user_id = u.user_id
4701 assoc.notification = notification
4704 assoc.notification = notification
4702
4705
4703 # if created_by is inside recipients mark his notification
4706 # if created_by is inside recipients mark his notification
4704 # as read
4707 # as read
4705 if u.user_id == created_by.user_id:
4708 if u.user_id == created_by.user_id:
4706 assoc.read = True
4709 assoc.read = True
4707 Session().add(assoc)
4710 Session().add(assoc)
4708
4711
4709 Session().add(notification)
4712 Session().add(notification)
4710
4713
4711 return notification
4714 return notification
4712
4715
4713
4716
4714 class UserNotification(Base, BaseModel):
4717 class UserNotification(Base, BaseModel):
4715 __tablename__ = 'user_to_notification'
4718 __tablename__ = 'user_to_notification'
4716 __table_args__ = (
4719 __table_args__ = (
4717 UniqueConstraint('user_id', 'notification_id'),
4720 UniqueConstraint('user_id', 'notification_id'),
4718 base_table_args
4721 base_table_args
4719 )
4722 )
4720
4723
4721 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4724 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4722 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4725 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4723 read = Column('read', Boolean, default=False)
4726 read = Column('read', Boolean, default=False)
4724 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4727 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4725
4728
4726 user = relationship('User', lazy="joined")
4729 user = relationship('User', lazy="joined")
4727 notification = relationship('Notification', lazy="joined",
4730 notification = relationship('Notification', lazy="joined",
4728 order_by=lambda: Notification.created_on.desc(),)
4731 order_by=lambda: Notification.created_on.desc(),)
4729
4732
4730 def mark_as_read(self):
4733 def mark_as_read(self):
4731 self.read = True
4734 self.read = True
4732 Session().add(self)
4735 Session().add(self)
4733
4736
4734
4737
4735 class UserNotice(Base, BaseModel):
4738 class UserNotice(Base, BaseModel):
4736 __tablename__ = 'user_notices'
4739 __tablename__ = 'user_notices'
4737 __table_args__ = (
4740 __table_args__ = (
4738 base_table_args
4741 base_table_args
4739 )
4742 )
4740
4743
4741 NOTIFICATION_TYPE_MESSAGE = 'message'
4744 NOTIFICATION_TYPE_MESSAGE = 'message'
4742 NOTIFICATION_TYPE_NOTICE = 'notice'
4745 NOTIFICATION_TYPE_NOTICE = 'notice'
4743
4746
4744 NOTIFICATION_LEVEL_INFO = 'info'
4747 NOTIFICATION_LEVEL_INFO = 'info'
4745 NOTIFICATION_LEVEL_WARNING = 'warning'
4748 NOTIFICATION_LEVEL_WARNING = 'warning'
4746 NOTIFICATION_LEVEL_ERROR = 'error'
4749 NOTIFICATION_LEVEL_ERROR = 'error'
4747
4750
4748 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4751 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4749
4752
4750 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4753 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4751 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4754 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4752
4755
4753 notice_read = Column('notice_read', Boolean, default=False)
4756 notice_read = Column('notice_read', Boolean, default=False)
4754
4757
4755 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4758 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4756 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4759 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4757
4760
4758 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4761 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4759 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4762 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4760
4763
4761 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4764 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4762 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4765 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4763
4766
4764 @classmethod
4767 @classmethod
4765 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4768 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4766
4769
4767 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4770 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4768 cls.NOTIFICATION_LEVEL_WARNING,
4771 cls.NOTIFICATION_LEVEL_WARNING,
4769 cls.NOTIFICATION_LEVEL_INFO]:
4772 cls.NOTIFICATION_LEVEL_INFO]:
4770 return
4773 return
4771
4774
4772 from rhodecode.model.user import UserModel
4775 from rhodecode.model.user import UserModel
4773 user = UserModel().get_user(user)
4776 user = UserModel().get_user(user)
4774
4777
4775 new_notice = UserNotice()
4778 new_notice = UserNotice()
4776 if not allow_duplicate:
4779 if not allow_duplicate:
4777 existing_msg = UserNotice().query() \
4780 existing_msg = UserNotice().query() \
4778 .filter(UserNotice.user == user) \
4781 .filter(UserNotice.user == user) \
4779 .filter(UserNotice.notice_body == body) \
4782 .filter(UserNotice.notice_body == body) \
4780 .filter(UserNotice.notice_read == false()) \
4783 .filter(UserNotice.notice_read == false()) \
4781 .scalar()
4784 .scalar()
4782 if existing_msg:
4785 if existing_msg:
4783 log.warning('Ignoring duplicate notice for user %s', user)
4786 log.warning('Ignoring duplicate notice for user %s', user)
4784 return
4787 return
4785
4788
4786 new_notice.user = user
4789 new_notice.user = user
4787 new_notice.notice_subject = subject
4790 new_notice.notice_subject = subject
4788 new_notice.notice_body = body
4791 new_notice.notice_body = body
4789 new_notice.notification_level = notice_level
4792 new_notice.notification_level = notice_level
4790 Session().add(new_notice)
4793 Session().add(new_notice)
4791 Session().commit()
4794 Session().commit()
4792
4795
4793
4796
4794 class Gist(Base, BaseModel):
4797 class Gist(Base, BaseModel):
4795 __tablename__ = 'gists'
4798 __tablename__ = 'gists'
4796 __table_args__ = (
4799 __table_args__ = (
4797 Index('g_gist_access_id_idx', 'gist_access_id'),
4800 Index('g_gist_access_id_idx', 'gist_access_id'),
4798 Index('g_created_on_idx', 'created_on'),
4801 Index('g_created_on_idx', 'created_on'),
4799 base_table_args
4802 base_table_args
4800 )
4803 )
4801
4804
4802 GIST_PUBLIC = u'public'
4805 GIST_PUBLIC = u'public'
4803 GIST_PRIVATE = u'private'
4806 GIST_PRIVATE = u'private'
4804 DEFAULT_FILENAME = u'gistfile1.txt'
4807 DEFAULT_FILENAME = u'gistfile1.txt'
4805
4808
4806 ACL_LEVEL_PUBLIC = u'acl_public'
4809 ACL_LEVEL_PUBLIC = u'acl_public'
4807 ACL_LEVEL_PRIVATE = u'acl_private'
4810 ACL_LEVEL_PRIVATE = u'acl_private'
4808
4811
4809 gist_id = Column('gist_id', Integer(), primary_key=True)
4812 gist_id = Column('gist_id', Integer(), primary_key=True)
4810 gist_access_id = Column('gist_access_id', Unicode(250))
4813 gist_access_id = Column('gist_access_id', Unicode(250))
4811 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4814 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4812 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4815 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4813 gist_expires = Column('gist_expires', Float(53), nullable=False)
4816 gist_expires = Column('gist_expires', Float(53), nullable=False)
4814 gist_type = Column('gist_type', Unicode(128), nullable=False)
4817 gist_type = Column('gist_type', Unicode(128), nullable=False)
4815 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4818 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4816 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4819 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4817 acl_level = Column('acl_level', Unicode(128), nullable=True)
4820 acl_level = Column('acl_level', Unicode(128), nullable=True)
4818
4821
4819 owner = relationship('User')
4822 owner = relationship('User')
4820
4823
4821 def __repr__(self):
4824 def __repr__(self):
4822 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4825 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4823
4826
4824 @hybrid_property
4827 @hybrid_property
4825 def description_safe(self):
4828 def description_safe(self):
4826 from rhodecode.lib import helpers as h
4829 from rhodecode.lib import helpers as h
4827 return h.escape(self.gist_description)
4830 return h.escape(self.gist_description)
4828
4831
4829 @classmethod
4832 @classmethod
4830 def get_or_404(cls, id_):
4833 def get_or_404(cls, id_):
4831 from pyramid.httpexceptions import HTTPNotFound
4834 from pyramid.httpexceptions import HTTPNotFound
4832
4835
4833 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4836 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4834 if not res:
4837 if not res:
4835 log.debug('WARN: No DB entry with id %s', id_)
4838 log.debug('WARN: No DB entry with id %s', id_)
4836 raise HTTPNotFound()
4839 raise HTTPNotFound()
4837 return res
4840 return res
4838
4841
4839 @classmethod
4842 @classmethod
4840 def get_by_access_id(cls, gist_access_id):
4843 def get_by_access_id(cls, gist_access_id):
4841 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4844 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4842
4845
4843 def gist_url(self):
4846 def gist_url(self):
4844 from rhodecode.model.gist import GistModel
4847 from rhodecode.model.gist import GistModel
4845 return GistModel().get_url(self)
4848 return GistModel().get_url(self)
4846
4849
4847 @classmethod
4850 @classmethod
4848 def base_path(cls):
4851 def base_path(cls):
4849 """
4852 """
4850 Returns base path when all gists are stored
4853 Returns base path when all gists are stored
4851
4854
4852 :param cls:
4855 :param cls:
4853 """
4856 """
4854 from rhodecode.model.gist import GIST_STORE_LOC
4857 from rhodecode.model.gist import GIST_STORE_LOC
4855 q = Session().query(RhodeCodeUi)\
4858 q = Session().query(RhodeCodeUi)\
4856 .filter(RhodeCodeUi.ui_key == URL_SEP)
4859 .filter(RhodeCodeUi.ui_key == URL_SEP)
4857 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4860 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4858 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4861 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4859
4862
4860 def get_api_data(self):
4863 def get_api_data(self):
4861 """
4864 """
4862 Common function for generating gist related data for API
4865 Common function for generating gist related data for API
4863 """
4866 """
4864 gist = self
4867 gist = self
4865 data = {
4868 data = {
4866 'gist_id': gist.gist_id,
4869 'gist_id': gist.gist_id,
4867 'type': gist.gist_type,
4870 'type': gist.gist_type,
4868 'access_id': gist.gist_access_id,
4871 'access_id': gist.gist_access_id,
4869 'description': gist.gist_description,
4872 'description': gist.gist_description,
4870 'url': gist.gist_url(),
4873 'url': gist.gist_url(),
4871 'expires': gist.gist_expires,
4874 'expires': gist.gist_expires,
4872 'created_on': gist.created_on,
4875 'created_on': gist.created_on,
4873 'modified_at': gist.modified_at,
4876 'modified_at': gist.modified_at,
4874 'content': None,
4877 'content': None,
4875 'acl_level': gist.acl_level,
4878 'acl_level': gist.acl_level,
4876 }
4879 }
4877 return data
4880 return data
4878
4881
4879 def __json__(self):
4882 def __json__(self):
4880 data = dict(
4883 data = dict(
4881 )
4884 )
4882 data.update(self.get_api_data())
4885 data.update(self.get_api_data())
4883 return data
4886 return data
4884 # SCM functions
4887 # SCM functions
4885
4888
4886 def scm_instance(self, **kwargs):
4889 def scm_instance(self, **kwargs):
4887 """
4890 """
4888 Get an instance of VCS Repository
4891 Get an instance of VCS Repository
4889
4892
4890 :param kwargs:
4893 :param kwargs:
4891 """
4894 """
4892 from rhodecode.model.gist import GistModel
4895 from rhodecode.model.gist import GistModel
4893 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4896 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4894 return get_vcs_instance(
4897 return get_vcs_instance(
4895 repo_path=safe_str(full_repo_path), create=False,
4898 repo_path=safe_str(full_repo_path), create=False,
4896 _vcs_alias=GistModel.vcs_backend)
4899 _vcs_alias=GistModel.vcs_backend)
4897
4900
4898
4901
4899 class ExternalIdentity(Base, BaseModel):
4902 class ExternalIdentity(Base, BaseModel):
4900 __tablename__ = 'external_identities'
4903 __tablename__ = 'external_identities'
4901 __table_args__ = (
4904 __table_args__ = (
4902 Index('local_user_id_idx', 'local_user_id'),
4905 Index('local_user_id_idx', 'local_user_id'),
4903 Index('external_id_idx', 'external_id'),
4906 Index('external_id_idx', 'external_id'),
4904 base_table_args
4907 base_table_args
4905 )
4908 )
4906
4909
4907 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4910 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4908 external_username = Column('external_username', Unicode(1024), default=u'')
4911 external_username = Column('external_username', Unicode(1024), default=u'')
4909 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4912 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4910 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4913 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4911 access_token = Column('access_token', String(1024), default=u'')
4914 access_token = Column('access_token', String(1024), default=u'')
4912 alt_token = Column('alt_token', String(1024), default=u'')
4915 alt_token = Column('alt_token', String(1024), default=u'')
4913 token_secret = Column('token_secret', String(1024), default=u'')
4916 token_secret = Column('token_secret', String(1024), default=u'')
4914
4917
4915 @classmethod
4918 @classmethod
4916 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4919 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4917 """
4920 """
4918 Returns ExternalIdentity instance based on search params
4921 Returns ExternalIdentity instance based on search params
4919
4922
4920 :param external_id:
4923 :param external_id:
4921 :param provider_name:
4924 :param provider_name:
4922 :return: ExternalIdentity
4925 :return: ExternalIdentity
4923 """
4926 """
4924 query = cls.query()
4927 query = cls.query()
4925 query = query.filter(cls.external_id == external_id)
4928 query = query.filter(cls.external_id == external_id)
4926 query = query.filter(cls.provider_name == provider_name)
4929 query = query.filter(cls.provider_name == provider_name)
4927 if local_user_id:
4930 if local_user_id:
4928 query = query.filter(cls.local_user_id == local_user_id)
4931 query = query.filter(cls.local_user_id == local_user_id)
4929 return query.first()
4932 return query.first()
4930
4933
4931 @classmethod
4934 @classmethod
4932 def user_by_external_id_and_provider(cls, external_id, provider_name):
4935 def user_by_external_id_and_provider(cls, external_id, provider_name):
4933 """
4936 """
4934 Returns User instance based on search params
4937 Returns User instance based on search params
4935
4938
4936 :param external_id:
4939 :param external_id:
4937 :param provider_name:
4940 :param provider_name:
4938 :return: User
4941 :return: User
4939 """
4942 """
4940 query = User.query()
4943 query = User.query()
4941 query = query.filter(cls.external_id == external_id)
4944 query = query.filter(cls.external_id == external_id)
4942 query = query.filter(cls.provider_name == provider_name)
4945 query = query.filter(cls.provider_name == provider_name)
4943 query = query.filter(User.user_id == cls.local_user_id)
4946 query = query.filter(User.user_id == cls.local_user_id)
4944 return query.first()
4947 return query.first()
4945
4948
4946 @classmethod
4949 @classmethod
4947 def by_local_user_id(cls, local_user_id):
4950 def by_local_user_id(cls, local_user_id):
4948 """
4951 """
4949 Returns all tokens for user
4952 Returns all tokens for user
4950
4953
4951 :param local_user_id:
4954 :param local_user_id:
4952 :return: ExternalIdentity
4955 :return: ExternalIdentity
4953 """
4956 """
4954 query = cls.query()
4957 query = cls.query()
4955 query = query.filter(cls.local_user_id == local_user_id)
4958 query = query.filter(cls.local_user_id == local_user_id)
4956 return query
4959 return query
4957
4960
4958 @classmethod
4961 @classmethod
4959 def load_provider_plugin(cls, plugin_id):
4962 def load_provider_plugin(cls, plugin_id):
4960 from rhodecode.authentication.base import loadplugin
4963 from rhodecode.authentication.base import loadplugin
4961 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4964 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4962 auth_plugin = loadplugin(_plugin_id)
4965 auth_plugin = loadplugin(_plugin_id)
4963 return auth_plugin
4966 return auth_plugin
4964
4967
4965
4968
4966 class Integration(Base, BaseModel):
4969 class Integration(Base, BaseModel):
4967 __tablename__ = 'integrations'
4970 __tablename__ = 'integrations'
4968 __table_args__ = (
4971 __table_args__ = (
4969 base_table_args
4972 base_table_args
4970 )
4973 )
4971
4974
4972 integration_id = Column('integration_id', Integer(), primary_key=True)
4975 integration_id = Column('integration_id', Integer(), primary_key=True)
4973 integration_type = Column('integration_type', String(255))
4976 integration_type = Column('integration_type', String(255))
4974 enabled = Column('enabled', Boolean(), nullable=False)
4977 enabled = Column('enabled', Boolean(), nullable=False)
4975 name = Column('name', String(255), nullable=False)
4978 name = Column('name', String(255), nullable=False)
4976 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4979 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4977 default=False)
4980 default=False)
4978
4981
4979 settings = Column(
4982 settings = Column(
4980 'settings_json', MutationObj.as_mutable(
4983 'settings_json', MutationObj.as_mutable(
4981 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4984 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4982 repo_id = Column(
4985 repo_id = Column(
4983 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4986 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4984 nullable=True, unique=None, default=None)
4987 nullable=True, unique=None, default=None)
4985 repo = relationship('Repository', lazy='joined')
4988 repo = relationship('Repository', lazy='joined')
4986
4989
4987 repo_group_id = Column(
4990 repo_group_id = Column(
4988 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4991 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4989 nullable=True, unique=None, default=None)
4992 nullable=True, unique=None, default=None)
4990 repo_group = relationship('RepoGroup', lazy='joined')
4993 repo_group = relationship('RepoGroup', lazy='joined')
4991
4994
4992 @property
4995 @property
4993 def scope(self):
4996 def scope(self):
4994 if self.repo:
4997 if self.repo:
4995 return repr(self.repo)
4998 return repr(self.repo)
4996 if self.repo_group:
4999 if self.repo_group:
4997 if self.child_repos_only:
5000 if self.child_repos_only:
4998 return repr(self.repo_group) + ' (child repos only)'
5001 return repr(self.repo_group) + ' (child repos only)'
4999 else:
5002 else:
5000 return repr(self.repo_group) + ' (recursive)'
5003 return repr(self.repo_group) + ' (recursive)'
5001 if self.child_repos_only:
5004 if self.child_repos_only:
5002 return 'root_repos'
5005 return 'root_repos'
5003 return 'global'
5006 return 'global'
5004
5007
5005 def __repr__(self):
5008 def __repr__(self):
5006 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5009 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5007
5010
5008
5011
5009 class RepoReviewRuleUser(Base, BaseModel):
5012 class RepoReviewRuleUser(Base, BaseModel):
5010 __tablename__ = 'repo_review_rules_users'
5013 __tablename__ = 'repo_review_rules_users'
5011 __table_args__ = (
5014 __table_args__ = (
5012 base_table_args
5015 base_table_args
5013 )
5016 )
5014 ROLE_REVIEWER = u'reviewer'
5017 ROLE_REVIEWER = u'reviewer'
5015 ROLE_OBSERVER = u'observer'
5018 ROLE_OBSERVER = u'observer'
5016 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5019 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5017
5020
5018 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5021 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5019 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5022 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5023 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5021 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5024 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5022 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5025 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5023 user = relationship('User')
5026 user = relationship('User')
5024
5027
5025 def rule_data(self):
5028 def rule_data(self):
5026 return {
5029 return {
5027 'mandatory': self.mandatory,
5030 'mandatory': self.mandatory,
5028 'role': self.role,
5031 'role': self.role,
5029 }
5032 }
5030
5033
5031
5034
5032 class RepoReviewRuleUserGroup(Base, BaseModel):
5035 class RepoReviewRuleUserGroup(Base, BaseModel):
5033 __tablename__ = 'repo_review_rules_users_groups'
5036 __tablename__ = 'repo_review_rules_users_groups'
5034 __table_args__ = (
5037 __table_args__ = (
5035 base_table_args
5038 base_table_args
5036 )
5039 )
5037
5040
5038 VOTE_RULE_ALL = -1
5041 VOTE_RULE_ALL = -1
5039 ROLE_REVIEWER = u'reviewer'
5042 ROLE_REVIEWER = u'reviewer'
5040 ROLE_OBSERVER = u'observer'
5043 ROLE_OBSERVER = u'observer'
5041 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5044 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5042
5045
5043 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5046 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5044 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5047 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5045 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5048 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5046 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5049 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5047 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5050 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5048 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5051 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5049 users_group = relationship('UserGroup')
5052 users_group = relationship('UserGroup')
5050
5053
5051 def rule_data(self):
5054 def rule_data(self):
5052 return {
5055 return {
5053 'mandatory': self.mandatory,
5056 'mandatory': self.mandatory,
5054 'role': self.role,
5057 'role': self.role,
5055 'vote_rule': self.vote_rule
5058 'vote_rule': self.vote_rule
5056 }
5059 }
5057
5060
5058 @property
5061 @property
5059 def vote_rule_label(self):
5062 def vote_rule_label(self):
5060 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5063 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5061 return 'all must vote'
5064 return 'all must vote'
5062 else:
5065 else:
5063 return 'min. vote {}'.format(self.vote_rule)
5066 return 'min. vote {}'.format(self.vote_rule)
5064
5067
5065
5068
5066 class RepoReviewRule(Base, BaseModel):
5069 class RepoReviewRule(Base, BaseModel):
5067 __tablename__ = 'repo_review_rules'
5070 __tablename__ = 'repo_review_rules'
5068 __table_args__ = (
5071 __table_args__ = (
5069 base_table_args
5072 base_table_args
5070 )
5073 )
5071
5074
5072 repo_review_rule_id = Column(
5075 repo_review_rule_id = Column(
5073 'repo_review_rule_id', Integer(), primary_key=True)
5076 'repo_review_rule_id', Integer(), primary_key=True)
5074 repo_id = Column(
5077 repo_id = Column(
5075 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5078 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5076 repo = relationship('Repository', backref='review_rules')
5079 repo = relationship('Repository', backref='review_rules')
5077
5080
5078 review_rule_name = Column('review_rule_name', String(255))
5081 review_rule_name = Column('review_rule_name', String(255))
5079 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5082 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5080 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5083 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5081 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5082
5085
5083 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5084
5087
5085 # Legacy fields, just for backward compat
5088 # Legacy fields, just for backward compat
5086 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5089 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5087 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5090 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5088
5091
5089 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5092 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5090 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5093 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5091
5094
5092 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5095 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5093
5096
5094 rule_users = relationship('RepoReviewRuleUser')
5097 rule_users = relationship('RepoReviewRuleUser')
5095 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5098 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5096
5099
5097 def _validate_pattern(self, value):
5100 def _validate_pattern(self, value):
5098 re.compile('^' + glob2re(value) + '$')
5101 re.compile('^' + glob2re(value) + '$')
5099
5102
5100 @hybrid_property
5103 @hybrid_property
5101 def source_branch_pattern(self):
5104 def source_branch_pattern(self):
5102 return self._branch_pattern or '*'
5105 return self._branch_pattern or '*'
5103
5106
5104 @source_branch_pattern.setter
5107 @source_branch_pattern.setter
5105 def source_branch_pattern(self, value):
5108 def source_branch_pattern(self, value):
5106 self._validate_pattern(value)
5109 self._validate_pattern(value)
5107 self._branch_pattern = value or '*'
5110 self._branch_pattern = value or '*'
5108
5111
5109 @hybrid_property
5112 @hybrid_property
5110 def target_branch_pattern(self):
5113 def target_branch_pattern(self):
5111 return self._target_branch_pattern or '*'
5114 return self._target_branch_pattern or '*'
5112
5115
5113 @target_branch_pattern.setter
5116 @target_branch_pattern.setter
5114 def target_branch_pattern(self, value):
5117 def target_branch_pattern(self, value):
5115 self._validate_pattern(value)
5118 self._validate_pattern(value)
5116 self._target_branch_pattern = value or '*'
5119 self._target_branch_pattern = value or '*'
5117
5120
5118 @hybrid_property
5121 @hybrid_property
5119 def file_pattern(self):
5122 def file_pattern(self):
5120 return self._file_pattern or '*'
5123 return self._file_pattern or '*'
5121
5124
5122 @file_pattern.setter
5125 @file_pattern.setter
5123 def file_pattern(self, value):
5126 def file_pattern(self, value):
5124 self._validate_pattern(value)
5127 self._validate_pattern(value)
5125 self._file_pattern = value or '*'
5128 self._file_pattern = value or '*'
5126
5129
5127 @hybrid_property
5130 @hybrid_property
5128 def forbid_pr_author_to_review(self):
5131 def forbid_pr_author_to_review(self):
5129 return self.pr_author == 'forbid_pr_author'
5132 return self.pr_author == 'forbid_pr_author'
5130
5133
5131 @hybrid_property
5134 @hybrid_property
5132 def include_pr_author_to_review(self):
5135 def include_pr_author_to_review(self):
5133 return self.pr_author == 'include_pr_author'
5136 return self.pr_author == 'include_pr_author'
5134
5137
5135 @hybrid_property
5138 @hybrid_property
5136 def forbid_commit_author_to_review(self):
5139 def forbid_commit_author_to_review(self):
5137 return self.commit_author == 'forbid_commit_author'
5140 return self.commit_author == 'forbid_commit_author'
5138
5141
5139 @hybrid_property
5142 @hybrid_property
5140 def include_commit_author_to_review(self):
5143 def include_commit_author_to_review(self):
5141 return self.commit_author == 'include_commit_author'
5144 return self.commit_author == 'include_commit_author'
5142
5145
5143 def matches(self, source_branch, target_branch, files_changed):
5146 def matches(self, source_branch, target_branch, files_changed):
5144 """
5147 """
5145 Check if this review rule matches a branch/files in a pull request
5148 Check if this review rule matches a branch/files in a pull request
5146
5149
5147 :param source_branch: source branch name for the commit
5150 :param source_branch: source branch name for the commit
5148 :param target_branch: target branch name for the commit
5151 :param target_branch: target branch name for the commit
5149 :param files_changed: list of file paths changed in the pull request
5152 :param files_changed: list of file paths changed in the pull request
5150 """
5153 """
5151
5154
5152 source_branch = source_branch or ''
5155 source_branch = source_branch or ''
5153 target_branch = target_branch or ''
5156 target_branch = target_branch or ''
5154 files_changed = files_changed or []
5157 files_changed = files_changed or []
5155
5158
5156 branch_matches = True
5159 branch_matches = True
5157 if source_branch or target_branch:
5160 if source_branch or target_branch:
5158 if self.source_branch_pattern == '*':
5161 if self.source_branch_pattern == '*':
5159 source_branch_match = True
5162 source_branch_match = True
5160 else:
5163 else:
5161 if self.source_branch_pattern.startswith('re:'):
5164 if self.source_branch_pattern.startswith('re:'):
5162 source_pattern = self.source_branch_pattern[3:]
5165 source_pattern = self.source_branch_pattern[3:]
5163 else:
5166 else:
5164 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5167 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5165 source_branch_regex = re.compile(source_pattern)
5168 source_branch_regex = re.compile(source_pattern)
5166 source_branch_match = bool(source_branch_regex.search(source_branch))
5169 source_branch_match = bool(source_branch_regex.search(source_branch))
5167 if self.target_branch_pattern == '*':
5170 if self.target_branch_pattern == '*':
5168 target_branch_match = True
5171 target_branch_match = True
5169 else:
5172 else:
5170 if self.target_branch_pattern.startswith('re:'):
5173 if self.target_branch_pattern.startswith('re:'):
5171 target_pattern = self.target_branch_pattern[3:]
5174 target_pattern = self.target_branch_pattern[3:]
5172 else:
5175 else:
5173 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5176 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5174 target_branch_regex = re.compile(target_pattern)
5177 target_branch_regex = re.compile(target_pattern)
5175 target_branch_match = bool(target_branch_regex.search(target_branch))
5178 target_branch_match = bool(target_branch_regex.search(target_branch))
5176
5179
5177 branch_matches = source_branch_match and target_branch_match
5180 branch_matches = source_branch_match and target_branch_match
5178
5181
5179 files_matches = True
5182 files_matches = True
5180 if self.file_pattern != '*':
5183 if self.file_pattern != '*':
5181 files_matches = False
5184 files_matches = False
5182 if self.file_pattern.startswith('re:'):
5185 if self.file_pattern.startswith('re:'):
5183 file_pattern = self.file_pattern[3:]
5186 file_pattern = self.file_pattern[3:]
5184 else:
5187 else:
5185 file_pattern = glob2re(self.file_pattern)
5188 file_pattern = glob2re(self.file_pattern)
5186 file_regex = re.compile(file_pattern)
5189 file_regex = re.compile(file_pattern)
5187 for file_data in files_changed:
5190 for file_data in files_changed:
5188 filename = file_data.get('filename')
5191 filename = file_data.get('filename')
5189
5192
5190 if file_regex.search(filename):
5193 if file_regex.search(filename):
5191 files_matches = True
5194 files_matches = True
5192 break
5195 break
5193
5196
5194 return branch_matches and files_matches
5197 return branch_matches and files_matches
5195
5198
5196 @property
5199 @property
5197 def review_users(self):
5200 def review_users(self):
5198 """ Returns the users which this rule applies to """
5201 """ Returns the users which this rule applies to """
5199
5202
5200 users = collections.OrderedDict()
5203 users = collections.OrderedDict()
5201
5204
5202 for rule_user in self.rule_users:
5205 for rule_user in self.rule_users:
5203 if rule_user.user.active:
5206 if rule_user.user.active:
5204 if rule_user.user not in users:
5207 if rule_user.user not in users:
5205 users[rule_user.user.username] = {
5208 users[rule_user.user.username] = {
5206 'user': rule_user.user,
5209 'user': rule_user.user,
5207 'source': 'user',
5210 'source': 'user',
5208 'source_data': {},
5211 'source_data': {},
5209 'data': rule_user.rule_data()
5212 'data': rule_user.rule_data()
5210 }
5213 }
5211
5214
5212 for rule_user_group in self.rule_user_groups:
5215 for rule_user_group in self.rule_user_groups:
5213 source_data = {
5216 source_data = {
5214 'user_group_id': rule_user_group.users_group.users_group_id,
5217 'user_group_id': rule_user_group.users_group.users_group_id,
5215 'name': rule_user_group.users_group.users_group_name,
5218 'name': rule_user_group.users_group.users_group_name,
5216 'members': len(rule_user_group.users_group.members)
5219 'members': len(rule_user_group.users_group.members)
5217 }
5220 }
5218 for member in rule_user_group.users_group.members:
5221 for member in rule_user_group.users_group.members:
5219 if member.user.active:
5222 if member.user.active:
5220 key = member.user.username
5223 key = member.user.username
5221 if key in users:
5224 if key in users:
5222 # skip this member as we have him already
5225 # skip this member as we have him already
5223 # this prevents from override the "first" matched
5226 # this prevents from override the "first" matched
5224 # users with duplicates in multiple groups
5227 # users with duplicates in multiple groups
5225 continue
5228 continue
5226
5229
5227 users[key] = {
5230 users[key] = {
5228 'user': member.user,
5231 'user': member.user,
5229 'source': 'user_group',
5232 'source': 'user_group',
5230 'source_data': source_data,
5233 'source_data': source_data,
5231 'data': rule_user_group.rule_data()
5234 'data': rule_user_group.rule_data()
5232 }
5235 }
5233
5236
5234 return users
5237 return users
5235
5238
5236 def user_group_vote_rule(self, user_id):
5239 def user_group_vote_rule(self, user_id):
5237
5240
5238 rules = []
5241 rules = []
5239 if not self.rule_user_groups:
5242 if not self.rule_user_groups:
5240 return rules
5243 return rules
5241
5244
5242 for user_group in self.rule_user_groups:
5245 for user_group in self.rule_user_groups:
5243 user_group_members = [x.user_id for x in user_group.users_group.members]
5246 user_group_members = [x.user_id for x in user_group.users_group.members]
5244 if user_id in user_group_members:
5247 if user_id in user_group_members:
5245 rules.append(user_group)
5248 rules.append(user_group)
5246 return rules
5249 return rules
5247
5250
5248 def __repr__(self):
5251 def __repr__(self):
5249 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5252 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5250 self.repo_review_rule_id, self.repo)
5253 self.repo_review_rule_id, self.repo)
5251
5254
5252
5255
5253 class ScheduleEntry(Base, BaseModel):
5256 class ScheduleEntry(Base, BaseModel):
5254 __tablename__ = 'schedule_entries'
5257 __tablename__ = 'schedule_entries'
5255 __table_args__ = (
5258 __table_args__ = (
5256 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5259 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5257 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5260 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5258 base_table_args,
5261 base_table_args,
5259 )
5262 )
5260
5263
5261 schedule_types = ['crontab', 'timedelta', 'integer']
5264 schedule_types = ['crontab', 'timedelta', 'integer']
5262 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5265 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5263
5266
5264 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5267 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5265 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5268 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5266 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5269 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5267
5270
5268 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5271 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5269 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5272 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5270
5273
5271 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5274 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5272 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5275 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5273
5276
5274 # task
5277 # task
5275 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5278 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5276 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5279 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5277 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5280 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5278 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5281 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5279
5282
5280 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5283 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5281 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5284 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5282
5285
5283 @hybrid_property
5286 @hybrid_property
5284 def schedule_type(self):
5287 def schedule_type(self):
5285 return self._schedule_type
5288 return self._schedule_type
5286
5289
5287 @schedule_type.setter
5290 @schedule_type.setter
5288 def schedule_type(self, val):
5291 def schedule_type(self, val):
5289 if val not in self.schedule_types:
5292 if val not in self.schedule_types:
5290 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5293 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5291 val, self.schedule_type))
5294 val, self.schedule_type))
5292
5295
5293 self._schedule_type = val
5296 self._schedule_type = val
5294
5297
5295 @classmethod
5298 @classmethod
5296 def get_uid(cls, obj):
5299 def get_uid(cls, obj):
5297 args = obj.task_args
5300 args = obj.task_args
5298 kwargs = obj.task_kwargs
5301 kwargs = obj.task_kwargs
5299 if isinstance(args, JsonRaw):
5302 if isinstance(args, JsonRaw):
5300 try:
5303 try:
5301 args = json.loads(args)
5304 args = json.loads(args)
5302 except ValueError:
5305 except ValueError:
5303 args = tuple()
5306 args = tuple()
5304
5307
5305 if isinstance(kwargs, JsonRaw):
5308 if isinstance(kwargs, JsonRaw):
5306 try:
5309 try:
5307 kwargs = json.loads(kwargs)
5310 kwargs = json.loads(kwargs)
5308 except ValueError:
5311 except ValueError:
5309 kwargs = dict()
5312 kwargs = dict()
5310
5313
5311 dot_notation = obj.task_dot_notation
5314 dot_notation = obj.task_dot_notation
5312 val = '.'.join(map(safe_str, [
5315 val = '.'.join(map(safe_str, [
5313 sorted(dot_notation), args, sorted(kwargs.items())]))
5316 sorted(dot_notation), args, sorted(kwargs.items())]))
5314 return hashlib.sha1(val).hexdigest()
5317 return hashlib.sha1(val).hexdigest()
5315
5318
5316 @classmethod
5319 @classmethod
5317 def get_by_schedule_name(cls, schedule_name):
5320 def get_by_schedule_name(cls, schedule_name):
5318 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5321 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5319
5322
5320 @classmethod
5323 @classmethod
5321 def get_by_schedule_id(cls, schedule_id):
5324 def get_by_schedule_id(cls, schedule_id):
5322 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5325 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5323
5326
5324 @property
5327 @property
5325 def task(self):
5328 def task(self):
5326 return self.task_dot_notation
5329 return self.task_dot_notation
5327
5330
5328 @property
5331 @property
5329 def schedule(self):
5332 def schedule(self):
5330 from rhodecode.lib.celerylib.utils import raw_2_schedule
5333 from rhodecode.lib.celerylib.utils import raw_2_schedule
5331 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5334 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5332 return schedule
5335 return schedule
5333
5336
5334 @property
5337 @property
5335 def args(self):
5338 def args(self):
5336 try:
5339 try:
5337 return list(self.task_args or [])
5340 return list(self.task_args or [])
5338 except ValueError:
5341 except ValueError:
5339 return list()
5342 return list()
5340
5343
5341 @property
5344 @property
5342 def kwargs(self):
5345 def kwargs(self):
5343 try:
5346 try:
5344 return dict(self.task_kwargs or {})
5347 return dict(self.task_kwargs or {})
5345 except ValueError:
5348 except ValueError:
5346 return dict()
5349 return dict()
5347
5350
5348 def _as_raw(self, val, indent=None):
5351 def _as_raw(self, val, indent=None):
5349 if hasattr(val, 'de_coerce'):
5352 if hasattr(val, 'de_coerce'):
5350 val = val.de_coerce()
5353 val = val.de_coerce()
5351 if val:
5354 if val:
5352 val = json.dumps(val, indent=indent, sort_keys=True)
5355 val = json.dumps(val, indent=indent, sort_keys=True)
5353
5356
5354 return val
5357 return val
5355
5358
5356 @property
5359 @property
5357 def schedule_definition_raw(self):
5360 def schedule_definition_raw(self):
5358 return self._as_raw(self.schedule_definition)
5361 return self._as_raw(self.schedule_definition)
5359
5362
5360 def args_raw(self, indent=None):
5363 def args_raw(self, indent=None):
5361 return self._as_raw(self.task_args, indent)
5364 return self._as_raw(self.task_args, indent)
5362
5365
5363 def kwargs_raw(self, indent=None):
5366 def kwargs_raw(self, indent=None):
5364 return self._as_raw(self.task_kwargs, indent)
5367 return self._as_raw(self.task_kwargs, indent)
5365
5368
5366 def __repr__(self):
5369 def __repr__(self):
5367 return '<DB:ScheduleEntry({}:{})>'.format(
5370 return '<DB:ScheduleEntry({}:{})>'.format(
5368 self.schedule_entry_id, self.schedule_name)
5371 self.schedule_entry_id, self.schedule_name)
5369
5372
5370
5373
5371 @event.listens_for(ScheduleEntry, 'before_update')
5374 @event.listens_for(ScheduleEntry, 'before_update')
5372 def update_task_uid(mapper, connection, target):
5375 def update_task_uid(mapper, connection, target):
5373 target.task_uid = ScheduleEntry.get_uid(target)
5376 target.task_uid = ScheduleEntry.get_uid(target)
5374
5377
5375
5378
5376 @event.listens_for(ScheduleEntry, 'before_insert')
5379 @event.listens_for(ScheduleEntry, 'before_insert')
5377 def set_task_uid(mapper, connection, target):
5380 def set_task_uid(mapper, connection, target):
5378 target.task_uid = ScheduleEntry.get_uid(target)
5381 target.task_uid = ScheduleEntry.get_uid(target)
5379
5382
5380
5383
5381 class _BaseBranchPerms(BaseModel):
5384 class _BaseBranchPerms(BaseModel):
5382 @classmethod
5385 @classmethod
5383 def compute_hash(cls, value):
5386 def compute_hash(cls, value):
5384 return sha1_safe(value)
5387 return sha1_safe(value)
5385
5388
5386 @hybrid_property
5389 @hybrid_property
5387 def branch_pattern(self):
5390 def branch_pattern(self):
5388 return self._branch_pattern or '*'
5391 return self._branch_pattern or '*'
5389
5392
5390 @hybrid_property
5393 @hybrid_property
5391 def branch_hash(self):
5394 def branch_hash(self):
5392 return self._branch_hash
5395 return self._branch_hash
5393
5396
5394 def _validate_glob(self, value):
5397 def _validate_glob(self, value):
5395 re.compile('^' + glob2re(value) + '$')
5398 re.compile('^' + glob2re(value) + '$')
5396
5399
5397 @branch_pattern.setter
5400 @branch_pattern.setter
5398 def branch_pattern(self, value):
5401 def branch_pattern(self, value):
5399 self._validate_glob(value)
5402 self._validate_glob(value)
5400 self._branch_pattern = value or '*'
5403 self._branch_pattern = value or '*'
5401 # set the Hash when setting the branch pattern
5404 # set the Hash when setting the branch pattern
5402 self._branch_hash = self.compute_hash(self._branch_pattern)
5405 self._branch_hash = self.compute_hash(self._branch_pattern)
5403
5406
5404 def matches(self, branch):
5407 def matches(self, branch):
5405 """
5408 """
5406 Check if this the branch matches entry
5409 Check if this the branch matches entry
5407
5410
5408 :param branch: branch name for the commit
5411 :param branch: branch name for the commit
5409 """
5412 """
5410
5413
5411 branch = branch or ''
5414 branch = branch or ''
5412
5415
5413 branch_matches = True
5416 branch_matches = True
5414 if branch:
5417 if branch:
5415 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5418 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5416 branch_matches = bool(branch_regex.search(branch))
5419 branch_matches = bool(branch_regex.search(branch))
5417
5420
5418 return branch_matches
5421 return branch_matches
5419
5422
5420
5423
5421 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5424 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5422 __tablename__ = 'user_to_repo_branch_permissions'
5425 __tablename__ = 'user_to_repo_branch_permissions'
5423 __table_args__ = (
5426 __table_args__ = (
5424 base_table_args
5427 base_table_args
5425 )
5428 )
5426
5429
5427 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5430 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5428
5431
5429 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5432 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5430 repo = relationship('Repository', backref='user_branch_perms')
5433 repo = relationship('Repository', backref='user_branch_perms')
5431
5434
5432 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5435 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5433 permission = relationship('Permission')
5436 permission = relationship('Permission')
5434
5437
5435 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5438 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5436 user_repo_to_perm = relationship('UserRepoToPerm')
5439 user_repo_to_perm = relationship('UserRepoToPerm')
5437
5440
5438 rule_order = Column('rule_order', Integer(), nullable=False)
5441 rule_order = Column('rule_order', Integer(), nullable=False)
5439 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5442 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5440 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5443 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5441
5444
5442 def __unicode__(self):
5445 def __unicode__(self):
5443 return u'<UserBranchPermission(%s => %r)>' % (
5446 return u'<UserBranchPermission(%s => %r)>' % (
5444 self.user_repo_to_perm, self.branch_pattern)
5447 self.user_repo_to_perm, self.branch_pattern)
5445
5448
5446
5449
5447 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5450 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5448 __tablename__ = 'user_group_to_repo_branch_permissions'
5451 __tablename__ = 'user_group_to_repo_branch_permissions'
5449 __table_args__ = (
5452 __table_args__ = (
5450 base_table_args
5453 base_table_args
5451 )
5454 )
5452
5455
5453 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5456 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5454
5457
5455 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5458 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5456 repo = relationship('Repository', backref='user_group_branch_perms')
5459 repo = relationship('Repository', backref='user_group_branch_perms')
5457
5460
5458 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5461 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5459 permission = relationship('Permission')
5462 permission = relationship('Permission')
5460
5463
5461 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5464 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5462 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5465 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5463
5466
5464 rule_order = Column('rule_order', Integer(), nullable=False)
5467 rule_order = Column('rule_order', Integer(), nullable=False)
5465 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5468 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5466 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5469 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5467
5470
5468 def __unicode__(self):
5471 def __unicode__(self):
5469 return u'<UserBranchPermission(%s => %r)>' % (
5472 return u'<UserBranchPermission(%s => %r)>' % (
5470 self.user_group_repo_to_perm, self.branch_pattern)
5473 self.user_group_repo_to_perm, self.branch_pattern)
5471
5474
5472
5475
5473 class UserBookmark(Base, BaseModel):
5476 class UserBookmark(Base, BaseModel):
5474 __tablename__ = 'user_bookmarks'
5477 __tablename__ = 'user_bookmarks'
5475 __table_args__ = (
5478 __table_args__ = (
5476 UniqueConstraint('user_id', 'bookmark_repo_id'),
5479 UniqueConstraint('user_id', 'bookmark_repo_id'),
5477 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5480 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5478 UniqueConstraint('user_id', 'bookmark_position'),
5481 UniqueConstraint('user_id', 'bookmark_position'),
5479 base_table_args
5482 base_table_args
5480 )
5483 )
5481
5484
5482 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5485 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5484 position = Column("bookmark_position", Integer(), nullable=False)
5487 position = Column("bookmark_position", Integer(), nullable=False)
5485 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5488 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5486 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5489 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5487 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5490 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5488
5491
5489 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5492 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5490 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5493 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5491
5494
5492 user = relationship("User")
5495 user = relationship("User")
5493
5496
5494 repository = relationship("Repository")
5497 repository = relationship("Repository")
5495 repository_group = relationship("RepoGroup")
5498 repository_group = relationship("RepoGroup")
5496
5499
5497 @classmethod
5500 @classmethod
5498 def get_by_position_for_user(cls, position, user_id):
5501 def get_by_position_for_user(cls, position, user_id):
5499 return cls.query() \
5502 return cls.query() \
5500 .filter(UserBookmark.user_id == user_id) \
5503 .filter(UserBookmark.user_id == user_id) \
5501 .filter(UserBookmark.position == position).scalar()
5504 .filter(UserBookmark.position == position).scalar()
5502
5505
5503 @classmethod
5506 @classmethod
5504 def get_bookmarks_for_user(cls, user_id, cache=True):
5507 def get_bookmarks_for_user(cls, user_id, cache=True):
5505 bookmarks = cls.query() \
5508 bookmarks = cls.query() \
5506 .filter(UserBookmark.user_id == user_id) \
5509 .filter(UserBookmark.user_id == user_id) \
5507 .options(joinedload(UserBookmark.repository)) \
5510 .options(joinedload(UserBookmark.repository)) \
5508 .options(joinedload(UserBookmark.repository_group)) \
5511 .options(joinedload(UserBookmark.repository_group)) \
5509 .order_by(UserBookmark.position.asc())
5512 .order_by(UserBookmark.position.asc())
5510
5513
5511 if cache:
5514 if cache:
5512 bookmarks = bookmarks.options(
5515 bookmarks = bookmarks.options(
5513 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5516 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5514 )
5517 )
5515
5518
5516 return bookmarks.all()
5519 return bookmarks.all()
5517
5520
5518 def __unicode__(self):
5521 def __unicode__(self):
5519 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5522 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5520
5523
5521
5524
5522 class FileStore(Base, BaseModel):
5525 class FileStore(Base, BaseModel):
5523 __tablename__ = 'file_store'
5526 __tablename__ = 'file_store'
5524 __table_args__ = (
5527 __table_args__ = (
5525 base_table_args
5528 base_table_args
5526 )
5529 )
5527
5530
5528 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5531 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5529 file_uid = Column('file_uid', String(1024), nullable=False)
5532 file_uid = Column('file_uid', String(1024), nullable=False)
5530 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5533 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5531 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5534 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5532 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5535 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5533
5536
5534 # sha256 hash
5537 # sha256 hash
5535 file_hash = Column('file_hash', String(512), nullable=False)
5538 file_hash = Column('file_hash', String(512), nullable=False)
5536 file_size = Column('file_size', BigInteger(), nullable=False)
5539 file_size = Column('file_size', BigInteger(), nullable=False)
5537
5540
5538 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5541 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5539 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5542 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5540 accessed_count = Column('accessed_count', Integer(), default=0)
5543 accessed_count = Column('accessed_count', Integer(), default=0)
5541
5544
5542 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5545 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5543
5546
5544 # if repo/repo_group reference is set, check for permissions
5547 # if repo/repo_group reference is set, check for permissions
5545 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5548 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5546
5549
5547 # hidden defines an attachment that should be hidden from showing in artifact listing
5550 # hidden defines an attachment that should be hidden from showing in artifact listing
5548 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5551 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5549
5552
5550 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5553 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5551 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5554 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5552
5555
5553 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5556 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5554
5557
5555 # scope limited to user, which requester have access to
5558 # scope limited to user, which requester have access to
5556 scope_user_id = Column(
5559 scope_user_id = Column(
5557 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5560 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5558 nullable=True, unique=None, default=None)
5561 nullable=True, unique=None, default=None)
5559 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5562 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5560
5563
5561 # scope limited to user group, which requester have access to
5564 # scope limited to user group, which requester have access to
5562 scope_user_group_id = Column(
5565 scope_user_group_id = Column(
5563 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5566 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5564 nullable=True, unique=None, default=None)
5567 nullable=True, unique=None, default=None)
5565 user_group = relationship('UserGroup', lazy='joined')
5568 user_group = relationship('UserGroup', lazy='joined')
5566
5569
5567 # scope limited to repo, which requester have access to
5570 # scope limited to repo, which requester have access to
5568 scope_repo_id = Column(
5571 scope_repo_id = Column(
5569 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5572 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5570 nullable=True, unique=None, default=None)
5573 nullable=True, unique=None, default=None)
5571 repo = relationship('Repository', lazy='joined')
5574 repo = relationship('Repository', lazy='joined')
5572
5575
5573 # scope limited to repo group, which requester have access to
5576 # scope limited to repo group, which requester have access to
5574 scope_repo_group_id = Column(
5577 scope_repo_group_id = Column(
5575 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5578 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5576 nullable=True, unique=None, default=None)
5579 nullable=True, unique=None, default=None)
5577 repo_group = relationship('RepoGroup', lazy='joined')
5580 repo_group = relationship('RepoGroup', lazy='joined')
5578
5581
5579 @classmethod
5582 @classmethod
5580 def get_by_store_uid(cls, file_store_uid, safe=False):
5583 def get_by_store_uid(cls, file_store_uid, safe=False):
5581 if safe:
5584 if safe:
5582 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5585 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5583 else:
5586 else:
5584 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5587 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5585
5588
5586 @classmethod
5589 @classmethod
5587 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5590 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5588 file_description='', enabled=True, hidden=False, check_acl=True,
5591 file_description='', enabled=True, hidden=False, check_acl=True,
5589 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5592 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5590
5593
5591 store_entry = FileStore()
5594 store_entry = FileStore()
5592 store_entry.file_uid = file_uid
5595 store_entry.file_uid = file_uid
5593 store_entry.file_display_name = file_display_name
5596 store_entry.file_display_name = file_display_name
5594 store_entry.file_org_name = filename
5597 store_entry.file_org_name = filename
5595 store_entry.file_size = file_size
5598 store_entry.file_size = file_size
5596 store_entry.file_hash = file_hash
5599 store_entry.file_hash = file_hash
5597 store_entry.file_description = file_description
5600 store_entry.file_description = file_description
5598
5601
5599 store_entry.check_acl = check_acl
5602 store_entry.check_acl = check_acl
5600 store_entry.enabled = enabled
5603 store_entry.enabled = enabled
5601 store_entry.hidden = hidden
5604 store_entry.hidden = hidden
5602
5605
5603 store_entry.user_id = user_id
5606 store_entry.user_id = user_id
5604 store_entry.scope_user_id = scope_user_id
5607 store_entry.scope_user_id = scope_user_id
5605 store_entry.scope_repo_id = scope_repo_id
5608 store_entry.scope_repo_id = scope_repo_id
5606 store_entry.scope_repo_group_id = scope_repo_group_id
5609 store_entry.scope_repo_group_id = scope_repo_group_id
5607
5610
5608 return store_entry
5611 return store_entry
5609
5612
5610 @classmethod
5613 @classmethod
5611 def store_metadata(cls, file_store_id, args, commit=True):
5614 def store_metadata(cls, file_store_id, args, commit=True):
5612 file_store = FileStore.get(file_store_id)
5615 file_store = FileStore.get(file_store_id)
5613 if file_store is None:
5616 if file_store is None:
5614 return
5617 return
5615
5618
5616 for section, key, value, value_type in args:
5619 for section, key, value, value_type in args:
5617 has_key = FileStoreMetadata().query() \
5620 has_key = FileStoreMetadata().query() \
5618 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5621 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5619 .filter(FileStoreMetadata.file_store_meta_section == section) \
5622 .filter(FileStoreMetadata.file_store_meta_section == section) \
5620 .filter(FileStoreMetadata.file_store_meta_key == key) \
5623 .filter(FileStoreMetadata.file_store_meta_key == key) \
5621 .scalar()
5624 .scalar()
5622 if has_key:
5625 if has_key:
5623 msg = 'key `{}` already defined under section `{}` for this file.'\
5626 msg = 'key `{}` already defined under section `{}` for this file.'\
5624 .format(key, section)
5627 .format(key, section)
5625 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5628 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5626
5629
5627 # NOTE(marcink): raises ArtifactMetadataBadValueType
5630 # NOTE(marcink): raises ArtifactMetadataBadValueType
5628 FileStoreMetadata.valid_value_type(value_type)
5631 FileStoreMetadata.valid_value_type(value_type)
5629
5632
5630 meta_entry = FileStoreMetadata()
5633 meta_entry = FileStoreMetadata()
5631 meta_entry.file_store = file_store
5634 meta_entry.file_store = file_store
5632 meta_entry.file_store_meta_section = section
5635 meta_entry.file_store_meta_section = section
5633 meta_entry.file_store_meta_key = key
5636 meta_entry.file_store_meta_key = key
5634 meta_entry.file_store_meta_value_type = value_type
5637 meta_entry.file_store_meta_value_type = value_type
5635 meta_entry.file_store_meta_value = value
5638 meta_entry.file_store_meta_value = value
5636
5639
5637 Session().add(meta_entry)
5640 Session().add(meta_entry)
5638
5641
5639 try:
5642 try:
5640 if commit:
5643 if commit:
5641 Session().commit()
5644 Session().commit()
5642 except IntegrityError:
5645 except IntegrityError:
5643 Session().rollback()
5646 Session().rollback()
5644 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5647 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5645
5648
5646 @classmethod
5649 @classmethod
5647 def bump_access_counter(cls, file_uid, commit=True):
5650 def bump_access_counter(cls, file_uid, commit=True):
5648 FileStore().query()\
5651 FileStore().query()\
5649 .filter(FileStore.file_uid == file_uid)\
5652 .filter(FileStore.file_uid == file_uid)\
5650 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5653 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5651 FileStore.accessed_on: datetime.datetime.now()})
5654 FileStore.accessed_on: datetime.datetime.now()})
5652 if commit:
5655 if commit:
5653 Session().commit()
5656 Session().commit()
5654
5657
5655 def __json__(self):
5658 def __json__(self):
5656 data = {
5659 data = {
5657 'filename': self.file_display_name,
5660 'filename': self.file_display_name,
5658 'filename_org': self.file_org_name,
5661 'filename_org': self.file_org_name,
5659 'file_uid': self.file_uid,
5662 'file_uid': self.file_uid,
5660 'description': self.file_description,
5663 'description': self.file_description,
5661 'hidden': self.hidden,
5664 'hidden': self.hidden,
5662 'size': self.file_size,
5665 'size': self.file_size,
5663 'created_on': self.created_on,
5666 'created_on': self.created_on,
5664 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5667 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5665 'downloaded_times': self.accessed_count,
5668 'downloaded_times': self.accessed_count,
5666 'sha256': self.file_hash,
5669 'sha256': self.file_hash,
5667 'metadata': self.file_metadata,
5670 'metadata': self.file_metadata,
5668 }
5671 }
5669
5672
5670 return data
5673 return data
5671
5674
5672 def __repr__(self):
5675 def __repr__(self):
5673 return '<FileStore({})>'.format(self.file_store_id)
5676 return '<FileStore({})>'.format(self.file_store_id)
5674
5677
5675
5678
5676 class FileStoreMetadata(Base, BaseModel):
5679 class FileStoreMetadata(Base, BaseModel):
5677 __tablename__ = 'file_store_metadata'
5680 __tablename__ = 'file_store_metadata'
5678 __table_args__ = (
5681 __table_args__ = (
5679 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5682 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5680 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5683 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5681 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5684 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5682 base_table_args
5685 base_table_args
5683 )
5686 )
5684 SETTINGS_TYPES = {
5687 SETTINGS_TYPES = {
5685 'str': safe_str,
5688 'str': safe_str,
5686 'int': safe_int,
5689 'int': safe_int,
5687 'unicode': safe_unicode,
5690 'unicode': safe_unicode,
5688 'bool': str2bool,
5691 'bool': str2bool,
5689 'list': functools.partial(aslist, sep=',')
5692 'list': functools.partial(aslist, sep=',')
5690 }
5693 }
5691
5694
5692 file_store_meta_id = Column(
5695 file_store_meta_id = Column(
5693 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5696 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5694 primary_key=True)
5697 primary_key=True)
5695 _file_store_meta_section = Column(
5698 _file_store_meta_section = Column(
5696 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5699 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5697 nullable=True, unique=None, default=None)
5700 nullable=True, unique=None, default=None)
5698 _file_store_meta_section_hash = Column(
5701 _file_store_meta_section_hash = Column(
5699 "file_store_meta_section_hash", String(255),
5702 "file_store_meta_section_hash", String(255),
5700 nullable=True, unique=None, default=None)
5703 nullable=True, unique=None, default=None)
5701 _file_store_meta_key = Column(
5704 _file_store_meta_key = Column(
5702 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5705 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5703 nullable=True, unique=None, default=None)
5706 nullable=True, unique=None, default=None)
5704 _file_store_meta_key_hash = Column(
5707 _file_store_meta_key_hash = Column(
5705 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5708 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5706 _file_store_meta_value = Column(
5709 _file_store_meta_value = Column(
5707 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5710 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5708 nullable=True, unique=None, default=None)
5711 nullable=True, unique=None, default=None)
5709 _file_store_meta_value_type = Column(
5712 _file_store_meta_value_type = Column(
5710 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5713 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5711 default='unicode')
5714 default='unicode')
5712
5715
5713 file_store_id = Column(
5716 file_store_id = Column(
5714 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5717 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5715 nullable=True, unique=None, default=None)
5718 nullable=True, unique=None, default=None)
5716
5719
5717 file_store = relationship('FileStore', lazy='joined')
5720 file_store = relationship('FileStore', lazy='joined')
5718
5721
5719 @classmethod
5722 @classmethod
5720 def valid_value_type(cls, value):
5723 def valid_value_type(cls, value):
5721 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5724 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5722 raise ArtifactMetadataBadValueType(
5725 raise ArtifactMetadataBadValueType(
5723 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5726 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5724
5727
5725 @hybrid_property
5728 @hybrid_property
5726 def file_store_meta_section(self):
5729 def file_store_meta_section(self):
5727 return self._file_store_meta_section
5730 return self._file_store_meta_section
5728
5731
5729 @file_store_meta_section.setter
5732 @file_store_meta_section.setter
5730 def file_store_meta_section(self, value):
5733 def file_store_meta_section(self, value):
5731 self._file_store_meta_section = value
5734 self._file_store_meta_section = value
5732 self._file_store_meta_section_hash = _hash_key(value)
5735 self._file_store_meta_section_hash = _hash_key(value)
5733
5736
5734 @hybrid_property
5737 @hybrid_property
5735 def file_store_meta_key(self):
5738 def file_store_meta_key(self):
5736 return self._file_store_meta_key
5739 return self._file_store_meta_key
5737
5740
5738 @file_store_meta_key.setter
5741 @file_store_meta_key.setter
5739 def file_store_meta_key(self, value):
5742 def file_store_meta_key(self, value):
5740 self._file_store_meta_key = value
5743 self._file_store_meta_key = value
5741 self._file_store_meta_key_hash = _hash_key(value)
5744 self._file_store_meta_key_hash = _hash_key(value)
5742
5745
5743 @hybrid_property
5746 @hybrid_property
5744 def file_store_meta_value(self):
5747 def file_store_meta_value(self):
5745 val = self._file_store_meta_value
5748 val = self._file_store_meta_value
5746
5749
5747 if self._file_store_meta_value_type:
5750 if self._file_store_meta_value_type:
5748 # e.g unicode.encrypted == unicode
5751 # e.g unicode.encrypted == unicode
5749 _type = self._file_store_meta_value_type.split('.')[0]
5752 _type = self._file_store_meta_value_type.split('.')[0]
5750 # decode the encrypted value if it's encrypted field type
5753 # decode the encrypted value if it's encrypted field type
5751 if '.encrypted' in self._file_store_meta_value_type:
5754 if '.encrypted' in self._file_store_meta_value_type:
5752 cipher = EncryptedTextValue()
5755 cipher = EncryptedTextValue()
5753 val = safe_unicode(cipher.process_result_value(val, None))
5756 val = safe_unicode(cipher.process_result_value(val, None))
5754 # do final type conversion
5757 # do final type conversion
5755 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5758 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5756 val = converter(val)
5759 val = converter(val)
5757
5760
5758 return val
5761 return val
5759
5762
5760 @file_store_meta_value.setter
5763 @file_store_meta_value.setter
5761 def file_store_meta_value(self, val):
5764 def file_store_meta_value(self, val):
5762 val = safe_unicode(val)
5765 val = safe_unicode(val)
5763 # encode the encrypted value
5766 # encode the encrypted value
5764 if '.encrypted' in self.file_store_meta_value_type:
5767 if '.encrypted' in self.file_store_meta_value_type:
5765 cipher = EncryptedTextValue()
5768 cipher = EncryptedTextValue()
5766 val = safe_unicode(cipher.process_bind_param(val, None))
5769 val = safe_unicode(cipher.process_bind_param(val, None))
5767 self._file_store_meta_value = val
5770 self._file_store_meta_value = val
5768
5771
5769 @hybrid_property
5772 @hybrid_property
5770 def file_store_meta_value_type(self):
5773 def file_store_meta_value_type(self):
5771 return self._file_store_meta_value_type
5774 return self._file_store_meta_value_type
5772
5775
5773 @file_store_meta_value_type.setter
5776 @file_store_meta_value_type.setter
5774 def file_store_meta_value_type(self, val):
5777 def file_store_meta_value_type(self, val):
5775 # e.g unicode.encrypted
5778 # e.g unicode.encrypted
5776 self.valid_value_type(val)
5779 self.valid_value_type(val)
5777 self._file_store_meta_value_type = val
5780 self._file_store_meta_value_type = val
5778
5781
5779 def __json__(self):
5782 def __json__(self):
5780 data = {
5783 data = {
5781 'artifact': self.file_store.file_uid,
5784 'artifact': self.file_store.file_uid,
5782 'section': self.file_store_meta_section,
5785 'section': self.file_store_meta_section,
5783 'key': self.file_store_meta_key,
5786 'key': self.file_store_meta_key,
5784 'value': self.file_store_meta_value,
5787 'value': self.file_store_meta_value,
5785 }
5788 }
5786
5789
5787 return data
5790 return data
5788
5791
5789 def __repr__(self):
5792 def __repr__(self):
5790 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5793 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5791 self.file_store_meta_key, self.file_store_meta_value)
5794 self.file_store_meta_key, self.file_store_meta_value)
5792
5795
5793
5796
5794 class DbMigrateVersion(Base, BaseModel):
5797 class DbMigrateVersion(Base, BaseModel):
5795 __tablename__ = 'db_migrate_version'
5798 __tablename__ = 'db_migrate_version'
5796 __table_args__ = (
5799 __table_args__ = (
5797 base_table_args,
5800 base_table_args,
5798 )
5801 )
5799
5802
5800 repository_id = Column('repository_id', String(250), primary_key=True)
5803 repository_id = Column('repository_id', String(250), primary_key=True)
5801 repository_path = Column('repository_path', Text)
5804 repository_path = Column('repository_path', Text)
5802 version = Column('version', Integer)
5805 version = Column('version', Integer)
5803
5806
5804 @classmethod
5807 @classmethod
5805 def set_version(cls, version):
5808 def set_version(cls, version):
5806 """
5809 """
5807 Helper for forcing a different version, usually for debugging purposes via ishell.
5810 Helper for forcing a different version, usually for debugging purposes via ishell.
5808 """
5811 """
5809 ver = DbMigrateVersion.query().first()
5812 ver = DbMigrateVersion.query().first()
5810 ver.version = version
5813 ver.version = version
5811 Session().commit()
5814 Session().commit()
5812
5815
5813
5816
5814 class DbSession(Base, BaseModel):
5817 class DbSession(Base, BaseModel):
5815 __tablename__ = 'db_session'
5818 __tablename__ = 'db_session'
5816 __table_args__ = (
5819 __table_args__ = (
5817 base_table_args,
5820 base_table_args,
5818 )
5821 )
5819
5822
5820 def __repr__(self):
5823 def __repr__(self):
5821 return '<DB:DbSession({})>'.format(self.id)
5824 return '<DB:DbSession({})>'.format(self.id)
5822
5825
5823 id = Column('id', Integer())
5826 id = Column('id', Integer())
5824 namespace = Column('namespace', String(255), primary_key=True)
5827 namespace = Column('namespace', String(255), primary_key=True)
5825 accessed = Column('accessed', DateTime, nullable=False)
5828 accessed = Column('accessed', DateTime, nullable=False)
5826 created = Column('created', DateTime, nullable=False)
5829 created = Column('created', DateTime, nullable=False)
5827 data = Column('data', PickleType, nullable=False)
5830 data = Column('data', PickleType, nullable=False)
@@ -1,1197 +1,1197 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 from rhodecode.lib import hooks_base
36 from rhodecode.lib import hooks_base
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, action_logger_generic)
41 get_current_rhodecode_user, safe_int, action_logger_generic)
42 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class RepoModel(BaseModel):
55 class RepoModel(BaseModel):
56
56
57 cls = Repository
57 cls = Repository
58
58
59 def _get_user_group(self, users_group):
59 def _get_user_group(self, users_group):
60 return self._get_instance(UserGroup, users_group,
60 return self._get_instance(UserGroup, users_group,
61 callback=UserGroup.get_by_group_name)
61 callback=UserGroup.get_by_group_name)
62
62
63 def _get_repo_group(self, repo_group):
63 def _get_repo_group(self, repo_group):
64 return self._get_instance(RepoGroup, repo_group,
64 return self._get_instance(RepoGroup, repo_group,
65 callback=RepoGroup.get_by_group_name)
65 callback=RepoGroup.get_by_group_name)
66
66
67 def _create_default_perms(self, repository, private):
67 def _create_default_perms(self, repository, private):
68 # create default permission
68 # create default permission
69 default = 'repository.read'
69 default = 'repository.read'
70 def_user = User.get_default_user()
70 def_user = User.get_default_user()
71 for p in def_user.user_perms:
71 for p in def_user.user_perms:
72 if p.permission.permission_name.startswith('repository.'):
72 if p.permission.permission_name.startswith('repository.'):
73 default = p.permission.permission_name
73 default = p.permission.permission_name
74 break
74 break
75
75
76 default_perm = 'repository.none' if private else default
76 default_perm = 'repository.none' if private else default
77
77
78 repo_to_perm = UserRepoToPerm()
78 repo_to_perm = UserRepoToPerm()
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
80
80
81 repo_to_perm.repository = repository
81 repo_to_perm.repository = repository
82 repo_to_perm.user_id = def_user.user_id
82 repo_to_perm.user_id = def_user.user_id
83
83
84 return repo_to_perm
84 return repo_to_perm
85
85
86 @LazyProperty
86 @LazyProperty
87 def repos_path(self):
87 def repos_path(self):
88 """
88 """
89 Gets the repositories root path from database
89 Gets the repositories root path from database
90 """
90 """
91 settings_model = VcsSettingsModel(sa=self.sa)
91 settings_model = VcsSettingsModel(sa=self.sa)
92 return settings_model.get_repos_location()
92 return settings_model.get_repos_location()
93
93
94 def get(self, repo_id):
94 def get(self, repo_id):
95 repo = self.sa.query(Repository) \
95 repo = self.sa.query(Repository) \
96 .filter(Repository.repo_id == repo_id)
96 .filter(Repository.repo_id == repo_id)
97
97
98 return repo.scalar()
98 return repo.scalar()
99
99
100 def get_repo(self, repository):
100 def get_repo(self, repository):
101 return self._get_repo(repository)
101 return self._get_repo(repository)
102
102
103 def get_by_repo_name(self, repo_name, cache=False):
103 def get_by_repo_name(self, repo_name, cache=False):
104 repo = self.sa.query(Repository) \
104 repo = self.sa.query(Repository) \
105 .filter(Repository.repo_name == repo_name)
105 .filter(Repository.repo_name == repo_name)
106
106
107 if cache:
107 if cache:
108 name_key = _hash_key(repo_name)
108 name_key = _hash_key(repo_name)
109 repo = repo.options(
109 repo = repo.options(
110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
110 FromCache("sql_cache_short", f"get_repo_{name_key}"))
111 return repo.scalar()
111 return repo.scalar()
112
112
113 def _extract_id_from_repo_name(self, repo_name):
113 def _extract_id_from_repo_name(self, repo_name):
114 if repo_name.startswith('/'):
114 if repo_name.startswith('/'):
115 repo_name = repo_name.lstrip('/')
115 repo_name = repo_name.lstrip('/')
116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 if by_id_match:
117 if by_id_match:
118 return by_id_match.groups()[0]
118 return by_id_match.groups()[0]
119
119
120 def get_repo_by_id(self, repo_name):
120 def get_repo_by_id(self, repo_name):
121 """
121 """
122 Extracts repo_name by id from special urls.
122 Extracts repo_name by id from special urls.
123 Example url is _11/repo_name
123 Example url is _11/repo_name
124
124
125 :param repo_name:
125 :param repo_name:
126 :return: repo object if matched else None
126 :return: repo object if matched else None
127 """
127 """
128 _repo_id = None
128 _repo_id = None
129 try:
129 try:
130 _repo_id = self._extract_id_from_repo_name(repo_name)
130 _repo_id = self._extract_id_from_repo_name(repo_name)
131 if _repo_id:
131 if _repo_id:
132 return self.get(_repo_id)
132 return self.get(_repo_id)
133 except Exception:
133 except Exception:
134 log.exception('Failed to extract repo_name from URL')
134 log.exception('Failed to extract repo_name from URL')
135 if _repo_id:
135 if _repo_id:
136 Session().rollback()
136 Session().rollback()
137
137
138 return None
138 return None
139
139
140 def get_repos_for_root(self, root, traverse=False):
140 def get_repos_for_root(self, root, traverse=False):
141 if traverse:
141 if traverse:
142 like_expression = u'{}%'.format(safe_unicode(root))
142 like_expression = u'{}%'.format(safe_unicode(root))
143 repos = Repository.query().filter(
143 repos = Repository.query().filter(
144 Repository.repo_name.like(like_expression)).all()
144 Repository.repo_name.like(like_expression)).all()
145 else:
145 else:
146 if root and not isinstance(root, RepoGroup):
146 if root and not isinstance(root, RepoGroup):
147 raise ValueError(
147 raise ValueError(
148 'Root must be an instance '
148 'Root must be an instance '
149 'of RepoGroup, got:{} instead'.format(type(root)))
149 'of RepoGroup, got:{} instead'.format(type(root)))
150 repos = Repository.query().filter(Repository.group == root).all()
150 repos = Repository.query().filter(Repository.group == root).all()
151 return repos
151 return repos
152
152
153 def get_url(self, repo, request=None, permalink=False):
153 def get_url(self, repo, request=None, permalink=False):
154 if not request:
154 if not request:
155 request = get_current_request()
155 request = get_current_request()
156
156
157 if not request:
157 if not request:
158 return
158 return
159
159
160 if permalink:
160 if permalink:
161 return request.route_url(
161 return request.route_url(
162 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
163 else:
163 else:
164 return request.route_url(
164 return request.route_url(
165 'repo_summary', repo_name=safe_str(repo.repo_name))
165 'repo_summary', repo_name=safe_str(repo.repo_name))
166
166
167 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
168 if not request:
168 if not request:
169 request = get_current_request()
169 request = get_current_request()
170
170
171 if not request:
171 if not request:
172 return
172 return
173
173
174 if permalink:
174 if permalink:
175 return request.route_url(
175 return request.route_url(
176 'repo_commit', repo_name=safe_str(repo.repo_id),
176 'repo_commit', repo_name=safe_str(repo.repo_id),
177 commit_id=commit_id)
177 commit_id=commit_id)
178
178
179 else:
179 else:
180 return request.route_url(
180 return request.route_url(
181 'repo_commit', repo_name=safe_str(repo.repo_name),
181 'repo_commit', repo_name=safe_str(repo.repo_name),
182 commit_id=commit_id)
182 commit_id=commit_id)
183
183
184 def get_repo_log(self, repo, filter_term):
184 def get_repo_log(self, repo, filter_term):
185 repo_log = UserLog.query()\
185 repo_log = UserLog.query()\
186 .filter(or_(UserLog.repository_id == repo.repo_id,
186 .filter(or_(UserLog.repository_id == repo.repo_id,
187 UserLog.repository_name == repo.repo_name))\
187 UserLog.repository_name == repo.repo_name))\
188 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.user))\
189 .options(joinedload(UserLog.repository))\
189 .options(joinedload(UserLog.repository))\
190 .order_by(UserLog.action_date.desc())
190 .order_by(UserLog.action_date.desc())
191
191
192 repo_log = user_log_filter(repo_log, filter_term)
192 repo_log = user_log_filter(repo_log, filter_term)
193 return repo_log
193 return repo_log
194
194
195 @classmethod
195 @classmethod
196 def update_commit_cache(cls, repositories=None):
196 def update_commit_cache(cls, repositories=None):
197 if not repositories:
197 if not repositories:
198 repositories = Repository.getAll()
198 repositories = Repository.getAll()
199 for repo in repositories:
199 for repo in repositories:
200 repo.update_commit_cache()
200 repo.update_commit_cache()
201
201
202 def get_repos_as_dict(self, repo_list=None, admin=False,
202 def get_repos_as_dict(self, repo_list=None, admin=False,
203 super_user_actions=False, short_name=None):
203 super_user_actions=False, short_name=None):
204
204
205 _render = get_current_request().get_partial_renderer(
205 _render = get_current_request().get_partial_renderer(
206 'rhodecode:templates/data_table/_dt_elements.mako')
206 'rhodecode:templates/data_table/_dt_elements.mako')
207 c = _render.get_call_context()
207 c = _render.get_call_context()
208 h = _render.get_helpers()
208 h = _render.get_helpers()
209
209
210 def quick_menu(repo_name):
210 def quick_menu(repo_name):
211 return _render('quick_menu', repo_name)
211 return _render('quick_menu', repo_name)
212
212
213 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
213 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
214 if short_name is not None:
214 if short_name is not None:
215 short_name_var = short_name
215 short_name_var = short_name
216 else:
216 else:
217 short_name_var = not admin
217 short_name_var = not admin
218 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
218 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
219 short_name=short_name_var, admin=False)
219 short_name=short_name_var, admin=False)
220
220
221 def last_change(last_change):
221 def last_change(last_change):
222 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
222 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
223 ts = time.time()
223 ts = time.time()
224 utc_offset = (datetime.datetime.fromtimestamp(ts)
224 utc_offset = (datetime.datetime.fromtimestamp(ts)
225 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
225 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
226 last_change = last_change + datetime.timedelta(seconds=utc_offset)
226 last_change = last_change + datetime.timedelta(seconds=utc_offset)
227
227
228 return _render("last_change", last_change)
228 return _render("last_change", last_change)
229
229
230 def rss_lnk(repo_name):
230 def rss_lnk(repo_name):
231 return _render("rss", repo_name)
231 return _render("rss", repo_name)
232
232
233 def atom_lnk(repo_name):
233 def atom_lnk(repo_name):
234 return _render("atom", repo_name)
234 return _render("atom", repo_name)
235
235
236 def last_rev(repo_name, cs_cache):
236 def last_rev(repo_name, cs_cache):
237 return _render('revision', repo_name, cs_cache.get('revision'),
237 return _render('revision', repo_name, cs_cache.get('revision'),
238 cs_cache.get('raw_id'), cs_cache.get('author'),
238 cs_cache.get('raw_id'), cs_cache.get('author'),
239 cs_cache.get('message'), cs_cache.get('date'))
239 cs_cache.get('message'), cs_cache.get('date'))
240
240
241 def desc(desc):
241 def desc(desc):
242 return _render('repo_desc', desc, c.visual.stylify_metatags)
242 return _render('repo_desc', desc, c.visual.stylify_metatags)
243
243
244 def state(repo_state):
244 def state(repo_state):
245 return _render("repo_state", repo_state)
245 return _render("repo_state", repo_state)
246
246
247 def repo_actions(repo_name):
247 def repo_actions(repo_name):
248 return _render('repo_actions', repo_name, super_user_actions)
248 return _render('repo_actions', repo_name, super_user_actions)
249
249
250 def user_profile(username):
250 def user_profile(username):
251 return _render('user_profile', username)
251 return _render('user_profile', username)
252
252
253 repos_data = []
253 repos_data = []
254 for repo in repo_list:
254 for repo in repo_list:
255 # NOTE(marcink): because we use only raw column we need to load it like that
255 # NOTE(marcink): because we use only raw column we need to load it like that
256 changeset_cache = Repository._load_changeset_cache(
256 changeset_cache = Repository._load_changeset_cache(
257 repo.repo_id, repo._changeset_cache)
257 repo.repo_id, repo._changeset_cache)
258
258
259 row = {
259 row = {
260 "menu": quick_menu(repo.repo_name),
260 "menu": quick_menu(repo.repo_name),
261
261
262 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
262 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
263 repo.private, repo.archived, repo.fork),
263 repo.private, repo.archived, repo.fork),
264
264
265 "desc": desc(h.escape(repo.description)),
265 "desc": desc(h.escape(repo.description)),
266
266
267 "last_change": last_change(repo.updated_on),
267 "last_change": last_change(repo.updated_on),
268
268
269 "last_changeset": last_rev(repo.repo_name, changeset_cache),
269 "last_changeset": last_rev(repo.repo_name, changeset_cache),
270 "last_changeset_raw": changeset_cache.get('revision'),
270 "last_changeset_raw": changeset_cache.get('revision'),
271
271
272 "owner": user_profile(repo.User.username),
272 "owner": user_profile(repo.User.username),
273
273
274 "state": state(repo.repo_state),
274 "state": state(repo.repo_state),
275 "rss": rss_lnk(repo.repo_name),
275 "rss": rss_lnk(repo.repo_name),
276 "atom": atom_lnk(repo.repo_name),
276 "atom": atom_lnk(repo.repo_name),
277 }
277 }
278 if admin:
278 if admin:
279 row.update({
279 row.update({
280 "action": repo_actions(repo.repo_name),
280 "action": repo_actions(repo.repo_name),
281 })
281 })
282 repos_data.append(row)
282 repos_data.append(row)
283
283
284 return repos_data
284 return repos_data
285
285
286 def get_repos_data_table(
286 def get_repos_data_table(
287 self, draw, start, limit,
287 self, draw, start, limit,
288 search_q, order_by, order_dir,
288 search_q, order_by, order_dir,
289 auth_user, repo_group_id):
289 auth_user, repo_group_id):
290 from rhodecode.model.scm import RepoList
290 from rhodecode.model.scm import RepoList
291
291
292 _perms = ['repository.read', 'repository.write', 'repository.admin']
292 _perms = ['repository.read', 'repository.write', 'repository.admin']
293
293
294 repos = Repository.query() \
294 repos = Repository.query() \
295 .filter(Repository.group_id == repo_group_id) \
295 .filter(Repository.group_id == repo_group_id) \
296 .all()
296 .all()
297 auth_repo_list = RepoList(
297 auth_repo_list = RepoList(
298 repos, perm_set=_perms,
298 repos, perm_set=_perms,
299 extra_kwargs=dict(user=auth_user))
299 extra_kwargs=dict(user=auth_user))
300
300
301 allowed_ids = [-1]
301 allowed_ids = [-1]
302 for repo in auth_repo_list:
302 for repo in auth_repo_list:
303 allowed_ids.append(repo.repo_id)
303 allowed_ids.append(repo.repo_id)
304
304
305 repos_data_total_count = Repository.query() \
305 repos_data_total_count = Repository.query() \
306 .filter(Repository.group_id == repo_group_id) \
306 .filter(Repository.group_id == repo_group_id) \
307 .filter(or_(
307 .filter(or_(
308 # generate multiple IN to fix limitation problems
308 # generate multiple IN to fix limitation problems
309 *in_filter_generator(Repository.repo_id, allowed_ids))
309 *in_filter_generator(Repository.repo_id, allowed_ids))
310 ) \
310 ) \
311 .count()
311 .count()
312
312
313 base_q = Session.query(
313 base_q = Session.query(
314 Repository.repo_id,
314 Repository.repo_id,
315 Repository.repo_name,
315 Repository.repo_name,
316 Repository.description,
316 Repository.description,
317 Repository.repo_type,
317 Repository.repo_type,
318 Repository.repo_state,
318 Repository.repo_state,
319 Repository.private,
319 Repository.private,
320 Repository.archived,
320 Repository.archived,
321 Repository.fork,
321 Repository.fork,
322 Repository.updated_on,
322 Repository.updated_on,
323 Repository._changeset_cache,
323 Repository._changeset_cache,
324 User,
324 User,
325 ) \
325 ) \
326 .filter(Repository.group_id == repo_group_id) \
326 .filter(Repository.group_id == repo_group_id) \
327 .filter(or_(
327 .filter(or_(
328 # generate multiple IN to fix limitation problems
328 # generate multiple IN to fix limitation problems
329 *in_filter_generator(Repository.repo_id, allowed_ids))
329 *in_filter_generator(Repository.repo_id, allowed_ids))
330 ) \
330 ) \
331 .join(User, User.user_id == Repository.user_id) \
331 .join(User, User.user_id == Repository.user_id) \
332 .group_by(Repository, User)
332 .group_by(Repository, User)
333
333
334 repos_data_total_filtered_count = base_q.count()
334 repos_data_total_filtered_count = base_q.count()
335
335
336 sort_defined = False
336 sort_defined = False
337 if order_by == 'repo_name':
337 if order_by == 'repo_name':
338 sort_col = func.lower(Repository.repo_name)
338 sort_col = func.lower(Repository.repo_name)
339 sort_defined = True
339 sort_defined = True
340 elif order_by == 'user_username':
340 elif order_by == 'user_username':
341 sort_col = User.username
341 sort_col = User.username
342 else:
342 else:
343 sort_col = getattr(Repository, order_by, None)
343 sort_col = getattr(Repository, order_by, None)
344
344
345 if sort_defined or sort_col:
345 if sort_defined or sort_col:
346 if order_dir == 'asc':
346 if order_dir == 'asc':
347 sort_col = sort_col.asc()
347 sort_col = sort_col.asc()
348 else:
348 else:
349 sort_col = sort_col.desc()
349 sort_col = sort_col.desc()
350
350
351 base_q = base_q.order_by(sort_col)
351 base_q = base_q.order_by(sort_col)
352 base_q = base_q.offset(start).limit(limit)
352 base_q = base_q.offset(start).limit(limit)
353
353
354 repos_list = base_q.all()
354 repos_list = base_q.all()
355
355
356 repos_data = RepoModel().get_repos_as_dict(
356 repos_data = RepoModel().get_repos_as_dict(
357 repo_list=repos_list, admin=False)
357 repo_list=repos_list, admin=False)
358
358
359 data = ({
359 data = ({
360 'draw': draw,
360 'draw': draw,
361 'data': repos_data,
361 'data': repos_data,
362 'recordsTotal': repos_data_total_count,
362 'recordsTotal': repos_data_total_count,
363 'recordsFiltered': repos_data_total_filtered_count,
363 'recordsFiltered': repos_data_total_filtered_count,
364 })
364 })
365 return data
365 return data
366
366
367 def _get_defaults(self, repo_name):
367 def _get_defaults(self, repo_name):
368 """
368 """
369 Gets information about repository, and returns a dict for
369 Gets information about repository, and returns a dict for
370 usage in forms
370 usage in forms
371
371
372 :param repo_name:
372 :param repo_name:
373 """
373 """
374
374
375 repo_info = Repository.get_by_repo_name(repo_name)
375 repo_info = Repository.get_by_repo_name(repo_name)
376
376
377 if repo_info is None:
377 if repo_info is None:
378 return None
378 return None
379
379
380 defaults = repo_info.get_dict()
380 defaults = repo_info.get_dict()
381 defaults['repo_name'] = repo_info.just_name
381 defaults['repo_name'] = repo_info.just_name
382
382
383 groups = repo_info.groups_with_parents
383 groups = repo_info.groups_with_parents
384 parent_group = groups[-1] if groups else None
384 parent_group = groups[-1] if groups else None
385
385
386 # we use -1 as this is how in HTML, we mark an empty group
386 # we use -1 as this is how in HTML, we mark an empty group
387 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
387 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
388
388
389 keys_to_process = (
389 keys_to_process = (
390 {'k': 'repo_type', 'strip': False},
390 {'k': 'repo_type', 'strip': False},
391 {'k': 'repo_enable_downloads', 'strip': True},
391 {'k': 'repo_enable_downloads', 'strip': True},
392 {'k': 'repo_description', 'strip': True},
392 {'k': 'repo_description', 'strip': True},
393 {'k': 'repo_enable_locking', 'strip': True},
393 {'k': 'repo_enable_locking', 'strip': True},
394 {'k': 'repo_landing_rev', 'strip': True},
394 {'k': 'repo_landing_rev', 'strip': True},
395 {'k': 'clone_uri', 'strip': False},
395 {'k': 'clone_uri', 'strip': False},
396 {'k': 'push_uri', 'strip': False},
396 {'k': 'push_uri', 'strip': False},
397 {'k': 'repo_private', 'strip': True},
397 {'k': 'repo_private', 'strip': True},
398 {'k': 'repo_enable_statistics', 'strip': True}
398 {'k': 'repo_enable_statistics', 'strip': True}
399 )
399 )
400
400
401 for item in keys_to_process:
401 for item in keys_to_process:
402 attr = item['k']
402 attr = item['k']
403 if item['strip']:
403 if item['strip']:
404 attr = remove_prefix(item['k'], 'repo_')
404 attr = remove_prefix(item['k'], 'repo_')
405
405
406 val = defaults[attr]
406 val = defaults[attr]
407 if item['k'] == 'repo_landing_rev':
407 if item['k'] == 'repo_landing_rev':
408 val = ':'.join(defaults[attr])
408 val = ':'.join(defaults[attr])
409 defaults[item['k']] = val
409 defaults[item['k']] = val
410 if item['k'] == 'clone_uri':
410 if item['k'] == 'clone_uri':
411 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
411 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
412 if item['k'] == 'push_uri':
412 if item['k'] == 'push_uri':
413 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
413 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
414
414
415 # fill owner
415 # fill owner
416 if repo_info.user:
416 if repo_info.user:
417 defaults.update({'user': repo_info.user.username})
417 defaults.update({'user': repo_info.user.username})
418 else:
418 else:
419 replacement_user = User.get_first_super_admin().username
419 replacement_user = User.get_first_super_admin().username
420 defaults.update({'user': replacement_user})
420 defaults.update({'user': replacement_user})
421
421
422 return defaults
422 return defaults
423
423
424 def update(self, repo, **kwargs):
424 def update(self, repo, **kwargs):
425 try:
425 try:
426 cur_repo = self._get_repo(repo)
426 cur_repo = self._get_repo(repo)
427 source_repo_name = cur_repo.repo_name
427 source_repo_name = cur_repo.repo_name
428
428
429 affected_user_ids = []
429 affected_user_ids = []
430 if 'user' in kwargs:
430 if 'user' in kwargs:
431 old_owner_id = cur_repo.user.user_id
431 old_owner_id = cur_repo.user.user_id
432 new_owner = User.get_by_username(kwargs['user'])
432 new_owner = User.get_by_username(kwargs['user'])
433 cur_repo.user = new_owner
433 cur_repo.user = new_owner
434
434
435 if old_owner_id != new_owner.user_id:
435 if old_owner_id != new_owner.user_id:
436 affected_user_ids = [new_owner.user_id, old_owner_id]
436 affected_user_ids = [new_owner.user_id, old_owner_id]
437
437
438 if 'repo_group' in kwargs:
438 if 'repo_group' in kwargs:
439 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
439 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
440 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
440 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
441
441
442 update_keys = [
442 update_keys = [
443 (1, 'repo_description'),
443 (1, 'repo_description'),
444 (1, 'repo_landing_rev'),
444 (1, 'repo_landing_rev'),
445 (1, 'repo_private'),
445 (1, 'repo_private'),
446 (1, 'repo_enable_downloads'),
446 (1, 'repo_enable_downloads'),
447 (1, 'repo_enable_locking'),
447 (1, 'repo_enable_locking'),
448 (1, 'repo_enable_statistics'),
448 (1, 'repo_enable_statistics'),
449 (0, 'clone_uri'),
449 (0, 'clone_uri'),
450 (0, 'push_uri'),
450 (0, 'push_uri'),
451 (0, 'fork_id')
451 (0, 'fork_id')
452 ]
452 ]
453 for strip, k in update_keys:
453 for strip, k in update_keys:
454 if k in kwargs:
454 if k in kwargs:
455 val = kwargs[k]
455 val = kwargs[k]
456 if strip:
456 if strip:
457 k = remove_prefix(k, 'repo_')
457 k = remove_prefix(k, 'repo_')
458
458
459 setattr(cur_repo, k, val)
459 setattr(cur_repo, k, val)
460
460
461 new_name = cur_repo.get_new_name(kwargs['repo_name'])
461 new_name = cur_repo.get_new_name(kwargs['repo_name'])
462 cur_repo.repo_name = new_name
462 cur_repo.repo_name = new_name
463
463
464 # if private flag is set, reset default permission to NONE
464 # if private flag is set, reset default permission to NONE
465 if kwargs.get('repo_private'):
465 if kwargs.get('repo_private'):
466 EMPTY_PERM = 'repository.none'
466 EMPTY_PERM = 'repository.none'
467 RepoModel().grant_user_permission(
467 RepoModel().grant_user_permission(
468 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
468 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
469 )
469 )
470 if kwargs.get('repo_landing_rev'):
470 if kwargs.get('repo_landing_rev'):
471 landing_rev_val = kwargs['repo_landing_rev']
471 landing_rev_val = kwargs['repo_landing_rev']
472 RepoModel().set_landing_rev(cur_repo, landing_rev_val)
472 RepoModel().set_landing_rev(cur_repo, landing_rev_val)
473
473
474 # handle extra fields
474 # handle extra fields
475 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
475 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
476 k = RepositoryField.un_prefix_key(field)
476 k = RepositoryField.un_prefix_key(field)
477 ex_field = RepositoryField.get_by_key_name(
477 ex_field = RepositoryField.get_by_key_name(
478 key=k, repo=cur_repo)
478 key=k, repo=cur_repo)
479 if ex_field:
479 if ex_field:
480 ex_field.field_value = kwargs[field]
480 ex_field.field_value = kwargs[field]
481 self.sa.add(ex_field)
481 self.sa.add(ex_field)
482
482
483 self.sa.add(cur_repo)
483 self.sa.add(cur_repo)
484
484
485 if source_repo_name != new_name:
485 if source_repo_name != new_name:
486 # rename repository
486 # rename repository
487 self._rename_filesystem_repo(
487 self._rename_filesystem_repo(
488 old=source_repo_name, new=new_name)
488 old=source_repo_name, new=new_name)
489
489
490 if affected_user_ids:
490 if affected_user_ids:
491 PermissionModel().trigger_permission_flush(affected_user_ids)
491 PermissionModel().trigger_permission_flush(affected_user_ids)
492
492
493 return cur_repo
493 return cur_repo
494 except Exception:
494 except Exception:
495 log.error(traceback.format_exc())
495 log.error(traceback.format_exc())
496 raise
496 raise
497
497
498 def _create_repo(self, repo_name, repo_type, description, owner,
498 def _create_repo(self, repo_name, repo_type, description, owner,
499 private=False, clone_uri=None, repo_group=None,
499 private=False, clone_uri=None, repo_group=None,
500 landing_rev=None, fork_of=None,
500 landing_rev=None, fork_of=None,
501 copy_fork_permissions=False, enable_statistics=False,
501 copy_fork_permissions=False, enable_statistics=False,
502 enable_locking=False, enable_downloads=False,
502 enable_locking=False, enable_downloads=False,
503 copy_group_permissions=False,
503 copy_group_permissions=False,
504 state=Repository.STATE_PENDING):
504 state=Repository.STATE_PENDING):
505 """
505 """
506 Create repository inside database with PENDING state, this should be
506 Create repository inside database with PENDING state, this should be
507 only executed by create() repo. With exception of importing existing
507 only executed by create() repo. With exception of importing existing
508 repos
508 repos
509 """
509 """
510 from rhodecode.model.scm import ScmModel
510 from rhodecode.model.scm import ScmModel
511
511
512 owner = self._get_user(owner)
512 owner = self._get_user(owner)
513 fork_of = self._get_repo(fork_of)
513 fork_of = self._get_repo(fork_of)
514 repo_group = self._get_repo_group(safe_int(repo_group))
514 repo_group = self._get_repo_group(safe_int(repo_group))
515 default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type)
515 default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type)
516 landing_rev = landing_rev or default_landing_ref
516 landing_rev = landing_rev or default_landing_ref
517
517
518 try:
518 try:
519 repo_name = safe_unicode(repo_name)
519 repo_name = safe_unicode(repo_name)
520 description = safe_unicode(description)
520 description = safe_unicode(description)
521 # repo name is just a name of repository
521 # repo name is just a name of repository
522 # while repo_name_full is a full qualified name that is combined
522 # while repo_name_full is a full qualified name that is combined
523 # with name and path of group
523 # with name and path of group
524 repo_name_full = repo_name
524 repo_name_full = repo_name
525 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
525 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
526
526
527 new_repo = Repository()
527 new_repo = Repository()
528 new_repo.repo_state = state
528 new_repo.repo_state = state
529 new_repo.enable_statistics = False
529 new_repo.enable_statistics = False
530 new_repo.repo_name = repo_name_full
530 new_repo.repo_name = repo_name_full
531 new_repo.repo_type = repo_type
531 new_repo.repo_type = repo_type
532 new_repo.user = owner
532 new_repo.user = owner
533 new_repo.group = repo_group
533 new_repo.group = repo_group
534 new_repo.description = description or repo_name
534 new_repo.description = description or repo_name
535 new_repo.private = private
535 new_repo.private = private
536 new_repo.archived = False
536 new_repo.archived = False
537 new_repo.clone_uri = clone_uri
537 new_repo.clone_uri = clone_uri
538 new_repo.landing_rev = landing_rev
538 new_repo.landing_rev = landing_rev
539
539
540 new_repo.enable_statistics = enable_statistics
540 new_repo.enable_statistics = enable_statistics
541 new_repo.enable_locking = enable_locking
541 new_repo.enable_locking = enable_locking
542 new_repo.enable_downloads = enable_downloads
542 new_repo.enable_downloads = enable_downloads
543
543
544 if repo_group:
544 if repo_group:
545 new_repo.enable_locking = repo_group.enable_locking
545 new_repo.enable_locking = repo_group.enable_locking
546
546
547 if fork_of:
547 if fork_of:
548 parent_repo = fork_of
548 parent_repo = fork_of
549 new_repo.fork = parent_repo
549 new_repo.fork = parent_repo
550
550
551 events.trigger(events.RepoPreCreateEvent(new_repo))
551 events.trigger(events.RepoPreCreateEvent(new_repo))
552
552
553 self.sa.add(new_repo)
553 self.sa.add(new_repo)
554
554
555 EMPTY_PERM = 'repository.none'
555 EMPTY_PERM = 'repository.none'
556 if fork_of and copy_fork_permissions:
556 if fork_of and copy_fork_permissions:
557 repo = fork_of
557 repo = fork_of
558 user_perms = UserRepoToPerm.query() \
558 user_perms = UserRepoToPerm.query() \
559 .filter(UserRepoToPerm.repository == repo).all()
559 .filter(UserRepoToPerm.repository == repo).all()
560 group_perms = UserGroupRepoToPerm.query() \
560 group_perms = UserGroupRepoToPerm.query() \
561 .filter(UserGroupRepoToPerm.repository == repo).all()
561 .filter(UserGroupRepoToPerm.repository == repo).all()
562
562
563 for perm in user_perms:
563 for perm in user_perms:
564 UserRepoToPerm.create(
564 UserRepoToPerm.create(
565 perm.user, new_repo, perm.permission)
565 perm.user, new_repo, perm.permission)
566
566
567 for perm in group_perms:
567 for perm in group_perms:
568 UserGroupRepoToPerm.create(
568 UserGroupRepoToPerm.create(
569 perm.users_group, new_repo, perm.permission)
569 perm.users_group, new_repo, perm.permission)
570 # in case we copy permissions and also set this repo to private
570 # in case we copy permissions and also set this repo to private
571 # override the default user permission to make it a private repo
571 # override the default user permission to make it a private repo
572 if private:
572 if private:
573 RepoModel(self.sa).grant_user_permission(
573 RepoModel(self.sa).grant_user_permission(
574 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
574 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
575
575
576 elif repo_group and copy_group_permissions:
576 elif repo_group and copy_group_permissions:
577 user_perms = UserRepoGroupToPerm.query() \
577 user_perms = UserRepoGroupToPerm.query() \
578 .filter(UserRepoGroupToPerm.group == repo_group).all()
578 .filter(UserRepoGroupToPerm.group == repo_group).all()
579
579
580 group_perms = UserGroupRepoGroupToPerm.query() \
580 group_perms = UserGroupRepoGroupToPerm.query() \
581 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
581 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
582
582
583 for perm in user_perms:
583 for perm in user_perms:
584 perm_name = perm.permission.permission_name.replace(
584 perm_name = perm.permission.permission_name.replace(
585 'group.', 'repository.')
585 'group.', 'repository.')
586 perm_obj = Permission.get_by_key(perm_name)
586 perm_obj = Permission.get_by_key(perm_name)
587 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
587 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
588
588
589 for perm in group_perms:
589 for perm in group_perms:
590 perm_name = perm.permission.permission_name.replace(
590 perm_name = perm.permission.permission_name.replace(
591 'group.', 'repository.')
591 'group.', 'repository.')
592 perm_obj = Permission.get_by_key(perm_name)
592 perm_obj = Permission.get_by_key(perm_name)
593 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
593 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
594
594
595 if private:
595 if private:
596 RepoModel(self.sa).grant_user_permission(
596 RepoModel(self.sa).grant_user_permission(
597 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
597 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
598
598
599 else:
599 else:
600 perm_obj = self._create_default_perms(new_repo, private)
600 perm_obj = self._create_default_perms(new_repo, private)
601 self.sa.add(perm_obj)
601 self.sa.add(perm_obj)
602
602
603 # now automatically start following this repository as owner
603 # now automatically start following this repository as owner
604 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
604 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
605
605
606 # we need to flush here, in order to check if database won't
606 # we need to flush here, in order to check if database won't
607 # throw any exceptions, create filesystem dirs at the very end
607 # throw any exceptions, create filesystem dirs at the very end
608 self.sa.flush()
608 self.sa.flush()
609 events.trigger(events.RepoCreateEvent(new_repo))
609 events.trigger(events.RepoCreateEvent(new_repo))
610 return new_repo
610 return new_repo
611
611
612 except Exception:
612 except Exception:
613 log.error(traceback.format_exc())
613 log.error(traceback.format_exc())
614 raise
614 raise
615
615
616 def create(self, form_data, cur_user):
616 def create(self, form_data, cur_user):
617 """
617 """
618 Create repository using celery tasks
618 Create repository using celery tasks
619
619
620 :param form_data:
620 :param form_data:
621 :param cur_user:
621 :param cur_user:
622 """
622 """
623 from rhodecode.lib.celerylib import tasks, run_task
623 from rhodecode.lib.celerylib import tasks, run_task
624 return run_task(tasks.create_repo, form_data, cur_user)
624 return run_task(tasks.create_repo, form_data, cur_user)
625
625
626 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
626 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
627 perm_deletions=None, check_perms=True,
627 perm_deletions=None, check_perms=True,
628 cur_user=None):
628 cur_user=None):
629 if not perm_additions:
629 if not perm_additions:
630 perm_additions = []
630 perm_additions = []
631 if not perm_updates:
631 if not perm_updates:
632 perm_updates = []
632 perm_updates = []
633 if not perm_deletions:
633 if not perm_deletions:
634 perm_deletions = []
634 perm_deletions = []
635
635
636 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
636 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
637
637
638 changes = {
638 changes = {
639 'added': [],
639 'added': [],
640 'updated': [],
640 'updated': [],
641 'deleted': [],
641 'deleted': [],
642 'default_user_changed': None
642 'default_user_changed': None
643 }
643 }
644
644
645 repo = self._get_repo(repo)
645 repo = self._get_repo(repo)
646
646
647 # update permissions
647 # update permissions
648 for member_id, perm, member_type in perm_updates:
648 for member_id, perm, member_type in perm_updates:
649 member_id = int(member_id)
649 member_id = int(member_id)
650 if member_type == 'user':
650 if member_type == 'user':
651 member_name = User.get(member_id).username
651 member_name = User.get(member_id).username
652 if member_name == User.DEFAULT_USER:
652 if member_name == User.DEFAULT_USER:
653 # NOTE(dan): detect if we changed permissions for default user
653 # NOTE(dan): detect if we changed permissions for default user
654 perm_obj = self.sa.query(UserRepoToPerm) \
654 perm_obj = self.sa.query(UserRepoToPerm) \
655 .filter(UserRepoToPerm.user_id == member_id) \
655 .filter(UserRepoToPerm.user_id == member_id) \
656 .filter(UserRepoToPerm.repository == repo) \
656 .filter(UserRepoToPerm.repository == repo) \
657 .scalar()
657 .scalar()
658 if perm_obj and perm_obj.permission.permission_name != perm:
658 if perm_obj and perm_obj.permission.permission_name != perm:
659 changes['default_user_changed'] = True
659 changes['default_user_changed'] = True
660
660
661 # this updates also current one if found
661 # this updates also current one if found
662 self.grant_user_permission(
662 self.grant_user_permission(
663 repo=repo, user=member_id, perm=perm)
663 repo=repo, user=member_id, perm=perm)
664 elif member_type == 'user_group':
664 elif member_type == 'user_group':
665 # check if we have permissions to alter this usergroup
665 # check if we have permissions to alter this usergroup
666 member_name = UserGroup.get(member_id).users_group_name
666 member_name = UserGroup.get(member_id).users_group_name
667 if not check_perms or HasUserGroupPermissionAny(
667 if not check_perms or HasUserGroupPermissionAny(
668 *req_perms)(member_name, user=cur_user):
668 *req_perms)(member_name, user=cur_user):
669 self.grant_user_group_permission(
669 self.grant_user_group_permission(
670 repo=repo, group_name=member_id, perm=perm)
670 repo=repo, group_name=member_id, perm=perm)
671 else:
671 else:
672 raise ValueError("member_type must be 'user' or 'user_group' "
672 raise ValueError("member_type must be 'user' or 'user_group' "
673 "got {} instead".format(member_type))
673 "got {} instead".format(member_type))
674 changes['updated'].append({'type': member_type, 'id': member_id,
674 changes['updated'].append({'type': member_type, 'id': member_id,
675 'name': member_name, 'new_perm': perm})
675 'name': member_name, 'new_perm': perm})
676
676
677 # set new permissions
677 # set new permissions
678 for member_id, perm, member_type in perm_additions:
678 for member_id, perm, member_type in perm_additions:
679 member_id = int(member_id)
679 member_id = int(member_id)
680 if member_type == 'user':
680 if member_type == 'user':
681 member_name = User.get(member_id).username
681 member_name = User.get(member_id).username
682 self.grant_user_permission(
682 self.grant_user_permission(
683 repo=repo, user=member_id, perm=perm)
683 repo=repo, user=member_id, perm=perm)
684 elif member_type == 'user_group':
684 elif member_type == 'user_group':
685 # check if we have permissions to alter this usergroup
685 # check if we have permissions to alter this usergroup
686 member_name = UserGroup.get(member_id).users_group_name
686 member_name = UserGroup.get(member_id).users_group_name
687 if not check_perms or HasUserGroupPermissionAny(
687 if not check_perms or HasUserGroupPermissionAny(
688 *req_perms)(member_name, user=cur_user):
688 *req_perms)(member_name, user=cur_user):
689 self.grant_user_group_permission(
689 self.grant_user_group_permission(
690 repo=repo, group_name=member_id, perm=perm)
690 repo=repo, group_name=member_id, perm=perm)
691 else:
691 else:
692 raise ValueError("member_type must be 'user' or 'user_group' "
692 raise ValueError("member_type must be 'user' or 'user_group' "
693 "got {} instead".format(member_type))
693 "got {} instead".format(member_type))
694
694
695 changes['added'].append({'type': member_type, 'id': member_id,
695 changes['added'].append({'type': member_type, 'id': member_id,
696 'name': member_name, 'new_perm': perm})
696 'name': member_name, 'new_perm': perm})
697 # delete permissions
697 # delete permissions
698 for member_id, perm, member_type in perm_deletions:
698 for member_id, perm, member_type in perm_deletions:
699 member_id = int(member_id)
699 member_id = int(member_id)
700 if member_type == 'user':
700 if member_type == 'user':
701 member_name = User.get(member_id).username
701 member_name = User.get(member_id).username
702 self.revoke_user_permission(repo=repo, user=member_id)
702 self.revoke_user_permission(repo=repo, user=member_id)
703 elif member_type == 'user_group':
703 elif member_type == 'user_group':
704 # check if we have permissions to alter this usergroup
704 # check if we have permissions to alter this usergroup
705 member_name = UserGroup.get(member_id).users_group_name
705 member_name = UserGroup.get(member_id).users_group_name
706 if not check_perms or HasUserGroupPermissionAny(
706 if not check_perms or HasUserGroupPermissionAny(
707 *req_perms)(member_name, user=cur_user):
707 *req_perms)(member_name, user=cur_user):
708 self.revoke_user_group_permission(
708 self.revoke_user_group_permission(
709 repo=repo, group_name=member_id)
709 repo=repo, group_name=member_id)
710 else:
710 else:
711 raise ValueError("member_type must be 'user' or 'user_group' "
711 raise ValueError("member_type must be 'user' or 'user_group' "
712 "got {} instead".format(member_type))
712 "got {} instead".format(member_type))
713
713
714 changes['deleted'].append({'type': member_type, 'id': member_id,
714 changes['deleted'].append({'type': member_type, 'id': member_id,
715 'name': member_name, 'new_perm': perm})
715 'name': member_name, 'new_perm': perm})
716 return changes
716 return changes
717
717
718 def create_fork(self, form_data, cur_user):
718 def create_fork(self, form_data, cur_user):
719 """
719 """
720 Simple wrapper into executing celery task for fork creation
720 Simple wrapper into executing celery task for fork creation
721
721
722 :param form_data:
722 :param form_data:
723 :param cur_user:
723 :param cur_user:
724 """
724 """
725 from rhodecode.lib.celerylib import tasks, run_task
725 from rhodecode.lib.celerylib import tasks, run_task
726 return run_task(tasks.create_repo_fork, form_data, cur_user)
726 return run_task(tasks.create_repo_fork, form_data, cur_user)
727
727
728 def archive(self, repo):
728 def archive(self, repo):
729 """
729 """
730 Archive given repository. Set archive flag.
730 Archive given repository. Set archive flag.
731
731
732 :param repo:
732 :param repo:
733 """
733 """
734 repo = self._get_repo(repo)
734 repo = self._get_repo(repo)
735 if repo:
735 if repo:
736
736
737 try:
737 try:
738 repo.archived = True
738 repo.archived = True
739 self.sa.add(repo)
739 self.sa.add(repo)
740 self.sa.commit()
740 self.sa.commit()
741 except Exception:
741 except Exception:
742 log.error(traceback.format_exc())
742 log.error(traceback.format_exc())
743 raise
743 raise
744
744
745 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
745 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
746 """
746 """
747 Delete given repository, forks parameter defines what do do with
747 Delete given repository, forks parameter defines what do do with
748 attached forks. Throws AttachedForksError if deleted repo has attached
748 attached forks. Throws AttachedForksError if deleted repo has attached
749 forks
749 forks
750
750
751 :param repo:
751 :param repo:
752 :param forks: str 'delete' or 'detach'
752 :param forks: str 'delete' or 'detach'
753 :param pull_requests: str 'delete' or None
753 :param pull_requests: str 'delete' or None
754 :param fs_remove: remove(archive) repo from filesystem
754 :param fs_remove: remove(archive) repo from filesystem
755 """
755 """
756 if not cur_user:
756 if not cur_user:
757 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
757 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
758 repo = self._get_repo(repo)
758 repo = self._get_repo(repo)
759 if repo:
759 if repo:
760 if forks == 'detach':
760 if forks == 'detach':
761 for r in repo.forks:
761 for r in repo.forks:
762 r.fork = None
762 r.fork = None
763 self.sa.add(r)
763 self.sa.add(r)
764 elif forks == 'delete':
764 elif forks == 'delete':
765 for r in repo.forks:
765 for r in repo.forks:
766 self.delete(r, forks='delete')
766 self.delete(r, forks='delete')
767 elif [f for f in repo.forks]:
767 elif [f for f in repo.forks]:
768 raise AttachedForksError()
768 raise AttachedForksError()
769
769
770 # check for pull requests
770 # check for pull requests
771 pr_sources = repo.pull_requests_source
771 pr_sources = repo.pull_requests_source
772 pr_targets = repo.pull_requests_target
772 pr_targets = repo.pull_requests_target
773 if pull_requests != 'delete' and (pr_sources or pr_targets):
773 if pull_requests != 'delete' and (pr_sources or pr_targets):
774 raise AttachedPullRequestsError()
774 raise AttachedPullRequestsError()
775
775
776 old_repo_dict = repo.get_dict()
776 old_repo_dict = repo.get_dict()
777 events.trigger(events.RepoPreDeleteEvent(repo))
777 events.trigger(events.RepoPreDeleteEvent(repo))
778 try:
778 try:
779 self.sa.delete(repo)
779 self.sa.delete(repo)
780 if fs_remove:
780 if fs_remove:
781 self._delete_filesystem_repo(repo)
781 self._delete_filesystem_repo(repo)
782 else:
782 else:
783 log.debug('skipping removal from filesystem')
783 log.debug('skipping removal from filesystem')
784 old_repo_dict.update({
784 old_repo_dict.update({
785 'deleted_by': cur_user,
785 'deleted_by': cur_user,
786 'deleted_on': time.time(),
786 'deleted_on': time.time(),
787 })
787 })
788 hooks_base.delete_repository(**old_repo_dict)
788 hooks_base.delete_repository(**old_repo_dict)
789 events.trigger(events.RepoDeleteEvent(repo))
789 events.trigger(events.RepoDeleteEvent(repo))
790 except Exception:
790 except Exception:
791 log.error(traceback.format_exc())
791 log.error(traceback.format_exc())
792 raise
792 raise
793
793
794 def grant_user_permission(self, repo, user, perm):
794 def grant_user_permission(self, repo, user, perm):
795 """
795 """
796 Grant permission for user on given repository, or update existing one
796 Grant permission for user on given repository, or update existing one
797 if found
797 if found
798
798
799 :param repo: Instance of Repository, repository_id, or repository name
799 :param repo: Instance of Repository, repository_id, or repository name
800 :param user: Instance of User, user_id or username
800 :param user: Instance of User, user_id or username
801 :param perm: Instance of Permission, or permission_name
801 :param perm: Instance of Permission, or permission_name
802 """
802 """
803 user = self._get_user(user)
803 user = self._get_user(user)
804 repo = self._get_repo(repo)
804 repo = self._get_repo(repo)
805 permission = self._get_perm(perm)
805 permission = self._get_perm(perm)
806
806
807 # check if we have that permission already
807 # check if we have that permission already
808 obj = self.sa.query(UserRepoToPerm) \
808 obj = self.sa.query(UserRepoToPerm) \
809 .filter(UserRepoToPerm.user == user) \
809 .filter(UserRepoToPerm.user == user) \
810 .filter(UserRepoToPerm.repository == repo) \
810 .filter(UserRepoToPerm.repository == repo) \
811 .scalar()
811 .scalar()
812 if obj is None:
812 if obj is None:
813 # create new !
813 # create new !
814 obj = UserRepoToPerm()
814 obj = UserRepoToPerm()
815 obj.repository = repo
815 obj.repository = repo
816 obj.user = user
816 obj.user = user
817 obj.permission = permission
817 obj.permission = permission
818 self.sa.add(obj)
818 self.sa.add(obj)
819 log.debug('Granted perm %s to %s on %s', perm, user, repo)
819 log.debug('Granted perm %s to %s on %s', perm, user, repo)
820 action_logger_generic(
820 action_logger_generic(
821 'granted permission: {} to user: {} on repo: {}'.format(
821 'granted permission: {} to user: {} on repo: {}'.format(
822 perm, user, repo), namespace='security.repo')
822 perm, user, repo), namespace='security.repo')
823 return obj
823 return obj
824
824
825 def revoke_user_permission(self, repo, user):
825 def revoke_user_permission(self, repo, user):
826 """
826 """
827 Revoke permission for user on given repository
827 Revoke permission for user on given repository
828
828
829 :param repo: Instance of Repository, repository_id, or repository name
829 :param repo: Instance of Repository, repository_id, or repository name
830 :param user: Instance of User, user_id or username
830 :param user: Instance of User, user_id or username
831 """
831 """
832
832
833 user = self._get_user(user)
833 user = self._get_user(user)
834 repo = self._get_repo(repo)
834 repo = self._get_repo(repo)
835
835
836 obj = self.sa.query(UserRepoToPerm) \
836 obj = self.sa.query(UserRepoToPerm) \
837 .filter(UserRepoToPerm.repository == repo) \
837 .filter(UserRepoToPerm.repository == repo) \
838 .filter(UserRepoToPerm.user == user) \
838 .filter(UserRepoToPerm.user == user) \
839 .scalar()
839 .scalar()
840 if obj:
840 if obj:
841 self.sa.delete(obj)
841 self.sa.delete(obj)
842 log.debug('Revoked perm on %s on %s', repo, user)
842 log.debug('Revoked perm on %s on %s', repo, user)
843 action_logger_generic(
843 action_logger_generic(
844 'revoked permission from user: {} on repo: {}'.format(
844 'revoked permission from user: {} on repo: {}'.format(
845 user, repo), namespace='security.repo')
845 user, repo), namespace='security.repo')
846
846
847 def grant_user_group_permission(self, repo, group_name, perm):
847 def grant_user_group_permission(self, repo, group_name, perm):
848 """
848 """
849 Grant permission for user group on given repository, or update
849 Grant permission for user group on given repository, or update
850 existing one if found
850 existing one if found
851
851
852 :param repo: Instance of Repository, repository_id, or repository name
852 :param repo: Instance of Repository, repository_id, or repository name
853 :param group_name: Instance of UserGroup, users_group_id,
853 :param group_name: Instance of UserGroup, users_group_id,
854 or user group name
854 or user group name
855 :param perm: Instance of Permission, or permission_name
855 :param perm: Instance of Permission, or permission_name
856 """
856 """
857 repo = self._get_repo(repo)
857 repo = self._get_repo(repo)
858 group_name = self._get_user_group(group_name)
858 group_name = self._get_user_group(group_name)
859 permission = self._get_perm(perm)
859 permission = self._get_perm(perm)
860
860
861 # check if we have that permission already
861 # check if we have that permission already
862 obj = self.sa.query(UserGroupRepoToPerm) \
862 obj = self.sa.query(UserGroupRepoToPerm) \
863 .filter(UserGroupRepoToPerm.users_group == group_name) \
863 .filter(UserGroupRepoToPerm.users_group == group_name) \
864 .filter(UserGroupRepoToPerm.repository == repo) \
864 .filter(UserGroupRepoToPerm.repository == repo) \
865 .scalar()
865 .scalar()
866
866
867 if obj is None:
867 if obj is None:
868 # create new
868 # create new
869 obj = UserGroupRepoToPerm()
869 obj = UserGroupRepoToPerm()
870
870
871 obj.repository = repo
871 obj.repository = repo
872 obj.users_group = group_name
872 obj.users_group = group_name
873 obj.permission = permission
873 obj.permission = permission
874 self.sa.add(obj)
874 self.sa.add(obj)
875 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
875 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
876 action_logger_generic(
876 action_logger_generic(
877 'granted permission: {} to usergroup: {} on repo: {}'.format(
877 'granted permission: {} to usergroup: {} on repo: {}'.format(
878 perm, group_name, repo), namespace='security.repo')
878 perm, group_name, repo), namespace='security.repo')
879
879
880 return obj
880 return obj
881
881
882 def revoke_user_group_permission(self, repo, group_name):
882 def revoke_user_group_permission(self, repo, group_name):
883 """
883 """
884 Revoke permission for user group on given repository
884 Revoke permission for user group on given repository
885
885
886 :param repo: Instance of Repository, repository_id, or repository name
886 :param repo: Instance of Repository, repository_id, or repository name
887 :param group_name: Instance of UserGroup, users_group_id,
887 :param group_name: Instance of UserGroup, users_group_id,
888 or user group name
888 or user group name
889 """
889 """
890 repo = self._get_repo(repo)
890 repo = self._get_repo(repo)
891 group_name = self._get_user_group(group_name)
891 group_name = self._get_user_group(group_name)
892
892
893 obj = self.sa.query(UserGroupRepoToPerm) \
893 obj = self.sa.query(UserGroupRepoToPerm) \
894 .filter(UserGroupRepoToPerm.repository == repo) \
894 .filter(UserGroupRepoToPerm.repository == repo) \
895 .filter(UserGroupRepoToPerm.users_group == group_name) \
895 .filter(UserGroupRepoToPerm.users_group == group_name) \
896 .scalar()
896 .scalar()
897 if obj:
897 if obj:
898 self.sa.delete(obj)
898 self.sa.delete(obj)
899 log.debug('Revoked perm to %s on %s', repo, group_name)
899 log.debug('Revoked perm to %s on %s', repo, group_name)
900 action_logger_generic(
900 action_logger_generic(
901 'revoked permission from usergroup: {} on repo: {}'.format(
901 'revoked permission from usergroup: {} on repo: {}'.format(
902 group_name, repo), namespace='security.repo')
902 group_name, repo), namespace='security.repo')
903
903
904 def delete_stats(self, repo_name):
904 def delete_stats(self, repo_name):
905 """
905 """
906 removes stats for given repo
906 removes stats for given repo
907
907
908 :param repo_name:
908 :param repo_name:
909 """
909 """
910 repo = self._get_repo(repo_name)
910 repo = self._get_repo(repo_name)
911 try:
911 try:
912 obj = self.sa.query(Statistics) \
912 obj = self.sa.query(Statistics) \
913 .filter(Statistics.repository == repo).scalar()
913 .filter(Statistics.repository == repo).scalar()
914 if obj:
914 if obj:
915 self.sa.delete(obj)
915 self.sa.delete(obj)
916 except Exception:
916 except Exception:
917 log.error(traceback.format_exc())
917 log.error(traceback.format_exc())
918 raise
918 raise
919
919
920 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
920 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
921 field_type='str', field_desc=''):
921 field_type='str', field_desc=''):
922
922
923 repo = self._get_repo(repo_name)
923 repo = self._get_repo(repo_name)
924
924
925 new_field = RepositoryField()
925 new_field = RepositoryField()
926 new_field.repository = repo
926 new_field.repository = repo
927 new_field.field_key = field_key
927 new_field.field_key = field_key
928 new_field.field_type = field_type # python type
928 new_field.field_type = field_type # python type
929 new_field.field_value = field_value
929 new_field.field_value = field_value
930 new_field.field_desc = field_desc
930 new_field.field_desc = field_desc
931 new_field.field_label = field_label
931 new_field.field_label = field_label
932 self.sa.add(new_field)
932 self.sa.add(new_field)
933 return new_field
933 return new_field
934
934
935 def delete_repo_field(self, repo_name, field_key):
935 def delete_repo_field(self, repo_name, field_key):
936 repo = self._get_repo(repo_name)
936 repo = self._get_repo(repo_name)
937 field = RepositoryField.get_by_key_name(field_key, repo)
937 field = RepositoryField.get_by_key_name(field_key, repo)
938 if field:
938 if field:
939 self.sa.delete(field)
939 self.sa.delete(field)
940
940
941 def set_landing_rev(self, repo, landing_rev_name):
941 def set_landing_rev(self, repo, landing_rev_name):
942 if landing_rev_name.startswith('branch:'):
942 if landing_rev_name.startswith('branch:'):
943 landing_rev_name = landing_rev_name.split('branch:')[-1]
943 landing_rev_name = landing_rev_name.split('branch:')[-1]
944 scm_instance = repo.scm_instance()
944 scm_instance = repo.scm_instance()
945 if scm_instance:
945 if scm_instance:
946 return scm_instance._remote.set_head_ref(landing_rev_name)
946 return scm_instance._remote.set_head_ref(landing_rev_name)
947
947
948 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
948 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
949 clone_uri=None, repo_store_location=None,
949 clone_uri=None, repo_store_location=None,
950 use_global_config=False, install_hooks=True):
950 use_global_config=False, install_hooks=True):
951 """
951 """
952 makes repository on filesystem. It's group aware means it'll create
952 makes repository on filesystem. It's group aware means it'll create
953 a repository within a group, and alter the paths accordingly of
953 a repository within a group, and alter the paths accordingly of
954 group location
954 group location
955
955
956 :param repo_name:
956 :param repo_name:
957 :param alias:
957 :param alias:
958 :param parent:
958 :param parent:
959 :param clone_uri:
959 :param clone_uri:
960 :param repo_store_location:
960 :param repo_store_location:
961 """
961 """
962 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
962 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
963 from rhodecode.model.scm import ScmModel
963 from rhodecode.model.scm import ScmModel
964
964
965 if Repository.NAME_SEP in repo_name:
965 if Repository.NAME_SEP in repo_name:
966 raise ValueError(
966 raise ValueError(
967 'repo_name must not contain groups got `%s`' % repo_name)
967 'repo_name must not contain groups got `%s`' % repo_name)
968
968
969 if isinstance(repo_group, RepoGroup):
969 if isinstance(repo_group, RepoGroup):
970 new_parent_path = os.sep.join(repo_group.full_path_splitted)
970 new_parent_path = os.sep.join(repo_group.full_path_splitted)
971 else:
971 else:
972 new_parent_path = repo_group or ''
972 new_parent_path = repo_group or ''
973
973
974 if repo_store_location:
974 if repo_store_location:
975 _paths = [repo_store_location]
975 _paths = [repo_store_location]
976 else:
976 else:
977 _paths = [self.repos_path, new_parent_path, repo_name]
977 _paths = [self.repos_path, new_parent_path, repo_name]
978 # we need to make it str for mercurial
978 # we need to make it str for mercurial
979 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
979 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
980
980
981 # check if this path is not a repository
981 # check if this path is not a repository
982 if is_valid_repo(repo_path, self.repos_path):
982 if is_valid_repo(repo_path, self.repos_path):
983 raise Exception('This path %s is a valid repository' % repo_path)
983 raise Exception('This path %s is a valid repository' % repo_path)
984
984
985 # check if this path is a group
985 # check if this path is a group
986 if is_valid_repo_group(repo_path, self.repos_path):
986 if is_valid_repo_group(repo_path, self.repos_path):
987 raise Exception('This path %s is a valid group' % repo_path)
987 raise Exception('This path %s is a valid group' % repo_path)
988
988
989 log.info('creating repo %s in %s from url: `%s`',
989 log.info('creating repo %s in %s from url: `%s`',
990 repo_name, safe_unicode(repo_path),
990 repo_name, safe_unicode(repo_path),
991 obfuscate_url_pw(clone_uri))
991 obfuscate_url_pw(clone_uri))
992
992
993 backend = get_backend(repo_type)
993 backend = get_backend(repo_type)
994
994
995 config_repo = None if use_global_config else repo_name
995 config_repo = None if use_global_config else repo_name
996 if config_repo and new_parent_path:
996 if config_repo and new_parent_path:
997 config_repo = Repository.NAME_SEP.join(
997 config_repo = Repository.NAME_SEP.join(
998 (new_parent_path, config_repo))
998 (new_parent_path, config_repo))
999 config = make_db_config(clear_session=False, repo=config_repo)
999 config = make_db_config(clear_session=False, repo=config_repo)
1000 config.set('extensions', 'largefiles', '')
1000 config.set('extensions', 'largefiles', '')
1001
1001
1002 # patch and reset hooks section of UI config to not run any
1002 # patch and reset hooks section of UI config to not run any
1003 # hooks on creating remote repo
1003 # hooks on creating remote repo
1004 config.clear_section('hooks')
1004 config.clear_section('hooks')
1005
1005
1006 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
1006 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
1007 if repo_type == 'git':
1007 if repo_type == 'git':
1008 repo = backend(
1008 repo = backend(
1009 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
1009 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
1010 with_wire={"cache": False})
1010 with_wire={"cache": False})
1011 else:
1011 else:
1012 repo = backend(
1012 repo = backend(
1013 repo_path, config=config, create=True, src_url=clone_uri,
1013 repo_path, config=config, create=True, src_url=clone_uri,
1014 with_wire={"cache": False})
1014 with_wire={"cache": False})
1015
1015
1016 if install_hooks:
1016 if install_hooks:
1017 repo.install_hooks()
1017 repo.install_hooks()
1018
1018
1019 log.debug('Created repo %s with %s backend',
1019 log.debug('Created repo %s with %s backend',
1020 safe_unicode(repo_name), safe_unicode(repo_type))
1020 safe_unicode(repo_name), safe_unicode(repo_type))
1021 return repo
1021 return repo
1022
1022
1023 def _rename_filesystem_repo(self, old, new):
1023 def _rename_filesystem_repo(self, old, new):
1024 """
1024 """
1025 renames repository on filesystem
1025 renames repository on filesystem
1026
1026
1027 :param old: old name
1027 :param old: old name
1028 :param new: new name
1028 :param new: new name
1029 """
1029 """
1030 log.info('renaming repo from %s to %s', old, new)
1030 log.info('renaming repo from %s to %s', old, new)
1031
1031
1032 old_path = os.path.join(self.repos_path, old)
1032 old_path = os.path.join(self.repos_path, old)
1033 new_path = os.path.join(self.repos_path, new)
1033 new_path = os.path.join(self.repos_path, new)
1034 if os.path.isdir(new_path):
1034 if os.path.isdir(new_path):
1035 raise Exception(
1035 raise Exception(
1036 'Was trying to rename to already existing dir %s' % new_path
1036 'Was trying to rename to already existing dir %s' % new_path
1037 )
1037 )
1038 shutil.move(old_path, new_path)
1038 shutil.move(old_path, new_path)
1039
1039
1040 def _delete_filesystem_repo(self, repo):
1040 def _delete_filesystem_repo(self, repo):
1041 """
1041 """
1042 removes repo from filesystem, the removal is acctually made by
1042 removes repo from filesystem, the removal is acctually made by
1043 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1043 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1044 repository is no longer valid for rhodecode, can be undeleted later on
1044 repository is no longer valid for rhodecode, can be undeleted later on
1045 by reverting the renames on this repository
1045 by reverting the renames on this repository
1046
1046
1047 :param repo: repo object
1047 :param repo: repo object
1048 """
1048 """
1049 rm_path = os.path.join(self.repos_path, repo.repo_name)
1049 rm_path = os.path.join(self.repos_path, repo.repo_name)
1050 repo_group = repo.group
1050 repo_group = repo.group
1051 log.info("Removing repository %s", rm_path)
1051 log.info("Removing repository %s", rm_path)
1052 # disable hg/git internal that it doesn't get detected as repo
1052 # disable hg/git internal that it doesn't get detected as repo
1053 alias = repo.repo_type
1053 alias = repo.repo_type
1054
1054
1055 config = make_db_config(clear_session=False)
1055 config = make_db_config(clear_session=False)
1056 config.set('extensions', 'largefiles', '')
1056 config.set('extensions', 'largefiles', '')
1057 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1057 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1058
1058
1059 # skip this for bare git repos
1059 # skip this for bare git repos
1060 if not bare:
1060 if not bare:
1061 # disable VCS repo
1061 # disable VCS repo
1062 vcs_path = os.path.join(rm_path, '.%s' % alias)
1062 vcs_path = os.path.join(rm_path, '.%s' % alias)
1063 if os.path.exists(vcs_path):
1063 if os.path.exists(vcs_path):
1064 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1064 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1065
1065
1066 _now = datetime.datetime.now()
1066 _now = datetime.datetime.now()
1067 _ms = str(_now.microsecond).rjust(6, '0')
1067 _ms = str(_now.microsecond).rjust(6, '0')
1068 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1068 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1069 repo.just_name)
1069 repo.just_name)
1070 if repo_group:
1070 if repo_group:
1071 # if repository is in group, prefix the removal path with the group
1071 # if repository is in group, prefix the removal path with the group
1072 args = repo_group.full_path_splitted + [_d]
1072 args = repo_group.full_path_splitted + [_d]
1073 _d = os.path.join(*args)
1073 _d = os.path.join(*args)
1074
1074
1075 if os.path.isdir(rm_path):
1075 if os.path.isdir(rm_path):
1076 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1076 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1077
1077
1078 # finally cleanup diff-cache if it exists
1078 # finally cleanup diff-cache if it exists
1079 cached_diffs_dir = repo.cached_diffs_dir
1079 cached_diffs_dir = repo.cached_diffs_dir
1080 if os.path.isdir(cached_diffs_dir):
1080 if os.path.isdir(cached_diffs_dir):
1081 shutil.rmtree(cached_diffs_dir)
1081 shutil.rmtree(cached_diffs_dir)
1082
1082
1083
1083
1084 class ReadmeFinder:
1084 class ReadmeFinder:
1085 """
1085 """
1086 Utility which knows how to find a readme for a specific commit.
1086 Utility which knows how to find a readme for a specific commit.
1087
1087
1088 The main idea is that this is a configurable algorithm. When creating an
1088 The main idea is that this is a configurable algorithm. When creating an
1089 instance you can define parameters, currently only the `default_renderer`.
1089 instance you can define parameters, currently only the `default_renderer`.
1090 Based on this configuration the method :meth:`search` behaves slightly
1090 Based on this configuration the method :meth:`search` behaves slightly
1091 different.
1091 different.
1092 """
1092 """
1093
1093
1094 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1094 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1095 path_re = re.compile(r'^docs?', re.IGNORECASE)
1095 path_re = re.compile(r'^docs?', re.IGNORECASE)
1096
1096
1097 default_priorities = {
1097 default_priorities = {
1098 None: 0,
1098 None: 0,
1099 '.text': 2,
1099 '.text': 2,
1100 '.txt': 3,
1100 '.txt': 3,
1101 '.rst': 1,
1101 '.rst': 1,
1102 '.rest': 2,
1102 '.rest': 2,
1103 '.md': 1,
1103 '.md': 1,
1104 '.mkdn': 2,
1104 '.mkdn': 2,
1105 '.mdown': 3,
1105 '.mdown': 3,
1106 '.markdown': 4,
1106 '.markdown': 4,
1107 }
1107 }
1108
1108
1109 path_priority = {
1109 path_priority = {
1110 'doc': 0,
1110 'doc': 0,
1111 'docs': 1,
1111 'docs': 1,
1112 }
1112 }
1113
1113
1114 FALLBACK_PRIORITY = 99
1114 FALLBACK_PRIORITY = 99
1115
1115
1116 RENDERER_TO_EXTENSION = {
1116 RENDERER_TO_EXTENSION = {
1117 'rst': ['.rst', '.rest'],
1117 'rst': ['.rst', '.rest'],
1118 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1118 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1119 }
1119 }
1120
1120
1121 def __init__(self, default_renderer=None):
1121 def __init__(self, default_renderer=None):
1122 self._default_renderer = default_renderer
1122 self._default_renderer = default_renderer
1123 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1123 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1124 default_renderer, [])
1124 default_renderer, [])
1125
1125
1126 def search(self, commit, path=u'/'):
1126 def search(self, commit, path=u'/'):
1127 """
1127 """
1128 Find a readme in the given `commit`.
1128 Find a readme in the given `commit`.
1129 """
1129 """
1130 nodes = commit.get_nodes(path)
1130 nodes = commit.get_nodes(path)
1131 matches = self._match_readmes(nodes)
1131 matches = self._match_readmes(nodes)
1132 matches = self._sort_according_to_priority(matches)
1132 matches = self._sort_according_to_priority(matches)
1133 if matches:
1133 if matches:
1134 return matches[0].node
1134 return matches[0].node
1135
1135
1136 paths = self._match_paths(nodes)
1136 paths = self._match_paths(nodes)
1137 paths = self._sort_paths_according_to_priority(paths)
1137 paths = self._sort_paths_according_to_priority(paths)
1138 for path in paths:
1138 for path in paths:
1139 match = self.search(commit, path=path)
1139 match = self.search(commit, path=path)
1140 if match:
1140 if match:
1141 return match
1141 return match
1142
1142
1143 return None
1143 return None
1144
1144
1145 def _match_readmes(self, nodes):
1145 def _match_readmes(self, nodes):
1146 for node in nodes:
1146 for node in nodes:
1147 if not node.is_file():
1147 if not node.is_file():
1148 continue
1148 continue
1149 path = node.path.rsplit('/', 1)[-1]
1149 path = node.path.rsplit('/', 1)[-1]
1150 match = self.readme_re.match(path)
1150 match = self.readme_re.match(path)
1151 if match:
1151 if match:
1152 extension = match.group(1)
1152 extension = match.group(1)
1153 yield ReadmeMatch(node, match, self._priority(extension))
1153 yield ReadmeMatch(node, match, self._priority(extension))
1154
1154
1155 def _match_paths(self, nodes):
1155 def _match_paths(self, nodes):
1156 for node in nodes:
1156 for node in nodes:
1157 if not node.is_dir():
1157 if not node.is_dir():
1158 continue
1158 continue
1159 match = self.path_re.match(node.path)
1159 match = self.path_re.match(node.path)
1160 if match:
1160 if match:
1161 yield node.path
1161 yield node.path
1162
1162
1163 def _priority(self, extension):
1163 def _priority(self, extension):
1164 renderer_priority = (
1164 renderer_priority = (
1165 0 if extension in self._renderer_extensions else 1)
1165 0 if extension in self._renderer_extensions else 1)
1166 extension_priority = self.default_priorities.get(
1166 extension_priority = self.default_priorities.get(
1167 extension, self.FALLBACK_PRIORITY)
1167 extension, self.FALLBACK_PRIORITY)
1168 return (renderer_priority, extension_priority)
1168 return (renderer_priority, extension_priority)
1169
1169
1170 def _sort_according_to_priority(self, matches):
1170 def _sort_according_to_priority(self, matches):
1171
1171
1172 def priority_and_path(match):
1172 def priority_and_path(match):
1173 return (match.priority, match.path)
1173 return (match.priority, match.path)
1174
1174
1175 return sorted(matches, key=priority_and_path)
1175 return sorted(matches, key=priority_and_path)
1176
1176
1177 def _sort_paths_according_to_priority(self, paths):
1177 def _sort_paths_according_to_priority(self, paths):
1178
1178
1179 def priority_and_path(path):
1179 def priority_and_path(path):
1180 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1180 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1181
1181
1182 return sorted(paths, key=priority_and_path)
1182 return sorted(paths, key=priority_and_path)
1183
1183
1184
1184
1185 class ReadmeMatch:
1185 class ReadmeMatch:
1186
1186
1187 def __init__(self, node, match, priority):
1187 def __init__(self, node, match, priority):
1188 self.node = node
1188 self.node = node
1189 self._match = match
1189 self._match = match
1190 self.priority = priority
1190 self.priority = priority
1191
1191
1192 @property
1192 @property
1193 def path(self):
1193 def path(self):
1194 return self.node.path
1194 return self.node.path
1195
1195
1196 def __repr__(self):
1196 def __repr__(self):
1197 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1197 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,897 +1,897 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import time
31 import time
32 import traceback
32 import traceback
33 import string
33 import string
34
34
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 UserGroup, Repository)
41 UserGroup, Repository)
42 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
44 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.caching_query import FromCache
45 from rhodecode.lib.utils2 import action_logger_generic
45 from rhodecode.lib.utils2 import action_logger_generic
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class RepoGroupModel(BaseModel):
50 class RepoGroupModel(BaseModel):
51
51
52 cls = RepoGroup
52 cls = RepoGroup
53 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
53 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
54 PERSONAL_GROUP_PATTERN = '${username}' # default
54 PERSONAL_GROUP_PATTERN = '${username}' # default
55
55
56 def _get_user_group(self, users_group):
56 def _get_user_group(self, users_group):
57 return self._get_instance(UserGroup, users_group,
57 return self._get_instance(UserGroup, users_group,
58 callback=UserGroup.get_by_group_name)
58 callback=UserGroup.get_by_group_name)
59
59
60 def _get_repo_group(self, repo_group):
60 def _get_repo_group(self, repo_group):
61 return self._get_instance(RepoGroup, repo_group,
61 return self._get_instance(RepoGroup, repo_group,
62 callback=RepoGroup.get_by_group_name)
62 callback=RepoGroup.get_by_group_name)
63
63
64 def get_repo_group(self, repo_group):
64 def get_repo_group(self, repo_group):
65 return self._get_repo_group(repo_group)
65 return self._get_repo_group(repo_group)
66
66
67 @LazyProperty
67 @LazyProperty
68 def repos_path(self):
68 def repos_path(self):
69 """
69 """
70 Gets the repositories root path from database
70 Gets the repositories root path from database
71 """
71 """
72
72
73 settings_model = VcsSettingsModel(sa=self.sa)
73 settings_model = VcsSettingsModel(sa=self.sa)
74 return settings_model.get_repos_location()
74 return settings_model.get_repos_location()
75
75
76 def get_by_group_name(self, repo_group_name, cache=None):
76 def get_by_group_name(self, repo_group_name, cache=None):
77 repo = self.sa.query(RepoGroup) \
77 repo = self.sa.query(RepoGroup) \
78 .filter(RepoGroup.group_name == repo_group_name)
78 .filter(RepoGroup.group_name == repo_group_name)
79
79
80 if cache:
80 if cache:
81 name_key = _hash_key(repo_group_name)
81 name_key = _hash_key(repo_group_name)
82 repo = repo.options(
82 repo = repo.options(
83 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
83 FromCache("sql_cache_short", f"get_repo_group_{name_key}"))
84 return repo.scalar()
84 return repo.scalar()
85
85
86 def get_default_create_personal_repo_group(self):
86 def get_default_create_personal_repo_group(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'create_personal_repo_group')
88 'create_personal_repo_group')
89 return value.app_settings_value if value else None or False
89 return value.app_settings_value if value else None or False
90
90
91 def get_personal_group_name_pattern(self):
91 def get_personal_group_name_pattern(self):
92 value = SettingsModel().get_setting_by_name(
92 value = SettingsModel().get_setting_by_name(
93 'personal_repo_group_pattern')
93 'personal_repo_group_pattern')
94 val = value.app_settings_value if value else None
94 val = value.app_settings_value if value else None
95 group_template = val or self.PERSONAL_GROUP_PATTERN
95 group_template = val or self.PERSONAL_GROUP_PATTERN
96
96
97 group_template = group_template.lstrip('/')
97 group_template = group_template.lstrip('/')
98 return group_template
98 return group_template
99
99
100 def get_personal_group_name(self, user):
100 def get_personal_group_name(self, user):
101 template = self.get_personal_group_name_pattern()
101 template = self.get_personal_group_name_pattern()
102 return string.Template(template).safe_substitute(
102 return string.Template(template).safe_substitute(
103 username=user.username,
103 username=user.username,
104 user_id=user.user_id,
104 user_id=user.user_id,
105 first_name=user.first_name,
105 first_name=user.first_name,
106 last_name=user.last_name,
106 last_name=user.last_name,
107 )
107 )
108
108
109 def create_personal_repo_group(self, user, commit_early=True):
109 def create_personal_repo_group(self, user, commit_early=True):
110 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
110 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
111 personal_repo_group_name = self.get_personal_group_name(user)
111 personal_repo_group_name = self.get_personal_group_name(user)
112
112
113 # create a new one
113 # create a new one
114 RepoGroupModel().create(
114 RepoGroupModel().create(
115 group_name=personal_repo_group_name,
115 group_name=personal_repo_group_name,
116 group_description=desc,
116 group_description=desc,
117 owner=user.username,
117 owner=user.username,
118 personal=True,
118 personal=True,
119 commit_early=commit_early)
119 commit_early=commit_early)
120
120
121 def _create_default_perms(self, new_group):
121 def _create_default_perms(self, new_group):
122 # create default permission
122 # create default permission
123 default_perm = 'group.read'
123 default_perm = 'group.read'
124 def_user = User.get_default_user()
124 def_user = User.get_default_user()
125 for p in def_user.user_perms:
125 for p in def_user.user_perms:
126 if p.permission.permission_name.startswith('group.'):
126 if p.permission.permission_name.startswith('group.'):
127 default_perm = p.permission.permission_name
127 default_perm = p.permission.permission_name
128 break
128 break
129
129
130 repo_group_to_perm = UserRepoGroupToPerm()
130 repo_group_to_perm = UserRepoGroupToPerm()
131 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
131 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
132
132
133 repo_group_to_perm.group = new_group
133 repo_group_to_perm.group = new_group
134 repo_group_to_perm.user_id = def_user.user_id
134 repo_group_to_perm.user_id = def_user.user_id
135 return repo_group_to_perm
135 return repo_group_to_perm
136
136
137 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
137 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
138 get_object=False):
138 get_object=False):
139 """
139 """
140 Get's the group name and a parent group name from given group name.
140 Get's the group name and a parent group name from given group name.
141 If repo_in_path is set to truth, we asume the full path also includes
141 If repo_in_path is set to truth, we asume the full path also includes
142 repo name, in such case we clean the last element.
142 repo name, in such case we clean the last element.
143
143
144 :param group_name_full:
144 :param group_name_full:
145 """
145 """
146 split_paths = 1
146 split_paths = 1
147 if repo_in_path:
147 if repo_in_path:
148 split_paths = 2
148 split_paths = 2
149 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
149 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
150
150
151 if repo_in_path and len(_parts) > 1:
151 if repo_in_path and len(_parts) > 1:
152 # such case last element is the repo_name
152 # such case last element is the repo_name
153 _parts.pop(-1)
153 _parts.pop(-1)
154 group_name_cleaned = _parts[-1] # just the group name
154 group_name_cleaned = _parts[-1] # just the group name
155 parent_repo_group_name = None
155 parent_repo_group_name = None
156
156
157 if len(_parts) > 1:
157 if len(_parts) > 1:
158 parent_repo_group_name = _parts[0]
158 parent_repo_group_name = _parts[0]
159
159
160 parent_group = None
160 parent_group = None
161 if parent_repo_group_name:
161 if parent_repo_group_name:
162 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
162 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
163
163
164 if get_object:
164 if get_object:
165 return group_name_cleaned, parent_repo_group_name, parent_group
165 return group_name_cleaned, parent_repo_group_name, parent_group
166
166
167 return group_name_cleaned, parent_repo_group_name
167 return group_name_cleaned, parent_repo_group_name
168
168
169 def check_exist_filesystem(self, group_name, exc_on_failure=True):
169 def check_exist_filesystem(self, group_name, exc_on_failure=True):
170 create_path = os.path.join(self.repos_path, group_name)
170 create_path = os.path.join(self.repos_path, group_name)
171 log.debug('creating new group in %s', create_path)
171 log.debug('creating new group in %s', create_path)
172
172
173 if os.path.isdir(create_path):
173 if os.path.isdir(create_path):
174 if exc_on_failure:
174 if exc_on_failure:
175 abs_create_path = os.path.abspath(create_path)
175 abs_create_path = os.path.abspath(create_path)
176 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
176 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
177 return False
177 return False
178 return True
178 return True
179
179
180 def _create_group(self, group_name):
180 def _create_group(self, group_name):
181 """
181 """
182 makes repository group on filesystem
182 makes repository group on filesystem
183
183
184 :param repo_name:
184 :param repo_name:
185 :param parent_id:
185 :param parent_id:
186 """
186 """
187
187
188 self.check_exist_filesystem(group_name)
188 self.check_exist_filesystem(group_name)
189 create_path = os.path.join(self.repos_path, group_name)
189 create_path = os.path.join(self.repos_path, group_name)
190 log.debug('creating new group in %s', create_path)
190 log.debug('creating new group in %s', create_path)
191 os.makedirs(create_path, mode=0o755)
191 os.makedirs(create_path, mode=0o755)
192 log.debug('created group in %s', create_path)
192 log.debug('created group in %s', create_path)
193
193
194 def _rename_group(self, old, new):
194 def _rename_group(self, old, new):
195 """
195 """
196 Renames a group on filesystem
196 Renames a group on filesystem
197
197
198 :param group_name:
198 :param group_name:
199 """
199 """
200
200
201 if old == new:
201 if old == new:
202 log.debug('skipping group rename')
202 log.debug('skipping group rename')
203 return
203 return
204
204
205 log.debug('renaming repository group from %s to %s', old, new)
205 log.debug('renaming repository group from %s to %s', old, new)
206
206
207 old_path = os.path.join(self.repos_path, old)
207 old_path = os.path.join(self.repos_path, old)
208 new_path = os.path.join(self.repos_path, new)
208 new_path = os.path.join(self.repos_path, new)
209
209
210 log.debug('renaming repos paths from %s to %s', old_path, new_path)
210 log.debug('renaming repos paths from %s to %s', old_path, new_path)
211
211
212 if os.path.isdir(new_path):
212 if os.path.isdir(new_path):
213 raise Exception('Was trying to rename to already '
213 raise Exception('Was trying to rename to already '
214 'existing dir %s' % new_path)
214 'existing dir %s' % new_path)
215 shutil.move(old_path, new_path)
215 shutil.move(old_path, new_path)
216
216
217 def _delete_filesystem_group(self, group, force_delete=False):
217 def _delete_filesystem_group(self, group, force_delete=False):
218 """
218 """
219 Deletes a group from a filesystem
219 Deletes a group from a filesystem
220
220
221 :param group: instance of group from database
221 :param group: instance of group from database
222 :param force_delete: use shutil rmtree to remove all objects
222 :param force_delete: use shutil rmtree to remove all objects
223 """
223 """
224 paths = group.full_path.split(RepoGroup.url_sep())
224 paths = group.full_path.split(RepoGroup.url_sep())
225 paths = os.sep.join(paths)
225 paths = os.sep.join(paths)
226
226
227 rm_path = os.path.join(self.repos_path, paths)
227 rm_path = os.path.join(self.repos_path, paths)
228 log.info("Removing group %s", rm_path)
228 log.info("Removing group %s", rm_path)
229 # delete only if that path really exists
229 # delete only if that path really exists
230 if os.path.isdir(rm_path):
230 if os.path.isdir(rm_path):
231 if force_delete:
231 if force_delete:
232 shutil.rmtree(rm_path)
232 shutil.rmtree(rm_path)
233 else:
233 else:
234 # archive that group`
234 # archive that group`
235 _now = datetime.datetime.now()
235 _now = datetime.datetime.now()
236 _ms = str(_now.microsecond).rjust(6, '0')
236 _ms = str(_now.microsecond).rjust(6, '0')
237 _d = 'rm__%s_GROUP_%s' % (
237 _d = 'rm__%s_GROUP_%s' % (
238 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
238 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
239 shutil.move(rm_path, os.path.join(self.repos_path, _d))
239 shutil.move(rm_path, os.path.join(self.repos_path, _d))
240
240
241 def create(self, group_name, group_description, owner, just_db=False,
241 def create(self, group_name, group_description, owner, just_db=False,
242 copy_permissions=False, personal=None, commit_early=True):
242 copy_permissions=False, personal=None, commit_early=True):
243
243
244 (group_name_cleaned,
244 (group_name_cleaned,
245 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
245 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
246
246
247 parent_group = None
247 parent_group = None
248 if parent_group_name:
248 if parent_group_name:
249 parent_group = self._get_repo_group(parent_group_name)
249 parent_group = self._get_repo_group(parent_group_name)
250 if not parent_group:
250 if not parent_group:
251 # we tried to create a nested group, but the parent is not
251 # we tried to create a nested group, but the parent is not
252 # existing
252 # existing
253 raise ValueError(
253 raise ValueError(
254 'Parent group `%s` given in `%s` group name '
254 'Parent group `%s` given in `%s` group name '
255 'is not yet existing.' % (parent_group_name, group_name))
255 'is not yet existing.' % (parent_group_name, group_name))
256
256
257 # because we are doing a cleanup, we need to check if such directory
257 # because we are doing a cleanup, we need to check if such directory
258 # already exists. If we don't do that we can accidentally delete
258 # already exists. If we don't do that we can accidentally delete
259 # existing directory via cleanup that can cause data issues, since
259 # existing directory via cleanup that can cause data issues, since
260 # delete does a folder rename to special syntax later cleanup
260 # delete does a folder rename to special syntax later cleanup
261 # functions can delete this
261 # functions can delete this
262 cleanup_group = self.check_exist_filesystem(group_name,
262 cleanup_group = self.check_exist_filesystem(group_name,
263 exc_on_failure=False)
263 exc_on_failure=False)
264 user = self._get_user(owner)
264 user = self._get_user(owner)
265 if not user:
265 if not user:
266 raise ValueError('Owner %s not found as rhodecode user', owner)
266 raise ValueError('Owner %s not found as rhodecode user', owner)
267
267
268 try:
268 try:
269 new_repo_group = RepoGroup()
269 new_repo_group = RepoGroup()
270 new_repo_group.user = user
270 new_repo_group.user = user
271 new_repo_group.group_description = group_description or group_name
271 new_repo_group.group_description = group_description or group_name
272 new_repo_group.parent_group = parent_group
272 new_repo_group.parent_group = parent_group
273 new_repo_group.group_name = group_name
273 new_repo_group.group_name = group_name
274 new_repo_group.personal = personal
274 new_repo_group.personal = personal
275
275
276 self.sa.add(new_repo_group)
276 self.sa.add(new_repo_group)
277
277
278 # create an ADMIN permission for owner except if we're super admin,
278 # create an ADMIN permission for owner except if we're super admin,
279 # later owner should go into the owner field of groups
279 # later owner should go into the owner field of groups
280 if not user.is_admin:
280 if not user.is_admin:
281 self.grant_user_permission(repo_group=new_repo_group,
281 self.grant_user_permission(repo_group=new_repo_group,
282 user=owner, perm='group.admin')
282 user=owner, perm='group.admin')
283
283
284 if parent_group and copy_permissions:
284 if parent_group and copy_permissions:
285 # copy permissions from parent
285 # copy permissions from parent
286 user_perms = UserRepoGroupToPerm.query() \
286 user_perms = UserRepoGroupToPerm.query() \
287 .filter(UserRepoGroupToPerm.group == parent_group).all()
287 .filter(UserRepoGroupToPerm.group == parent_group).all()
288
288
289 group_perms = UserGroupRepoGroupToPerm.query() \
289 group_perms = UserGroupRepoGroupToPerm.query() \
290 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
290 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
291
291
292 for perm in user_perms:
292 for perm in user_perms:
293 # don't copy over the permission for user who is creating
293 # don't copy over the permission for user who is creating
294 # this group, if he is not super admin he get's admin
294 # this group, if he is not super admin he get's admin
295 # permission set above
295 # permission set above
296 if perm.user != user or user.is_admin:
296 if perm.user != user or user.is_admin:
297 UserRepoGroupToPerm.create(
297 UserRepoGroupToPerm.create(
298 perm.user, new_repo_group, perm.permission)
298 perm.user, new_repo_group, perm.permission)
299
299
300 for perm in group_perms:
300 for perm in group_perms:
301 UserGroupRepoGroupToPerm.create(
301 UserGroupRepoGroupToPerm.create(
302 perm.users_group, new_repo_group, perm.permission)
302 perm.users_group, new_repo_group, perm.permission)
303 else:
303 else:
304 perm_obj = self._create_default_perms(new_repo_group)
304 perm_obj = self._create_default_perms(new_repo_group)
305 self.sa.add(perm_obj)
305 self.sa.add(perm_obj)
306
306
307 # now commit the changes, earlier so we are sure everything is in
307 # now commit the changes, earlier so we are sure everything is in
308 # the database.
308 # the database.
309 if commit_early:
309 if commit_early:
310 self.sa.commit()
310 self.sa.commit()
311 if not just_db:
311 if not just_db:
312 self._create_group(new_repo_group.group_name)
312 self._create_group(new_repo_group.group_name)
313
313
314 # trigger the post hook
314 # trigger the post hook
315 from rhodecode.lib import hooks_base
315 from rhodecode.lib import hooks_base
316 repo_group = RepoGroup.get_by_group_name(group_name)
316 repo_group = RepoGroup.get_by_group_name(group_name)
317
317
318 # update repo group commit caches initially
318 # update repo group commit caches initially
319 repo_group.update_commit_cache()
319 repo_group.update_commit_cache()
320
320
321 hooks_base.create_repository_group(
321 hooks_base.create_repository_group(
322 created_by=user.username, **repo_group.get_dict())
322 created_by=user.username, **repo_group.get_dict())
323
323
324 # Trigger create event.
324 # Trigger create event.
325 events.trigger(events.RepoGroupCreateEvent(repo_group))
325 events.trigger(events.RepoGroupCreateEvent(repo_group))
326
326
327 return new_repo_group
327 return new_repo_group
328 except Exception:
328 except Exception:
329 self.sa.rollback()
329 self.sa.rollback()
330 log.exception('Exception occurred when creating repository group, '
330 log.exception('Exception occurred when creating repository group, '
331 'doing cleanup...')
331 'doing cleanup...')
332 # rollback things manually !
332 # rollback things manually !
333 repo_group = RepoGroup.get_by_group_name(group_name)
333 repo_group = RepoGroup.get_by_group_name(group_name)
334 if repo_group:
334 if repo_group:
335 RepoGroup.delete(repo_group.group_id)
335 RepoGroup.delete(repo_group.group_id)
336 self.sa.commit()
336 self.sa.commit()
337 if cleanup_group:
337 if cleanup_group:
338 RepoGroupModel()._delete_filesystem_group(repo_group)
338 RepoGroupModel()._delete_filesystem_group(repo_group)
339 raise
339 raise
340
340
341 def update_permissions(
341 def update_permissions(
342 self, repo_group, perm_additions=None, perm_updates=None,
342 self, repo_group, perm_additions=None, perm_updates=None,
343 perm_deletions=None, recursive=None, check_perms=True,
343 perm_deletions=None, recursive=None, check_perms=True,
344 cur_user=None):
344 cur_user=None):
345 from rhodecode.model.repo import RepoModel
345 from rhodecode.model.repo import RepoModel
346 from rhodecode.lib.auth import HasUserGroupPermissionAny
346 from rhodecode.lib.auth import HasUserGroupPermissionAny
347
347
348 if not perm_additions:
348 if not perm_additions:
349 perm_additions = []
349 perm_additions = []
350 if not perm_updates:
350 if not perm_updates:
351 perm_updates = []
351 perm_updates = []
352 if not perm_deletions:
352 if not perm_deletions:
353 perm_deletions = []
353 perm_deletions = []
354
354
355 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
355 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
356
356
357 changes = {
357 changes = {
358 'added': [],
358 'added': [],
359 'updated': [],
359 'updated': [],
360 'deleted': [],
360 'deleted': [],
361 'default_user_changed': None
361 'default_user_changed': None
362 }
362 }
363
363
364 def _set_perm_user(obj, user, perm):
364 def _set_perm_user(obj, user, perm):
365 if isinstance(obj, RepoGroup):
365 if isinstance(obj, RepoGroup):
366 self.grant_user_permission(
366 self.grant_user_permission(
367 repo_group=obj, user=user, perm=perm)
367 repo_group=obj, user=user, perm=perm)
368 elif isinstance(obj, Repository):
368 elif isinstance(obj, Repository):
369 # private repos will not allow to change the default
369 # private repos will not allow to change the default
370 # permissions using recursive mode
370 # permissions using recursive mode
371 if obj.private and user == User.DEFAULT_USER:
371 if obj.private and user == User.DEFAULT_USER:
372 return
372 return
373
373
374 # we set group permission but we have to switch to repo
374 # we set group permission but we have to switch to repo
375 # permission
375 # permission
376 perm = perm.replace('group.', 'repository.')
376 perm = perm.replace('group.', 'repository.')
377 RepoModel().grant_user_permission(
377 RepoModel().grant_user_permission(
378 repo=obj, user=user, perm=perm)
378 repo=obj, user=user, perm=perm)
379
379
380 def _set_perm_group(obj, users_group, perm):
380 def _set_perm_group(obj, users_group, perm):
381 if isinstance(obj, RepoGroup):
381 if isinstance(obj, RepoGroup):
382 self.grant_user_group_permission(
382 self.grant_user_group_permission(
383 repo_group=obj, group_name=users_group, perm=perm)
383 repo_group=obj, group_name=users_group, perm=perm)
384 elif isinstance(obj, Repository):
384 elif isinstance(obj, Repository):
385 # we set group permission but we have to switch to repo
385 # we set group permission but we have to switch to repo
386 # permission
386 # permission
387 perm = perm.replace('group.', 'repository.')
387 perm = perm.replace('group.', 'repository.')
388 RepoModel().grant_user_group_permission(
388 RepoModel().grant_user_group_permission(
389 repo=obj, group_name=users_group, perm=perm)
389 repo=obj, group_name=users_group, perm=perm)
390
390
391 def _revoke_perm_user(obj, user):
391 def _revoke_perm_user(obj, user):
392 if isinstance(obj, RepoGroup):
392 if isinstance(obj, RepoGroup):
393 self.revoke_user_permission(repo_group=obj, user=user)
393 self.revoke_user_permission(repo_group=obj, user=user)
394 elif isinstance(obj, Repository):
394 elif isinstance(obj, Repository):
395 RepoModel().revoke_user_permission(repo=obj, user=user)
395 RepoModel().revoke_user_permission(repo=obj, user=user)
396
396
397 def _revoke_perm_group(obj, user_group):
397 def _revoke_perm_group(obj, user_group):
398 if isinstance(obj, RepoGroup):
398 if isinstance(obj, RepoGroup):
399 self.revoke_user_group_permission(
399 self.revoke_user_group_permission(
400 repo_group=obj, group_name=user_group)
400 repo_group=obj, group_name=user_group)
401 elif isinstance(obj, Repository):
401 elif isinstance(obj, Repository):
402 RepoModel().revoke_user_group_permission(
402 RepoModel().revoke_user_group_permission(
403 repo=obj, group_name=user_group)
403 repo=obj, group_name=user_group)
404
404
405 # start updates
405 # start updates
406 log.debug('Now updating permissions for %s in recursive mode:%s',
406 log.debug('Now updating permissions for %s in recursive mode:%s',
407 repo_group, recursive)
407 repo_group, recursive)
408
408
409 # initialize check function, we'll call that multiple times
409 # initialize check function, we'll call that multiple times
410 has_group_perm = HasUserGroupPermissionAny(*req_perms)
410 has_group_perm = HasUserGroupPermissionAny(*req_perms)
411
411
412 for obj in repo_group.recursive_groups_and_repos():
412 for obj in repo_group.recursive_groups_and_repos():
413 # iterated obj is an instance of a repos group or repository in
413 # iterated obj is an instance of a repos group or repository in
414 # that group, recursive option can be: none, repos, groups, all
414 # that group, recursive option can be: none, repos, groups, all
415 if recursive == 'all':
415 if recursive == 'all':
416 obj = obj
416 obj = obj
417 elif recursive == 'repos':
417 elif recursive == 'repos':
418 # skip groups, other than this one
418 # skip groups, other than this one
419 if isinstance(obj, RepoGroup) and not obj == repo_group:
419 if isinstance(obj, RepoGroup) and not obj == repo_group:
420 continue
420 continue
421 elif recursive == 'groups':
421 elif recursive == 'groups':
422 # skip repos
422 # skip repos
423 if isinstance(obj, Repository):
423 if isinstance(obj, Repository):
424 continue
424 continue
425 else: # recursive == 'none':
425 else: # recursive == 'none':
426 # DEFAULT option - don't apply to iterated objects
426 # DEFAULT option - don't apply to iterated objects
427 # also we do a break at the end of this loop. if we are not
427 # also we do a break at the end of this loop. if we are not
428 # in recursive mode
428 # in recursive mode
429 obj = repo_group
429 obj = repo_group
430
430
431 change_obj = obj.get_api_data()
431 change_obj = obj.get_api_data()
432
432
433 # update permissions
433 # update permissions
434 for member_id, perm, member_type in perm_updates:
434 for member_id, perm, member_type in perm_updates:
435 member_id = int(member_id)
435 member_id = int(member_id)
436 if member_type == 'user':
436 if member_type == 'user':
437 member_name = User.get(member_id).username
437 member_name = User.get(member_id).username
438 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
438 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
439 # NOTE(dan): detect if we changed permissions for default user
439 # NOTE(dan): detect if we changed permissions for default user
440 perm_obj = self.sa.query(UserRepoGroupToPerm) \
440 perm_obj = self.sa.query(UserRepoGroupToPerm) \
441 .filter(UserRepoGroupToPerm.user_id == member_id) \
441 .filter(UserRepoGroupToPerm.user_id == member_id) \
442 .filter(UserRepoGroupToPerm.group == repo_group) \
442 .filter(UserRepoGroupToPerm.group == repo_group) \
443 .scalar()
443 .scalar()
444 if perm_obj and perm_obj.permission.permission_name != perm:
444 if perm_obj and perm_obj.permission.permission_name != perm:
445 changes['default_user_changed'] = True
445 changes['default_user_changed'] = True
446
446
447 # this updates also current one if found
447 # this updates also current one if found
448 _set_perm_user(obj, user=member_id, perm=perm)
448 _set_perm_user(obj, user=member_id, perm=perm)
449 elif member_type == 'user_group':
449 elif member_type == 'user_group':
450 member_name = UserGroup.get(member_id).users_group_name
450 member_name = UserGroup.get(member_id).users_group_name
451 if not check_perms or has_group_perm(member_name,
451 if not check_perms or has_group_perm(member_name,
452 user=cur_user):
452 user=cur_user):
453 _set_perm_group(obj, users_group=member_id, perm=perm)
453 _set_perm_group(obj, users_group=member_id, perm=perm)
454 else:
454 else:
455 raise ValueError("member_type must be 'user' or 'user_group' "
455 raise ValueError("member_type must be 'user' or 'user_group' "
456 "got {} instead".format(member_type))
456 "got {} instead".format(member_type))
457
457
458 changes['updated'].append(
458 changes['updated'].append(
459 {'change_obj': change_obj, 'type': member_type,
459 {'change_obj': change_obj, 'type': member_type,
460 'id': member_id, 'name': member_name, 'new_perm': perm})
460 'id': member_id, 'name': member_name, 'new_perm': perm})
461
461
462 # set new permissions
462 # set new permissions
463 for member_id, perm, member_type in perm_additions:
463 for member_id, perm, member_type in perm_additions:
464 member_id = int(member_id)
464 member_id = int(member_id)
465 if member_type == 'user':
465 if member_type == 'user':
466 member_name = User.get(member_id).username
466 member_name = User.get(member_id).username
467 _set_perm_user(obj, user=member_id, perm=perm)
467 _set_perm_user(obj, user=member_id, perm=perm)
468 elif member_type == 'user_group':
468 elif member_type == 'user_group':
469 # check if we have permissions to alter this usergroup
469 # check if we have permissions to alter this usergroup
470 member_name = UserGroup.get(member_id).users_group_name
470 member_name = UserGroup.get(member_id).users_group_name
471 if not check_perms or has_group_perm(member_name,
471 if not check_perms or has_group_perm(member_name,
472 user=cur_user):
472 user=cur_user):
473 _set_perm_group(obj, users_group=member_id, perm=perm)
473 _set_perm_group(obj, users_group=member_id, perm=perm)
474 else:
474 else:
475 raise ValueError("member_type must be 'user' or 'user_group' "
475 raise ValueError("member_type must be 'user' or 'user_group' "
476 "got {} instead".format(member_type))
476 "got {} instead".format(member_type))
477
477
478 changes['added'].append(
478 changes['added'].append(
479 {'change_obj': change_obj, 'type': member_type,
479 {'change_obj': change_obj, 'type': member_type,
480 'id': member_id, 'name': member_name, 'new_perm': perm})
480 'id': member_id, 'name': member_name, 'new_perm': perm})
481
481
482 # delete permissions
482 # delete permissions
483 for member_id, perm, member_type in perm_deletions:
483 for member_id, perm, member_type in perm_deletions:
484 member_id = int(member_id)
484 member_id = int(member_id)
485 if member_type == 'user':
485 if member_type == 'user':
486 member_name = User.get(member_id).username
486 member_name = User.get(member_id).username
487 _revoke_perm_user(obj, user=member_id)
487 _revoke_perm_user(obj, user=member_id)
488 elif member_type == 'user_group':
488 elif member_type == 'user_group':
489 # check if we have permissions to alter this usergroup
489 # check if we have permissions to alter this usergroup
490 member_name = UserGroup.get(member_id).users_group_name
490 member_name = UserGroup.get(member_id).users_group_name
491 if not check_perms or has_group_perm(member_name,
491 if not check_perms or has_group_perm(member_name,
492 user=cur_user):
492 user=cur_user):
493 _revoke_perm_group(obj, user_group=member_id)
493 _revoke_perm_group(obj, user_group=member_id)
494 else:
494 else:
495 raise ValueError("member_type must be 'user' or 'user_group' "
495 raise ValueError("member_type must be 'user' or 'user_group' "
496 "got {} instead".format(member_type))
496 "got {} instead".format(member_type))
497
497
498 changes['deleted'].append(
498 changes['deleted'].append(
499 {'change_obj': change_obj, 'type': member_type,
499 {'change_obj': change_obj, 'type': member_type,
500 'id': member_id, 'name': member_name, 'new_perm': perm})
500 'id': member_id, 'name': member_name, 'new_perm': perm})
501
501
502 # if it's not recursive call for all,repos,groups
502 # if it's not recursive call for all,repos,groups
503 # break the loop and don't proceed with other changes
503 # break the loop and don't proceed with other changes
504 if recursive not in ['all', 'repos', 'groups']:
504 if recursive not in ['all', 'repos', 'groups']:
505 break
505 break
506
506
507 return changes
507 return changes
508
508
509 def update(self, repo_group, form_data):
509 def update(self, repo_group, form_data):
510 try:
510 try:
511 repo_group = self._get_repo_group(repo_group)
511 repo_group = self._get_repo_group(repo_group)
512 old_path = repo_group.full_path
512 old_path = repo_group.full_path
513
513
514 # change properties
514 # change properties
515 if 'group_description' in form_data:
515 if 'group_description' in form_data:
516 repo_group.group_description = form_data['group_description']
516 repo_group.group_description = form_data['group_description']
517
517
518 if 'enable_locking' in form_data:
518 if 'enable_locking' in form_data:
519 repo_group.enable_locking = form_data['enable_locking']
519 repo_group.enable_locking = form_data['enable_locking']
520
520
521 if 'group_parent_id' in form_data:
521 if 'group_parent_id' in form_data:
522 parent_group = (
522 parent_group = (
523 self._get_repo_group(form_data['group_parent_id']))
523 self._get_repo_group(form_data['group_parent_id']))
524 repo_group.group_parent_id = (
524 repo_group.group_parent_id = (
525 parent_group.group_id if parent_group else None)
525 parent_group.group_id if parent_group else None)
526 repo_group.parent_group = parent_group
526 repo_group.parent_group = parent_group
527
527
528 # mikhail: to update the full_path, we have to explicitly
528 # mikhail: to update the full_path, we have to explicitly
529 # update group_name
529 # update group_name
530 group_name = form_data.get('group_name', repo_group.name)
530 group_name = form_data.get('group_name', repo_group.name)
531 repo_group.group_name = repo_group.get_new_name(group_name)
531 repo_group.group_name = repo_group.get_new_name(group_name)
532
532
533 new_path = repo_group.full_path
533 new_path = repo_group.full_path
534
534
535 affected_user_ids = []
535 affected_user_ids = []
536 if 'user' in form_data:
536 if 'user' in form_data:
537 old_owner_id = repo_group.user.user_id
537 old_owner_id = repo_group.user.user_id
538 new_owner = User.get_by_username(form_data['user'])
538 new_owner = User.get_by_username(form_data['user'])
539 repo_group.user = new_owner
539 repo_group.user = new_owner
540
540
541 if old_owner_id != new_owner.user_id:
541 if old_owner_id != new_owner.user_id:
542 affected_user_ids = [new_owner.user_id, old_owner_id]
542 affected_user_ids = [new_owner.user_id, old_owner_id]
543
543
544 self.sa.add(repo_group)
544 self.sa.add(repo_group)
545
545
546 # iterate over all members of this groups and do fixes
546 # iterate over all members of this groups and do fixes
547 # set locking if given
547 # set locking if given
548 # if obj is a repoGroup also fix the name of the group according
548 # if obj is a repoGroup also fix the name of the group according
549 # to the parent
549 # to the parent
550 # if obj is a Repo fix it's name
550 # if obj is a Repo fix it's name
551 # this can be potentially heavy operation
551 # this can be potentially heavy operation
552 for obj in repo_group.recursive_groups_and_repos():
552 for obj in repo_group.recursive_groups_and_repos():
553 # set the value from it's parent
553 # set the value from it's parent
554 obj.enable_locking = repo_group.enable_locking
554 obj.enable_locking = repo_group.enable_locking
555 if isinstance(obj, RepoGroup):
555 if isinstance(obj, RepoGroup):
556 new_name = obj.get_new_name(obj.name)
556 new_name = obj.get_new_name(obj.name)
557 log.debug('Fixing group %s to new name %s',
557 log.debug('Fixing group %s to new name %s',
558 obj.group_name, new_name)
558 obj.group_name, new_name)
559 obj.group_name = new_name
559 obj.group_name = new_name
560
560
561 elif isinstance(obj, Repository):
561 elif isinstance(obj, Repository):
562 # we need to get all repositories from this new group and
562 # we need to get all repositories from this new group and
563 # rename them accordingly to new group path
563 # rename them accordingly to new group path
564 new_name = obj.get_new_name(obj.just_name)
564 new_name = obj.get_new_name(obj.just_name)
565 log.debug('Fixing repo %s to new name %s',
565 log.debug('Fixing repo %s to new name %s',
566 obj.repo_name, new_name)
566 obj.repo_name, new_name)
567 obj.repo_name = new_name
567 obj.repo_name = new_name
568
568
569 self.sa.add(obj)
569 self.sa.add(obj)
570
570
571 self._rename_group(old_path, new_path)
571 self._rename_group(old_path, new_path)
572
572
573 # Trigger update event.
573 # Trigger update event.
574 events.trigger(events.RepoGroupUpdateEvent(repo_group))
574 events.trigger(events.RepoGroupUpdateEvent(repo_group))
575
575
576 if affected_user_ids:
576 if affected_user_ids:
577 PermissionModel().trigger_permission_flush(affected_user_ids)
577 PermissionModel().trigger_permission_flush(affected_user_ids)
578
578
579 return repo_group
579 return repo_group
580 except Exception:
580 except Exception:
581 log.error(traceback.format_exc())
581 log.error(traceback.format_exc())
582 raise
582 raise
583
583
584 def delete(self, repo_group, force_delete=False, fs_remove=True):
584 def delete(self, repo_group, force_delete=False, fs_remove=True):
585 repo_group = self._get_repo_group(repo_group)
585 repo_group = self._get_repo_group(repo_group)
586 if not repo_group:
586 if not repo_group:
587 return False
587 return False
588 try:
588 try:
589 self.sa.delete(repo_group)
589 self.sa.delete(repo_group)
590 if fs_remove:
590 if fs_remove:
591 self._delete_filesystem_group(repo_group, force_delete)
591 self._delete_filesystem_group(repo_group, force_delete)
592 else:
592 else:
593 log.debug('skipping removal from filesystem')
593 log.debug('skipping removal from filesystem')
594
594
595 # Trigger delete event.
595 # Trigger delete event.
596 events.trigger(events.RepoGroupDeleteEvent(repo_group))
596 events.trigger(events.RepoGroupDeleteEvent(repo_group))
597 return True
597 return True
598
598
599 except Exception:
599 except Exception:
600 log.error('Error removing repo_group %s', repo_group)
600 log.error('Error removing repo_group %s', repo_group)
601 raise
601 raise
602
602
603 def grant_user_permission(self, repo_group, user, perm):
603 def grant_user_permission(self, repo_group, user, perm):
604 """
604 """
605 Grant permission for user on given repository group, or update
605 Grant permission for user on given repository group, or update
606 existing one if found
606 existing one if found
607
607
608 :param repo_group: Instance of RepoGroup, repositories_group_id,
608 :param repo_group: Instance of RepoGroup, repositories_group_id,
609 or repositories_group name
609 or repositories_group name
610 :param user: Instance of User, user_id or username
610 :param user: Instance of User, user_id or username
611 :param perm: Instance of Permission, or permission_name
611 :param perm: Instance of Permission, or permission_name
612 """
612 """
613
613
614 repo_group = self._get_repo_group(repo_group)
614 repo_group = self._get_repo_group(repo_group)
615 user = self._get_user(user)
615 user = self._get_user(user)
616 permission = self._get_perm(perm)
616 permission = self._get_perm(perm)
617
617
618 # check if we have that permission already
618 # check if we have that permission already
619 obj = self.sa.query(UserRepoGroupToPerm)\
619 obj = self.sa.query(UserRepoGroupToPerm)\
620 .filter(UserRepoGroupToPerm.user == user)\
620 .filter(UserRepoGroupToPerm.user == user)\
621 .filter(UserRepoGroupToPerm.group == repo_group)\
621 .filter(UserRepoGroupToPerm.group == repo_group)\
622 .scalar()
622 .scalar()
623 if obj is None:
623 if obj is None:
624 # create new !
624 # create new !
625 obj = UserRepoGroupToPerm()
625 obj = UserRepoGroupToPerm()
626 obj.group = repo_group
626 obj.group = repo_group
627 obj.user = user
627 obj.user = user
628 obj.permission = permission
628 obj.permission = permission
629 self.sa.add(obj)
629 self.sa.add(obj)
630 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
630 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
631 action_logger_generic(
631 action_logger_generic(
632 'granted permission: {} to user: {} on repogroup: {}'.format(
632 'granted permission: {} to user: {} on repogroup: {}'.format(
633 perm, user, repo_group), namespace='security.repogroup')
633 perm, user, repo_group), namespace='security.repogroup')
634 return obj
634 return obj
635
635
636 def revoke_user_permission(self, repo_group, user):
636 def revoke_user_permission(self, repo_group, user):
637 """
637 """
638 Revoke permission for user on given repository group
638 Revoke permission for user on given repository group
639
639
640 :param repo_group: Instance of RepoGroup, repositories_group_id,
640 :param repo_group: Instance of RepoGroup, repositories_group_id,
641 or repositories_group name
641 or repositories_group name
642 :param user: Instance of User, user_id or username
642 :param user: Instance of User, user_id or username
643 """
643 """
644
644
645 repo_group = self._get_repo_group(repo_group)
645 repo_group = self._get_repo_group(repo_group)
646 user = self._get_user(user)
646 user = self._get_user(user)
647
647
648 obj = self.sa.query(UserRepoGroupToPerm)\
648 obj = self.sa.query(UserRepoGroupToPerm)\
649 .filter(UserRepoGroupToPerm.user == user)\
649 .filter(UserRepoGroupToPerm.user == user)\
650 .filter(UserRepoGroupToPerm.group == repo_group)\
650 .filter(UserRepoGroupToPerm.group == repo_group)\
651 .scalar()
651 .scalar()
652 if obj:
652 if obj:
653 self.sa.delete(obj)
653 self.sa.delete(obj)
654 log.debug('Revoked perm on %s on %s', repo_group, user)
654 log.debug('Revoked perm on %s on %s', repo_group, user)
655 action_logger_generic(
655 action_logger_generic(
656 'revoked permission from user: {} on repogroup: {}'.format(
656 'revoked permission from user: {} on repogroup: {}'.format(
657 user, repo_group), namespace='security.repogroup')
657 user, repo_group), namespace='security.repogroup')
658
658
659 def grant_user_group_permission(self, repo_group, group_name, perm):
659 def grant_user_group_permission(self, repo_group, group_name, perm):
660 """
660 """
661 Grant permission for user group on given repository group, or update
661 Grant permission for user group on given repository group, or update
662 existing one if found
662 existing one if found
663
663
664 :param repo_group: Instance of RepoGroup, repositories_group_id,
664 :param repo_group: Instance of RepoGroup, repositories_group_id,
665 or repositories_group name
665 or repositories_group name
666 :param group_name: Instance of UserGroup, users_group_id,
666 :param group_name: Instance of UserGroup, users_group_id,
667 or user group name
667 or user group name
668 :param perm: Instance of Permission, or permission_name
668 :param perm: Instance of Permission, or permission_name
669 """
669 """
670 repo_group = self._get_repo_group(repo_group)
670 repo_group = self._get_repo_group(repo_group)
671 group_name = self._get_user_group(group_name)
671 group_name = self._get_user_group(group_name)
672 permission = self._get_perm(perm)
672 permission = self._get_perm(perm)
673
673
674 # check if we have that permission already
674 # check if we have that permission already
675 obj = self.sa.query(UserGroupRepoGroupToPerm)\
675 obj = self.sa.query(UserGroupRepoGroupToPerm)\
676 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
676 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
677 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
677 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
678 .scalar()
678 .scalar()
679
679
680 if obj is None:
680 if obj is None:
681 # create new
681 # create new
682 obj = UserGroupRepoGroupToPerm()
682 obj = UserGroupRepoGroupToPerm()
683
683
684 obj.group = repo_group
684 obj.group = repo_group
685 obj.users_group = group_name
685 obj.users_group = group_name
686 obj.permission = permission
686 obj.permission = permission
687 self.sa.add(obj)
687 self.sa.add(obj)
688 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
688 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
689 action_logger_generic(
689 action_logger_generic(
690 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
690 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
691 perm, group_name, repo_group), namespace='security.repogroup')
691 perm, group_name, repo_group), namespace='security.repogroup')
692 return obj
692 return obj
693
693
694 def revoke_user_group_permission(self, repo_group, group_name):
694 def revoke_user_group_permission(self, repo_group, group_name):
695 """
695 """
696 Revoke permission for user group on given repository group
696 Revoke permission for user group on given repository group
697
697
698 :param repo_group: Instance of RepoGroup, repositories_group_id,
698 :param repo_group: Instance of RepoGroup, repositories_group_id,
699 or repositories_group name
699 or repositories_group name
700 :param group_name: Instance of UserGroup, users_group_id,
700 :param group_name: Instance of UserGroup, users_group_id,
701 or user group name
701 or user group name
702 """
702 """
703 repo_group = self._get_repo_group(repo_group)
703 repo_group = self._get_repo_group(repo_group)
704 group_name = self._get_user_group(group_name)
704 group_name = self._get_user_group(group_name)
705
705
706 obj = self.sa.query(UserGroupRepoGroupToPerm)\
706 obj = self.sa.query(UserGroupRepoGroupToPerm)\
707 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
707 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
708 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
708 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
709 .scalar()
709 .scalar()
710 if obj:
710 if obj:
711 self.sa.delete(obj)
711 self.sa.delete(obj)
712 log.debug('Revoked perm to %s on %s', repo_group, group_name)
712 log.debug('Revoked perm to %s on %s', repo_group, group_name)
713 action_logger_generic(
713 action_logger_generic(
714 'revoked permission from usergroup: {} on repogroup: {}'.format(
714 'revoked permission from usergroup: {} on repogroup: {}'.format(
715 group_name, repo_group), namespace='security.repogroup')
715 group_name, repo_group), namespace='security.repogroup')
716
716
717 @classmethod
717 @classmethod
718 def update_commit_cache(cls, repo_groups=None):
718 def update_commit_cache(cls, repo_groups=None):
719 if not repo_groups:
719 if not repo_groups:
720 repo_groups = RepoGroup.getAll()
720 repo_groups = RepoGroup.getAll()
721 for repo_group in repo_groups:
721 for repo_group in repo_groups:
722 repo_group.update_commit_cache()
722 repo_group.update_commit_cache()
723
723
724 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
724 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
725 super_user_actions=False):
725 super_user_actions=False):
726
726
727 from pyramid.threadlocal import get_current_request
727 from pyramid.threadlocal import get_current_request
728 _render = get_current_request().get_partial_renderer(
728 _render = get_current_request().get_partial_renderer(
729 'rhodecode:templates/data_table/_dt_elements.mako')
729 'rhodecode:templates/data_table/_dt_elements.mako')
730 c = _render.get_call_context()
730 c = _render.get_call_context()
731 h = _render.get_helpers()
731 h = _render.get_helpers()
732
732
733 def quick_menu(repo_group_name):
733 def quick_menu(repo_group_name):
734 return _render('quick_repo_group_menu', repo_group_name)
734 return _render('quick_repo_group_menu', repo_group_name)
735
735
736 def repo_group_lnk(repo_group_name):
736 def repo_group_lnk(repo_group_name):
737 return _render('repo_group_name', repo_group_name)
737 return _render('repo_group_name', repo_group_name)
738
738
739 def last_change(last_change):
739 def last_change(last_change):
740 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
740 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
741 ts = time.time()
741 ts = time.time()
742 utc_offset = (datetime.datetime.fromtimestamp(ts)
742 utc_offset = (datetime.datetime.fromtimestamp(ts)
743 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
743 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
744 last_change = last_change + datetime.timedelta(seconds=utc_offset)
744 last_change = last_change + datetime.timedelta(seconds=utc_offset)
745 return _render("last_change", last_change)
745 return _render("last_change", last_change)
746
746
747 def desc(desc, personal):
747 def desc(desc, personal):
748 return _render(
748 return _render(
749 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
749 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
750
750
751 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
751 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
752 return _render(
752 return _render(
753 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
753 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
754
754
755 def repo_group_name(repo_group_name, children_groups):
755 def repo_group_name(repo_group_name, children_groups):
756 return _render("repo_group_name", repo_group_name, children_groups)
756 return _render("repo_group_name", repo_group_name, children_groups)
757
757
758 def user_profile(username):
758 def user_profile(username):
759 return _render('user_profile', username)
759 return _render('user_profile', username)
760
760
761 repo_group_data = []
761 repo_group_data = []
762 for group in repo_group_list:
762 for group in repo_group_list:
763 # NOTE(marcink): because we use only raw column we need to load it like that
763 # NOTE(marcink): because we use only raw column we need to load it like that
764 changeset_cache = RepoGroup._load_changeset_cache(
764 changeset_cache = RepoGroup._load_changeset_cache(
765 '', group._changeset_cache)
765 '', group._changeset_cache)
766 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
766 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
767 row = {
767 row = {
768 "menu": quick_menu(group.group_name),
768 "menu": quick_menu(group.group_name),
769 "name": repo_group_lnk(group.group_name),
769 "name": repo_group_lnk(group.group_name),
770 "name_raw": group.group_name,
770 "name_raw": group.group_name,
771
771
772 "last_change": last_change(last_commit_change),
772 "last_change": last_change(last_commit_change),
773
773
774 "last_changeset": "",
774 "last_changeset": "",
775 "last_changeset_raw": "",
775 "last_changeset_raw": "",
776
776
777 "desc": desc(h.escape(group.group_description), group.personal),
777 "desc": desc(h.escape(group.group_description), group.personal),
778 "top_level_repos": 0,
778 "top_level_repos": 0,
779 "owner": user_profile(group.User.username)
779 "owner": user_profile(group.User.username)
780 }
780 }
781 if admin:
781 if admin:
782 repo_count = group.repositories.count()
782 repo_count = group.repositories.count()
783 children_groups = map(
783 children_groups = map(
784 h.safe_unicode,
784 h.safe_unicode,
785 itertools.chain((g.name for g in group.parents),
785 itertools.chain((g.name for g in group.parents),
786 (x.name for x in [group])))
786 (x.name for x in [group])))
787 row.update({
787 row.update({
788 "action": repo_group_actions(
788 "action": repo_group_actions(
789 group.group_id, group.group_name, repo_count),
789 group.group_id, group.group_name, repo_count),
790 "top_level_repos": repo_count,
790 "top_level_repos": repo_count,
791 "name": repo_group_name(group.group_name, children_groups),
791 "name": repo_group_name(group.group_name, children_groups),
792
792
793 })
793 })
794 repo_group_data.append(row)
794 repo_group_data.append(row)
795
795
796 return repo_group_data
796 return repo_group_data
797
797
798 def get_repo_groups_data_table(
798 def get_repo_groups_data_table(
799 self, draw, start, limit,
799 self, draw, start, limit,
800 search_q, order_by, order_dir,
800 search_q, order_by, order_dir,
801 auth_user, repo_group_id):
801 auth_user, repo_group_id):
802 from rhodecode.model.scm import RepoGroupList
802 from rhodecode.model.scm import RepoGroupList
803
803
804 _perms = ['group.read', 'group.write', 'group.admin']
804 _perms = ['group.read', 'group.write', 'group.admin']
805 repo_groups = RepoGroup.query() \
805 repo_groups = RepoGroup.query() \
806 .filter(RepoGroup.group_parent_id == repo_group_id) \
806 .filter(RepoGroup.group_parent_id == repo_group_id) \
807 .all()
807 .all()
808 auth_repo_group_list = RepoGroupList(
808 auth_repo_group_list = RepoGroupList(
809 repo_groups, perm_set=_perms,
809 repo_groups, perm_set=_perms,
810 extra_kwargs=dict(user=auth_user))
810 extra_kwargs=dict(user=auth_user))
811
811
812 allowed_ids = [-1]
812 allowed_ids = [-1]
813 for repo_group in auth_repo_group_list:
813 for repo_group in auth_repo_group_list:
814 allowed_ids.append(repo_group.group_id)
814 allowed_ids.append(repo_group.group_id)
815
815
816 repo_groups_data_total_count = RepoGroup.query() \
816 repo_groups_data_total_count = RepoGroup.query() \
817 .filter(RepoGroup.group_parent_id == repo_group_id) \
817 .filter(RepoGroup.group_parent_id == repo_group_id) \
818 .filter(or_(
818 .filter(or_(
819 # generate multiple IN to fix limitation problems
819 # generate multiple IN to fix limitation problems
820 *in_filter_generator(RepoGroup.group_id, allowed_ids))
820 *in_filter_generator(RepoGroup.group_id, allowed_ids))
821 ) \
821 ) \
822 .count()
822 .count()
823
823
824 base_q = Session.query(
824 base_q = Session.query(
825 RepoGroup.group_name,
825 RepoGroup.group_name,
826 RepoGroup.group_name_hash,
826 RepoGroup.group_name_hash,
827 RepoGroup.group_description,
827 RepoGroup.group_description,
828 RepoGroup.group_id,
828 RepoGroup.group_id,
829 RepoGroup.personal,
829 RepoGroup.personal,
830 RepoGroup.updated_on,
830 RepoGroup.updated_on,
831 RepoGroup._changeset_cache,
831 RepoGroup._changeset_cache,
832 User,
832 User,
833 ) \
833 ) \
834 .filter(RepoGroup.group_parent_id == repo_group_id) \
834 .filter(RepoGroup.group_parent_id == repo_group_id) \
835 .filter(or_(
835 .filter(or_(
836 # generate multiple IN to fix limitation problems
836 # generate multiple IN to fix limitation problems
837 *in_filter_generator(RepoGroup.group_id, allowed_ids))
837 *in_filter_generator(RepoGroup.group_id, allowed_ids))
838 ) \
838 ) \
839 .join(User, User.user_id == RepoGroup.user_id) \
839 .join(User, User.user_id == RepoGroup.user_id) \
840 .group_by(RepoGroup, User)
840 .group_by(RepoGroup, User)
841
841
842 repo_groups_data_total_filtered_count = base_q.count()
842 repo_groups_data_total_filtered_count = base_q.count()
843
843
844 sort_defined = False
844 sort_defined = False
845
845
846 if order_by == 'group_name':
846 if order_by == 'group_name':
847 sort_col = func.lower(RepoGroup.group_name)
847 sort_col = func.lower(RepoGroup.group_name)
848 sort_defined = True
848 sort_defined = True
849 elif order_by == 'user_username':
849 elif order_by == 'user_username':
850 sort_col = User.username
850 sort_col = User.username
851 else:
851 else:
852 sort_col = getattr(RepoGroup, order_by, None)
852 sort_col = getattr(RepoGroup, order_by, None)
853
853
854 if sort_defined or sort_col:
854 if sort_defined or sort_col:
855 if order_dir == 'asc':
855 if order_dir == 'asc':
856 sort_col = sort_col.asc()
856 sort_col = sort_col.asc()
857 else:
857 else:
858 sort_col = sort_col.desc()
858 sort_col = sort_col.desc()
859
859
860 base_q = base_q.order_by(sort_col)
860 base_q = base_q.order_by(sort_col)
861 base_q = base_q.offset(start).limit(limit)
861 base_q = base_q.offset(start).limit(limit)
862
862
863 repo_group_list = base_q.all()
863 repo_group_list = base_q.all()
864
864
865 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
865 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
866 repo_group_list=repo_group_list, admin=False)
866 repo_group_list=repo_group_list, admin=False)
867
867
868 data = ({
868 data = ({
869 'draw': draw,
869 'draw': draw,
870 'data': repo_groups_data,
870 'data': repo_groups_data,
871 'recordsTotal': repo_groups_data_total_count,
871 'recordsTotal': repo_groups_data_total_count,
872 'recordsFiltered': repo_groups_data_total_filtered_count,
872 'recordsFiltered': repo_groups_data_total_filtered_count,
873 })
873 })
874 return data
874 return data
875
875
876 def _get_defaults(self, repo_group_name):
876 def _get_defaults(self, repo_group_name):
877 repo_group = RepoGroup.get_by_group_name(repo_group_name)
877 repo_group = RepoGroup.get_by_group_name(repo_group_name)
878
878
879 if repo_group is None:
879 if repo_group is None:
880 return None
880 return None
881
881
882 defaults = repo_group.get_dict()
882 defaults = repo_group.get_dict()
883 defaults['repo_group_name'] = repo_group.name
883 defaults['repo_group_name'] = repo_group.name
884 defaults['repo_group_description'] = repo_group.group_description
884 defaults['repo_group_description'] = repo_group.group_description
885 defaults['repo_group_enable_locking'] = repo_group.enable_locking
885 defaults['repo_group_enable_locking'] = repo_group.enable_locking
886
886
887 # we use -1 as this is how in HTML, we mark an empty group
887 # we use -1 as this is how in HTML, we mark an empty group
888 defaults['repo_group'] = defaults['group_parent_id'] or -1
888 defaults['repo_group'] = defaults['group_parent_id'] or -1
889
889
890 # fill owner
890 # fill owner
891 if repo_group.user:
891 if repo_group.user:
892 defaults.update({'user': repo_group.user.username})
892 defaults.update({'user': repo_group.user.username})
893 else:
893 else:
894 replacement_user = User.get_first_super_admin().username
894 replacement_user = User.get_first_super_admin().username
895 defaults.update({'user': replacement_user})
895 defaults.update({'user': replacement_user})
896
896
897 return defaults
897 return defaults
@@ -1,1047 +1,1047 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", f"get_user_{user_id}"))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 # sort by len to have top most matches first
99 # sort by len to have top most matches first
100 query = query.order_by(func.length(User.username))\
100 query = query.order_by(func.length(User.username))\
101 .order_by(User.username)
101 .order_by(User.username)
102 query = query.limit(limit)
102 query = query.limit(limit)
103
103
104 users = query.all()
104 users = query.all()
105
105
106 _users = [
106 _users = [
107 self._serialize_user(user) for user in users
107 self._serialize_user(user) for user in users
108 ]
108 ]
109 return _users
109 return _users
110
110
111 def get_by_username(self, username, cache=False, case_insensitive=False):
111 def get_by_username(self, username, cache=False, case_insensitive=False):
112
112
113 if case_insensitive:
113 if case_insensitive:
114 user = self.sa.query(User).filter(User.username.ilike(username))
114 user = self.sa.query(User).filter(User.username.ilike(username))
115 else:
115 else:
116 user = self.sa.query(User)\
116 user = self.sa.query(User)\
117 .filter(User.username == username)
117 .filter(User.username == username)
118 if cache:
118 if cache:
119 name_key = _hash_key(username)
119 name_key = _hash_key(username)
120 user = user.options(
120 user = user.options(
121 FromCache("sql_cache_short", "get_user_%s" % name_key))
121 FromCache("sql_cache_short", f"get_user_{name_key}"))
122 return user.scalar()
122 return user.scalar()
123
123
124 def get_by_email(self, email, cache=False, case_insensitive=False):
124 def get_by_email(self, email, cache=False, case_insensitive=False):
125 return User.get_by_email(email, case_insensitive, cache)
125 return User.get_by_email(email, case_insensitive, cache)
126
126
127 def get_by_auth_token(self, auth_token, cache=False):
127 def get_by_auth_token(self, auth_token, cache=False):
128 return User.get_by_auth_token(auth_token, cache)
128 return User.get_by_auth_token(auth_token, cache)
129
129
130 def get_active_user_count(self, cache=False):
130 def get_active_user_count(self, cache=False):
131 qry = User.query().filter(
131 qry = User.query().filter(
132 User.active == true()).filter(
132 User.active == true()).filter(
133 User.username != User.DEFAULT_USER)
133 User.username != User.DEFAULT_USER)
134 if cache:
134 if cache:
135 qry = qry.options(
135 qry = qry.options(
136 FromCache("sql_cache_short", "get_active_users"))
136 FromCache("sql_cache_short", "get_active_users"))
137 return qry.count()
137 return qry.count()
138
138
139 def create(self, form_data, cur_user=None):
139 def create(self, form_data, cur_user=None):
140 if not cur_user:
140 if not cur_user:
141 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
141 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
142
142
143 user_data = {
143 user_data = {
144 'username': form_data['username'],
144 'username': form_data['username'],
145 'password': form_data['password'],
145 'password': form_data['password'],
146 'email': form_data['email'],
146 'email': form_data['email'],
147 'firstname': form_data['firstname'],
147 'firstname': form_data['firstname'],
148 'lastname': form_data['lastname'],
148 'lastname': form_data['lastname'],
149 'active': form_data['active'],
149 'active': form_data['active'],
150 'extern_type': form_data['extern_type'],
150 'extern_type': form_data['extern_type'],
151 'extern_name': form_data['extern_name'],
151 'extern_name': form_data['extern_name'],
152 'admin': False,
152 'admin': False,
153 'cur_user': cur_user
153 'cur_user': cur_user
154 }
154 }
155
155
156 if 'create_repo_group' in form_data:
156 if 'create_repo_group' in form_data:
157 user_data['create_repo_group'] = str2bool(
157 user_data['create_repo_group'] = str2bool(
158 form_data.get('create_repo_group'))
158 form_data.get('create_repo_group'))
159
159
160 try:
160 try:
161 if form_data.get('password_change'):
161 if form_data.get('password_change'):
162 user_data['force_password_change'] = True
162 user_data['force_password_change'] = True
163 return UserModel().create_or_update(**user_data)
163 return UserModel().create_or_update(**user_data)
164 except Exception:
164 except Exception:
165 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
166 raise
166 raise
167
167
168 def update_user(self, user, skip_attrs=None, **kwargs):
168 def update_user(self, user, skip_attrs=None, **kwargs):
169 from rhodecode.lib.auth import get_crypt_password
169 from rhodecode.lib.auth import get_crypt_password
170
170
171 user = self._get_user(user)
171 user = self._get_user(user)
172 if user.username == User.DEFAULT_USER:
172 if user.username == User.DEFAULT_USER:
173 raise DefaultUserException(
173 raise DefaultUserException(
174 "You can't edit this user (`%(username)s`) since it's "
174 "You can't edit this user (`%(username)s`) since it's "
175 "crucial for entire application" % {
175 "crucial for entire application" % {
176 'username': user.username})
176 'username': user.username})
177
177
178 # first store only defaults
178 # first store only defaults
179 user_attrs = {
179 user_attrs = {
180 'updating_user_id': user.user_id,
180 'updating_user_id': user.user_id,
181 'username': user.username,
181 'username': user.username,
182 'password': user.password,
182 'password': user.password,
183 'email': user.email,
183 'email': user.email,
184 'firstname': user.name,
184 'firstname': user.name,
185 'lastname': user.lastname,
185 'lastname': user.lastname,
186 'description': user.description,
186 'description': user.description,
187 'active': user.active,
187 'active': user.active,
188 'admin': user.admin,
188 'admin': user.admin,
189 'extern_name': user.extern_name,
189 'extern_name': user.extern_name,
190 'extern_type': user.extern_type,
190 'extern_type': user.extern_type,
191 'language': user.user_data.get('language')
191 'language': user.user_data.get('language')
192 }
192 }
193
193
194 # in case there's new_password, that comes from form, use it to
194 # in case there's new_password, that comes from form, use it to
195 # store password
195 # store password
196 if kwargs.get('new_password'):
196 if kwargs.get('new_password'):
197 kwargs['password'] = kwargs['new_password']
197 kwargs['password'] = kwargs['new_password']
198
198
199 # cleanups, my_account password change form
199 # cleanups, my_account password change form
200 kwargs.pop('current_password', None)
200 kwargs.pop('current_password', None)
201 kwargs.pop('new_password', None)
201 kwargs.pop('new_password', None)
202
202
203 # cleanups, user edit password change form
203 # cleanups, user edit password change form
204 kwargs.pop('password_confirmation', None)
204 kwargs.pop('password_confirmation', None)
205 kwargs.pop('password_change', None)
205 kwargs.pop('password_change', None)
206
206
207 # create repo group on user creation
207 # create repo group on user creation
208 kwargs.pop('create_repo_group', None)
208 kwargs.pop('create_repo_group', None)
209
209
210 # legacy forms send name, which is the firstname
210 # legacy forms send name, which is the firstname
211 firstname = kwargs.pop('name', None)
211 firstname = kwargs.pop('name', None)
212 if firstname:
212 if firstname:
213 kwargs['firstname'] = firstname
213 kwargs['firstname'] = firstname
214
214
215 for k, v in kwargs.items():
215 for k, v in kwargs.items():
216 # skip if we don't want to update this
216 # skip if we don't want to update this
217 if skip_attrs and k in skip_attrs:
217 if skip_attrs and k in skip_attrs:
218 continue
218 continue
219
219
220 user_attrs[k] = v
220 user_attrs[k] = v
221
221
222 try:
222 try:
223 return self.create_or_update(**user_attrs)
223 return self.create_or_update(**user_attrs)
224 except Exception:
224 except Exception:
225 log.error(traceback.format_exc())
225 log.error(traceback.format_exc())
226 raise
226 raise
227
227
228 def create_or_update(
228 def create_or_update(
229 self, username, password, email, firstname='', lastname='',
229 self, username, password, email, firstname='', lastname='',
230 active=True, admin=False, extern_type=None, extern_name=None,
230 active=True, admin=False, extern_type=None, extern_name=None,
231 cur_user=None, plugin=None, force_password_change=False,
231 cur_user=None, plugin=None, force_password_change=False,
232 allow_to_create_user=True, create_repo_group=None,
232 allow_to_create_user=True, create_repo_group=None,
233 updating_user_id=None, language=None, description='',
233 updating_user_id=None, language=None, description='',
234 strict_creation_check=True):
234 strict_creation_check=True):
235 """
235 """
236 Creates a new instance if not found, or updates current one
236 Creates a new instance if not found, or updates current one
237
237
238 :param username:
238 :param username:
239 :param password:
239 :param password:
240 :param email:
240 :param email:
241 :param firstname:
241 :param firstname:
242 :param lastname:
242 :param lastname:
243 :param active:
243 :param active:
244 :param admin:
244 :param admin:
245 :param extern_type:
245 :param extern_type:
246 :param extern_name:
246 :param extern_name:
247 :param cur_user:
247 :param cur_user:
248 :param plugin: optional plugin this method was called from
248 :param plugin: optional plugin this method was called from
249 :param force_password_change: toggles new or existing user flag
249 :param force_password_change: toggles new or existing user flag
250 for password change
250 for password change
251 :param allow_to_create_user: Defines if the method can actually create
251 :param allow_to_create_user: Defines if the method can actually create
252 new users
252 new users
253 :param create_repo_group: Defines if the method should also
253 :param create_repo_group: Defines if the method should also
254 create an repo group with user name, and owner
254 create an repo group with user name, and owner
255 :param updating_user_id: if we set it up this is the user we want to
255 :param updating_user_id: if we set it up this is the user we want to
256 update this allows to editing username.
256 update this allows to editing username.
257 :param language: language of user from interface.
257 :param language: language of user from interface.
258 :param description: user description
258 :param description: user description
259 :param strict_creation_check: checks for allowed creation license wise etc.
259 :param strict_creation_check: checks for allowed creation license wise etc.
260
260
261 :returns: new User object with injected `is_new_user` attribute.
261 :returns: new User object with injected `is_new_user` attribute.
262 """
262 """
263
263
264 if not cur_user:
264 if not cur_user:
265 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
265 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
266
266
267 from rhodecode.lib.auth import (
267 from rhodecode.lib.auth import (
268 get_crypt_password, check_password)
268 get_crypt_password, check_password)
269 from rhodecode.lib import hooks_base
269 from rhodecode.lib import hooks_base
270
270
271 def _password_change(new_user, password):
271 def _password_change(new_user, password):
272 old_password = new_user.password or ''
272 old_password = new_user.password or ''
273 # empty password
273 # empty password
274 if not old_password:
274 if not old_password:
275 return False
275 return False
276
276
277 # password check is only needed for RhodeCode internal auth calls
277 # password check is only needed for RhodeCode internal auth calls
278 # in case it's a plugin we don't care
278 # in case it's a plugin we don't care
279 if not plugin:
279 if not plugin:
280
280
281 # first check if we gave crypted password back, and if it
281 # first check if we gave crypted password back, and if it
282 # matches it's not password change
282 # matches it's not password change
283 if new_user.password == password:
283 if new_user.password == password:
284 return False
284 return False
285
285
286 password_match = check_password(password, old_password)
286 password_match = check_password(password, old_password)
287 if not password_match:
287 if not password_match:
288 return True
288 return True
289
289
290 return False
290 return False
291
291
292 # read settings on default personal repo group creation
292 # read settings on default personal repo group creation
293 if create_repo_group is None:
293 if create_repo_group is None:
294 default_create_repo_group = RepoGroupModel()\
294 default_create_repo_group = RepoGroupModel()\
295 .get_default_create_personal_repo_group()
295 .get_default_create_personal_repo_group()
296 create_repo_group = default_create_repo_group
296 create_repo_group = default_create_repo_group
297
297
298 user_data = {
298 user_data = {
299 'username': username,
299 'username': username,
300 'password': password,
300 'password': password,
301 'email': email,
301 'email': email,
302 'firstname': firstname,
302 'firstname': firstname,
303 'lastname': lastname,
303 'lastname': lastname,
304 'active': active,
304 'active': active,
305 'admin': admin
305 'admin': admin
306 }
306 }
307
307
308 if updating_user_id:
308 if updating_user_id:
309 log.debug('Checking for existing account in RhodeCode '
309 log.debug('Checking for existing account in RhodeCode '
310 'database with user_id `%s` ', updating_user_id)
310 'database with user_id `%s` ', updating_user_id)
311 user = User.get(updating_user_id)
311 user = User.get(updating_user_id)
312 else:
312 else:
313 log.debug('Checking for existing account in RhodeCode '
313 log.debug('Checking for existing account in RhodeCode '
314 'database with username `%s` ', username)
314 'database with username `%s` ', username)
315 user = User.get_by_username(username, case_insensitive=True)
315 user = User.get_by_username(username, case_insensitive=True)
316
316
317 if user is None:
317 if user is None:
318 # we check internal flag if this method is actually allowed to
318 # we check internal flag if this method is actually allowed to
319 # create new user
319 # create new user
320 if not allow_to_create_user:
320 if not allow_to_create_user:
321 msg = ('Method wants to create new user, but it is not '
321 msg = ('Method wants to create new user, but it is not '
322 'allowed to do so')
322 'allowed to do so')
323 log.warning(msg)
323 log.warning(msg)
324 raise NotAllowedToCreateUserError(msg)
324 raise NotAllowedToCreateUserError(msg)
325
325
326 log.debug('Creating new user %s', username)
326 log.debug('Creating new user %s', username)
327
327
328 # only if we create user that is active
328 # only if we create user that is active
329 new_active_user = active
329 new_active_user = active
330 if new_active_user and strict_creation_check:
330 if new_active_user and strict_creation_check:
331 # raises UserCreationError if it's not allowed for any reason to
331 # raises UserCreationError if it's not allowed for any reason to
332 # create new active user, this also executes pre-create hooks
332 # create new active user, this also executes pre-create hooks
333 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
333 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
334 events.trigger(events.UserPreCreate(user_data))
334 events.trigger(events.UserPreCreate(user_data))
335 new_user = User()
335 new_user = User()
336 edit = False
336 edit = False
337 else:
337 else:
338 log.debug('updating user `%s`', username)
338 log.debug('updating user `%s`', username)
339 events.trigger(events.UserPreUpdate(user, user_data))
339 events.trigger(events.UserPreUpdate(user, user_data))
340 new_user = user
340 new_user = user
341 edit = True
341 edit = True
342
342
343 # we're not allowed to edit default user
343 # we're not allowed to edit default user
344 if user.username == User.DEFAULT_USER:
344 if user.username == User.DEFAULT_USER:
345 raise DefaultUserException(
345 raise DefaultUserException(
346 "You can't edit this user (`%(username)s`) since it's "
346 "You can't edit this user (`%(username)s`) since it's "
347 "crucial for entire application"
347 "crucial for entire application"
348 % {'username': user.username})
348 % {'username': user.username})
349
349
350 # inject special attribute that will tell us if User is new or old
350 # inject special attribute that will tell us if User is new or old
351 new_user.is_new_user = not edit
351 new_user.is_new_user = not edit
352 # for users that didn's specify auth type, we use RhodeCode built in
352 # for users that didn's specify auth type, we use RhodeCode built in
353 from rhodecode.authentication.plugins import auth_rhodecode
353 from rhodecode.authentication.plugins import auth_rhodecode
354 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
354 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
355 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
355 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
356
356
357 try:
357 try:
358 new_user.username = username
358 new_user.username = username
359 new_user.admin = admin
359 new_user.admin = admin
360 new_user.email = email
360 new_user.email = email
361 new_user.active = active
361 new_user.active = active
362 new_user.extern_name = safe_unicode(extern_name)
362 new_user.extern_name = safe_unicode(extern_name)
363 new_user.extern_type = safe_unicode(extern_type)
363 new_user.extern_type = safe_unicode(extern_type)
364 new_user.name = firstname
364 new_user.name = firstname
365 new_user.lastname = lastname
365 new_user.lastname = lastname
366 new_user.description = description
366 new_user.description = description
367
367
368 # set password only if creating an user or password is changed
368 # set password only if creating an user or password is changed
369 if not edit or _password_change(new_user, password):
369 if not edit or _password_change(new_user, password):
370 reason = 'new password' if edit else 'new user'
370 reason = 'new password' if edit else 'new user'
371 log.debug('Updating password reason=>%s', reason)
371 log.debug('Updating password reason=>%s', reason)
372 new_user.password = get_crypt_password(password) if password else None
372 new_user.password = get_crypt_password(password) if password else None
373
373
374 if force_password_change:
374 if force_password_change:
375 new_user.update_userdata(force_password_change=True)
375 new_user.update_userdata(force_password_change=True)
376 if language:
376 if language:
377 new_user.update_userdata(language=language)
377 new_user.update_userdata(language=language)
378 new_user.update_userdata(notification_status=True)
378 new_user.update_userdata(notification_status=True)
379
379
380 self.sa.add(new_user)
380 self.sa.add(new_user)
381
381
382 if not edit and create_repo_group:
382 if not edit and create_repo_group:
383 RepoGroupModel().create_personal_repo_group(
383 RepoGroupModel().create_personal_repo_group(
384 new_user, commit_early=False)
384 new_user, commit_early=False)
385
385
386 if not edit:
386 if not edit:
387 # add the RSS token
387 # add the RSS token
388 self.add_auth_token(
388 self.add_auth_token(
389 user=username, lifetime_minutes=-1,
389 user=username, lifetime_minutes=-1,
390 role=self.auth_token_role.ROLE_FEED,
390 role=self.auth_token_role.ROLE_FEED,
391 description=u'Generated feed token')
391 description=u'Generated feed token')
392
392
393 kwargs = new_user.get_dict()
393 kwargs = new_user.get_dict()
394 # backward compat, require api_keys present
394 # backward compat, require api_keys present
395 kwargs['api_keys'] = kwargs['auth_tokens']
395 kwargs['api_keys'] = kwargs['auth_tokens']
396 hooks_base.create_user(created_by=cur_user, **kwargs)
396 hooks_base.create_user(created_by=cur_user, **kwargs)
397 events.trigger(events.UserPostCreate(user_data))
397 events.trigger(events.UserPostCreate(user_data))
398 return new_user
398 return new_user
399 except (DatabaseError,):
399 except (DatabaseError,):
400 log.error(traceback.format_exc())
400 log.error(traceback.format_exc())
401 raise
401 raise
402
402
403 def create_registration(self, form_data,
403 def create_registration(self, form_data,
404 extern_name='rhodecode', extern_type='rhodecode'):
404 extern_name='rhodecode', extern_type='rhodecode'):
405 from rhodecode.model.notification import NotificationModel
405 from rhodecode.model.notification import NotificationModel
406 from rhodecode.model.notification import EmailNotificationModel
406 from rhodecode.model.notification import EmailNotificationModel
407
407
408 try:
408 try:
409 form_data['admin'] = False
409 form_data['admin'] = False
410 form_data['extern_name'] = extern_name
410 form_data['extern_name'] = extern_name
411 form_data['extern_type'] = extern_type
411 form_data['extern_type'] = extern_type
412 new_user = self.create(form_data)
412 new_user = self.create(form_data)
413
413
414 self.sa.add(new_user)
414 self.sa.add(new_user)
415 self.sa.flush()
415 self.sa.flush()
416
416
417 user_data = new_user.get_dict()
417 user_data = new_user.get_dict()
418 user_data.update({
418 user_data.update({
419 'first_name': user_data.get('firstname'),
419 'first_name': user_data.get('firstname'),
420 'last_name': user_data.get('lastname'),
420 'last_name': user_data.get('lastname'),
421 })
421 })
422 kwargs = {
422 kwargs = {
423 # use SQLALCHEMY safe dump of user data
423 # use SQLALCHEMY safe dump of user data
424 'user': AttributeDict(user_data),
424 'user': AttributeDict(user_data),
425 'date': datetime.datetime.now()
425 'date': datetime.datetime.now()
426 }
426 }
427 notification_type = EmailNotificationModel.TYPE_REGISTRATION
427 notification_type = EmailNotificationModel.TYPE_REGISTRATION
428
428
429 # create notification objects, and emails
429 # create notification objects, and emails
430 NotificationModel().create(
430 NotificationModel().create(
431 created_by=new_user,
431 created_by=new_user,
432 notification_subject='', # Filled in based on the notification_type
432 notification_subject='', # Filled in based on the notification_type
433 notification_body='', # Filled in based on the notification_type
433 notification_body='', # Filled in based on the notification_type
434 notification_type=notification_type,
434 notification_type=notification_type,
435 recipients=None, # all admins
435 recipients=None, # all admins
436 email_kwargs=kwargs,
436 email_kwargs=kwargs,
437 )
437 )
438
438
439 return new_user
439 return new_user
440 except Exception:
440 except Exception:
441 log.error(traceback.format_exc())
441 log.error(traceback.format_exc())
442 raise
442 raise
443
443
444 def _handle_user_repos(self, username, repositories, handle_user,
444 def _handle_user_repos(self, username, repositories, handle_user,
445 handle_mode=None):
445 handle_mode=None):
446
446
447 left_overs = True
447 left_overs = True
448
448
449 from rhodecode.model.repo import RepoModel
449 from rhodecode.model.repo import RepoModel
450
450
451 if handle_mode == 'detach':
451 if handle_mode == 'detach':
452 for obj in repositories:
452 for obj in repositories:
453 obj.user = handle_user
453 obj.user = handle_user
454 # set description we know why we super admin now owns
454 # set description we know why we super admin now owns
455 # additional repositories that were orphaned !
455 # additional repositories that were orphaned !
456 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
456 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
457 self.sa.add(obj)
457 self.sa.add(obj)
458 left_overs = False
458 left_overs = False
459 elif handle_mode == 'delete':
459 elif handle_mode == 'delete':
460 for obj in repositories:
460 for obj in repositories:
461 RepoModel().delete(obj, forks='detach')
461 RepoModel().delete(obj, forks='detach')
462 left_overs = False
462 left_overs = False
463
463
464 # if nothing is done we have left overs left
464 # if nothing is done we have left overs left
465 return left_overs
465 return left_overs
466
466
467 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
467 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
468 handle_mode=None):
468 handle_mode=None):
469
469
470 left_overs = True
470 left_overs = True
471
471
472 from rhodecode.model.repo_group import RepoGroupModel
472 from rhodecode.model.repo_group import RepoGroupModel
473
473
474 if handle_mode == 'detach':
474 if handle_mode == 'detach':
475 for r in repository_groups:
475 for r in repository_groups:
476 r.user = handle_user
476 r.user = handle_user
477 # set description we know why we super admin now owns
477 # set description we know why we super admin now owns
478 # additional repositories that were orphaned !
478 # additional repositories that were orphaned !
479 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
479 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
480 r.personal = False
480 r.personal = False
481 self.sa.add(r)
481 self.sa.add(r)
482 left_overs = False
482 left_overs = False
483 elif handle_mode == 'delete':
483 elif handle_mode == 'delete':
484 for r in repository_groups:
484 for r in repository_groups:
485 RepoGroupModel().delete(r)
485 RepoGroupModel().delete(r)
486 left_overs = False
486 left_overs = False
487
487
488 # if nothing is done we have left overs left
488 # if nothing is done we have left overs left
489 return left_overs
489 return left_overs
490
490
491 def _handle_user_user_groups(self, username, user_groups, handle_user,
491 def _handle_user_user_groups(self, username, user_groups, handle_user,
492 handle_mode=None):
492 handle_mode=None):
493
493
494 left_overs = True
494 left_overs = True
495
495
496 from rhodecode.model.user_group import UserGroupModel
496 from rhodecode.model.user_group import UserGroupModel
497
497
498 if handle_mode == 'detach':
498 if handle_mode == 'detach':
499 for r in user_groups:
499 for r in user_groups:
500 for user_user_group_to_perm in r.user_user_group_to_perm:
500 for user_user_group_to_perm in r.user_user_group_to_perm:
501 if user_user_group_to_perm.user.username == username:
501 if user_user_group_to_perm.user.username == username:
502 user_user_group_to_perm.user = handle_user
502 user_user_group_to_perm.user = handle_user
503 r.user = handle_user
503 r.user = handle_user
504 # set description we know why we super admin now owns
504 # set description we know why we super admin now owns
505 # additional repositories that were orphaned !
505 # additional repositories that were orphaned !
506 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
506 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
507 self.sa.add(r)
507 self.sa.add(r)
508 left_overs = False
508 left_overs = False
509 elif handle_mode == 'delete':
509 elif handle_mode == 'delete':
510 for r in user_groups:
510 for r in user_groups:
511 UserGroupModel().delete(r)
511 UserGroupModel().delete(r)
512 left_overs = False
512 left_overs = False
513
513
514 # if nothing is done we have left overs left
514 # if nothing is done we have left overs left
515 return left_overs
515 return left_overs
516
516
517 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
517 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
518 handle_mode=None):
518 handle_mode=None):
519 left_overs = True
519 left_overs = True
520
520
521 from rhodecode.model.pull_request import PullRequestModel
521 from rhodecode.model.pull_request import PullRequestModel
522
522
523 if handle_mode == 'detach':
523 if handle_mode == 'detach':
524 for pr in pull_requests:
524 for pr in pull_requests:
525 pr.user_id = handle_user.user_id
525 pr.user_id = handle_user.user_id
526 # set description we know why we super admin now owns
526 # set description we know why we super admin now owns
527 # additional repositories that were orphaned !
527 # additional repositories that were orphaned !
528 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
528 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
529 self.sa.add(pr)
529 self.sa.add(pr)
530 left_overs = False
530 left_overs = False
531 elif handle_mode == 'delete':
531 elif handle_mode == 'delete':
532 for pr in pull_requests:
532 for pr in pull_requests:
533 PullRequestModel().delete(pr)
533 PullRequestModel().delete(pr)
534
534
535 left_overs = False
535 left_overs = False
536
536
537 # if nothing is done we have left overs left
537 # if nothing is done we have left overs left
538 return left_overs
538 return left_overs
539
539
540 def _handle_user_artifacts(self, username, artifacts, handle_user,
540 def _handle_user_artifacts(self, username, artifacts, handle_user,
541 handle_mode=None):
541 handle_mode=None):
542
542
543 left_overs = True
543 left_overs = True
544
544
545 if handle_mode == 'detach':
545 if handle_mode == 'detach':
546 for a in artifacts:
546 for a in artifacts:
547 a.upload_user = handle_user
547 a.upload_user = handle_user
548 # set description we know why we super admin now owns
548 # set description we know why we super admin now owns
549 # additional artifacts that were orphaned !
549 # additional artifacts that were orphaned !
550 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
550 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
551 self.sa.add(a)
551 self.sa.add(a)
552 left_overs = False
552 left_overs = False
553 elif handle_mode == 'delete':
553 elif handle_mode == 'delete':
554 from rhodecode.apps.file_store import utils as store_utils
554 from rhodecode.apps.file_store import utils as store_utils
555 request = get_current_request()
555 request = get_current_request()
556 storage = store_utils.get_file_storage(request.registry.settings)
556 storage = store_utils.get_file_storage(request.registry.settings)
557 for a in artifacts:
557 for a in artifacts:
558 file_uid = a.file_uid
558 file_uid = a.file_uid
559 storage.delete(file_uid)
559 storage.delete(file_uid)
560 self.sa.delete(a)
560 self.sa.delete(a)
561
561
562 left_overs = False
562 left_overs = False
563
563
564 # if nothing is done we have left overs left
564 # if nothing is done we have left overs left
565 return left_overs
565 return left_overs
566
566
567 def delete(self, user, cur_user=None, handle_repos=None,
567 def delete(self, user, cur_user=None, handle_repos=None,
568 handle_repo_groups=None, handle_user_groups=None,
568 handle_repo_groups=None, handle_user_groups=None,
569 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
569 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
570 from rhodecode.lib import hooks_base
570 from rhodecode.lib import hooks_base
571
571
572 if not cur_user:
572 if not cur_user:
573 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
573 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
574
574
575 user = self._get_user(user)
575 user = self._get_user(user)
576
576
577 try:
577 try:
578 if user.username == User.DEFAULT_USER:
578 if user.username == User.DEFAULT_USER:
579 raise DefaultUserException(
579 raise DefaultUserException(
580 u"You can't remove this user since it's"
580 u"You can't remove this user since it's"
581 u" crucial for entire application")
581 u" crucial for entire application")
582 handle_user = handle_new_owner or self.cls.get_first_super_admin()
582 handle_user = handle_new_owner or self.cls.get_first_super_admin()
583 log.debug('New detached objects owner %s', handle_user)
583 log.debug('New detached objects owner %s', handle_user)
584
584
585 left_overs = self._handle_user_repos(
585 left_overs = self._handle_user_repos(
586 user.username, user.repositories, handle_user, handle_repos)
586 user.username, user.repositories, handle_user, handle_repos)
587 if left_overs and user.repositories:
587 if left_overs and user.repositories:
588 repos = [x.repo_name for x in user.repositories]
588 repos = [x.repo_name for x in user.repositories]
589 raise UserOwnsReposException(
589 raise UserOwnsReposException(
590 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
590 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
591 u'removed. Switch owners or remove those repositories:%(list_repos)s'
591 u'removed. Switch owners or remove those repositories:%(list_repos)s'
592 % {'username': user.username, 'len_repos': len(repos),
592 % {'username': user.username, 'len_repos': len(repos),
593 'list_repos': ', '.join(repos)})
593 'list_repos': ', '.join(repos)})
594
594
595 left_overs = self._handle_user_repo_groups(
595 left_overs = self._handle_user_repo_groups(
596 user.username, user.repository_groups, handle_user, handle_repo_groups)
596 user.username, user.repository_groups, handle_user, handle_repo_groups)
597 if left_overs and user.repository_groups:
597 if left_overs and user.repository_groups:
598 repo_groups = [x.group_name for x in user.repository_groups]
598 repo_groups = [x.group_name for x in user.repository_groups]
599 raise UserOwnsRepoGroupsException(
599 raise UserOwnsRepoGroupsException(
600 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
600 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
601 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
601 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
602 % {'username': user.username, 'len_repo_groups': len(repo_groups),
602 % {'username': user.username, 'len_repo_groups': len(repo_groups),
603 'list_repo_groups': ', '.join(repo_groups)})
603 'list_repo_groups': ', '.join(repo_groups)})
604
604
605 left_overs = self._handle_user_user_groups(
605 left_overs = self._handle_user_user_groups(
606 user.username, user.user_groups, handle_user, handle_user_groups)
606 user.username, user.user_groups, handle_user, handle_user_groups)
607 if left_overs and user.user_groups:
607 if left_overs and user.user_groups:
608 user_groups = [x.users_group_name for x in user.user_groups]
608 user_groups = [x.users_group_name for x in user.user_groups]
609 raise UserOwnsUserGroupsException(
609 raise UserOwnsUserGroupsException(
610 u'user "%s" still owns %s user groups and cannot be '
610 u'user "%s" still owns %s user groups and cannot be '
611 u'removed. Switch owners or remove those user groups:%s'
611 u'removed. Switch owners or remove those user groups:%s'
612 % (user.username, len(user_groups), ', '.join(user_groups)))
612 % (user.username, len(user_groups), ', '.join(user_groups)))
613
613
614 left_overs = self._handle_user_pull_requests(
614 left_overs = self._handle_user_pull_requests(
615 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
615 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
616 if left_overs and user.user_pull_requests:
616 if left_overs and user.user_pull_requests:
617 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
617 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
618 raise UserOwnsPullRequestsException(
618 raise UserOwnsPullRequestsException(
619 u'user "%s" still owns %s pull requests and cannot be '
619 u'user "%s" still owns %s pull requests and cannot be '
620 u'removed. Switch owners or remove those pull requests:%s'
620 u'removed. Switch owners or remove those pull requests:%s'
621 % (user.username, len(pull_requests), ', '.join(pull_requests)))
621 % (user.username, len(pull_requests), ', '.join(pull_requests)))
622
622
623 left_overs = self._handle_user_artifacts(
623 left_overs = self._handle_user_artifacts(
624 user.username, user.artifacts, handle_user, handle_artifacts)
624 user.username, user.artifacts, handle_user, handle_artifacts)
625 if left_overs and user.artifacts:
625 if left_overs and user.artifacts:
626 artifacts = [x.file_uid for x in user.artifacts]
626 artifacts = [x.file_uid for x in user.artifacts]
627 raise UserOwnsArtifactsException(
627 raise UserOwnsArtifactsException(
628 u'user "%s" still owns %s artifacts and cannot be '
628 u'user "%s" still owns %s artifacts and cannot be '
629 u'removed. Switch owners or remove those artifacts:%s'
629 u'removed. Switch owners or remove those artifacts:%s'
630 % (user.username, len(artifacts), ', '.join(artifacts)))
630 % (user.username, len(artifacts), ', '.join(artifacts)))
631
631
632 user_data = user.get_dict() # fetch user data before expire
632 user_data = user.get_dict() # fetch user data before expire
633
633
634 # we might change the user data with detach/delete, make sure
634 # we might change the user data with detach/delete, make sure
635 # the object is marked as expired before actually deleting !
635 # the object is marked as expired before actually deleting !
636 self.sa.expire(user)
636 self.sa.expire(user)
637 self.sa.delete(user)
637 self.sa.delete(user)
638
638
639 hooks_base.delete_user(deleted_by=cur_user, **user_data)
639 hooks_base.delete_user(deleted_by=cur_user, **user_data)
640 except Exception:
640 except Exception:
641 log.error(traceback.format_exc())
641 log.error(traceback.format_exc())
642 raise
642 raise
643
643
644 def reset_password_link(self, data, pwd_reset_url):
644 def reset_password_link(self, data, pwd_reset_url):
645 from rhodecode.lib.celerylib import tasks, run_task
645 from rhodecode.lib.celerylib import tasks, run_task
646 from rhodecode.model.notification import EmailNotificationModel
646 from rhodecode.model.notification import EmailNotificationModel
647 user_email = data['email']
647 user_email = data['email']
648 try:
648 try:
649 user = User.get_by_email(user_email)
649 user = User.get_by_email(user_email)
650 if user:
650 if user:
651 log.debug('password reset user found %s', user)
651 log.debug('password reset user found %s', user)
652
652
653 email_kwargs = {
653 email_kwargs = {
654 'password_reset_url': pwd_reset_url,
654 'password_reset_url': pwd_reset_url,
655 'user': user,
655 'user': user,
656 'email': user_email,
656 'email': user_email,
657 'date': datetime.datetime.now(),
657 'date': datetime.datetime.now(),
658 'first_admin_email': User.get_first_super_admin().email
658 'first_admin_email': User.get_first_super_admin().email
659 }
659 }
660
660
661 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
661 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
662 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
662 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
663
663
664 recipients = [user_email]
664 recipients = [user_email]
665
665
666 action_logger_generic(
666 action_logger_generic(
667 'sending password reset email to user: {}'.format(
667 'sending password reset email to user: {}'.format(
668 user), namespace='security.password_reset')
668 user), namespace='security.password_reset')
669
669
670 run_task(tasks.send_email, recipients, subject,
670 run_task(tasks.send_email, recipients, subject,
671 email_body_plaintext, email_body)
671 email_body_plaintext, email_body)
672
672
673 else:
673 else:
674 log.debug("password reset email %s not found", user_email)
674 log.debug("password reset email %s not found", user_email)
675 except Exception:
675 except Exception:
676 log.error(traceback.format_exc())
676 log.error(traceback.format_exc())
677 return False
677 return False
678
678
679 return True
679 return True
680
680
681 def reset_password(self, data):
681 def reset_password(self, data):
682 from rhodecode.lib.celerylib import tasks, run_task
682 from rhodecode.lib.celerylib import tasks, run_task
683 from rhodecode.model.notification import EmailNotificationModel
683 from rhodecode.model.notification import EmailNotificationModel
684 from rhodecode.lib import auth
684 from rhodecode.lib import auth
685 user_email = data['email']
685 user_email = data['email']
686 pre_db = True
686 pre_db = True
687 try:
687 try:
688 user = User.get_by_email(user_email)
688 user = User.get_by_email(user_email)
689 new_passwd = auth.PasswordGenerator().gen_password(
689 new_passwd = auth.PasswordGenerator().gen_password(
690 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
690 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
691 if user:
691 if user:
692 user.password = auth.get_crypt_password(new_passwd)
692 user.password = auth.get_crypt_password(new_passwd)
693 # also force this user to reset his password !
693 # also force this user to reset his password !
694 user.update_userdata(force_password_change=True)
694 user.update_userdata(force_password_change=True)
695
695
696 Session().add(user)
696 Session().add(user)
697
697
698 # now delete the token in question
698 # now delete the token in question
699 UserApiKeys = AuthTokenModel.cls
699 UserApiKeys = AuthTokenModel.cls
700 UserApiKeys().query().filter(
700 UserApiKeys().query().filter(
701 UserApiKeys.api_key == data['token']).delete()
701 UserApiKeys.api_key == data['token']).delete()
702
702
703 Session().commit()
703 Session().commit()
704 log.info('successfully reset password for `%s`', user_email)
704 log.info('successfully reset password for `%s`', user_email)
705
705
706 if new_passwd is None:
706 if new_passwd is None:
707 raise Exception('unable to generate new password')
707 raise Exception('unable to generate new password')
708
708
709 pre_db = False
709 pre_db = False
710
710
711 email_kwargs = {
711 email_kwargs = {
712 'new_password': new_passwd,
712 'new_password': new_passwd,
713 'user': user,
713 'user': user,
714 'email': user_email,
714 'email': user_email,
715 'date': datetime.datetime.now(),
715 'date': datetime.datetime.now(),
716 'first_admin_email': User.get_first_super_admin().email
716 'first_admin_email': User.get_first_super_admin().email
717 }
717 }
718
718
719 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
719 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
720 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
720 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
721 **email_kwargs)
721 **email_kwargs)
722
722
723 recipients = [user_email]
723 recipients = [user_email]
724
724
725 action_logger_generic(
725 action_logger_generic(
726 'sent new password to user: {} with email: {}'.format(
726 'sent new password to user: {} with email: {}'.format(
727 user, user_email), namespace='security.password_reset')
727 user, user_email), namespace='security.password_reset')
728
728
729 run_task(tasks.send_email, recipients, subject,
729 run_task(tasks.send_email, recipients, subject,
730 email_body_plaintext, email_body)
730 email_body_plaintext, email_body)
731
731
732 except Exception:
732 except Exception:
733 log.error('Failed to update user password')
733 log.error('Failed to update user password')
734 log.error(traceback.format_exc())
734 log.error(traceback.format_exc())
735 if pre_db:
735 if pre_db:
736 # we rollback only if local db stuff fails. If it goes into
736 # we rollback only if local db stuff fails. If it goes into
737 # run_task, we're pass rollback state this wouldn't work then
737 # run_task, we're pass rollback state this wouldn't work then
738 Session().rollback()
738 Session().rollback()
739
739
740 return True
740 return True
741
741
742 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
742 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
743 """
743 """
744 Fetches auth_user by user_id,or api_key if present.
744 Fetches auth_user by user_id,or api_key if present.
745 Fills auth_user attributes with those taken from database.
745 Fills auth_user attributes with those taken from database.
746 Additionally set's is_authenitated if lookup fails
746 Additionally set's is_authenitated if lookup fails
747 present in database
747 present in database
748
748
749 :param auth_user: instance of user to set attributes
749 :param auth_user: instance of user to set attributes
750 :param user_id: user id to fetch by
750 :param user_id: user id to fetch by
751 :param api_key: api key to fetch by
751 :param api_key: api key to fetch by
752 :param username: username to fetch by
752 :param username: username to fetch by
753 """
753 """
754 def token_obfuscate(token):
754 def token_obfuscate(token):
755 if token:
755 if token:
756 return token[:4] + "****"
756 return token[:4] + "****"
757
757
758 if user_id is None and api_key is None and username is None:
758 if user_id is None and api_key is None and username is None:
759 raise Exception('You need to pass user_id, api_key or username')
759 raise Exception('You need to pass user_id, api_key or username')
760
760
761 log.debug(
761 log.debug(
762 'AuthUser: fill data execution based on: '
762 'AuthUser: fill data execution based on: '
763 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
763 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
764 try:
764 try:
765 dbuser = None
765 dbuser = None
766 if user_id:
766 if user_id:
767 dbuser = self.get(user_id)
767 dbuser = self.get(user_id)
768 elif api_key:
768 elif api_key:
769 dbuser = self.get_by_auth_token(api_key)
769 dbuser = self.get_by_auth_token(api_key)
770 elif username:
770 elif username:
771 dbuser = self.get_by_username(username)
771 dbuser = self.get_by_username(username)
772
772
773 if not dbuser:
773 if not dbuser:
774 log.warning(
774 log.warning(
775 'Unable to lookup user by id:%s api_key:%s username:%s',
775 'Unable to lookup user by id:%s api_key:%s username:%s',
776 user_id, token_obfuscate(api_key), username)
776 user_id, token_obfuscate(api_key), username)
777 return False
777 return False
778 if not dbuser.active:
778 if not dbuser.active:
779 log.debug('User `%s:%s` is inactive, skipping fill data',
779 log.debug('User `%s:%s` is inactive, skipping fill data',
780 username, user_id)
780 username, user_id)
781 return False
781 return False
782
782
783 log.debug('AuthUser: filling found user:%s data', dbuser)
783 log.debug('AuthUser: filling found user:%s data', dbuser)
784
784
785 attrs = {
785 attrs = {
786 'user_id': dbuser.user_id,
786 'user_id': dbuser.user_id,
787 'username': dbuser.username,
787 'username': dbuser.username,
788 'name': dbuser.name,
788 'name': dbuser.name,
789 'first_name': dbuser.first_name,
789 'first_name': dbuser.first_name,
790 'firstname': dbuser.firstname,
790 'firstname': dbuser.firstname,
791 'last_name': dbuser.last_name,
791 'last_name': dbuser.last_name,
792 'lastname': dbuser.lastname,
792 'lastname': dbuser.lastname,
793 'admin': dbuser.admin,
793 'admin': dbuser.admin,
794 'active': dbuser.active,
794 'active': dbuser.active,
795
795
796 'email': dbuser.email,
796 'email': dbuser.email,
797 'emails': dbuser.emails_cached(),
797 'emails': dbuser.emails_cached(),
798 'short_contact': dbuser.short_contact,
798 'short_contact': dbuser.short_contact,
799 'full_contact': dbuser.full_contact,
799 'full_contact': dbuser.full_contact,
800 'full_name': dbuser.full_name,
800 'full_name': dbuser.full_name,
801 'full_name_or_username': dbuser.full_name_or_username,
801 'full_name_or_username': dbuser.full_name_or_username,
802
802
803 '_api_key': dbuser._api_key,
803 '_api_key': dbuser._api_key,
804 '_user_data': dbuser._user_data,
804 '_user_data': dbuser._user_data,
805
805
806 'created_on': dbuser.created_on,
806 'created_on': dbuser.created_on,
807 'extern_name': dbuser.extern_name,
807 'extern_name': dbuser.extern_name,
808 'extern_type': dbuser.extern_type,
808 'extern_type': dbuser.extern_type,
809
809
810 'inherit_default_permissions': dbuser.inherit_default_permissions,
810 'inherit_default_permissions': dbuser.inherit_default_permissions,
811
811
812 'language': dbuser.language,
812 'language': dbuser.language,
813 'last_activity': dbuser.last_activity,
813 'last_activity': dbuser.last_activity,
814 'last_login': dbuser.last_login,
814 'last_login': dbuser.last_login,
815 'password': dbuser.password,
815 'password': dbuser.password,
816 }
816 }
817 auth_user.__dict__.update(attrs)
817 auth_user.__dict__.update(attrs)
818 except Exception:
818 except Exception:
819 log.error(traceback.format_exc())
819 log.error(traceback.format_exc())
820 auth_user.is_authenticated = False
820 auth_user.is_authenticated = False
821 return False
821 return False
822
822
823 return True
823 return True
824
824
825 def has_perm(self, user, perm):
825 def has_perm(self, user, perm):
826 perm = self._get_perm(perm)
826 perm = self._get_perm(perm)
827 user = self._get_user(user)
827 user = self._get_user(user)
828
828
829 return UserToPerm.query().filter(UserToPerm.user == user)\
829 return UserToPerm.query().filter(UserToPerm.user == user)\
830 .filter(UserToPerm.permission == perm).scalar() is not None
830 .filter(UserToPerm.permission == perm).scalar() is not None
831
831
832 def grant_perm(self, user, perm):
832 def grant_perm(self, user, perm):
833 """
833 """
834 Grant user global permissions
834 Grant user global permissions
835
835
836 :param user:
836 :param user:
837 :param perm:
837 :param perm:
838 """
838 """
839 user = self._get_user(user)
839 user = self._get_user(user)
840 perm = self._get_perm(perm)
840 perm = self._get_perm(perm)
841 # if this permission is already granted skip it
841 # if this permission is already granted skip it
842 _perm = UserToPerm.query()\
842 _perm = UserToPerm.query()\
843 .filter(UserToPerm.user == user)\
843 .filter(UserToPerm.user == user)\
844 .filter(UserToPerm.permission == perm)\
844 .filter(UserToPerm.permission == perm)\
845 .scalar()
845 .scalar()
846 if _perm:
846 if _perm:
847 return
847 return
848 new = UserToPerm()
848 new = UserToPerm()
849 new.user = user
849 new.user = user
850 new.permission = perm
850 new.permission = perm
851 self.sa.add(new)
851 self.sa.add(new)
852 return new
852 return new
853
853
854 def revoke_perm(self, user, perm):
854 def revoke_perm(self, user, perm):
855 """
855 """
856 Revoke users global permissions
856 Revoke users global permissions
857
857
858 :param user:
858 :param user:
859 :param perm:
859 :param perm:
860 """
860 """
861 user = self._get_user(user)
861 user = self._get_user(user)
862 perm = self._get_perm(perm)
862 perm = self._get_perm(perm)
863
863
864 obj = UserToPerm.query()\
864 obj = UserToPerm.query()\
865 .filter(UserToPerm.user == user)\
865 .filter(UserToPerm.user == user)\
866 .filter(UserToPerm.permission == perm)\
866 .filter(UserToPerm.permission == perm)\
867 .scalar()
867 .scalar()
868 if obj:
868 if obj:
869 self.sa.delete(obj)
869 self.sa.delete(obj)
870
870
871 def add_extra_email(self, user, email):
871 def add_extra_email(self, user, email):
872 """
872 """
873 Adds email address to UserEmailMap
873 Adds email address to UserEmailMap
874
874
875 :param user:
875 :param user:
876 :param email:
876 :param email:
877 """
877 """
878
878
879 user = self._get_user(user)
879 user = self._get_user(user)
880
880
881 obj = UserEmailMap()
881 obj = UserEmailMap()
882 obj.user = user
882 obj.user = user
883 obj.email = email
883 obj.email = email
884 self.sa.add(obj)
884 self.sa.add(obj)
885 return obj
885 return obj
886
886
887 def delete_extra_email(self, user, email_id):
887 def delete_extra_email(self, user, email_id):
888 """
888 """
889 Removes email address from UserEmailMap
889 Removes email address from UserEmailMap
890
890
891 :param user:
891 :param user:
892 :param email_id:
892 :param email_id:
893 """
893 """
894 user = self._get_user(user)
894 user = self._get_user(user)
895 obj = UserEmailMap.query().get(email_id)
895 obj = UserEmailMap.query().get(email_id)
896 if obj and obj.user_id == user.user_id:
896 if obj and obj.user_id == user.user_id:
897 self.sa.delete(obj)
897 self.sa.delete(obj)
898
898
899 def parse_ip_range(self, ip_range):
899 def parse_ip_range(self, ip_range):
900 ip_list = []
900 ip_list = []
901
901
902 def make_unique(value):
902 def make_unique(value):
903 seen = []
903 seen = []
904 return [c for c in value if not (c in seen or seen.append(c))]
904 return [c for c in value if not (c in seen or seen.append(c))]
905
905
906 # firsts split by commas
906 # firsts split by commas
907 for ip_range in ip_range.split(','):
907 for ip_range in ip_range.split(','):
908 if not ip_range:
908 if not ip_range:
909 continue
909 continue
910 ip_range = ip_range.strip()
910 ip_range = ip_range.strip()
911 if '-' in ip_range:
911 if '-' in ip_range:
912 start_ip, end_ip = ip_range.split('-', 1)
912 start_ip, end_ip = ip_range.split('-', 1)
913 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
913 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
914 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
914 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
915 parsed_ip_range = []
915 parsed_ip_range = []
916
916
917 for index in range(int(start_ip), int(end_ip) + 1):
917 for index in range(int(start_ip), int(end_ip) + 1):
918 new_ip = ipaddress.ip_address(index)
918 new_ip = ipaddress.ip_address(index)
919 parsed_ip_range.append(str(new_ip))
919 parsed_ip_range.append(str(new_ip))
920 ip_list.extend(parsed_ip_range)
920 ip_list.extend(parsed_ip_range)
921 else:
921 else:
922 ip_list.append(ip_range)
922 ip_list.append(ip_range)
923
923
924 return make_unique(ip_list)
924 return make_unique(ip_list)
925
925
926 def add_extra_ip(self, user, ip, description=None):
926 def add_extra_ip(self, user, ip, description=None):
927 """
927 """
928 Adds ip address to UserIpMap
928 Adds ip address to UserIpMap
929
929
930 :param user:
930 :param user:
931 :param ip:
931 :param ip:
932 """
932 """
933
933
934 user = self._get_user(user)
934 user = self._get_user(user)
935 obj = UserIpMap()
935 obj = UserIpMap()
936 obj.user = user
936 obj.user = user
937 obj.ip_addr = ip
937 obj.ip_addr = ip
938 obj.description = description
938 obj.description = description
939 self.sa.add(obj)
939 self.sa.add(obj)
940 return obj
940 return obj
941
941
942 auth_token_role = AuthTokenModel.cls
942 auth_token_role = AuthTokenModel.cls
943
943
944 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
944 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
945 scope_callback=None):
945 scope_callback=None):
946 """
946 """
947 Add AuthToken for user.
947 Add AuthToken for user.
948
948
949 :param user: username/user_id
949 :param user: username/user_id
950 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
950 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
951 :param role: one of AuthTokenModel.cls.ROLE_*
951 :param role: one of AuthTokenModel.cls.ROLE_*
952 :param description: optional string description
952 :param description: optional string description
953 """
953 """
954
954
955 token = AuthTokenModel().create(
955 token = AuthTokenModel().create(
956 user, description, lifetime_minutes, role)
956 user, description, lifetime_minutes, role)
957 if scope_callback and callable(scope_callback):
957 if scope_callback and callable(scope_callback):
958 # call the callback if we provide, used to attach scope for EE edition
958 # call the callback if we provide, used to attach scope for EE edition
959 scope_callback(token)
959 scope_callback(token)
960 return token
960 return token
961
961
962 def delete_extra_ip(self, user, ip_id):
962 def delete_extra_ip(self, user, ip_id):
963 """
963 """
964 Removes ip address from UserIpMap
964 Removes ip address from UserIpMap
965
965
966 :param user:
966 :param user:
967 :param ip_id:
967 :param ip_id:
968 """
968 """
969 user = self._get_user(user)
969 user = self._get_user(user)
970 obj = UserIpMap.query().get(ip_id)
970 obj = UserIpMap.query().get(ip_id)
971 if obj and obj.user_id == user.user_id:
971 if obj and obj.user_id == user.user_id:
972 self.sa.delete(obj)
972 self.sa.delete(obj)
973
973
974 def get_accounts_in_creation_order(self, current_user=None):
974 def get_accounts_in_creation_order(self, current_user=None):
975 """
975 """
976 Get accounts in order of creation for deactivation for license limits
976 Get accounts in order of creation for deactivation for license limits
977
977
978 pick currently logged in user, and append to the list in position 0
978 pick currently logged in user, and append to the list in position 0
979 pick all super-admins in order of creation date and add it to the list
979 pick all super-admins in order of creation date and add it to the list
980 pick all other accounts in order of creation and add it to the list.
980 pick all other accounts in order of creation and add it to the list.
981
981
982 Based on that list, the last accounts can be disabled as they are
982 Based on that list, the last accounts can be disabled as they are
983 created at the end and don't include any of the super admins as well
983 created at the end and don't include any of the super admins as well
984 as the current user.
984 as the current user.
985
985
986 :param current_user: optionally current user running this operation
986 :param current_user: optionally current user running this operation
987 """
987 """
988
988
989 if not current_user:
989 if not current_user:
990 current_user = get_current_rhodecode_user()
990 current_user = get_current_rhodecode_user()
991 active_super_admins = [
991 active_super_admins = [
992 x.user_id for x in User.query()
992 x.user_id for x in User.query()
993 .filter(User.user_id != current_user.user_id)
993 .filter(User.user_id != current_user.user_id)
994 .filter(User.active == true())
994 .filter(User.active == true())
995 .filter(User.admin == true())
995 .filter(User.admin == true())
996 .order_by(User.created_on.asc())]
996 .order_by(User.created_on.asc())]
997
997
998 active_regular_users = [
998 active_regular_users = [
999 x.user_id for x in User.query()
999 x.user_id for x in User.query()
1000 .filter(User.user_id != current_user.user_id)
1000 .filter(User.user_id != current_user.user_id)
1001 .filter(User.active == true())
1001 .filter(User.active == true())
1002 .filter(User.admin == false())
1002 .filter(User.admin == false())
1003 .order_by(User.created_on.asc())]
1003 .order_by(User.created_on.asc())]
1004
1004
1005 list_of_accounts = [current_user.user_id]
1005 list_of_accounts = [current_user.user_id]
1006 list_of_accounts += active_super_admins
1006 list_of_accounts += active_super_admins
1007 list_of_accounts += active_regular_users
1007 list_of_accounts += active_regular_users
1008
1008
1009 return list_of_accounts
1009 return list_of_accounts
1010
1010
1011 def deactivate_last_users(self, expected_users, current_user=None):
1011 def deactivate_last_users(self, expected_users, current_user=None):
1012 """
1012 """
1013 Deactivate accounts that are over the license limits.
1013 Deactivate accounts that are over the license limits.
1014 Algorithm of which accounts to disabled is based on the formula:
1014 Algorithm of which accounts to disabled is based on the formula:
1015
1015
1016 Get current user, then super admins in creation order, then regular
1016 Get current user, then super admins in creation order, then regular
1017 active users in creation order.
1017 active users in creation order.
1018
1018
1019 Using that list we mark all accounts from the end of it as inactive.
1019 Using that list we mark all accounts from the end of it as inactive.
1020 This way we block only latest created accounts.
1020 This way we block only latest created accounts.
1021
1021
1022 :param expected_users: list of users in special order, we deactivate
1022 :param expected_users: list of users in special order, we deactivate
1023 the end N amount of users from that list
1023 the end N amount of users from that list
1024 """
1024 """
1025
1025
1026 list_of_accounts = self.get_accounts_in_creation_order(
1026 list_of_accounts = self.get_accounts_in_creation_order(
1027 current_user=current_user)
1027 current_user=current_user)
1028
1028
1029 for acc_id in list_of_accounts[expected_users + 1:]:
1029 for acc_id in list_of_accounts[expected_users + 1:]:
1030 user = User.get(acc_id)
1030 user = User.get(acc_id)
1031 log.info('Deactivating account %s for license unlock', user)
1031 log.info('Deactivating account %s for license unlock', user)
1032 user.active = False
1032 user.active = False
1033 Session().add(user)
1033 Session().add(user)
1034 Session().commit()
1034 Session().commit()
1035
1035
1036 return
1036 return
1037
1037
1038 def get_user_log(self, user, filter_term):
1038 def get_user_log(self, user, filter_term):
1039 user_log = UserLog.query()\
1039 user_log = UserLog.query()\
1040 .filter(or_(UserLog.user_id == user.user_id,
1040 .filter(or_(UserLog.user_id == user.user_id,
1041 UserLog.username == user.username))\
1041 UserLog.username == user.username))\
1042 .options(joinedload(UserLog.user))\
1042 .options(joinedload(UserLog.user))\
1043 .options(joinedload(UserLog.repository))\
1043 .options(joinedload(UserLog.repository))\
1044 .order_by(UserLog.action_date.desc())
1044 .order_by(UserLog.action_date.desc())
1045
1045
1046 user_log = user_log_filter(user_log, filter_term)
1046 user_log = user_log_filter(user_log, filter_term)
1047 return user_log
1047 return user_log
General Comments 0
You need to be logged in to leave comments. Login now