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