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