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