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