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