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