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