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