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