##// END OF EJS Templates
python3: removed usage of .iteritems()
super-admin -
r4932:457fc374 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,171 +1,171 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import csv
23 23 import datetime
24 24
25 25 import pytest
26 26
27 27 from rhodecode.tests import *
28 28 from rhodecode.tests.fixture import FIXTURES
29 29 from rhodecode.model.db import UserLog
30 30 from rhodecode.model.meta import Session
31 31 from rhodecode.lib.utils2 import safe_unicode
32 32
33 33
34 34 def route_path(name, params=None, **kwargs):
35 35 import urllib.request, urllib.parse, urllib.error
36 36 from rhodecode.apps._base import ADMIN_PREFIX
37 37
38 38 base_url = {
39 39 'admin_home': ADMIN_PREFIX,
40 40 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs',
41 41
42 42 }[name].format(**kwargs)
43 43
44 44 if params:
45 45 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
46 46 return base_url
47 47
48 48
49 49 @pytest.mark.usefixtures('app')
50 50 class TestAdminController(object):
51 51
52 52 @pytest.fixture(scope='class', autouse=True)
53 53 def prepare(self, request, baseapp):
54 54 UserLog.query().delete()
55 55 Session().commit()
56 56
57 57 def strptime(val):
58 58 fmt = '%Y-%m-%d %H:%M:%S'
59 59 if '.' not in val:
60 60 return datetime.datetime.strptime(val, fmt)
61 61
62 62 nofrag, frag = val.split(".")
63 63 date = datetime.datetime.strptime(nofrag, fmt)
64 64
65 65 frag = frag[:6] # truncate to microseconds
66 66 frag += (6 - len(frag)) * '0' # add 0s
67 67 return date.replace(microsecond=int(frag))
68 68
69 69 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
70 70 for row in csv.DictReader(f):
71 71 ul = UserLog()
72 for k, v in row.iteritems():
72 for k, v in row.items():
73 73 v = safe_unicode(v)
74 74 if k == 'action_date':
75 75 v = strptime(v)
76 76 if k in ['user_id', 'repository_id']:
77 77 # nullable due to FK problems
78 78 v = None
79 79 setattr(ul, k, v)
80 80 Session().add(ul)
81 81 Session().commit()
82 82
83 83 @request.addfinalizer
84 84 def cleanup():
85 85 UserLog.query().delete()
86 86 Session().commit()
87 87
88 88 def test_index(self, autologin_user):
89 89 response = self.app.get(route_path('admin_audit_logs'))
90 90 response.mustcontain('Admin audit logs')
91 91
92 92 def test_filter_all_entries(self, autologin_user):
93 93 response = self.app.get(route_path('admin_audit_logs'))
94 94 all_count = UserLog.query().count()
95 95 response.mustcontain('%s entries' % all_count)
96 96
97 97 def test_filter_journal_filter_exact_match_on_repository(self, autologin_user):
98 98 response = self.app.get(route_path('admin_audit_logs',
99 99 params=dict(filter='repository:rhodecode')))
100 100 response.mustcontain('3 entries')
101 101
102 102 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self, autologin_user):
103 103 response = self.app.get(route_path('admin_audit_logs',
104 104 params=dict(filter='repository:RhodeCode')))
105 105 response.mustcontain('3 entries')
106 106
107 107 def test_filter_journal_filter_wildcard_on_repository(self, autologin_user):
108 108 response = self.app.get(route_path('admin_audit_logs',
109 109 params=dict(filter='repository:*test*')))
110 110 response.mustcontain('862 entries')
111 111
112 112 def test_filter_journal_filter_prefix_on_repository(self, autologin_user):
113 113 response = self.app.get(route_path('admin_audit_logs',
114 114 params=dict(filter='repository:test*')))
115 115 response.mustcontain('257 entries')
116 116
117 117 def test_filter_journal_filter_prefix_on_repository_CamelCase(self, autologin_user):
118 118 response = self.app.get(route_path('admin_audit_logs',
119 119 params=dict(filter='repository:Test*')))
120 120 response.mustcontain('257 entries')
121 121
122 122 def test_filter_journal_filter_prefix_on_repository_and_user(self, autologin_user):
123 123 response = self.app.get(route_path('admin_audit_logs',
124 124 params=dict(filter='repository:test* AND username:demo')))
125 125 response.mustcontain('130 entries')
126 126
127 127 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self, autologin_user):
128 128 response = self.app.get(route_path('admin_audit_logs',
129 129 params=dict(filter='repository:test* OR repository:rhodecode')))
130 130 response.mustcontain('260 entries') # 257 + 3
131 131
132 132 def test_filter_journal_filter_exact_match_on_username(self, autologin_user):
133 133 response = self.app.get(route_path('admin_audit_logs',
134 134 params=dict(filter='username:demo')))
135 135 response.mustcontain('1087 entries')
136 136
137 137 def test_filter_journal_filter_exact_match_on_username_camelCase(self, autologin_user):
138 138 response = self.app.get(route_path('admin_audit_logs',
139 139 params=dict(filter='username:DemO')))
140 140 response.mustcontain('1087 entries')
141 141
142 142 def test_filter_journal_filter_wildcard_on_username(self, autologin_user):
143 143 response = self.app.get(route_path('admin_audit_logs',
144 144 params=dict(filter='username:*test*')))
145 145 entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count()
146 146 response.mustcontain('{} entries'.format(entries_count))
147 147
148 148 def test_filter_journal_filter_prefix_on_username(self, autologin_user):
149 149 response = self.app.get(route_path('admin_audit_logs',
150 150 params=dict(filter='username:demo*')))
151 151 response.mustcontain('1101 entries')
152 152
153 153 def test_filter_journal_filter_prefix_on_user_or_other_user(self, autologin_user):
154 154 response = self.app.get(route_path('admin_audit_logs',
155 155 params=dict(filter='username:demo OR username:volcan')))
156 156 response.mustcontain('1095 entries') # 1087 + 8
157 157
158 158 def test_filter_journal_filter_wildcard_on_action(self, autologin_user):
159 159 response = self.app.get(route_path('admin_audit_logs',
160 160 params=dict(filter='action:*pull_request*')))
161 161 response.mustcontain('187 entries')
162 162
163 163 def test_filter_journal_filter_on_date(self, autologin_user):
164 164 response = self.app.get(route_path('admin_audit_logs',
165 165 params=dict(filter='date:20121010')))
166 166 response.mustcontain('47 entries')
167 167
168 168 def test_filter_journal_filter_on_date_2(self, autologin_user):
169 169 response = self.app.get(route_path('admin_audit_logs',
170 170 params=dict(filter='date:20121020')))
171 171 response.mustcontain('17 entries')
@@ -1,767 +1,767 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.apps._base import ADMIN_PREFIX
26 26 from rhodecode.lib.utils2 import md5
27 27 from rhodecode.model.db import RhodeCodeUi
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 30 from rhodecode.tests import assert_session_flash
31 31 from rhodecode.tests.utils import AssertResponse
32 32
33 33
34 34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
35 35
36 36
37 37 def route_path(name, params=None, **kwargs):
38 38 import urllib.request, urllib.parse, urllib.error
39 39 from rhodecode.apps._base import ADMIN_PREFIX
40 40
41 41 base_url = {
42 42
43 43 'admin_settings':
44 44 ADMIN_PREFIX +'/settings',
45 45 'admin_settings_update':
46 46 ADMIN_PREFIX + '/settings/update',
47 47 'admin_settings_global':
48 48 ADMIN_PREFIX + '/settings/global',
49 49 'admin_settings_global_update':
50 50 ADMIN_PREFIX + '/settings/global/update',
51 51 'admin_settings_vcs':
52 52 ADMIN_PREFIX + '/settings/vcs',
53 53 'admin_settings_vcs_update':
54 54 ADMIN_PREFIX + '/settings/vcs/update',
55 55 'admin_settings_vcs_svn_pattern_delete':
56 56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 57 'admin_settings_mapping':
58 58 ADMIN_PREFIX + '/settings/mapping',
59 59 'admin_settings_mapping_update':
60 60 ADMIN_PREFIX + '/settings/mapping/update',
61 61 'admin_settings_visual':
62 62 ADMIN_PREFIX + '/settings/visual',
63 63 'admin_settings_visual_update':
64 64 ADMIN_PREFIX + '/settings/visual/update',
65 65 'admin_settings_issuetracker':
66 66 ADMIN_PREFIX + '/settings/issue-tracker',
67 67 'admin_settings_issuetracker_update':
68 68 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 69 'admin_settings_issuetracker_test':
70 70 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 71 'admin_settings_issuetracker_delete':
72 72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 73 'admin_settings_email':
74 74 ADMIN_PREFIX + '/settings/email',
75 75 'admin_settings_email_update':
76 76 ADMIN_PREFIX + '/settings/email/update',
77 77 'admin_settings_hooks':
78 78 ADMIN_PREFIX + '/settings/hooks',
79 79 'admin_settings_hooks_update':
80 80 ADMIN_PREFIX + '/settings/hooks/update',
81 81 'admin_settings_hooks_delete':
82 82 ADMIN_PREFIX + '/settings/hooks/delete',
83 83 'admin_settings_search':
84 84 ADMIN_PREFIX + '/settings/search',
85 85 'admin_settings_labs':
86 86 ADMIN_PREFIX + '/settings/labs',
87 87 'admin_settings_labs_update':
88 88 ADMIN_PREFIX + '/settings/labs/update',
89 89
90 90 'admin_settings_sessions':
91 91 ADMIN_PREFIX + '/settings/sessions',
92 92 'admin_settings_sessions_cleanup':
93 93 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 94 'admin_settings_system':
95 95 ADMIN_PREFIX + '/settings/system',
96 96 'admin_settings_system_update':
97 97 ADMIN_PREFIX + '/settings/system/updates',
98 98 'admin_settings_open_source':
99 99 ADMIN_PREFIX + '/settings/open_source',
100 100
101 101
102 102 }[name].format(**kwargs)
103 103
104 104 if params:
105 105 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
106 106 return base_url
107 107
108 108
109 109 @pytest.mark.usefixtures('autologin_user', 'app')
110 110 class TestAdminSettingsController(object):
111 111
112 112 @pytest.mark.parametrize('urlname', [
113 113 'admin_settings_vcs',
114 114 'admin_settings_mapping',
115 115 'admin_settings_global',
116 116 'admin_settings_visual',
117 117 'admin_settings_email',
118 118 'admin_settings_hooks',
119 119 'admin_settings_search',
120 120 ])
121 121 def test_simple_get(self, urlname):
122 122 self.app.get(route_path(urlname))
123 123
124 124 def test_create_custom_hook(self, csrf_token):
125 125 response = self.app.post(
126 126 route_path('admin_settings_hooks_update'),
127 127 params={
128 128 'new_hook_ui_key': 'test_hooks_1',
129 129 'new_hook_ui_value': 'cd /tmp',
130 130 'csrf_token': csrf_token})
131 131
132 132 response = response.follow()
133 133 response.mustcontain('test_hooks_1')
134 134 response.mustcontain('cd /tmp')
135 135
136 136 def test_create_custom_hook_delete(self, csrf_token):
137 137 response = self.app.post(
138 138 route_path('admin_settings_hooks_update'),
139 139 params={
140 140 'new_hook_ui_key': 'test_hooks_2',
141 141 'new_hook_ui_value': 'cd /tmp2',
142 142 'csrf_token': csrf_token})
143 143
144 144 response = response.follow()
145 145 response.mustcontain('test_hooks_2')
146 146 response.mustcontain('cd /tmp2')
147 147
148 148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149 149
150 150 # delete
151 151 self.app.post(
152 152 route_path('admin_settings_hooks_delete'),
153 153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 154 response = self.app.get(route_path('admin_settings_hooks'))
155 155 response.mustcontain(no=['test_hooks_2'])
156 156 response.mustcontain(no=['cd /tmp2'])
157 157
158 158
159 159 @pytest.mark.usefixtures('autologin_user', 'app')
160 160 class TestAdminSettingsGlobal(object):
161 161
162 162 def test_pre_post_code_code_active(self, csrf_token):
163 163 pre_code = 'rc-pre-code-187652122'
164 164 post_code = 'rc-postcode-98165231'
165 165
166 166 response = self.post_and_verify_settings({
167 167 'rhodecode_pre_code': pre_code,
168 168 'rhodecode_post_code': post_code,
169 169 'csrf_token': csrf_token,
170 170 })
171 171
172 172 response = response.follow()
173 173 response.mustcontain(pre_code, post_code)
174 174
175 175 def test_pre_post_code_code_inactive(self, csrf_token):
176 176 pre_code = 'rc-pre-code-187652122'
177 177 post_code = 'rc-postcode-98165231'
178 178 response = self.post_and_verify_settings({
179 179 'rhodecode_pre_code': '',
180 180 'rhodecode_post_code': '',
181 181 'csrf_token': csrf_token,
182 182 })
183 183
184 184 response = response.follow()
185 185 response.mustcontain(no=[pre_code, post_code])
186 186
187 187 def test_captcha_activate(self, csrf_token):
188 188 self.post_and_verify_settings({
189 189 'rhodecode_captcha_private_key': '1234567890',
190 190 'rhodecode_captcha_public_key': '1234567890',
191 191 'csrf_token': csrf_token,
192 192 })
193 193
194 194 response = self.app.get(ADMIN_PREFIX + '/register')
195 195 response.mustcontain('captcha')
196 196
197 197 def test_captcha_deactivate(self, csrf_token):
198 198 self.post_and_verify_settings({
199 199 'rhodecode_captcha_private_key': '',
200 200 'rhodecode_captcha_public_key': '1234567890',
201 201 'csrf_token': csrf_token,
202 202 })
203 203
204 204 response = self.app.get(ADMIN_PREFIX + '/register')
205 205 response.mustcontain(no=['captcha'])
206 206
207 207 def test_title_change(self, csrf_token):
208 208 old_title = 'RhodeCode'
209 209
210 210 for new_title in ['Changed', 'Żółwik', old_title]:
211 211 response = self.post_and_verify_settings({
212 212 'rhodecode_title': new_title,
213 213 'csrf_token': csrf_token,
214 214 })
215 215
216 216 response = response.follow()
217 217 response.mustcontain(new_title)
218 218
219 219 def post_and_verify_settings(self, settings):
220 220 old_title = 'RhodeCode'
221 221 old_realm = 'RhodeCode authentication'
222 222 params = {
223 223 'rhodecode_title': old_title,
224 224 'rhodecode_realm': old_realm,
225 225 'rhodecode_pre_code': '',
226 226 'rhodecode_post_code': '',
227 227 'rhodecode_captcha_private_key': '',
228 228 'rhodecode_captcha_public_key': '',
229 229 'rhodecode_create_personal_repo_group': False,
230 230 'rhodecode_personal_repo_group_pattern': '${username}',
231 231 }
232 232 params.update(settings)
233 233 response = self.app.post(
234 234 route_path('admin_settings_global_update'), params=params)
235 235
236 236 assert_session_flash(response, 'Updated application settings')
237 237 app_settings = SettingsModel().get_all_settings()
238 238 del settings['csrf_token']
239 for key, value in settings.iteritems():
239 for key, value in settings.items():
240 240 assert app_settings[key] == value.decode('utf-8')
241 241
242 242 return response
243 243
244 244
245 245 @pytest.mark.usefixtures('autologin_user', 'app')
246 246 class TestAdminSettingsVcs(object):
247 247
248 248 def test_contains_svn_default_patterns(self):
249 249 response = self.app.get(route_path('admin_settings_vcs'))
250 250 expected_patterns = [
251 251 '/trunk',
252 252 '/branches/*',
253 253 '/tags/*',
254 254 ]
255 255 for pattern in expected_patterns:
256 256 response.mustcontain(pattern)
257 257
258 258 def test_add_new_svn_branch_and_tag_pattern(
259 259 self, backend_svn, form_defaults, disable_sql_cache,
260 260 csrf_token):
261 261 form_defaults.update({
262 262 'new_svn_branch': '/exp/branches/*',
263 263 'new_svn_tag': '/important_tags/*',
264 264 'csrf_token': csrf_token,
265 265 })
266 266
267 267 response = self.app.post(
268 268 route_path('admin_settings_vcs_update'),
269 269 params=form_defaults, status=302)
270 270 response = response.follow()
271 271
272 272 # Expect to find the new values on the page
273 273 response.mustcontain('/exp/branches/*')
274 274 response.mustcontain('/important_tags/*')
275 275
276 276 # Expect that those patterns are used to match branches and tags now
277 277 repo = backend_svn['svn-simple-layout'].scm_instance()
278 278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
279 279 assert 'important_tags/v0.5' in repo.tags
280 280
281 281 def test_add_same_svn_value_twice_shows_an_error_message(
282 282 self, form_defaults, csrf_token, settings_util):
283 283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
284 284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
285 285
286 286 response = self.app.post(
287 287 route_path('admin_settings_vcs_update'),
288 288 params={
289 289 'paths_root_path': form_defaults['paths_root_path'],
290 290 'new_svn_branch': '/test',
291 291 'new_svn_tag': '/test',
292 292 'csrf_token': csrf_token,
293 293 },
294 294 status=200)
295 295
296 296 response.mustcontain("Pattern already exists")
297 297 response.mustcontain("Some form inputs contain invalid data.")
298 298
299 299 @pytest.mark.parametrize('section', [
300 300 'vcs_svn_branch',
301 301 'vcs_svn_tag',
302 302 ])
303 303 def test_delete_svn_patterns(
304 304 self, section, csrf_token, settings_util):
305 305 setting = settings_util.create_rhodecode_ui(
306 306 section, '/test_delete', cleanup=False)
307 307
308 308 self.app.post(
309 309 route_path('admin_settings_vcs_svn_pattern_delete'),
310 310 params={
311 311 'delete_svn_pattern': setting.ui_id,
312 312 'csrf_token': csrf_token},
313 313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
314 314
315 315 @pytest.mark.parametrize('section', [
316 316 'vcs_svn_branch',
317 317 'vcs_svn_tag',
318 318 ])
319 319 def test_delete_svn_patterns_raises_404_when_no_xhr(
320 320 self, section, csrf_token, settings_util):
321 321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
322 322
323 323 self.app.post(
324 324 route_path('admin_settings_vcs_svn_pattern_delete'),
325 325 params={
326 326 'delete_svn_pattern': setting.ui_id,
327 327 'csrf_token': csrf_token},
328 328 status=404)
329 329
330 330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
331 331 form_defaults.update({
332 332 'csrf_token': csrf_token,
333 333 'extensions_hgsubversion': 'True',
334 334 })
335 335 response = self.app.post(
336 336 route_path('admin_settings_vcs_update'),
337 337 params=form_defaults,
338 338 status=302)
339 339
340 340 response = response.follow()
341 341 extensions_input = (
342 342 '<input id="extensions_hgsubversion" '
343 343 'name="extensions_hgsubversion" type="checkbox" '
344 344 'value="True" checked="checked" />')
345 345 response.mustcontain(extensions_input)
346 346
347 347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
348 348 form_defaults.update({
349 349 'csrf_token': csrf_token,
350 350 'extensions_evolve': 'True',
351 351 })
352 352 response = self.app.post(
353 353 route_path('admin_settings_vcs_update'),
354 354 params=form_defaults,
355 355 status=302)
356 356
357 357 response = response.follow()
358 358 extensions_input = (
359 359 '<input id="extensions_evolve" '
360 360 'name="extensions_evolve" type="checkbox" '
361 361 'value="True" checked="checked" />')
362 362 response.mustcontain(extensions_input)
363 363
364 364 def test_has_a_section_for_pull_request_settings(self):
365 365 response = self.app.get(route_path('admin_settings_vcs'))
366 366 response.mustcontain('Pull Request Settings')
367 367
368 368 def test_has_an_input_for_invalidation_of_inline_comments(self):
369 369 response = self.app.get(route_path('admin_settings_vcs'))
370 370 assert_response = response.assert_response()
371 371 assert_response.one_element_exists(
372 372 '[name=rhodecode_use_outdated_comments]')
373 373
374 374 @pytest.mark.parametrize('new_value', [True, False])
375 375 def test_allows_to_change_invalidation_of_inline_comments(
376 376 self, form_defaults, csrf_token, new_value):
377 377 setting_key = 'use_outdated_comments'
378 378 setting = SettingsModel().create_or_update_setting(
379 379 setting_key, not new_value, 'bool')
380 380 Session().add(setting)
381 381 Session().commit()
382 382
383 383 form_defaults.update({
384 384 'csrf_token': csrf_token,
385 385 'rhodecode_use_outdated_comments': str(new_value),
386 386 })
387 387 response = self.app.post(
388 388 route_path('admin_settings_vcs_update'),
389 389 params=form_defaults,
390 390 status=302)
391 391 response = response.follow()
392 392 setting = SettingsModel().get_setting_by_name(setting_key)
393 393 assert setting.app_settings_value is new_value
394 394
395 395 @pytest.mark.parametrize('new_value', [True, False])
396 396 def test_allows_to_change_hg_rebase_merge_strategy(
397 397 self, form_defaults, csrf_token, new_value):
398 398 setting_key = 'hg_use_rebase_for_merging'
399 399
400 400 form_defaults.update({
401 401 'csrf_token': csrf_token,
402 402 'rhodecode_' + setting_key: str(new_value),
403 403 })
404 404
405 405 with mock.patch.dict(
406 406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
407 407 self.app.post(
408 408 route_path('admin_settings_vcs_update'),
409 409 params=form_defaults,
410 410 status=302)
411 411
412 412 setting = SettingsModel().get_setting_by_name(setting_key)
413 413 assert setting.app_settings_value is new_value
414 414
415 415 @pytest.fixture()
416 416 def disable_sql_cache(self, request):
417 417 patcher = mock.patch(
418 418 'rhodecode.lib.caching_query.FromCache.process_query')
419 419 request.addfinalizer(patcher.stop)
420 420 patcher.start()
421 421
422 422 @pytest.fixture()
423 423 def form_defaults(self):
424 424 from rhodecode.apps.admin.views.settings import AdminSettingsView
425 425 return AdminSettingsView._form_defaults()
426 426
427 427 # TODO: johbo: What we really want is to checkpoint before a test run and
428 428 # reset the session afterwards.
429 429 @pytest.fixture(scope='class', autouse=True)
430 430 def cleanup_settings(self, request, baseapp):
431 431 ui_id = RhodeCodeUi.ui_id
432 432 original_ids = list(
433 433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
434 434
435 435 @request.addfinalizer
436 436 def cleanup():
437 437 RhodeCodeUi.query().filter(
438 438 ui_id.notin_(original_ids)).delete(False)
439 439
440 440
441 441 @pytest.mark.usefixtures('autologin_user', 'app')
442 442 class TestLabsSettings(object):
443 443 def test_get_settings_page_disabled(self):
444 444 with mock.patch.dict(
445 445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
446 446
447 447 response = self.app.get(
448 448 route_path('admin_settings_labs'), status=302)
449 449
450 450 assert response.location.endswith(route_path('admin_settings'))
451 451
452 452 def test_get_settings_page_enabled(self):
453 453 from rhodecode.apps.admin.views import settings
454 454 lab_settings = [
455 455 settings.LabSetting(
456 456 key='rhodecode_bool',
457 457 type='bool',
458 458 group='bool group',
459 459 label='bool label',
460 460 help='bool help'
461 461 ),
462 462 settings.LabSetting(
463 463 key='rhodecode_text',
464 464 type='unicode',
465 465 group='text group',
466 466 label='text label',
467 467 help='text help'
468 468 ),
469 469 ]
470 470 with mock.patch.dict(rhodecode.CONFIG,
471 471 {'labs_settings_active': 'true'}):
472 472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
473 473 response = self.app.get(route_path('admin_settings_labs'))
474 474
475 475 assert '<label>bool group:</label>' in response
476 476 assert '<label for="rhodecode_bool">bool label</label>' in response
477 477 assert '<p class="help-block">bool help</p>' in response
478 478 assert 'name="rhodecode_bool" type="checkbox"' in response
479 479
480 480 assert '<label>text group:</label>' in response
481 481 assert '<label for="rhodecode_text">text label</label>' in response
482 482 assert '<p class="help-block">text help</p>' in response
483 483 assert 'name="rhodecode_text" size="60" type="text"' in response
484 484
485 485
486 486 @pytest.mark.usefixtures('app')
487 487 class TestOpenSourceLicenses(object):
488 488
489 489 def test_records_are_displayed(self, autologin_user):
490 490 sample_licenses = [
491 491 {
492 492 "license": [
493 493 {
494 494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
495 495 "shortName": "bsdOriginal",
496 496 "spdxId": "BSD-4-Clause",
497 497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
498 498 }
499 499 ],
500 500 "name": "python2.7-coverage-3.7.1"
501 501 },
502 502 {
503 503 "license": [
504 504 {
505 505 "fullName": "MIT License",
506 506 "shortName": "mit",
507 507 "spdxId": "MIT",
508 508 "url": "http://spdx.org/licenses/MIT.html"
509 509 }
510 510 ],
511 511 "name": "python2.7-bootstrapped-pip-9.0.1"
512 512 },
513 513 ]
514 514 read_licenses_patch = mock.patch(
515 515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
516 516 return_value=sample_licenses)
517 517 with read_licenses_patch:
518 518 response = self.app.get(
519 519 route_path('admin_settings_open_source'), status=200)
520 520
521 521 assert_response = response.assert_response()
522 522 assert_response.element_contains(
523 523 '.panel-heading', 'Licenses of Third Party Packages')
524 524 for license_data in sample_licenses:
525 525 response.mustcontain(license_data["license"][0]["spdxId"])
526 526 assert_response.element_contains('.panel-body', license_data["name"])
527 527
528 528 def test_records_can_be_read(self, autologin_user):
529 529 response = self.app.get(
530 530 route_path('admin_settings_open_source'), status=200)
531 531 assert_response = response.assert_response()
532 532 assert_response.element_contains(
533 533 '.panel-heading', 'Licenses of Third Party Packages')
534 534
535 535 def test_forbidden_when_normal_user(self, autologin_regular_user):
536 536 self.app.get(
537 537 route_path('admin_settings_open_source'), status=404)
538 538
539 539
540 540 @pytest.mark.usefixtures('app')
541 541 class TestUserSessions(object):
542 542
543 543 def test_forbidden_when_normal_user(self, autologin_regular_user):
544 544 self.app.get(route_path('admin_settings_sessions'), status=404)
545 545
546 546 def test_show_sessions_page(self, autologin_user):
547 547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
548 548 response.mustcontain('file')
549 549
550 550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
551 551
552 552 post_data = {
553 553 'csrf_token': csrf_token,
554 554 'expire_days': '60'
555 555 }
556 556 response = self.app.post(
557 557 route_path('admin_settings_sessions_cleanup'), params=post_data,
558 558 status=302)
559 559 assert_session_flash(response, 'Cleaned up old sessions')
560 560
561 561
562 562 @pytest.mark.usefixtures('app')
563 563 class TestAdminSystemInfo(object):
564 564
565 565 def test_forbidden_when_normal_user(self, autologin_regular_user):
566 566 self.app.get(route_path('admin_settings_system'), status=404)
567 567
568 568 def test_system_info_page(self, autologin_user):
569 569 response = self.app.get(route_path('admin_settings_system'))
570 570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
571 571 rhodecode.__version__))
572 572
573 573 def test_system_update_new_version(self, autologin_user):
574 574 update_data = {
575 575 'versions': [
576 576 {
577 577 'version': '100.3.1415926535',
578 578 'general': 'The latest version we are ever going to ship'
579 579 },
580 580 {
581 581 'version': '0.0.0',
582 582 'general': 'The first version we ever shipped'
583 583 }
584 584 ]
585 585 }
586 586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 587 response = self.app.get(route_path('admin_settings_system_update'))
588 588 response.mustcontain('A <b>new version</b> is available')
589 589
590 590 def test_system_update_nothing_new(self, autologin_user):
591 591 update_data = {
592 592 'versions': [
593 593 {
594 594 'version': '0.0.0',
595 595 'general': 'The first version we ever shipped'
596 596 }
597 597 ]
598 598 }
599 599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
600 600 response = self.app.get(route_path('admin_settings_system_update'))
601 601 response.mustcontain(
602 602 'This instance is already running the <b>latest</b> stable version')
603 603
604 604 def test_system_update_bad_response(self, autologin_user):
605 605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
606 606 response = self.app.get(route_path('admin_settings_system_update'))
607 607 response.mustcontain(
608 608 'Bad data sent from update server')
609 609
610 610
611 611 @pytest.mark.usefixtures("app")
612 612 class TestAdminSettingsIssueTracker(object):
613 613 RC_PREFIX = 'rhodecode_'
614 614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
615 615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
616 616 DESC_KEY = RC_PREFIX + 'issuetracker_desc_'
617 617
618 618 def test_issuetracker_index(self, autologin_user):
619 619 response = self.app.get(route_path('admin_settings_issuetracker'))
620 620 assert response.status_code == 200
621 621
622 622 def test_add_empty_issuetracker_pattern(
623 623 self, request, autologin_user, csrf_token):
624 624 post_url = route_path('admin_settings_issuetracker_update')
625 625 post_data = {
626 626 'csrf_token': csrf_token
627 627 }
628 628 self.app.post(post_url, post_data, status=302)
629 629
630 630 def test_add_issuetracker_pattern(
631 631 self, request, autologin_user, csrf_token):
632 632 pattern = 'issuetracker_pat'
633 633 another_pattern = pattern+'1'
634 634 post_url = route_path('admin_settings_issuetracker_update')
635 635 post_data = {
636 636 'new_pattern_pattern_0': pattern,
637 637 'new_pattern_url_0': 'http://url',
638 638 'new_pattern_prefix_0': 'prefix',
639 639 'new_pattern_description_0': 'description',
640 640 'new_pattern_pattern_1': another_pattern,
641 641 'new_pattern_url_1': 'https://url1',
642 642 'new_pattern_prefix_1': 'prefix1',
643 643 'new_pattern_description_1': 'description1',
644 644 'csrf_token': csrf_token
645 645 }
646 646 self.app.post(post_url, post_data, status=302)
647 647 settings = SettingsModel().get_all_settings()
648 648 self.uid = md5(pattern)
649 649 assert settings[self.PATTERN_KEY+self.uid] == pattern
650 650 self.another_uid = md5(another_pattern)
651 651 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
652 652
653 653 @request.addfinalizer
654 654 def cleanup():
655 655 defaults = SettingsModel().get_all_settings()
656 656
657 657 entries = [name for name in defaults if (
658 658 (self.uid in name) or (self.another_uid) in name)]
659 659 start = len(self.RC_PREFIX)
660 660 for del_key in entries:
661 661 # TODO: anderson: get_by_name needs name without prefix
662 662 entry = SettingsModel().get_setting_by_name(del_key[start:])
663 663 Session().delete(entry)
664 664
665 665 Session().commit()
666 666
667 667 def test_edit_issuetracker_pattern(
668 668 self, autologin_user, backend, csrf_token, request):
669 669
670 670 old_pattern = 'issuetracker_pat1'
671 671 old_uid = md5(old_pattern)
672 672
673 673 post_url = route_path('admin_settings_issuetracker_update')
674 674 post_data = {
675 675 'new_pattern_pattern_0': old_pattern,
676 676 'new_pattern_url_0': 'http://url',
677 677 'new_pattern_prefix_0': 'prefix',
678 678 'new_pattern_description_0': 'description',
679 679
680 680 'csrf_token': csrf_token
681 681 }
682 682 self.app.post(post_url, post_data, status=302)
683 683
684 684 new_pattern = 'issuetracker_pat1_edited'
685 685 self.new_uid = md5(new_pattern)
686 686
687 687 post_url = route_path('admin_settings_issuetracker_update')
688 688 post_data = {
689 689 'new_pattern_pattern_{}'.format(old_uid): new_pattern,
690 690 'new_pattern_url_{}'.format(old_uid): 'https://url_edited',
691 691 'new_pattern_prefix_{}'.format(old_uid): 'prefix_edited',
692 692 'new_pattern_description_{}'.format(old_uid): 'description_edited',
693 693 'uid': old_uid,
694 694 'csrf_token': csrf_token
695 695 }
696 696 self.app.post(post_url, post_data, status=302)
697 697
698 698 settings = SettingsModel().get_all_settings()
699 699 assert settings[self.PATTERN_KEY+self.new_uid] == new_pattern
700 700 assert settings[self.DESC_KEY + self.new_uid] == 'description_edited'
701 701 assert self.PATTERN_KEY+old_uid not in settings
702 702
703 703 @request.addfinalizer
704 704 def cleanup():
705 705 IssueTrackerSettingsModel().delete_entries(old_uid)
706 706 IssueTrackerSettingsModel().delete_entries(self.new_uid)
707 707
708 708 def test_replace_issuetracker_pattern_description(
709 709 self, autologin_user, csrf_token, request, settings_util):
710 710 prefix = 'issuetracker'
711 711 pattern = 'issuetracker_pat'
712 712 self.uid = md5(pattern)
713 713 pattern_key = '_'.join([prefix, 'pat', self.uid])
714 714 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
715 715 desc_key = '_'.join([prefix, 'desc', self.uid])
716 716 rc_desc_key = '_'.join(['rhodecode', desc_key])
717 717 new_description = 'new_description'
718 718
719 719 settings_util.create_rhodecode_setting(
720 720 pattern_key, pattern, 'unicode', cleanup=False)
721 721 settings_util.create_rhodecode_setting(
722 722 desc_key, 'old description', 'unicode', cleanup=False)
723 723
724 724 post_url = route_path('admin_settings_issuetracker_update')
725 725 post_data = {
726 726 'new_pattern_pattern_0': pattern,
727 727 'new_pattern_url_0': 'https://url',
728 728 'new_pattern_prefix_0': 'prefix',
729 729 'new_pattern_description_0': new_description,
730 730 'uid': self.uid,
731 731 'csrf_token': csrf_token
732 732 }
733 733 self.app.post(post_url, post_data, status=302)
734 734 settings = SettingsModel().get_all_settings()
735 735 assert settings[rc_pattern_key] == pattern
736 736 assert settings[rc_desc_key] == new_description
737 737
738 738 @request.addfinalizer
739 739 def cleanup():
740 740 IssueTrackerSettingsModel().delete_entries(self.uid)
741 741
742 742 def test_delete_issuetracker_pattern(
743 743 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
744 744
745 745 old_pattern = 'issuetracker_pat_deleted'
746 746 old_uid = md5(old_pattern)
747 747
748 748 post_url = route_path('admin_settings_issuetracker_update')
749 749 post_data = {
750 750 'new_pattern_pattern_0': old_pattern,
751 751 'new_pattern_url_0': 'http://url',
752 752 'new_pattern_prefix_0': 'prefix',
753 753 'new_pattern_description_0': 'description',
754 754
755 755 'csrf_token': csrf_token
756 756 }
757 757 self.app.post(post_url, post_data, status=302)
758 758
759 759 post_url = route_path('admin_settings_issuetracker_delete')
760 760 post_data = {
761 761 'uid': old_uid,
762 762 'csrf_token': csrf_token
763 763 }
764 764 self.app.post(post_url, post_data, extra_environ=xhr_header, status=200)
765 765 settings = SettingsModel().get_all_settings()
766 766 assert self.PATTERN_KEY+old_uid not in settings
767 767 assert self.DESC_KEY + old_uid not in settings
@@ -1,103 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode.apps._base import BaseAppView
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model.forms import DefaultsForm
35 35 from rhodecode.model.meta import Session
36 36 from rhodecode import BACKENDS
37 37 from rhodecode.model.settings import SettingsModel
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class AdminDefaultSettingsView(BaseAppView):
43 43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46 46 return c
47 47
48 48 @LoginRequired()
49 49 @HasPermissionAllDecorator('hg.admin')
50 50 def defaults_repository_show(self):
51 51 c = self.load_default_context()
52 52 c.backends = BACKENDS.keys()
53 53 c.active = 'repositories'
54 54 defaults = SettingsModel().get_default_repo_settings()
55 55
56 56 data = render(
57 57 'rhodecode:templates/admin/defaults/defaults.mako',
58 58 self._get_template_context(c), self.request)
59 59 html = formencode.htmlfill.render(
60 60 data,
61 61 defaults=defaults,
62 62 encoding="UTF-8",
63 63 force_defaults=False
64 64 )
65 65 return Response(html)
66 66
67 67 @LoginRequired()
68 68 @HasPermissionAllDecorator('hg.admin')
69 69 @CSRFRequired()
70 70 def defaults_repository_update(self):
71 71 _ = self.request.translate
72 72 c = self.load_default_context()
73 73 c.active = 'repositories'
74 74 form = DefaultsForm(self.request.translate)()
75 75
76 76 try:
77 77 form_result = form.to_python(dict(self.request.POST))
78 for k, v in form_result.iteritems():
78 for k, v in form_result.items():
79 79 setting = SettingsModel().create_or_update_setting(k, v)
80 80 Session().add(setting)
81 81 Session().commit()
82 82 h.flash(_('Default settings updated successfully'),
83 83 category='success')
84 84
85 85 except formencode.Invalid as errors:
86 86 data = render(
87 87 'rhodecode:templates/admin/defaults/defaults.mako',
88 88 self._get_template_context(c), self.request)
89 89 html = formencode.htmlfill.render(
90 90 data,
91 91 defaults=errors.value,
92 92 errors=errors.error_dict or {},
93 93 prefix_error=False,
94 94 encoding="UTF-8",
95 95 force_defaults=False
96 96 )
97 97 return Response(html)
98 98 except Exception:
99 99 log.exception('Exception in update action')
100 100 h.flash(_('Error occurred during update of default values'),
101 101 category='error')
102 102
103 103 raise HTTPFound(h.route_path('admin_defaults_repositories'))
@@ -1,290 +1,290 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import (
38 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 39 from rhodecode.model.db import Statistics, CacheKey, User
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _load_commits_context(self, c):
56 56 p = safe_int(self.request.GET.get('page'), 1)
57 57 size = safe_int(self.request.GET.get('size'), 10)
58 58
59 59 def url_generator(page_num):
60 60 query_params = {
61 61 'page': page_num,
62 62 'size': size
63 63 }
64 64 return h.route_path(
65 65 'repo_summary_commits',
66 66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67 67
68 68 pre_load = self.get_commit_preload_attrs()
69 69
70 70 try:
71 71 collection = self.rhodecode_vcs_repo.get_commits(
72 72 pre_load=pre_load, translate_tags=False)
73 73 except EmptyRepositoryError:
74 74 collection = self.rhodecode_vcs_repo
75 75
76 76 c.repo_commits = h.RepoPage(
77 77 collection, page=p, items_per_page=size, url_maker=url_generator)
78 78 page_ids = [x.raw_id for x in c.repo_commits]
79 79 c.comments = self.db_repo.get_comments(page_ids)
80 80 c.statuses = self.db_repo.statuses(page_ids)
81 81
82 82 def _prepare_and_set_clone_url(self, c):
83 83 username = ''
84 84 if self._rhodecode_user.username != User.DEFAULT_USER:
85 85 username = safe_str(self._rhodecode_user.username)
86 86
87 87 _def_clone_uri = c.clone_uri_tmpl
88 88 _def_clone_uri_id = c.clone_uri_id_tmpl
89 89 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
90 90
91 91 c.clone_repo_url = self.db_repo.clone_url(
92 92 user=username, uri_tmpl=_def_clone_uri)
93 93 c.clone_repo_url_id = self.db_repo.clone_url(
94 94 user=username, uri_tmpl=_def_clone_uri_id)
95 95 c.clone_repo_url_ssh = self.db_repo.clone_url(
96 96 uri_tmpl=_def_clone_uri_ssh, ssh=True)
97 97
98 98 @LoginRequired()
99 99 @HasRepoPermissionAnyDecorator(
100 100 'repository.read', 'repository.write', 'repository.admin')
101 101 def summary_commits(self):
102 102 c = self.load_default_context()
103 103 self._prepare_and_set_clone_url(c)
104 104 self._load_commits_context(c)
105 105 return self._get_template_context(c)
106 106
107 107 @LoginRequired()
108 108 @HasRepoPermissionAnyDecorator(
109 109 'repository.read', 'repository.write', 'repository.admin')
110 110 def summary(self):
111 111 c = self.load_default_context()
112 112
113 113 # Prepare the clone URL
114 114 self._prepare_and_set_clone_url(c)
115 115
116 116 # If enabled, get statistics data
117 117 c.show_stats = bool(self.db_repo.enable_statistics)
118 118
119 119 stats = Session().query(Statistics) \
120 120 .filter(Statistics.repository == self.db_repo) \
121 121 .scalar()
122 122
123 123 c.stats_percentage = 0
124 124
125 125 if stats and stats.languages:
126 126 c.no_data = False is self.db_repo.enable_statistics
127 127 lang_stats_d = json.loads(stats.languages)
128 128
129 129 # Sort first by decreasing count and second by the file extension,
130 130 # so we have a consistent output.
131 lang_stats_items = sorted(lang_stats_d.iteritems(),
131 lang_stats_items = sorted(lang_stats_d.items(),
132 132 key=lambda k: (-k[1], k[0]))[:10]
133 133 lang_stats = [(x, {"count": y,
134 134 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
135 135 for x, y in lang_stats_items]
136 136
137 137 c.trending_languages = json.dumps(lang_stats)
138 138 else:
139 139 c.no_data = True
140 140 c.trending_languages = json.dumps({})
141 141
142 142 scm_model = ScmModel()
143 143 c.enable_downloads = self.db_repo.enable_downloads
144 144 c.repository_followers = scm_model.get_followers(self.db_repo)
145 145 c.repository_forks = scm_model.get_forks(self.db_repo)
146 146
147 147 # first interaction with the VCS instance after here...
148 148 if c.repository_requirements_missing:
149 149 self.request.override_renderer = \
150 150 'rhodecode:templates/summary/missing_requirements.mako'
151 151 return self._get_template_context(c)
152 152
153 153 c.readme_data, c.readme_file = \
154 154 self._get_readme_data(self.db_repo, c.visual.default_renderer)
155 155
156 156 # loads the summary commits template context
157 157 self._load_commits_context(c)
158 158
159 159 return self._get_template_context(c)
160 160
161 161 @LoginRequired()
162 162 @HasRepoPermissionAnyDecorator(
163 163 'repository.read', 'repository.write', 'repository.admin')
164 164 def repo_stats(self):
165 165 show_stats = bool(self.db_repo.enable_statistics)
166 166 repo_id = self.db_repo.repo_id
167 167
168 168 landing_commit = self.db_repo.get_landing_commit()
169 169 if isinstance(landing_commit, EmptyCommit):
170 170 return {'size': 0, 'code_stats': {}}
171 171
172 172 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
173 173 cache_on = cache_seconds > 0
174 174
175 175 log.debug(
176 176 'Computing REPO STATS for repo_id %s commit_id `%s` '
177 177 'with caching: %s[TTL: %ss]' % (
178 178 repo_id, landing_commit, cache_on, cache_seconds or 0))
179 179
180 180 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
181 181 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
182 182
183 183 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
184 184 condition=cache_on)
185 185 def compute_stats(repo_id, commit_id, _show_stats):
186 186 code_stats = {}
187 187 size = 0
188 188 try:
189 189 commit = self.db_repo.get_commit(commit_id)
190 190
191 191 for node in commit.get_filenodes_generator():
192 192 size += node.size
193 193 if not _show_stats:
194 194 continue
195 195 ext = string.lower(node.extension)
196 196 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
197 197 if ext_info:
198 198 if ext in code_stats:
199 199 code_stats[ext]['count'] += 1
200 200 else:
201 201 code_stats[ext] = {"count": 1, "desc": ext_info}
202 202 except (EmptyRepositoryError, CommitDoesNotExistError):
203 203 pass
204 204 return {'size': h.format_byte_size_binary(size),
205 205 'code_stats': code_stats}
206 206
207 207 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
208 208 return stats
209 209
210 210 @LoginRequired()
211 211 @HasRepoPermissionAnyDecorator(
212 212 'repository.read', 'repository.write', 'repository.admin')
213 213 def repo_refs_data(self):
214 214 _ = self.request.translate
215 215 self.load_default_context()
216 216
217 217 repo = self.rhodecode_vcs_repo
218 218 refs_to_create = [
219 219 (_("Branch"), repo.branches, 'branch'),
220 220 (_("Tag"), repo.tags, 'tag'),
221 221 (_("Bookmark"), repo.bookmarks, 'book'),
222 222 ]
223 223 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
224 224 data = {
225 225 'more': False,
226 226 'results': res
227 227 }
228 228 return data
229 229
230 230 @LoginRequired()
231 231 @HasRepoPermissionAnyDecorator(
232 232 'repository.read', 'repository.write', 'repository.admin')
233 233 def repo_refs_changelog_data(self):
234 234 _ = self.request.translate
235 235 self.load_default_context()
236 236
237 237 repo = self.rhodecode_vcs_repo
238 238
239 239 refs_to_create = [
240 240 (_("Branches"), repo.branches, 'branch'),
241 241 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
242 242 # TODO: enable when vcs can handle bookmarks filters
243 243 # (_("Bookmarks"), repo.bookmarks, "book"),
244 244 ]
245 245 res = self._create_reference_data(
246 246 repo, self.db_repo_name, refs_to_create)
247 247 data = {
248 248 'more': False,
249 249 'results': res
250 250 }
251 251 return data
252 252
253 253 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
254 254 format_ref_id = get_format_ref_id(repo)
255 255
256 256 result = []
257 257 for title, refs, ref_type in refs_to_create:
258 258 if refs:
259 259 result.append({
260 260 'text': title,
261 261 'children': self._create_reference_items(
262 262 repo, full_repo_name, refs, ref_type,
263 263 format_ref_id),
264 264 })
265 265 return result
266 266
267 267 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
268 268 result = []
269 269 is_svn = h.is_svn(repo)
270 for ref_name, raw_id in refs.iteritems():
270 for ref_name, raw_id in refs.items():
271 271 files_url = self._create_files_url(
272 272 repo, full_repo_name, ref_name, raw_id, is_svn)
273 273 result.append({
274 274 'text': ref_name,
275 275 'id': format_ref_id(ref_name, raw_id),
276 276 'raw_id': raw_id,
277 277 'type': ref_type,
278 278 'files_url': files_url,
279 279 'idx': 0,
280 280 })
281 281 return result
282 282
283 283 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
284 284 use_commit_id = '/' in ref_name or is_svn
285 285 return h.route_path(
286 286 'repo_files',
287 287 repo_name=full_repo_name,
288 288 f_path=ref_name if is_svn else '',
289 289 commit_id=raw_id if use_commit_id else ref_name,
290 290 _query=dict(at=ref_name))
@@ -1,2516 +1,2516 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26
27 27 import colander
28 28 import time
29 29 import collections
30 30 import fnmatch
31 31 import hashlib
32 32 import itertools
33 33 import logging
34 34 import random
35 35 import traceback
36 36 from functools import wraps
37 37
38 38 import ipaddress
39 39
40 40 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
41 41 from sqlalchemy.orm.exc import ObjectDeletedError
42 42 from sqlalchemy.orm import joinedload
43 43 from zope.cachedescriptors.property import Lazy as LazyProperty
44 44
45 45 import rhodecode
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import (
50 50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
52 52 from rhodecode.lib import rc_cache
53 53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
54 54 from rhodecode.lib.utils import (
55 55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 56 from rhodecode.lib.caching_query import FromCache
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default', 1
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin', 2
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, perm_origin_obj_id):
370 370 (perm, origin, obj_id) = perm_origin_obj_id
371 371 self.perm_origin_stack.setdefault(key, []).append((perm, origin, obj_id))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, pattern_perm_origin):
390 390 (pattern, perm, origin) = pattern_perm_origin
391 391
392 392 self.perm_origin_stack.setdefault(key, {}) \
393 393 .setdefault(pattern, []).append((perm, origin))
394 394
395 395 if key in self:
396 396 self[key].__setitem__(pattern, perm)
397 397 else:
398 398 patterns = collections.OrderedDict()
399 399 patterns[pattern] = perm
400 400 dict.__setitem__(self, key, patterns)
401 401
402 402
403 403 class PermissionCalculator(object):
404 404
405 405 def __init__(
406 406 self, user_id, scope, user_is_admin,
407 407 user_inherit_default_permissions, explicit, algo,
408 408 calculate_super_admin_as_user=False):
409 409
410 410 self.user_id = user_id
411 411 self.user_is_admin = user_is_admin
412 412 self.inherit_default_permissions = user_inherit_default_permissions
413 413 self.explicit = explicit
414 414 self.algo = algo
415 415 self.calculate_super_admin_as_user = calculate_super_admin_as_user
416 416
417 417 scope = scope or {}
418 418 self.scope_repo_id = scope.get('repo_id')
419 419 self.scope_repo_group_id = scope.get('repo_group_id')
420 420 self.scope_user_group_id = scope.get('user_group_id')
421 421
422 422 self.default_user_id = User.get_default_user(cache=True).user_id
423 423
424 424 self.permissions_repositories = PermOriginDict()
425 425 self.permissions_repository_groups = PermOriginDict()
426 426 self.permissions_user_groups = PermOriginDict()
427 427 self.permissions_repository_branches = BranchPermOriginDict()
428 428 self.permissions_global = set()
429 429
430 430 self.default_repo_perms = Permission.get_default_repo_perms(
431 431 self.default_user_id, self.scope_repo_id)
432 432 self.default_repo_groups_perms = Permission.get_default_group_perms(
433 433 self.default_user_id, self.scope_repo_group_id)
434 434 self.default_user_group_perms = \
435 435 Permission.get_default_user_group_perms(
436 436 self.default_user_id, self.scope_user_group_id)
437 437
438 438 # default branch perms
439 439 self.default_branch_repo_perms = \
440 440 Permission.get_default_repo_branch_perms(
441 441 self.default_user_id, self.scope_repo_id)
442 442
443 443 def calculate(self):
444 444 if self.user_is_admin and not self.calculate_super_admin_as_user:
445 445 return self._calculate_super_admin_permissions()
446 446
447 447 self._calculate_global_default_permissions()
448 448 self._calculate_global_permissions()
449 449 self._calculate_default_permissions()
450 450 self._calculate_repository_permissions()
451 451 self._calculate_repository_branch_permissions()
452 452 self._calculate_repository_group_permissions()
453 453 self._calculate_user_group_permissions()
454 454 return self._permission_structure()
455 455
456 456 def _calculate_super_admin_permissions(self):
457 457 """
458 458 super-admin user have all default rights for repositories
459 459 and groups set to admin
460 460 """
461 461 self.permissions_global.add('hg.admin')
462 462 self.permissions_global.add('hg.create.write_on_repogroup.true')
463 463
464 464 # repositories
465 465 for perm in self.default_repo_perms:
466 466 r_k = perm.UserRepoToPerm.repository.repo_name
467 467 obj_id = perm.UserRepoToPerm.repository.repo_id
468 468 archived = perm.UserRepoToPerm.repository.archived
469 469 p = 'repository.admin'
470 470 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
471 471 # special case for archived repositories, which we block still even for
472 472 # super admins
473 473 if archived:
474 474 p = 'repository.read'
475 475 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
476 476
477 477 # repository groups
478 478 for perm in self.default_repo_groups_perms:
479 479 rg_k = perm.UserRepoGroupToPerm.group.group_name
480 480 obj_id = perm.UserRepoGroupToPerm.group.group_id
481 481 p = 'group.admin'
482 482 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
483 483
484 484 # user groups
485 485 for perm in self.default_user_group_perms:
486 486 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
487 487 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
488 488 p = 'usergroup.admin'
489 489 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
490 490
491 491 # branch permissions
492 492 # since super-admin also can have custom rule permissions
493 493 # we *always* need to calculate those inherited from default, and also explicit
494 494 self._calculate_default_permissions_repository_branches(
495 495 user_inherit_object_permissions=False)
496 496 self._calculate_repository_branch_permissions()
497 497
498 498 return self._permission_structure()
499 499
500 500 def _calculate_global_default_permissions(self):
501 501 """
502 502 global permissions taken from the default user
503 503 """
504 504 default_global_perms = UserToPerm.query()\
505 505 .filter(UserToPerm.user_id == self.default_user_id)\
506 506 .options(joinedload(UserToPerm.permission))
507 507
508 508 for perm in default_global_perms:
509 509 self.permissions_global.add(perm.permission.permission_name)
510 510
511 511 if self.user_is_admin:
512 512 self.permissions_global.add('hg.admin')
513 513 self.permissions_global.add('hg.create.write_on_repogroup.true')
514 514
515 515 def _calculate_global_permissions(self):
516 516 """
517 517 Set global system permissions with user permissions or permissions
518 518 taken from the user groups of the current user.
519 519
520 520 The permissions include repo creating, repo group creating, forking
521 521 etc.
522 522 """
523 523
524 524 # now we read the defined permissions and overwrite what we have set
525 525 # before those can be configured from groups or users explicitly.
526 526
527 527 # In case we want to extend this list we should make sure
528 528 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
529 529 from rhodecode.model.permission import PermissionModel
530 530
531 531 _configurable = frozenset([
532 532 PermissionModel.FORKING_DISABLED, PermissionModel.FORKING_ENABLED,
533 533 'hg.create.none', 'hg.create.repository',
534 534 'hg.usergroup.create.false', 'hg.usergroup.create.true',
535 535 'hg.repogroup.create.false', 'hg.repogroup.create.true',
536 536 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
537 537 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
538 538 ])
539 539
540 540 # USER GROUPS comes first user group global permissions
541 541 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
542 542 .options(joinedload(UserGroupToPerm.permission))\
543 543 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
544 544 UserGroupMember.users_group_id))\
545 545 .filter(UserGroupMember.user_id == self.user_id)\
546 546 .order_by(UserGroupToPerm.users_group_id)\
547 547 .all()
548 548
549 549 # need to group here by groups since user can be in more than
550 550 # one group, so we get all groups
551 551 _explicit_grouped_perms = [
552 552 [x, list(y)] for x, y in
553 553 itertools.groupby(user_perms_from_users_groups,
554 554 lambda _x: _x.users_group)]
555 555
556 556 for gr, perms in _explicit_grouped_perms:
557 557 # since user can be in multiple groups iterate over them and
558 558 # select the lowest permissions first (more explicit)
559 559 # TODO(marcink): do this^^
560 560
561 561 # group doesn't inherit default permissions so we actually set them
562 562 if not gr.inherit_default_permissions:
563 563 # NEED TO IGNORE all previously set configurable permissions
564 564 # and replace them with explicitly set from this user
565 565 # group permissions
566 566 self.permissions_global = self.permissions_global.difference(
567 567 _configurable)
568 568 for perm in perms:
569 569 self.permissions_global.add(perm.permission.permission_name)
570 570
571 571 # user explicit global permissions
572 572 user_perms = Session().query(UserToPerm)\
573 573 .options(joinedload(UserToPerm.permission))\
574 574 .filter(UserToPerm.user_id == self.user_id).all()
575 575
576 576 if not self.inherit_default_permissions:
577 577 # NEED TO IGNORE all configurable permissions and
578 578 # replace them with explicitly set from this user permissions
579 579 self.permissions_global = self.permissions_global.difference(
580 580 _configurable)
581 581 for perm in user_perms:
582 582 self.permissions_global.add(perm.permission.permission_name)
583 583
584 584 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
585 585 for perm in self.default_repo_perms:
586 586 r_k = perm.UserRepoToPerm.repository.repo_name
587 587 obj_id = perm.UserRepoToPerm.repository.repo_id
588 588 archived = perm.UserRepoToPerm.repository.archived
589 589 p = perm.Permission.permission_name
590 590 o = PermOrigin.REPO_DEFAULT
591 591 self.permissions_repositories[r_k] = p, o, obj_id
592 592
593 593 # if we decide this user isn't inheriting permissions from
594 594 # default user we set him to .none so only explicit
595 595 # permissions work
596 596 if not user_inherit_object_permissions:
597 597 p = 'repository.none'
598 598 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
599 599 self.permissions_repositories[r_k] = p, o, obj_id
600 600
601 601 if perm.Repository.private and not (
602 602 perm.Repository.user_id == self.user_id):
603 603 # disable defaults for private repos,
604 604 p = 'repository.none'
605 605 o = PermOrigin.REPO_PRIVATE
606 606 self.permissions_repositories[r_k] = p, o, obj_id
607 607
608 608 elif perm.Repository.user_id == self.user_id:
609 609 # set admin if owner
610 610 p = 'repository.admin'
611 611 o = PermOrigin.REPO_OWNER
612 612 self.permissions_repositories[r_k] = p, o, obj_id
613 613
614 614 if self.user_is_admin:
615 615 p = 'repository.admin'
616 616 o = PermOrigin.SUPER_ADMIN
617 617 self.permissions_repositories[r_k] = p, o, obj_id
618 618
619 619 # finally in case of archived repositories, we downgrade higher
620 620 # permissions to read
621 621 if archived:
622 622 current_perm = self.permissions_repositories[r_k]
623 623 if current_perm in ['repository.write', 'repository.admin']:
624 624 p = 'repository.read'
625 625 o = PermOrigin.ARCHIVED
626 626 self.permissions_repositories[r_k] = p, o, obj_id
627 627
628 628 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
629 629 for perm in self.default_branch_repo_perms:
630 630
631 631 r_k = perm.UserRepoToPerm.repository.repo_name
632 632 p = perm.Permission.permission_name
633 633 pattern = perm.UserToRepoBranchPermission.branch_pattern
634 634 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
635 635
636 636 if not self.explicit:
637 637 cur_perm = self.permissions_repository_branches.get(r_k)
638 638 if cur_perm:
639 639 cur_perm = cur_perm[pattern]
640 640 cur_perm = cur_perm or 'branch.none'
641 641
642 642 p = self._choose_permission(p, cur_perm)
643 643
644 644 # NOTE(marcink): register all pattern/perm instances in this
645 645 # special dict that aggregates entries
646 646 self.permissions_repository_branches[r_k] = pattern, p, o
647 647
648 648 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
649 649 for perm in self.default_repo_groups_perms:
650 650 rg_k = perm.UserRepoGroupToPerm.group.group_name
651 651 obj_id = perm.UserRepoGroupToPerm.group.group_id
652 652 p = perm.Permission.permission_name
653 653 o = PermOrigin.REPOGROUP_DEFAULT
654 654 self.permissions_repository_groups[rg_k] = p, o, obj_id
655 655
656 656 # if we decide this user isn't inheriting permissions from default
657 657 # user we set him to .none so only explicit permissions work
658 658 if not user_inherit_object_permissions:
659 659 p = 'group.none'
660 660 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
661 661 self.permissions_repository_groups[rg_k] = p, o, obj_id
662 662
663 663 if perm.RepoGroup.user_id == self.user_id:
664 664 # set admin if owner
665 665 p = 'group.admin'
666 666 o = PermOrigin.REPOGROUP_OWNER
667 667 self.permissions_repository_groups[rg_k] = p, o, obj_id
668 668
669 669 if self.user_is_admin:
670 670 p = 'group.admin'
671 671 o = PermOrigin.SUPER_ADMIN
672 672 self.permissions_repository_groups[rg_k] = p, o, obj_id
673 673
674 674 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
675 675 for perm in self.default_user_group_perms:
676 676 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
677 677 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
678 678 p = perm.Permission.permission_name
679 679 o = PermOrigin.USERGROUP_DEFAULT
680 680 self.permissions_user_groups[u_k] = p, o, obj_id
681 681
682 682 # if we decide this user isn't inheriting permissions from default
683 683 # user we set him to .none so only explicit permissions work
684 684 if not user_inherit_object_permissions:
685 685 p = 'usergroup.none'
686 686 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
687 687 self.permissions_user_groups[u_k] = p, o, obj_id
688 688
689 689 if perm.UserGroup.user_id == self.user_id:
690 690 # set admin if owner
691 691 p = 'usergroup.admin'
692 692 o = PermOrigin.USERGROUP_OWNER
693 693 self.permissions_user_groups[u_k] = p, o, obj_id
694 694
695 695 if self.user_is_admin:
696 696 p = 'usergroup.admin'
697 697 o = PermOrigin.SUPER_ADMIN
698 698 self.permissions_user_groups[u_k] = p, o, obj_id
699 699
700 700 def _calculate_default_permissions(self):
701 701 """
702 702 Set default user permissions for repositories, repository branches,
703 703 repository groups, user groups taken from the default user.
704 704
705 705 Calculate inheritance of object permissions based on what we have now
706 706 in GLOBAL permissions. We check if .false is in GLOBAL since this is
707 707 explicitly set. Inherit is the opposite of .false being there.
708 708
709 709 .. note::
710 710
711 711 the syntax is little bit odd but what we need to check here is
712 712 the opposite of .false permission being in the list so even for
713 713 inconsistent state when both .true/.false is there
714 714 .false is more important
715 715
716 716 """
717 717 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
718 718 in self.permissions_global)
719 719
720 720 # default permissions inherited from `default` user permissions
721 721 self._calculate_default_permissions_repositories(
722 722 user_inherit_object_permissions)
723 723
724 724 self._calculate_default_permissions_repository_branches(
725 725 user_inherit_object_permissions)
726 726
727 727 self._calculate_default_permissions_repository_groups(
728 728 user_inherit_object_permissions)
729 729
730 730 self._calculate_default_permissions_user_groups(
731 731 user_inherit_object_permissions)
732 732
733 733 def _calculate_repository_permissions(self):
734 734 """
735 735 Repository access permissions for the current user.
736 736
737 737 Check if the user is part of user groups for this repository and
738 738 fill in the permission from it. `_choose_permission` decides of which
739 739 permission should be selected based on selected method.
740 740 """
741 741
742 742 # user group for repositories permissions
743 743 user_repo_perms_from_user_group = Permission\
744 744 .get_default_repo_perms_from_user_group(
745 745 self.user_id, self.scope_repo_id)
746 746
747 747 multiple_counter = collections.defaultdict(int)
748 748 for perm in user_repo_perms_from_user_group:
749 749 r_k = perm.UserGroupRepoToPerm.repository.repo_name
750 750 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
751 751 multiple_counter[r_k] += 1
752 752 p = perm.Permission.permission_name
753 753 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
754 754 .users_group.users_group_name
755 755
756 756 if multiple_counter[r_k] > 1:
757 757 cur_perm = self.permissions_repositories[r_k]
758 758 p = self._choose_permission(p, cur_perm)
759 759
760 760 self.permissions_repositories[r_k] = p, o, obj_id
761 761
762 762 if perm.Repository.user_id == self.user_id:
763 763 # set admin if owner
764 764 p = 'repository.admin'
765 765 o = PermOrigin.REPO_OWNER
766 766 self.permissions_repositories[r_k] = p, o, obj_id
767 767
768 768 if self.user_is_admin:
769 769 p = 'repository.admin'
770 770 o = PermOrigin.SUPER_ADMIN
771 771 self.permissions_repositories[r_k] = p, o, obj_id
772 772
773 773 # user explicit permissions for repositories, overrides any specified
774 774 # by the group permission
775 775 user_repo_perms = Permission.get_default_repo_perms(
776 776 self.user_id, self.scope_repo_id)
777 777 for perm in user_repo_perms:
778 778 r_k = perm.UserRepoToPerm.repository.repo_name
779 779 obj_id = perm.UserRepoToPerm.repository.repo_id
780 780 archived = perm.UserRepoToPerm.repository.archived
781 781 p = perm.Permission.permission_name
782 782 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
783 783
784 784 if not self.explicit:
785 785 cur_perm = self.permissions_repositories.get(
786 786 r_k, 'repository.none')
787 787 p = self._choose_permission(p, cur_perm)
788 788
789 789 self.permissions_repositories[r_k] = p, o, obj_id
790 790
791 791 if perm.Repository.user_id == self.user_id:
792 792 # set admin if owner
793 793 p = 'repository.admin'
794 794 o = PermOrigin.REPO_OWNER
795 795 self.permissions_repositories[r_k] = p, o, obj_id
796 796
797 797 if self.user_is_admin:
798 798 p = 'repository.admin'
799 799 o = PermOrigin.SUPER_ADMIN
800 800 self.permissions_repositories[r_k] = p, o, obj_id
801 801
802 802 # finally in case of archived repositories, we downgrade higher
803 803 # permissions to read
804 804 if archived:
805 805 current_perm = self.permissions_repositories[r_k]
806 806 if current_perm in ['repository.write', 'repository.admin']:
807 807 p = 'repository.read'
808 808 o = PermOrigin.ARCHIVED
809 809 self.permissions_repositories[r_k] = p, o, obj_id
810 810
811 811 def _calculate_repository_branch_permissions(self):
812 812 # user group for repositories permissions
813 813 user_repo_branch_perms_from_user_group = Permission\
814 814 .get_default_repo_branch_perms_from_user_group(
815 815 self.user_id, self.scope_repo_id)
816 816
817 817 multiple_counter = collections.defaultdict(int)
818 818 for perm in user_repo_branch_perms_from_user_group:
819 819 r_k = perm.UserGroupRepoToPerm.repository.repo_name
820 820 p = perm.Permission.permission_name
821 821 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
822 822 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
823 823 .users_group.users_group_name
824 824
825 825 multiple_counter[r_k] += 1
826 826 if multiple_counter[r_k] > 1:
827 827 cur_perm = self.permissions_repository_branches[r_k][pattern]
828 828 p = self._choose_permission(p, cur_perm)
829 829
830 830 self.permissions_repository_branches[r_k] = pattern, p, o
831 831
832 832 # user explicit branch permissions for repositories, overrides
833 833 # any specified by the group permission
834 834 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
835 835 self.user_id, self.scope_repo_id)
836 836
837 837 for perm in user_repo_branch_perms:
838 838
839 839 r_k = perm.UserRepoToPerm.repository.repo_name
840 840 p = perm.Permission.permission_name
841 841 pattern = perm.UserToRepoBranchPermission.branch_pattern
842 842 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
843 843
844 844 if not self.explicit:
845 845 cur_perm = self.permissions_repository_branches.get(r_k)
846 846 if cur_perm:
847 847 cur_perm = cur_perm[pattern]
848 848 cur_perm = cur_perm or 'branch.none'
849 849 p = self._choose_permission(p, cur_perm)
850 850
851 851 # NOTE(marcink): register all pattern/perm instances in this
852 852 # special dict that aggregates entries
853 853 self.permissions_repository_branches[r_k] = pattern, p, o
854 854
855 855 def _calculate_repository_group_permissions(self):
856 856 """
857 857 Repository group permissions for the current user.
858 858
859 859 Check if the user is part of user groups for repository groups and
860 860 fill in the permissions from it. `_choose_permission` decides of which
861 861 permission should be selected based on selected method.
862 862 """
863 863 # user group for repo groups permissions
864 864 user_repo_group_perms_from_user_group = Permission\
865 865 .get_default_group_perms_from_user_group(
866 866 self.user_id, self.scope_repo_group_id)
867 867
868 868 multiple_counter = collections.defaultdict(int)
869 869 for perm in user_repo_group_perms_from_user_group:
870 870 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
871 871 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
872 872 multiple_counter[rg_k] += 1
873 873 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
874 874 .users_group.users_group_name
875 875 p = perm.Permission.permission_name
876 876
877 877 if multiple_counter[rg_k] > 1:
878 878 cur_perm = self.permissions_repository_groups[rg_k]
879 879 p = self._choose_permission(p, cur_perm)
880 880 self.permissions_repository_groups[rg_k] = p, o, obj_id
881 881
882 882 if perm.RepoGroup.user_id == self.user_id:
883 883 # set admin if owner, even for member of other user group
884 884 p = 'group.admin'
885 885 o = PermOrigin.REPOGROUP_OWNER
886 886 self.permissions_repository_groups[rg_k] = p, o, obj_id
887 887
888 888 if self.user_is_admin:
889 889 p = 'group.admin'
890 890 o = PermOrigin.SUPER_ADMIN
891 891 self.permissions_repository_groups[rg_k] = p, o, obj_id
892 892
893 893 # user explicit permissions for repository groups
894 894 user_repo_groups_perms = Permission.get_default_group_perms(
895 895 self.user_id, self.scope_repo_group_id)
896 896 for perm in user_repo_groups_perms:
897 897 rg_k = perm.UserRepoGroupToPerm.group.group_name
898 898 obj_id = perm.UserRepoGroupToPerm.group.group_id
899 899 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
900 900 .user.username
901 901 p = perm.Permission.permission_name
902 902
903 903 if not self.explicit:
904 904 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
905 905 p = self._choose_permission(p, cur_perm)
906 906
907 907 self.permissions_repository_groups[rg_k] = p, o, obj_id
908 908
909 909 if perm.RepoGroup.user_id == self.user_id:
910 910 # set admin if owner
911 911 p = 'group.admin'
912 912 o = PermOrigin.REPOGROUP_OWNER
913 913 self.permissions_repository_groups[rg_k] = p, o, obj_id
914 914
915 915 if self.user_is_admin:
916 916 p = 'group.admin'
917 917 o = PermOrigin.SUPER_ADMIN
918 918 self.permissions_repository_groups[rg_k] = p, o, obj_id
919 919
920 920 def _calculate_user_group_permissions(self):
921 921 """
922 922 User group permissions for the current user.
923 923 """
924 924 # user group for user group permissions
925 925 user_group_from_user_group = Permission\
926 926 .get_default_user_group_perms_from_user_group(
927 927 self.user_id, self.scope_user_group_id)
928 928
929 929 multiple_counter = collections.defaultdict(int)
930 930 for perm in user_group_from_user_group:
931 931 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
932 932 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
933 933 multiple_counter[ug_k] += 1
934 934 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
935 935 .user_group.users_group_name
936 936 p = perm.Permission.permission_name
937 937
938 938 if multiple_counter[ug_k] > 1:
939 939 cur_perm = self.permissions_user_groups[ug_k]
940 940 p = self._choose_permission(p, cur_perm)
941 941
942 942 self.permissions_user_groups[ug_k] = p, o, obj_id
943 943
944 944 if perm.UserGroup.user_id == self.user_id:
945 945 # set admin if owner, even for member of other user group
946 946 p = 'usergroup.admin'
947 947 o = PermOrigin.USERGROUP_OWNER
948 948 self.permissions_user_groups[ug_k] = p, o, obj_id
949 949
950 950 if self.user_is_admin:
951 951 p = 'usergroup.admin'
952 952 o = PermOrigin.SUPER_ADMIN
953 953 self.permissions_user_groups[ug_k] = p, o, obj_id
954 954
955 955 # user explicit permission for user groups
956 956 user_user_groups_perms = Permission.get_default_user_group_perms(
957 957 self.user_id, self.scope_user_group_id)
958 958 for perm in user_user_groups_perms:
959 959 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
960 960 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
961 961 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
962 962 .user.username
963 963 p = perm.Permission.permission_name
964 964
965 965 if not self.explicit:
966 966 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
967 967 p = self._choose_permission(p, cur_perm)
968 968
969 969 self.permissions_user_groups[ug_k] = p, o, obj_id
970 970
971 971 if perm.UserGroup.user_id == self.user_id:
972 972 # set admin if owner
973 973 p = 'usergroup.admin'
974 974 o = PermOrigin.USERGROUP_OWNER
975 975 self.permissions_user_groups[ug_k] = p, o, obj_id
976 976
977 977 if self.user_is_admin:
978 978 p = 'usergroup.admin'
979 979 o = PermOrigin.SUPER_ADMIN
980 980 self.permissions_user_groups[ug_k] = p, o, obj_id
981 981
982 982 def _choose_permission(self, new_perm, cur_perm):
983 983 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
984 984 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
985 985 if self.algo == 'higherwin':
986 986 if new_perm_val > cur_perm_val:
987 987 return new_perm
988 988 return cur_perm
989 989 elif self.algo == 'lowerwin':
990 990 if new_perm_val < cur_perm_val:
991 991 return new_perm
992 992 return cur_perm
993 993
994 994 def _permission_structure(self):
995 995 return {
996 996 'global': self.permissions_global,
997 997 'repositories': self.permissions_repositories,
998 998 'repository_branches': self.permissions_repository_branches,
999 999 'repositories_groups': self.permissions_repository_groups,
1000 1000 'user_groups': self.permissions_user_groups,
1001 1001 }
1002 1002
1003 1003
1004 1004 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
1005 1005 """
1006 1006 Check if given controller_name is in whitelist of auth token access
1007 1007 """
1008 1008 if not whitelist:
1009 1009 from rhodecode import CONFIG
1010 1010 whitelist = aslist(
1011 1011 CONFIG.get('api_access_controllers_whitelist'), sep=',')
1012 1012 # backward compat translation
1013 1013 compat = {
1014 1014 # old controller, new VIEW
1015 1015 'ChangesetController:*': 'RepoCommitsView:*',
1016 1016 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1017 1017 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1018 1018 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1019 1019 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1020 1020 'GistsController:*': 'GistView:*',
1021 1021 }
1022 1022
1023 1023 log.debug(
1024 1024 'Allowed views for AUTH TOKEN access: %s', whitelist)
1025 1025 auth_token_access_valid = False
1026 1026
1027 1027 for entry in whitelist:
1028 1028 token_match = True
1029 1029 if entry in compat:
1030 1030 # translate from old Controllers to Pyramid Views
1031 1031 entry = compat[entry]
1032 1032
1033 1033 if '@' in entry:
1034 1034 # specific AuthToken
1035 1035 entry, allowed_token = entry.split('@', 1)
1036 1036 token_match = auth_token == allowed_token
1037 1037
1038 1038 if fnmatch.fnmatch(view_name, entry) and token_match:
1039 1039 auth_token_access_valid = True
1040 1040 break
1041 1041
1042 1042 if auth_token_access_valid:
1043 1043 log.debug('view: `%s` matches entry in whitelist: %s',
1044 1044 view_name, whitelist)
1045 1045
1046 1046 else:
1047 1047 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1048 1048 % (view_name, whitelist))
1049 1049 if auth_token:
1050 1050 # if we use auth token key and don't have access it's a warning
1051 1051 log.warning(msg)
1052 1052 else:
1053 1053 log.debug(msg)
1054 1054
1055 1055 return auth_token_access_valid
1056 1056
1057 1057
1058 1058 class AuthUser(object):
1059 1059 """
1060 1060 A simple object that handles all attributes of user in RhodeCode
1061 1061
1062 1062 It does lookup based on API key,given user, or user present in session
1063 1063 Then it fills all required information for such user. It also checks if
1064 1064 anonymous access is enabled and if so, it returns default user as logged in
1065 1065 """
1066 1066 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1067 1067 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1068 1068 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1069 1069 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1070 1070
1071 1071 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1072 1072
1073 1073 self.user_id = user_id
1074 1074 self._api_key = api_key
1075 1075
1076 1076 self.api_key = None
1077 1077 self.username = username
1078 1078 self.ip_addr = ip_addr
1079 1079 self.name = ''
1080 1080 self.lastname = ''
1081 1081 self.first_name = ''
1082 1082 self.last_name = ''
1083 1083 self.email = ''
1084 1084 self.is_authenticated = False
1085 1085 self.admin = False
1086 1086 self.inherit_default_permissions = False
1087 1087 self.password = ''
1088 1088
1089 1089 self.anonymous_user = None # propagated on propagate_data
1090 1090 self.propagate_data()
1091 1091 self._instance = None
1092 1092 self._permissions_scoped_cache = {} # used to bind scoped calculation
1093 1093
1094 1094 @LazyProperty
1095 1095 def permissions(self):
1096 1096 return self.get_perms(user=self, cache=None)
1097 1097
1098 1098 @LazyProperty
1099 1099 def permissions_safe(self):
1100 1100 """
1101 1101 Filtered permissions excluding not allowed repositories
1102 1102 """
1103 1103 perms = self.get_perms(user=self, cache=None)
1104 1104
1105 1105 perms['repositories'] = {
1106 1106 k: v for k, v in perms['repositories'].items()
1107 1107 if v != 'repository.none'}
1108 1108 perms['repositories_groups'] = {
1109 1109 k: v for k, v in perms['repositories_groups'].items()
1110 1110 if v != 'group.none'}
1111 1111 perms['user_groups'] = {
1112 1112 k: v for k, v in perms['user_groups'].items()
1113 1113 if v != 'usergroup.none'}
1114 1114 perms['repository_branches'] = {
1115 k: v for k, v in perms['repository_branches'].iteritems()
1115 k: v for k, v in perms['repository_branches'].items()
1116 1116 if v != 'branch.none'}
1117 1117 return perms
1118 1118
1119 1119 @LazyProperty
1120 1120 def permissions_full_details(self):
1121 1121 return self.get_perms(
1122 1122 user=self, cache=None, calculate_super_admin=True)
1123 1123
1124 1124 def permissions_with_scope(self, scope):
1125 1125 """
1126 1126 Call the get_perms function with scoped data. The scope in that function
1127 1127 narrows the SQL calls to the given ID of objects resulting in fetching
1128 1128 Just particular permission we want to obtain. If scope is an empty dict
1129 1129 then it basically narrows the scope to GLOBAL permissions only.
1130 1130
1131 1131 :param scope: dict
1132 1132 """
1133 1133 if 'repo_name' in scope:
1134 1134 obj = Repository.get_by_repo_name(scope['repo_name'])
1135 1135 if obj:
1136 1136 scope['repo_id'] = obj.repo_id
1137 1137 _scope = collections.OrderedDict()
1138 1138 _scope['repo_id'] = -1
1139 1139 _scope['user_group_id'] = -1
1140 1140 _scope['repo_group_id'] = -1
1141 1141
1142 1142 for k in sorted(scope.keys()):
1143 1143 _scope[k] = scope[k]
1144 1144
1145 1145 # store in cache to mimic how the @LazyProperty works,
1146 1146 # the difference here is that we use the unique key calculated
1147 1147 # from params and values
1148 1148 return self.get_perms(user=self, cache=None, scope=_scope)
1149 1149
1150 1150 def get_instance(self):
1151 1151 return User.get(self.user_id)
1152 1152
1153 1153 def propagate_data(self):
1154 1154 """
1155 1155 Fills in user data and propagates values to this instance. Maps fetched
1156 1156 user attributes to this class instance attributes
1157 1157 """
1158 1158 log.debug('AuthUser: starting data propagation for new potential user')
1159 1159 user_model = UserModel()
1160 1160 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1161 1161 is_user_loaded = False
1162 1162
1163 1163 # lookup by userid
1164 1164 if self.user_id is not None and self.user_id != anon_user.user_id:
1165 1165 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1166 1166 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1167 1167
1168 1168 # try go get user by api key
1169 1169 elif self._api_key and self._api_key != anon_user.api_key:
1170 1170 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1171 1171 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1172 1172
1173 1173 # lookup by username
1174 1174 elif self.username:
1175 1175 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1176 1176 is_user_loaded = user_model.fill_data(self, username=self.username)
1177 1177 else:
1178 1178 log.debug('No data in %s that could been used to log in', self)
1179 1179
1180 1180 if not is_user_loaded:
1181 1181 log.debug(
1182 1182 'Failed to load user. Fallback to default user %s', anon_user)
1183 1183 # if we cannot authenticate user try anonymous
1184 1184 if anon_user.active:
1185 1185 log.debug('default user is active, using it as a session user')
1186 1186 user_model.fill_data(self, user_id=anon_user.user_id)
1187 1187 # then we set this user is logged in
1188 1188 self.is_authenticated = True
1189 1189 else:
1190 1190 log.debug('default user is NOT active')
1191 1191 # in case of disabled anonymous user we reset some of the
1192 1192 # parameters so such user is "corrupted", skipping the fill_data
1193 1193 for attr in ['user_id', 'username', 'admin', 'active']:
1194 1194 setattr(self, attr, None)
1195 1195 self.is_authenticated = False
1196 1196
1197 1197 if not self.username:
1198 1198 self.username = 'None'
1199 1199
1200 1200 log.debug('AuthUser: propagated user is now %s', self)
1201 1201
1202 1202 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1203 1203 calculate_super_admin=False, cache=None):
1204 1204 """
1205 1205 Fills user permission attribute with permissions taken from database
1206 1206 works for permissions given for repositories, and for permissions that
1207 1207 are granted to groups
1208 1208
1209 1209 :param user: instance of User object from database
1210 1210 :param explicit: In case there are permissions both for user and a group
1211 1211 that user is part of, explicit flag will defiine if user will
1212 1212 explicitly override permissions from group, if it's False it will
1213 1213 make decision based on the algo
1214 1214 :param algo: algorithm to decide what permission should be choose if
1215 1215 it's multiple defined, eg user in two different groups. It also
1216 1216 decides if explicit flag is turned off how to specify the permission
1217 1217 for case when user is in a group + have defined separate permission
1218 1218 :param calculate_super_admin: calculate permissions for super-admin in the
1219 1219 same way as for regular user without speedups
1220 1220 :param cache: Use caching for calculation, None = let the cache backend decide
1221 1221 """
1222 1222 user_id = user.user_id
1223 1223 user_is_admin = user.is_admin
1224 1224
1225 1225 # inheritance of global permissions like create repo/fork repo etc
1226 1226 user_inherit_default_permissions = user.inherit_default_permissions
1227 1227
1228 1228 cache_seconds = safe_int(
1229 1229 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1230 1230
1231 1231 if cache is None:
1232 1232 # let the backend cache decide
1233 1233 cache_on = cache_seconds > 0
1234 1234 else:
1235 1235 cache_on = cache
1236 1236
1237 1237 log.debug(
1238 1238 'Computing PERMISSION tree for user %s scope `%s` '
1239 1239 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1240 1240
1241 1241 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1242 1242 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1243 1243
1244 1244 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1245 1245 condition=cache_on)
1246 1246 def compute_perm_tree(cache_name, cache_ver,
1247 1247 user_id, scope, user_is_admin,user_inherit_default_permissions,
1248 1248 explicit, algo, calculate_super_admin):
1249 1249 return _cached_perms_data(
1250 1250 user_id, scope, user_is_admin, user_inherit_default_permissions,
1251 1251 explicit, algo, calculate_super_admin)
1252 1252
1253 1253 start = time.time()
1254 1254 result = compute_perm_tree(
1255 1255 'permissions', 'v1', user_id, scope, user_is_admin,
1256 1256 user_inherit_default_permissions, explicit, algo,
1257 1257 calculate_super_admin)
1258 1258
1259 1259 result_repr = []
1260 1260 for k in result:
1261 1261 result_repr.append((k, len(result[k])))
1262 1262 total = time.time() - start
1263 1263 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1264 1264 user, total, result_repr)
1265 1265
1266 1266 return result
1267 1267
1268 1268 @property
1269 1269 def is_default(self):
1270 1270 return self.username == User.DEFAULT_USER
1271 1271
1272 1272 @property
1273 1273 def is_admin(self):
1274 1274 return self.admin
1275 1275
1276 1276 @property
1277 1277 def is_user_object(self):
1278 1278 return self.user_id is not None
1279 1279
1280 1280 @property
1281 1281 def repositories_admin(self):
1282 1282 """
1283 1283 Returns list of repositories you're an admin of
1284 1284 """
1285 1285 return [
1286 1286 x[0] for x in self.permissions['repositories'].items()
1287 1287 if x[1] == 'repository.admin']
1288 1288
1289 1289 @property
1290 1290 def repository_groups_admin(self):
1291 1291 """
1292 1292 Returns list of repository groups you're an admin of
1293 1293 """
1294 1294 return [
1295 1295 x[0] for x in self.permissions['repositories_groups'].items()
1296 1296 if x[1] == 'group.admin']
1297 1297
1298 1298 @property
1299 1299 def user_groups_admin(self):
1300 1300 """
1301 1301 Returns list of user groups you're an admin of
1302 1302 """
1303 1303 return [
1304 1304 x[0] for x in self.permissions['user_groups'].items()
1305 1305 if x[1] == 'usergroup.admin']
1306 1306
1307 1307 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1308 1308 if not perms:
1309 1309 perms = AuthUser.repo_read_perms
1310 1310 allowed_ids = []
1311 1311 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1312 1312 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1313 1313 if prefix_filter and not k.startswith(prefix_filter):
1314 1314 continue
1315 1315 if perm in perms:
1316 1316 allowed_ids.append(obj_id)
1317 1317 return allowed_ids
1318 1318
1319 1319 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1320 1320 """
1321 1321 Returns list of repository ids that user have access to based on given
1322 1322 perms. The cache flag should be only used in cases that are used for
1323 1323 display purposes, NOT IN ANY CASE for permission checks.
1324 1324 """
1325 1325 from rhodecode.model.scm import RepoList
1326 1326 if not perms:
1327 1327 perms = AuthUser.repo_read_perms
1328 1328
1329 1329 if not isinstance(perms, list):
1330 1330 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1331 1331
1332 1332 def _cached_repo_acl(perm_def, _name_filter):
1333 1333 qry = Repository.query()
1334 1334 if _name_filter:
1335 1335 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1336 1336 qry = qry.filter(
1337 1337 Repository.repo_name.ilike(ilike_expression))
1338 1338
1339 1339 return [x.repo_id for x in
1340 1340 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1341 1341
1342 1342 log.debug('Computing REPO ACL IDS user %s', self)
1343 1343
1344 1344 cache_namespace_uid = 'cache_user_repo_acl_ids.{}'.format(self.user_id)
1345 1345 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1346 1346
1347 1347 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1348 1348 def compute_repo_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1349 1349 return _cached_repo_acl(perm_def, _name_filter)
1350 1350
1351 1351 start = time.time()
1352 1352 result = compute_repo_acl_ids('v1', self.user_id, perms, name_filter)
1353 1353 total = time.time() - start
1354 1354 log.debug('REPO ACL IDS for user %s computed in %.4fs', self, total)
1355 1355
1356 1356 return result
1357 1357
1358 1358 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1359 1359 if not perms:
1360 1360 perms = AuthUser.repo_group_read_perms
1361 1361 allowed_ids = []
1362 1362 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1363 1363 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1364 1364 if prefix_filter and not k.startswith(prefix_filter):
1365 1365 continue
1366 1366 if perm in perms:
1367 1367 allowed_ids.append(obj_id)
1368 1368 return allowed_ids
1369 1369
1370 1370 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1371 1371 """
1372 1372 Returns list of repository group ids that user have access to based on given
1373 1373 perms. The cache flag should be only used in cases that are used for
1374 1374 display purposes, NOT IN ANY CASE for permission checks.
1375 1375 """
1376 1376 from rhodecode.model.scm import RepoGroupList
1377 1377 if not perms:
1378 1378 perms = AuthUser.repo_group_read_perms
1379 1379
1380 1380 if not isinstance(perms, list):
1381 1381 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1382 1382
1383 1383 def _cached_repo_group_acl(perm_def, _name_filter):
1384 1384 qry = RepoGroup.query()
1385 1385 if _name_filter:
1386 1386 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1387 1387 qry = qry.filter(
1388 1388 RepoGroup.group_name.ilike(ilike_expression))
1389 1389
1390 1390 return [x.group_id for x in
1391 1391 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1392 1392
1393 1393 log.debug('Computing REPO GROUP ACL IDS user %s', self)
1394 1394
1395 1395 cache_namespace_uid = 'cache_user_repo_group_acl_ids.{}'.format(self.user_id)
1396 1396 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1397 1397
1398 1398 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1399 1399 def compute_repo_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1400 1400 return _cached_repo_group_acl(perm_def, _name_filter)
1401 1401
1402 1402 start = time.time()
1403 1403 result = compute_repo_group_acl_ids('v1', self.user_id, perms, name_filter)
1404 1404 total = time.time() - start
1405 1405 log.debug('REPO GROUP ACL IDS for user %s computed in %.4fs', self, total)
1406 1406
1407 1407 return result
1408 1408
1409 1409 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1410 1410 if not perms:
1411 1411 perms = AuthUser.user_group_read_perms
1412 1412 allowed_ids = []
1413 1413 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1414 1414 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1415 1415 if perm in perms:
1416 1416 allowed_ids.append(obj_id)
1417 1417 return allowed_ids
1418 1418
1419 1419 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1420 1420 """
1421 1421 Returns list of user group ids that user have access to based on given
1422 1422 perms. The cache flag should be only used in cases that are used for
1423 1423 display purposes, NOT IN ANY CASE for permission checks.
1424 1424 """
1425 1425 from rhodecode.model.scm import UserGroupList
1426 1426 if not perms:
1427 1427 perms = AuthUser.user_group_read_perms
1428 1428
1429 1429 if not isinstance(perms, list):
1430 1430 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1431 1431
1432 1432 def _cached_user_group_acl(perm_def, _name_filter):
1433 1433 qry = UserGroup.query()
1434 1434 if _name_filter:
1435 1435 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1436 1436 qry = qry.filter(
1437 1437 UserGroup.users_group_name.ilike(ilike_expression))
1438 1438
1439 1439 return [x.users_group_id for x in
1440 1440 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1441 1441
1442 1442 log.debug('Computing USER GROUP ACL IDS user %s', self)
1443 1443
1444 1444 cache_namespace_uid = 'cache_user_user_group_acl_ids.{}'.format(self.user_id)
1445 1445 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1446 1446
1447 1447 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1448 1448 def compute_user_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1449 1449 return _cached_user_group_acl(perm_def, _name_filter)
1450 1450
1451 1451 start = time.time()
1452 1452 result = compute_user_group_acl_ids('v1', self.user_id, perms, name_filter)
1453 1453 total = time.time() - start
1454 1454 log.debug('USER GROUP ACL IDS for user %s computed in %.4fs', self, total)
1455 1455
1456 1456 return result
1457 1457
1458 1458 @property
1459 1459 def ip_allowed(self):
1460 1460 """
1461 1461 Checks if ip_addr used in constructor is allowed from defined list of
1462 1462 allowed ip_addresses for user
1463 1463
1464 1464 :returns: boolean, True if ip is in allowed ip range
1465 1465 """
1466 1466 # check IP
1467 1467 inherit = self.inherit_default_permissions
1468 1468 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1469 1469 inherit_from_default=inherit)
1470 1470
1471 1471 @property
1472 1472 def personal_repo_group(self):
1473 1473 return RepoGroup.get_user_personal_repo_group(self.user_id)
1474 1474
1475 1475 @LazyProperty
1476 1476 def feed_token(self):
1477 1477 return self.get_instance().feed_token
1478 1478
1479 1479 @LazyProperty
1480 1480 def artifact_token(self):
1481 1481 return self.get_instance().artifact_token
1482 1482
1483 1483 @classmethod
1484 1484 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1485 1485 allowed_ips = AuthUser.get_allowed_ips(
1486 1486 user_id, cache=True, inherit_from_default=inherit_from_default)
1487 1487 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1488 1488 log.debug('IP:%s for user %s is in range of %s',
1489 1489 ip_addr, user_id, allowed_ips)
1490 1490 return True
1491 1491 else:
1492 1492 log.info('Access for IP:%s forbidden for user %s, '
1493 1493 'not in %s', ip_addr, user_id, allowed_ips,
1494 1494 extra={"ip": ip_addr, "user_id": user_id})
1495 1495 return False
1496 1496
1497 1497 def get_branch_permissions(self, repo_name, perms=None):
1498 1498 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1499 1499 branch_perms = perms.get('repository_branches', {})
1500 1500 if not branch_perms:
1501 1501 return {}
1502 1502 repo_branch_perms = branch_perms.get(repo_name)
1503 1503 return repo_branch_perms or {}
1504 1504
1505 1505 def get_rule_and_branch_permission(self, repo_name, branch_name):
1506 1506 """
1507 1507 Check if this AuthUser has defined any permissions for branches. If any of
1508 1508 the rules match in order, we return the matching permissions
1509 1509 """
1510 1510
1511 1511 rule = default_perm = ''
1512 1512
1513 1513 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1514 1514 if not repo_branch_perms:
1515 1515 return rule, default_perm
1516 1516
1517 1517 # now calculate the permissions
1518 1518 for pattern, branch_perm in repo_branch_perms.items():
1519 1519 if fnmatch.fnmatch(branch_name, pattern):
1520 1520 rule = '`{}`=>{}'.format(pattern, branch_perm)
1521 1521 return rule, branch_perm
1522 1522
1523 1523 return rule, default_perm
1524 1524
1525 1525 def get_notice_messages(self):
1526 1526
1527 1527 notice_level = 'notice-error'
1528 1528 notice_messages = []
1529 1529 if self.is_default:
1530 1530 return [], notice_level
1531 1531
1532 1532 notices = UserNotice.query()\
1533 1533 .filter(UserNotice.user_id == self.user_id)\
1534 1534 .filter(UserNotice.notice_read == false())\
1535 1535 .all()
1536 1536
1537 1537 try:
1538 1538 for entry in notices:
1539 1539
1540 1540 msg = {
1541 1541 'msg_id': entry.user_notice_id,
1542 1542 'level': entry.notification_level,
1543 1543 'subject': entry.notice_subject,
1544 1544 'body': entry.notice_body,
1545 1545 }
1546 1546 notice_messages.append(msg)
1547 1547
1548 1548 log.debug('Got user %s %s messages', self, len(notice_messages))
1549 1549
1550 1550 levels = [x['level'] for x in notice_messages]
1551 1551 notice_level = 'notice-error' if 'error' in levels else 'notice-warning'
1552 1552 except Exception:
1553 1553 pass
1554 1554
1555 1555 return notice_messages, notice_level
1556 1556
1557 1557 def __repr__(self):
1558 1558 return self.repr_user(self.user_id, self.username, self.ip_addr, self.is_authenticated)
1559 1559
1560 1560 def set_authenticated(self, authenticated=True):
1561 1561 if self.user_id != self.anonymous_user.user_id:
1562 1562 self.is_authenticated = authenticated
1563 1563
1564 1564 def get_cookie_store(self):
1565 1565 return {
1566 1566 'username': self.username,
1567 1567 'password': md5(self.password or ''),
1568 1568 'user_id': self.user_id,
1569 1569 'is_authenticated': self.is_authenticated
1570 1570 }
1571 1571
1572 1572 @classmethod
1573 1573 def repr_user(cls, user_id=0, username='ANONYMOUS', ip='0.0.0.0', is_authenticated=False):
1574 1574 tmpl = "<AuthUser('id:{}[{}] ip:{} auth:{}')>"
1575 1575 return tmpl.format(user_id, username, ip, is_authenticated)
1576 1576
1577 1577 @classmethod
1578 1578 def from_cookie_store(cls, cookie_store):
1579 1579 """
1580 1580 Creates AuthUser from a cookie store
1581 1581
1582 1582 :param cls:
1583 1583 :param cookie_store:
1584 1584 """
1585 1585 user_id = cookie_store.get('user_id')
1586 1586 username = cookie_store.get('username')
1587 1587 api_key = cookie_store.get('api_key')
1588 1588 return AuthUser(user_id, api_key, username)
1589 1589
1590 1590 @classmethod
1591 1591 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1592 1592 _set = set()
1593 1593
1594 1594 if inherit_from_default:
1595 1595 def_user_id = User.get_default_user(cache=True).user_id
1596 1596 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1597 1597 if cache:
1598 1598 default_ips = default_ips.options(
1599 1599 FromCache("sql_cache_short", "get_user_ips_default"))
1600 1600
1601 1601 # populate from default user
1602 1602 for ip in default_ips:
1603 1603 try:
1604 1604 _set.add(ip.ip_addr)
1605 1605 except ObjectDeletedError:
1606 1606 # since we use heavy caching sometimes it happens that
1607 1607 # we get deleted objects here, we just skip them
1608 1608 pass
1609 1609
1610 1610 # NOTE:(marcink) we don't want to load any rules for empty
1611 1611 # user_id which is the case of access of non logged users when anonymous
1612 1612 # access is disabled
1613 1613 user_ips = []
1614 1614 if user_id:
1615 1615 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1616 1616 if cache:
1617 1617 user_ips = user_ips.options(
1618 1618 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1619 1619
1620 1620 for ip in user_ips:
1621 1621 try:
1622 1622 _set.add(ip.ip_addr)
1623 1623 except ObjectDeletedError:
1624 1624 # since we use heavy caching sometimes it happens that we get
1625 1625 # deleted objects here, we just skip them
1626 1626 pass
1627 1627 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1628 1628
1629 1629
1630 1630 def set_available_permissions(settings):
1631 1631 """
1632 1632 This function will propagate pyramid settings with all available defined
1633 1633 permission given in db. We don't want to check each time from db for new
1634 1634 permissions since adding a new permission also requires application restart
1635 1635 ie. to decorate new views with the newly created permission
1636 1636
1637 1637 :param settings: current pyramid registry.settings
1638 1638
1639 1639 """
1640 1640 log.debug('auth: getting information about all available permissions')
1641 1641 try:
1642 1642 sa = meta.Session
1643 1643 all_perms = sa.query(Permission).all()
1644 1644 settings.setdefault('available_permissions',
1645 1645 [x.permission_name for x in all_perms])
1646 1646 log.debug('auth: set available permissions')
1647 1647 except Exception:
1648 1648 log.exception('Failed to fetch permissions from the database.')
1649 1649 raise
1650 1650
1651 1651
1652 1652 def get_csrf_token(session, force_new=False, save_if_missing=True):
1653 1653 """
1654 1654 Return the current authentication token, creating one if one doesn't
1655 1655 already exist and the save_if_missing flag is present.
1656 1656
1657 1657 :param session: pass in the pyramid session, else we use the global ones
1658 1658 :param force_new: force to re-generate the token and store it in session
1659 1659 :param save_if_missing: save the newly generated token if it's missing in
1660 1660 session
1661 1661 """
1662 1662 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1663 1663 # from pyramid.csrf import get_csrf_token
1664 1664
1665 1665 if (csrf_token_key not in session and save_if_missing) or force_new:
1666 1666 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1667 1667 session[csrf_token_key] = token
1668 1668 if hasattr(session, 'save'):
1669 1669 session.save()
1670 1670 return session.get(csrf_token_key)
1671 1671
1672 1672
1673 1673 def get_request(perm_class_instance):
1674 1674 from pyramid.threadlocal import get_current_request
1675 1675 pyramid_request = get_current_request()
1676 1676 return pyramid_request
1677 1677
1678 1678
1679 1679 # CHECK DECORATORS
1680 1680 class CSRFRequired(object):
1681 1681 """
1682 1682 Decorator for authenticating a form
1683 1683
1684 1684 This decorator uses an authorization token stored in the client's
1685 1685 session for prevention of certain Cross-site request forgery (CSRF)
1686 1686 attacks (See
1687 1687 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1688 1688 information).
1689 1689
1690 1690 For use with the ``secure_form`` helper functions.
1691 1691
1692 1692 """
1693 1693 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1694 1694 self.token = token
1695 1695 self.header = header
1696 1696 self.except_methods = except_methods or []
1697 1697
1698 1698 def __call__(self, func):
1699 1699 return get_cython_compat_decorator(self.__wrapper, func)
1700 1700
1701 1701 def _get_csrf(self, _request):
1702 1702 return _request.POST.get(self.token, _request.headers.get(self.header))
1703 1703
1704 1704 def check_csrf(self, _request, cur_token):
1705 1705 supplied_token = self._get_csrf(_request)
1706 1706 return supplied_token and supplied_token == cur_token
1707 1707
1708 1708 def _get_request(self):
1709 1709 return get_request(self)
1710 1710
1711 1711 def __wrapper(self, func, *fargs, **fkwargs):
1712 1712 request = self._get_request()
1713 1713
1714 1714 if request.method in self.except_methods:
1715 1715 return func(*fargs, **fkwargs)
1716 1716
1717 1717 cur_token = get_csrf_token(request.session, save_if_missing=False)
1718 1718 if self.check_csrf(request, cur_token):
1719 1719 if request.POST.get(self.token):
1720 1720 del request.POST[self.token]
1721 1721 return func(*fargs, **fkwargs)
1722 1722 else:
1723 1723 reason = 'token-missing'
1724 1724 supplied_token = self._get_csrf(request)
1725 1725 if supplied_token and cur_token != supplied_token:
1726 1726 reason = 'token-mismatch [%s:%s]' % (
1727 1727 cur_token or ''[:6], supplied_token or ''[:6])
1728 1728
1729 1729 csrf_message = \
1730 1730 ("Cross-site request forgery detected, request denied. See "
1731 1731 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1732 1732 "more information.")
1733 1733 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1734 1734 'REMOTE_ADDR:%s, HEADERS:%s' % (
1735 1735 request, reason, request.remote_addr, request.headers))
1736 1736
1737 1737 raise HTTPForbidden(explanation=csrf_message)
1738 1738
1739 1739
1740 1740 class LoginRequired(object):
1741 1741 """
1742 1742 Must be logged in to execute this function else
1743 1743 redirect to login page
1744 1744
1745 1745 :param api_access: if enabled this checks only for valid auth token
1746 1746 and grants access based on valid token
1747 1747 """
1748 1748 def __init__(self, auth_token_access=None):
1749 1749 self.auth_token_access = auth_token_access
1750 1750 if self.auth_token_access:
1751 1751 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1752 1752 if not valid_type:
1753 1753 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1754 1754 UserApiKeys.ROLES, auth_token_access))
1755 1755
1756 1756 def __call__(self, func):
1757 1757 return get_cython_compat_decorator(self.__wrapper, func)
1758 1758
1759 1759 def _get_request(self):
1760 1760 return get_request(self)
1761 1761
1762 1762 def __wrapper(self, func, *fargs, **fkwargs):
1763 1763 from rhodecode.lib import helpers as h
1764 1764 cls = fargs[0]
1765 1765 user = cls._rhodecode_user
1766 1766 request = self._get_request()
1767 1767 _ = request.translate
1768 1768
1769 1769 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1770 1770 log.debug('Starting login restriction checks for user: %s', user)
1771 1771 # check if our IP is allowed
1772 1772 ip_access_valid = True
1773 1773 if not user.ip_allowed:
1774 1774 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1775 1775 category='warning')
1776 1776 ip_access_valid = False
1777 1777
1778 1778 # we used stored token that is extract from GET or URL param (if any)
1779 1779 _auth_token = request.user_auth_token
1780 1780
1781 1781 # check if we used an AUTH_TOKEN and it's a valid one
1782 1782 # defined white-list of controllers which API access will be enabled
1783 1783 whitelist = None
1784 1784 if self.auth_token_access:
1785 1785 # since this location is allowed by @LoginRequired decorator it's our
1786 1786 # only whitelist
1787 1787 whitelist = [loc]
1788 1788 auth_token_access_valid = allowed_auth_token_access(
1789 1789 loc, whitelist=whitelist, auth_token=_auth_token)
1790 1790
1791 1791 # explicit controller is enabled or API is in our whitelist
1792 1792 if auth_token_access_valid:
1793 1793 log.debug('Checking AUTH TOKEN access for %s', cls)
1794 1794 db_user = user.get_instance()
1795 1795
1796 1796 if db_user:
1797 1797 if self.auth_token_access:
1798 1798 roles = self.auth_token_access
1799 1799 else:
1800 1800 roles = [UserApiKeys.ROLE_HTTP]
1801 1801 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1802 1802 db_user, roles)
1803 1803 token_match = db_user.authenticate_by_token(
1804 1804 _auth_token, roles=roles)
1805 1805 else:
1806 1806 log.debug('Unable to fetch db instance for auth user: %s', user)
1807 1807 token_match = False
1808 1808
1809 1809 if _auth_token and token_match:
1810 1810 auth_token_access_valid = True
1811 1811 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1812 1812 else:
1813 1813 auth_token_access_valid = False
1814 1814 if not _auth_token:
1815 1815 log.debug("AUTH TOKEN *NOT* present in request")
1816 1816 else:
1817 1817 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1818 1818
1819 1819 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1820 1820 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1821 1821 else 'AUTH_TOKEN_AUTH'
1822 1822
1823 1823 if ip_access_valid and (
1824 1824 user.is_authenticated or auth_token_access_valid):
1825 1825 log.info('user %s authenticating with:%s IS authenticated on func %s',
1826 1826 user, reason, loc)
1827 1827
1828 1828 return func(*fargs, **fkwargs)
1829 1829 else:
1830 1830 log.warning(
1831 1831 'user %s authenticating with:%s NOT authenticated on '
1832 1832 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1833 1833 user, reason, loc, ip_access_valid, auth_token_access_valid)
1834 1834 # we preserve the get PARAM
1835 1835 came_from = get_came_from(request)
1836 1836
1837 1837 log.debug('redirecting to login page with %s', came_from)
1838 1838 raise HTTPFound(
1839 1839 h.route_path('login', _query={'came_from': came_from}))
1840 1840
1841 1841
1842 1842 class NotAnonymous(object):
1843 1843 """
1844 1844 Must be logged in to execute this function else
1845 1845 redirect to login page
1846 1846 """
1847 1847
1848 1848 def __call__(self, func):
1849 1849 return get_cython_compat_decorator(self.__wrapper, func)
1850 1850
1851 1851 def _get_request(self):
1852 1852 return get_request(self)
1853 1853
1854 1854 def __wrapper(self, func, *fargs, **fkwargs):
1855 1855 import rhodecode.lib.helpers as h
1856 1856 cls = fargs[0]
1857 1857 self.user = cls._rhodecode_user
1858 1858 request = self._get_request()
1859 1859 _ = request.translate
1860 1860 log.debug('Checking if user is not anonymous @%s', cls)
1861 1861
1862 1862 anonymous = self.user.username == User.DEFAULT_USER
1863 1863
1864 1864 if anonymous:
1865 1865 came_from = get_came_from(request)
1866 1866 h.flash(_('You need to be a registered user to '
1867 1867 'perform this action'),
1868 1868 category='warning')
1869 1869 raise HTTPFound(
1870 1870 h.route_path('login', _query={'came_from': came_from}))
1871 1871 else:
1872 1872 return func(*fargs, **fkwargs)
1873 1873
1874 1874
1875 1875 class PermsDecorator(object):
1876 1876 """
1877 1877 Base class for controller decorators, we extract the current user from
1878 1878 the class itself, which has it stored in base controllers
1879 1879 """
1880 1880
1881 1881 def __init__(self, *required_perms):
1882 1882 self.required_perms = set(required_perms)
1883 1883
1884 1884 def __call__(self, func):
1885 1885 return get_cython_compat_decorator(self.__wrapper, func)
1886 1886
1887 1887 def _get_request(self):
1888 1888 return get_request(self)
1889 1889
1890 1890 def __wrapper(self, func, *fargs, **fkwargs):
1891 1891 import rhodecode.lib.helpers as h
1892 1892 cls = fargs[0]
1893 1893 _user = cls._rhodecode_user
1894 1894 request = self._get_request()
1895 1895 _ = request.translate
1896 1896
1897 1897 log.debug('checking %s permissions %s for %s %s',
1898 1898 self.__class__.__name__, self.required_perms, cls, _user)
1899 1899
1900 1900 if self.check_permissions(_user):
1901 1901 log.debug('Permission granted for %s %s', cls, _user)
1902 1902 return func(*fargs, **fkwargs)
1903 1903
1904 1904 else:
1905 1905 log.debug('Permission denied for %s %s', cls, _user)
1906 1906 anonymous = _user.username == User.DEFAULT_USER
1907 1907
1908 1908 if anonymous:
1909 1909 came_from = get_came_from(self._get_request())
1910 1910 h.flash(_('You need to be signed in to view this page'),
1911 1911 category='warning')
1912 1912 raise HTTPFound(
1913 1913 h.route_path('login', _query={'came_from': came_from}))
1914 1914
1915 1915 else:
1916 1916 # redirect with 404 to prevent resource discovery
1917 1917 raise HTTPNotFound()
1918 1918
1919 1919 def check_permissions(self, user):
1920 1920 """Dummy function for overriding"""
1921 1921 raise NotImplementedError(
1922 1922 'You have to write this function in child class')
1923 1923
1924 1924
1925 1925 class HasPermissionAllDecorator(PermsDecorator):
1926 1926 """
1927 1927 Checks for access permission for all given predicates. All of them
1928 1928 have to be meet in order to fulfill the request
1929 1929 """
1930 1930
1931 1931 def check_permissions(self, user):
1932 1932 perms = user.permissions_with_scope({})
1933 1933 if self.required_perms.issubset(perms['global']):
1934 1934 return True
1935 1935 return False
1936 1936
1937 1937
1938 1938 class HasPermissionAnyDecorator(PermsDecorator):
1939 1939 """
1940 1940 Checks for access permission for any of given predicates. In order to
1941 1941 fulfill the request any of predicates must be meet
1942 1942 """
1943 1943
1944 1944 def check_permissions(self, user):
1945 1945 perms = user.permissions_with_scope({})
1946 1946 if self.required_perms.intersection(perms['global']):
1947 1947 return True
1948 1948 return False
1949 1949
1950 1950
1951 1951 class HasRepoPermissionAllDecorator(PermsDecorator):
1952 1952 """
1953 1953 Checks for access permission for all given predicates for specific
1954 1954 repository. All of them have to be meet in order to fulfill the request
1955 1955 """
1956 1956 def _get_repo_name(self):
1957 1957 _request = self._get_request()
1958 1958 return get_repo_slug(_request)
1959 1959
1960 1960 def check_permissions(self, user):
1961 1961 perms = user.permissions
1962 1962 repo_name = self._get_repo_name()
1963 1963
1964 1964 try:
1965 1965 user_perms = {perms['repositories'][repo_name]}
1966 1966 except KeyError:
1967 1967 log.debug('cannot locate repo with name: `%s` in permissions defs',
1968 1968 repo_name)
1969 1969 return False
1970 1970
1971 1971 log.debug('checking `%s` permissions for repo `%s`',
1972 1972 user_perms, repo_name)
1973 1973 if self.required_perms.issubset(user_perms):
1974 1974 return True
1975 1975 return False
1976 1976
1977 1977
1978 1978 class HasRepoPermissionAnyDecorator(PermsDecorator):
1979 1979 """
1980 1980 Checks for access permission for any of given predicates for specific
1981 1981 repository. In order to fulfill the request any of predicates must be meet
1982 1982 """
1983 1983 def _get_repo_name(self):
1984 1984 _request = self._get_request()
1985 1985 return get_repo_slug(_request)
1986 1986
1987 1987 def check_permissions(self, user):
1988 1988 perms = user.permissions
1989 1989 repo_name = self._get_repo_name()
1990 1990
1991 1991 try:
1992 1992 user_perms = {perms['repositories'][repo_name]}
1993 1993 except KeyError:
1994 1994 log.debug(
1995 1995 'cannot locate repo with name: `%s` in permissions defs',
1996 1996 repo_name)
1997 1997 return False
1998 1998
1999 1999 log.debug('checking `%s` permissions for repo `%s`',
2000 2000 user_perms, repo_name)
2001 2001 if self.required_perms.intersection(user_perms):
2002 2002 return True
2003 2003 return False
2004 2004
2005 2005
2006 2006 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
2007 2007 """
2008 2008 Checks for access permission for all given predicates for specific
2009 2009 repository group. All of them have to be meet in order to
2010 2010 fulfill the request
2011 2011 """
2012 2012 def _get_repo_group_name(self):
2013 2013 _request = self._get_request()
2014 2014 return get_repo_group_slug(_request)
2015 2015
2016 2016 def check_permissions(self, user):
2017 2017 perms = user.permissions
2018 2018 group_name = self._get_repo_group_name()
2019 2019 try:
2020 2020 user_perms = {perms['repositories_groups'][group_name]}
2021 2021 except KeyError:
2022 2022 log.debug(
2023 2023 'cannot locate repo group with name: `%s` in permissions defs',
2024 2024 group_name)
2025 2025 return False
2026 2026
2027 2027 log.debug('checking `%s` permissions for repo group `%s`',
2028 2028 user_perms, group_name)
2029 2029 if self.required_perms.issubset(user_perms):
2030 2030 return True
2031 2031 return False
2032 2032
2033 2033
2034 2034 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
2035 2035 """
2036 2036 Checks for access permission for any of given predicates for specific
2037 2037 repository group. In order to fulfill the request any
2038 2038 of predicates must be met
2039 2039 """
2040 2040 def _get_repo_group_name(self):
2041 2041 _request = self._get_request()
2042 2042 return get_repo_group_slug(_request)
2043 2043
2044 2044 def check_permissions(self, user):
2045 2045 perms = user.permissions
2046 2046 group_name = self._get_repo_group_name()
2047 2047
2048 2048 try:
2049 2049 user_perms = {perms['repositories_groups'][group_name]}
2050 2050 except KeyError:
2051 2051 log.debug(
2052 2052 'cannot locate repo group with name: `%s` in permissions defs',
2053 2053 group_name)
2054 2054 return False
2055 2055
2056 2056 log.debug('checking `%s` permissions for repo group `%s`',
2057 2057 user_perms, group_name)
2058 2058 if self.required_perms.intersection(user_perms):
2059 2059 return True
2060 2060 return False
2061 2061
2062 2062
2063 2063 class HasUserGroupPermissionAllDecorator(PermsDecorator):
2064 2064 """
2065 2065 Checks for access permission for all given predicates for specific
2066 2066 user group. All of them have to be meet in order to fulfill the request
2067 2067 """
2068 2068 def _get_user_group_name(self):
2069 2069 _request = self._get_request()
2070 2070 return get_user_group_slug(_request)
2071 2071
2072 2072 def check_permissions(self, user):
2073 2073 perms = user.permissions
2074 2074 group_name = self._get_user_group_name()
2075 2075 try:
2076 2076 user_perms = {perms['user_groups'][group_name]}
2077 2077 except KeyError:
2078 2078 return False
2079 2079
2080 2080 if self.required_perms.issubset(user_perms):
2081 2081 return True
2082 2082 return False
2083 2083
2084 2084
2085 2085 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
2086 2086 """
2087 2087 Checks for access permission for any of given predicates for specific
2088 2088 user group. In order to fulfill the request any of predicates must be meet
2089 2089 """
2090 2090 def _get_user_group_name(self):
2091 2091 _request = self._get_request()
2092 2092 return get_user_group_slug(_request)
2093 2093
2094 2094 def check_permissions(self, user):
2095 2095 perms = user.permissions
2096 2096 group_name = self._get_user_group_name()
2097 2097 try:
2098 2098 user_perms = {perms['user_groups'][group_name]}
2099 2099 except KeyError:
2100 2100 return False
2101 2101
2102 2102 if self.required_perms.intersection(user_perms):
2103 2103 return True
2104 2104 return False
2105 2105
2106 2106
2107 2107 # CHECK FUNCTIONS
2108 2108 class PermsFunction(object):
2109 2109 """Base function for other check functions"""
2110 2110
2111 2111 def __init__(self, *perms):
2112 2112 self.required_perms = set(perms)
2113 2113 self.repo_name = None
2114 2114 self.repo_group_name = None
2115 2115 self.user_group_name = None
2116 2116
2117 2117 def __bool__(self):
2118 2118 import inspect
2119 2119 frame = inspect.currentframe()
2120 2120 stack_trace = traceback.format_stack(frame)
2121 2121 log.error('Checking bool value on a class instance of perm '
2122 2122 'function is not allowed: %s', ''.join(stack_trace))
2123 2123 # rather than throwing errors, here we always return False so if by
2124 2124 # accident someone checks truth for just an instance it will always end
2125 2125 # up in returning False
2126 2126 return False
2127 2127 __nonzero__ = __bool__
2128 2128
2129 2129 def __call__(self, check_location='', user=None):
2130 2130 if not user:
2131 2131 log.debug('Using user attribute from global request')
2132 2132 request = self._get_request()
2133 2133 user = request.user
2134 2134
2135 2135 # init auth user if not already given
2136 2136 if not isinstance(user, AuthUser):
2137 2137 log.debug('Wrapping user %s into AuthUser', user)
2138 2138 user = AuthUser(user.user_id)
2139 2139
2140 2140 cls_name = self.__class__.__name__
2141 2141 check_scope = self._get_check_scope(cls_name)
2142 2142 check_location = check_location or 'unspecified location'
2143 2143
2144 2144 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2145 2145 self.required_perms, user, check_scope, check_location)
2146 2146 if not user:
2147 2147 log.warning('Empty user given for permission check')
2148 2148 return False
2149 2149
2150 2150 if self.check_permissions(user):
2151 2151 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2152 2152 check_scope, user, check_location)
2153 2153 return True
2154 2154
2155 2155 else:
2156 2156 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2157 2157 check_scope, user, check_location)
2158 2158 return False
2159 2159
2160 2160 def _get_request(self):
2161 2161 return get_request(self)
2162 2162
2163 2163 def _get_check_scope(self, cls_name):
2164 2164 return {
2165 2165 'HasPermissionAll': 'GLOBAL',
2166 2166 'HasPermissionAny': 'GLOBAL',
2167 2167 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2168 2168 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2169 2169 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2170 2170 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2171 2171 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2172 2172 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2173 2173 }.get(cls_name, '?:%s' % cls_name)
2174 2174
2175 2175 def check_permissions(self, user):
2176 2176 """Dummy function for overriding"""
2177 2177 raise Exception('You have to write this function in child class')
2178 2178
2179 2179
2180 2180 class HasPermissionAll(PermsFunction):
2181 2181 def check_permissions(self, user):
2182 2182 perms = user.permissions_with_scope({})
2183 2183 if self.required_perms.issubset(perms.get('global')):
2184 2184 return True
2185 2185 return False
2186 2186
2187 2187
2188 2188 class HasPermissionAny(PermsFunction):
2189 2189 def check_permissions(self, user):
2190 2190 perms = user.permissions_with_scope({})
2191 2191 if self.required_perms.intersection(perms.get('global')):
2192 2192 return True
2193 2193 return False
2194 2194
2195 2195
2196 2196 class HasRepoPermissionAll(PermsFunction):
2197 2197 def __call__(self, repo_name=None, check_location='', user=None):
2198 2198 self.repo_name = repo_name
2199 2199 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2200 2200
2201 2201 def _get_repo_name(self):
2202 2202 if not self.repo_name:
2203 2203 _request = self._get_request()
2204 2204 self.repo_name = get_repo_slug(_request)
2205 2205 return self.repo_name
2206 2206
2207 2207 def check_permissions(self, user):
2208 2208 self.repo_name = self._get_repo_name()
2209 2209 perms = user.permissions
2210 2210 try:
2211 2211 user_perms = {perms['repositories'][self.repo_name]}
2212 2212 except KeyError:
2213 2213 return False
2214 2214 if self.required_perms.issubset(user_perms):
2215 2215 return True
2216 2216 return False
2217 2217
2218 2218
2219 2219 class HasRepoPermissionAny(PermsFunction):
2220 2220 def __call__(self, repo_name=None, check_location='', user=None):
2221 2221 self.repo_name = repo_name
2222 2222 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2223 2223
2224 2224 def _get_repo_name(self):
2225 2225 if not self.repo_name:
2226 2226 _request = self._get_request()
2227 2227 self.repo_name = get_repo_slug(_request)
2228 2228 return self.repo_name
2229 2229
2230 2230 def check_permissions(self, user):
2231 2231 self.repo_name = self._get_repo_name()
2232 2232 perms = user.permissions
2233 2233 try:
2234 2234 user_perms = {perms['repositories'][self.repo_name]}
2235 2235 except KeyError:
2236 2236 return False
2237 2237 if self.required_perms.intersection(user_perms):
2238 2238 return True
2239 2239 return False
2240 2240
2241 2241
2242 2242 class HasRepoGroupPermissionAny(PermsFunction):
2243 2243 def __call__(self, group_name=None, check_location='', user=None):
2244 2244 self.repo_group_name = group_name
2245 2245 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2246 2246
2247 2247 def check_permissions(self, user):
2248 2248 perms = user.permissions
2249 2249 try:
2250 2250 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2251 2251 except KeyError:
2252 2252 return False
2253 2253 if self.required_perms.intersection(user_perms):
2254 2254 return True
2255 2255 return False
2256 2256
2257 2257
2258 2258 class HasRepoGroupPermissionAll(PermsFunction):
2259 2259 def __call__(self, group_name=None, check_location='', user=None):
2260 2260 self.repo_group_name = group_name
2261 2261 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2262 2262
2263 2263 def check_permissions(self, user):
2264 2264 perms = user.permissions
2265 2265 try:
2266 2266 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2267 2267 except KeyError:
2268 2268 return False
2269 2269 if self.required_perms.issubset(user_perms):
2270 2270 return True
2271 2271 return False
2272 2272
2273 2273
2274 2274 class HasUserGroupPermissionAny(PermsFunction):
2275 2275 def __call__(self, user_group_name=None, check_location='', user=None):
2276 2276 self.user_group_name = user_group_name
2277 2277 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2278 2278
2279 2279 def check_permissions(self, user):
2280 2280 perms = user.permissions
2281 2281 try:
2282 2282 user_perms = {perms['user_groups'][self.user_group_name]}
2283 2283 except KeyError:
2284 2284 return False
2285 2285 if self.required_perms.intersection(user_perms):
2286 2286 return True
2287 2287 return False
2288 2288
2289 2289
2290 2290 class HasUserGroupPermissionAll(PermsFunction):
2291 2291 def __call__(self, user_group_name=None, check_location='', user=None):
2292 2292 self.user_group_name = user_group_name
2293 2293 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2294 2294
2295 2295 def check_permissions(self, user):
2296 2296 perms = user.permissions
2297 2297 try:
2298 2298 user_perms = {perms['user_groups'][self.user_group_name]}
2299 2299 except KeyError:
2300 2300 return False
2301 2301 if self.required_perms.issubset(user_perms):
2302 2302 return True
2303 2303 return False
2304 2304
2305 2305
2306 2306 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2307 2307 class HasPermissionAnyMiddleware(object):
2308 2308 def __init__(self, *perms):
2309 2309 self.required_perms = set(perms)
2310 2310
2311 2311 def __call__(self, auth_user, repo_name):
2312 2312 # repo_name MUST be unicode, since we handle keys in permission
2313 2313 # dict by unicode
2314 2314 repo_name = safe_unicode(repo_name)
2315 2315 log.debug(
2316 2316 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2317 2317 self.required_perms, auth_user, repo_name)
2318 2318
2319 2319 if self.check_permissions(auth_user, repo_name):
2320 2320 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2321 2321 repo_name, auth_user, 'PermissionMiddleware')
2322 2322 return True
2323 2323
2324 2324 else:
2325 2325 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2326 2326 repo_name, auth_user, 'PermissionMiddleware')
2327 2327 return False
2328 2328
2329 2329 def check_permissions(self, user, repo_name):
2330 2330 perms = user.permissions_with_scope({'repo_name': repo_name})
2331 2331
2332 2332 try:
2333 2333 user_perms = {perms['repositories'][repo_name]}
2334 2334 except Exception:
2335 2335 log.exception('Error while accessing user permissions')
2336 2336 return False
2337 2337
2338 2338 if self.required_perms.intersection(user_perms):
2339 2339 return True
2340 2340 return False
2341 2341
2342 2342
2343 2343 # SPECIAL VERSION TO HANDLE API AUTH
2344 2344 class _BaseApiPerm(object):
2345 2345 def __init__(self, *perms):
2346 2346 self.required_perms = set(perms)
2347 2347
2348 2348 def __call__(self, check_location=None, user=None, repo_name=None,
2349 2349 group_name=None, user_group_name=None):
2350 2350 cls_name = self.__class__.__name__
2351 2351 check_scope = 'global:%s' % (self.required_perms,)
2352 2352 if repo_name:
2353 2353 check_scope += ', repo_name:%s' % (repo_name,)
2354 2354
2355 2355 if group_name:
2356 2356 check_scope += ', repo_group_name:%s' % (group_name,)
2357 2357
2358 2358 if user_group_name:
2359 2359 check_scope += ', user_group_name:%s' % (user_group_name,)
2360 2360
2361 2361 log.debug('checking cls:%s %s %s @ %s',
2362 2362 cls_name, self.required_perms, check_scope, check_location)
2363 2363 if not user:
2364 2364 log.debug('Empty User passed into arguments')
2365 2365 return False
2366 2366
2367 2367 # process user
2368 2368 if not isinstance(user, AuthUser):
2369 2369 user = AuthUser(user.user_id)
2370 2370 if not check_location:
2371 2371 check_location = 'unspecified'
2372 2372 if self.check_permissions(user.permissions, repo_name, group_name,
2373 2373 user_group_name):
2374 2374 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2375 2375 check_scope, user, check_location)
2376 2376 return True
2377 2377
2378 2378 else:
2379 2379 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2380 2380 check_scope, user, check_location)
2381 2381 return False
2382 2382
2383 2383 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2384 2384 user_group_name=None):
2385 2385 """
2386 2386 implement in child class should return True if permissions are ok,
2387 2387 False otherwise
2388 2388
2389 2389 :param perm_defs: dict with permission definitions
2390 2390 :param repo_name: repo name
2391 2391 """
2392 2392 raise NotImplementedError()
2393 2393
2394 2394
2395 2395 class HasPermissionAllApi(_BaseApiPerm):
2396 2396 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2397 2397 user_group_name=None):
2398 2398 if self.required_perms.issubset(perm_defs.get('global')):
2399 2399 return True
2400 2400 return False
2401 2401
2402 2402
2403 2403 class HasPermissionAnyApi(_BaseApiPerm):
2404 2404 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2405 2405 user_group_name=None):
2406 2406 if self.required_perms.intersection(perm_defs.get('global')):
2407 2407 return True
2408 2408 return False
2409 2409
2410 2410
2411 2411 class HasRepoPermissionAllApi(_BaseApiPerm):
2412 2412 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2413 2413 user_group_name=None):
2414 2414 try:
2415 2415 _user_perms = {perm_defs['repositories'][repo_name]}
2416 2416 except KeyError:
2417 2417 log.warning(traceback.format_exc())
2418 2418 return False
2419 2419 if self.required_perms.issubset(_user_perms):
2420 2420 return True
2421 2421 return False
2422 2422
2423 2423
2424 2424 class HasRepoPermissionAnyApi(_BaseApiPerm):
2425 2425 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2426 2426 user_group_name=None):
2427 2427 try:
2428 2428 _user_perms = {perm_defs['repositories'][repo_name]}
2429 2429 except KeyError:
2430 2430 log.warning(traceback.format_exc())
2431 2431 return False
2432 2432 if self.required_perms.intersection(_user_perms):
2433 2433 return True
2434 2434 return False
2435 2435
2436 2436
2437 2437 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2438 2438 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2439 2439 user_group_name=None):
2440 2440 try:
2441 2441 _user_perms = {perm_defs['repositories_groups'][group_name]}
2442 2442 except KeyError:
2443 2443 log.warning(traceback.format_exc())
2444 2444 return False
2445 2445 if self.required_perms.intersection(_user_perms):
2446 2446 return True
2447 2447 return False
2448 2448
2449 2449
2450 2450 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2451 2451 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2452 2452 user_group_name=None):
2453 2453 try:
2454 2454 _user_perms = {perm_defs['repositories_groups'][group_name]}
2455 2455 except KeyError:
2456 2456 log.warning(traceback.format_exc())
2457 2457 return False
2458 2458 if self.required_perms.issubset(_user_perms):
2459 2459 return True
2460 2460 return False
2461 2461
2462 2462
2463 2463 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2464 2464 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2465 2465 user_group_name=None):
2466 2466 try:
2467 2467 _user_perms = {perm_defs['user_groups'][user_group_name]}
2468 2468 except KeyError:
2469 2469 log.warning(traceback.format_exc())
2470 2470 return False
2471 2471 if self.required_perms.intersection(_user_perms):
2472 2472 return True
2473 2473 return False
2474 2474
2475 2475
2476 2476 def check_ip_access(source_ip, allowed_ips=None):
2477 2477 """
2478 2478 Checks if source_ip is a subnet of any of allowed_ips.
2479 2479
2480 2480 :param source_ip:
2481 2481 :param allowed_ips: list of allowed ips together with mask
2482 2482 """
2483 2483 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2484 2484 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2485 2485 if isinstance(allowed_ips, (tuple, list, set)):
2486 2486 for ip in allowed_ips:
2487 2487 ip = safe_unicode(ip)
2488 2488 try:
2489 2489 network_address = ipaddress.ip_network(ip, strict=False)
2490 2490 if source_ip_address in network_address:
2491 2491 log.debug('IP %s is network %s', source_ip_address, network_address)
2492 2492 return True
2493 2493 # for any case we cannot determine the IP, don't crash just
2494 2494 # skip it and log as error, we want to say forbidden still when
2495 2495 # sending bad IP
2496 2496 except Exception:
2497 2497 log.error(traceback.format_exc())
2498 2498 continue
2499 2499 return False
2500 2500
2501 2501
2502 2502 def get_cython_compat_decorator(wrapper, func):
2503 2503 """
2504 2504 Creates a cython compatible decorator. The previously used
2505 2505 decorator.decorator() function seems to be incompatible with cython.
2506 2506
2507 2507 :param wrapper: __wrapper method of the decorator class
2508 2508 :param func: decorated function
2509 2509 """
2510 2510 @wraps(func)
2511 2511 def local_wrapper(*args, **kwds):
2512 2512 return wrapper(func, *args, **kwds)
2513 2513 local_wrapper.__wrapped__ = func
2514 2514 return local_wrapper
2515 2515
2516 2516
@@ -1,302 +1,302 b''
1 1 """
2 2 Code to generate a Python model from a database or differences
3 3 between a model and database.
4 4
5 5 Some of this is borrowed heavily from the AutoCode project at:
6 6 http://code.google.com/p/sqlautocode/
7 7 """
8 8
9 9 import sys
10 10 import logging
11 11
12 12 import sqlalchemy
13 13
14 14 import rhodecode.lib.dbmigrate.migrate
15 15 import rhodecode.lib.dbmigrate.migrate.changeset
16 16
17 17
18 18 log = logging.getLogger(__name__)
19 19 HEADER = """
20 20 ## File autogenerated by genmodel.py
21 21
22 22 from sqlalchemy import *
23 23 """
24 24
25 25 META_DEFINITION = "meta = MetaData()"
26 26
27 27 DECLARATIVE_DEFINITION = """
28 28 from sqlalchemy.ext import declarative
29 29
30 30 Base = declarative.declarative_base()
31 31 """
32 32
33 33
34 34 class ModelGenerator(object):
35 35 """Various transformations from an A, B diff.
36 36
37 37 In the implementation, A tends to be called the model and B
38 38 the database (although this is not true of all diffs).
39 39 The diff is directionless, but transformations apply the diff
40 40 in a particular direction, described in the method name.
41 41 """
42 42
43 43 def __init__(self, diff, engine, declarative=False):
44 44 self.diff = diff
45 45 self.engine = engine
46 46 self.declarative = declarative
47 47
48 48 def column_repr(self, col):
49 49 kwarg = []
50 50 if col.key != col.name:
51 51 kwarg.append('key')
52 52 if col.primary_key:
53 53 col.primary_key = True # otherwise it dumps it as 1
54 54 kwarg.append('primary_key')
55 55 if not col.nullable:
56 56 kwarg.append('nullable')
57 57 if col.onupdate:
58 58 kwarg.append('onupdate')
59 59 if col.default:
60 60 if col.primary_key:
61 61 # I found that PostgreSQL automatically creates a
62 62 # default value for the sequence, but let's not show
63 63 # that.
64 64 pass
65 65 else:
66 66 kwarg.append('default')
67 67 args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg]
68 68
69 69 # crs: not sure if this is good idea, but it gets rid of extra
70 70 # u''
71 71 name = col.name.encode('utf8')
72 72
73 73 type_ = col.type
74 74 for cls in col.type.__class__.__mro__:
75 75 if cls.__module__ == 'sqlalchemy.types' and \
76 76 not cls.__name__.isupper():
77 77 if cls is not type_.__class__:
78 78 type_ = cls()
79 79 break
80 80
81 81 type_repr = repr(type_)
82 82 if type_repr.endswith('()'):
83 83 type_repr = type_repr[:-2]
84 84
85 85 constraints = [repr(cn) for cn in col.constraints]
86 86
87 87 data = {
88 88 'name': name,
89 89 'commonStuff': ', '.join([type_repr] + constraints + args),
90 90 }
91 91
92 92 if self.declarative:
93 93 return """%(name)s = Column(%(commonStuff)s)""" % data
94 94 else:
95 95 return """Column(%(name)r, %(commonStuff)s)""" % data
96 96
97 97 def _getTableDefn(self, table, metaName='meta'):
98 98 out = []
99 99 tableName = table.name
100 100 if self.declarative:
101 101 out.append("class %(table)s(Base):" % {'table': tableName})
102 102 out.append(" __tablename__ = '%(table)s'\n" %
103 103 {'table': tableName})
104 104 for col in table.columns:
105 105 out.append(" %s" % self.column_repr(col))
106 106 out.append('\n')
107 107 else:
108 108 out.append("%(table)s = Table('%(table)s', %(meta)s," %
109 109 {'table': tableName, 'meta': metaName})
110 110 for col in table.columns:
111 111 out.append(" %s," % self.column_repr(col))
112 112 out.append(")\n")
113 113 return out
114 114
115 115 def _get_tables(self,missingA=False,missingB=False,modified=False):
116 116 to_process = []
117 117 for bool_,names,metadata in (
118 118 (missingA,self.diff.tables_missing_from_A,self.diff.metadataB),
119 119 (missingB,self.diff.tables_missing_from_B,self.diff.metadataA),
120 120 (modified,self.diff.tables_different,self.diff.metadataA),
121 121 ):
122 122 if bool_:
123 123 for name in names:
124 124 yield metadata.tables.get(name)
125 125
126 126 def _genModelHeader(self, tables):
127 127 out = []
128 128 import_index = []
129 129
130 130 out.append(HEADER)
131 131
132 132 for table in tables:
133 133 for col in table.columns:
134 134 if "dialects" in col.type.__module__ and \
135 135 col.type.__class__ not in import_index:
136 136 out.append("from " + col.type.__module__ +
137 137 " import " + col.type.__class__.__name__)
138 138 import_index.append(col.type.__class__)
139 139
140 140 out.append("")
141 141
142 142 if self.declarative:
143 143 out.append(DECLARATIVE_DEFINITION)
144 144 else:
145 145 out.append(META_DEFINITION)
146 146 out.append("")
147 147
148 148 return out
149 149
150 150 def genBDefinition(self):
151 151 """Generates the source code for a definition of B.
152 152
153 153 Assumes a diff where A is empty.
154 154
155 155 Was: toPython. Assume database (B) is current and model (A) is empty.
156 156 """
157 157
158 158 out = []
159 159 out.extend(self._genModelHeader(self._get_tables(missingA=True)))
160 160 for table in self._get_tables(missingA=True):
161 161 out.extend(self._getTableDefn(table))
162 162 return '\n'.join(out)
163 163
164 164 def genB2AMigration(self, indent=' '):
165 165 """Generate a migration from B to A.
166 166
167 167 Was: toUpgradeDowngradePython
168 168 Assume model (A) is most current and database (B) is out-of-date.
169 169 """
170 170
171 171 decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema',
172 172 'pre_meta = MetaData()',
173 173 'post_meta = MetaData()',
174 174 ]
175 175 upgradeCommands = ['pre_meta.bind = migrate_engine',
176 176 'post_meta.bind = migrate_engine']
177 177 downgradeCommands = list(upgradeCommands)
178 178
179 179 for tn in self.diff.tables_missing_from_A:
180 180 pre_table = self.diff.metadataB.tables[tn]
181 181 decls.extend(self._getTableDefn(pre_table, metaName='pre_meta'))
182 182 upgradeCommands.append(
183 183 "pre_meta.tables[%(table)r].drop()" % {'table': tn})
184 184 downgradeCommands.append(
185 185 "pre_meta.tables[%(table)r].create()" % {'table': tn})
186 186
187 187 for tn in self.diff.tables_missing_from_B:
188 188 post_table = self.diff.metadataA.tables[tn]
189 189 decls.extend(self._getTableDefn(post_table, metaName='post_meta'))
190 190 upgradeCommands.append(
191 191 "post_meta.tables[%(table)r].create()" % {'table': tn})
192 192 downgradeCommands.append(
193 193 "post_meta.tables[%(table)r].drop()" % {'table': tn})
194 194
195 for (tn, td) in self.diff.tables_different.iteritems():
195 for (tn, td) in self.diff.tables_different.items():
196 196 if td.columns_missing_from_A or td.columns_different:
197 197 pre_table = self.diff.metadataB.tables[tn]
198 198 decls.extend(self._getTableDefn(
199 199 pre_table, metaName='pre_meta'))
200 200 if td.columns_missing_from_B or td.columns_different:
201 201 post_table = self.diff.metadataA.tables[tn]
202 202 decls.extend(self._getTableDefn(
203 203 post_table, metaName='post_meta'))
204 204
205 205 for col in td.columns_missing_from_A:
206 206 upgradeCommands.append(
207 207 'pre_meta.tables[%r].columns[%r].drop()' % (tn, col))
208 208 downgradeCommands.append(
209 209 'pre_meta.tables[%r].columns[%r].create()' % (tn, col))
210 210 for col in td.columns_missing_from_B:
211 211 upgradeCommands.append(
212 212 'post_meta.tables[%r].columns[%r].create()' % (tn, col))
213 213 downgradeCommands.append(
214 214 'post_meta.tables[%r].columns[%r].drop()' % (tn, col))
215 215 for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different:
216 216 upgradeCommands.append(
217 217 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
218 218 tn, modelCol.name, databaseCol.name))
219 219 downgradeCommands.append(
220 220 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
221 221 tn, modelCol.name, databaseCol.name))
222 222
223 223 return (
224 224 '\n'.join(decls),
225 225 '\n'.join('%s%s' % (indent, line) for line in upgradeCommands),
226 226 '\n'.join('%s%s' % (indent, line) for line in downgradeCommands))
227 227
228 228 def _db_can_handle_this_change(self,td):
229 229 """Check if the database can handle going from B to A."""
230 230
231 231 if (td.columns_missing_from_B
232 232 and not td.columns_missing_from_A
233 233 and not td.columns_different):
234 234 # Even sqlite can handle column additions.
235 235 return True
236 236 else:
237 237 return not self.engine.url.drivername.startswith('sqlite')
238 238
239 239 def runB2A(self):
240 240 """Goes from B to A.
241 241
242 242 Was: applyModel. Apply model (A) to current database (B).
243 243 """
244 244
245 245 meta = sqlalchemy.MetaData(self.engine)
246 246
247 247 for table in self._get_tables(missingA=True):
248 248 table = table.tometadata(meta)
249 249 table.drop()
250 250 for table in self._get_tables(missingB=True):
251 251 table = table.tometadata(meta)
252 252 table.create()
253 253 for modelTable in self._get_tables(modified=True):
254 254 tableName = modelTable.name
255 255 modelTable = modelTable.tometadata(meta)
256 256 dbTable = self.diff.metadataB.tables[tableName]
257 257
258 258 td = self.diff.tables_different[tableName]
259 259
260 260 if self._db_can_handle_this_change(td):
261 261
262 262 for col in td.columns_missing_from_B:
263 263 modelTable.columns[col].create()
264 264 for col in td.columns_missing_from_A:
265 265 dbTable.columns[col].drop()
266 266 # XXX handle column changes here.
267 267 else:
268 268 # Sqlite doesn't support drop column, so you have to
269 269 # do more: create temp table, copy data to it, drop
270 270 # old table, create new table, copy data back.
271 271 #
272 272 # I wonder if this is guaranteed to be unique?
273 273 tempName = '_temp_%s' % modelTable.name
274 274
275 275 def getCopyStatement():
276 276 preparer = self.engine.dialect.preparer
277 277 commonCols = []
278 278 for modelCol in modelTable.columns:
279 279 if modelCol.name in dbTable.columns:
280 280 commonCols.append(modelCol.name)
281 281 commonColsStr = ', '.join(commonCols)
282 282 return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \
283 283 (tableName, commonColsStr, commonColsStr, tempName)
284 284
285 285 # Move the data in one transaction, so that we don't
286 286 # leave the database in a nasty state.
287 287 connection = self.engine.connect()
288 288 trans = connection.begin()
289 289 try:
290 290 connection.execute(
291 291 'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \
292 292 (tempName, modelTable.name))
293 293 # make sure the drop takes place inside our
294 294 # transaction with the bind parameter
295 295 modelTable.drop(bind=connection)
296 296 modelTable.create(bind=connection)
297 297 connection.execute(getCopyStatement())
298 298 connection.execute('DROP TABLE %s' % tempName)
299 299 trans.commit()
300 300 except:
301 301 trans.rollback()
302 302 raise
@@ -1,215 +1,215 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
3 3
4 4 """The migrate command-line tool."""
5 5
6 6 import sys
7 7 import inspect
8 8 import logging
9 9 from optparse import OptionParser, BadOptionError
10 10
11 11 from rhodecode.lib.dbmigrate.migrate import exceptions
12 12 from rhodecode.lib.dbmigrate.migrate.versioning import api
13 13 from rhodecode.lib.dbmigrate.migrate.versioning.config import *
14 14 from rhodecode.lib.dbmigrate.migrate.versioning.util import asbool
15 15
16 16
17 17 alias = {
18 18 's': api.script,
19 19 'vc': api.version_control,
20 20 'dbv': api.db_version,
21 21 'v': api.version,
22 22 }
23 23
24 24 def alias_setup():
25 25 global alias
26 for key, val in alias.iteritems():
26 for key, val in alias.items():
27 27 setattr(api, key, val)
28 28 alias_setup()
29 29
30 30
31 31 class PassiveOptionParser(OptionParser):
32 32
33 33 def _process_args(self, largs, rargs, values):
34 34 """little hack to support all --some_option=value parameters"""
35 35
36 36 while rargs:
37 37 arg = rargs[0]
38 38 if arg == "--":
39 39 del rargs[0]
40 40 return
41 41 elif arg[0:2] == "--":
42 42 # if parser does not know about the option
43 43 # pass it along (make it anonymous)
44 44 try:
45 45 opt = arg.split('=', 1)[0]
46 46 self._match_long_opt(opt)
47 47 except BadOptionError:
48 48 largs.append(arg)
49 49 del rargs[0]
50 50 else:
51 51 self._process_long_opt(rargs, values)
52 52 elif arg[:1] == "-" and len(arg) > 1:
53 53 self._process_short_opts(rargs, values)
54 54 elif self.allow_interspersed_args:
55 55 largs.append(arg)
56 56 del rargs[0]
57 57
58 58 def main(argv=None, **kwargs):
59 59 """Shell interface to :mod:`migrate.versioning.api`.
60 60
61 61 kwargs are default options that can be overriden with passing
62 62 --some_option as command line option
63 63
64 64 :param disable_logging: Let migrate configure logging
65 65 :type disable_logging: bool
66 66 """
67 67 if argv is not None:
68 68 argv = argv
69 69 else:
70 70 argv = list(sys.argv[1:])
71 71 commands = list(api.__all__)
72 72 commands.sort()
73 73
74 74 usage = """%%prog COMMAND ...
75 75
76 76 Available commands:
77 77 %s
78 78
79 79 Enter "%%prog help COMMAND" for information on a particular command.
80 80 """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands])
81 81
82 82 parser = PassiveOptionParser(usage=usage)
83 83 parser.add_option("-d", "--debug",
84 84 action="store_true",
85 85 dest="debug",
86 86 default=False,
87 87 help="Shortcut to turn on DEBUG mode for logging")
88 88 parser.add_option("-q", "--disable_logging",
89 89 action="store_true",
90 90 dest="disable_logging",
91 91 default=False,
92 92 help="Use this option to disable logging configuration")
93 93 help_commands = ['help', '-h', '--help']
94 94 HELP = False
95 95
96 96 try:
97 97 command = argv.pop(0)
98 98 if command in help_commands:
99 99 HELP = True
100 100 command = argv.pop(0)
101 101 except IndexError:
102 102 parser.print_help()
103 103 return
104 104
105 105 command_func = getattr(api, command, None)
106 106 if command_func is None or command.startswith('_'):
107 107 parser.error("Invalid command %s" % command)
108 108
109 109 parser.set_usage(inspect.getdoc(command_func))
110 110 f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func)
111 111 for arg in f_args:
112 112 parser.add_option(
113 113 "--%s" % arg,
114 114 dest=arg,
115 115 action='store',
116 116 type="string")
117 117
118 118 # display help of the current command
119 119 if HELP:
120 120 parser.print_help()
121 121 return
122 122
123 123 options, args = parser.parse_args(argv)
124 124
125 125 # override kwargs with anonymous parameters
126 126 override_kwargs = {}
127 127 for arg in list(args):
128 128 if arg.startswith('--'):
129 129 args.remove(arg)
130 130 if '=' in arg:
131 131 opt, value = arg[2:].split('=', 1)
132 132 else:
133 133 opt = arg[2:]
134 134 value = True
135 135 override_kwargs[opt] = value
136 136
137 137 # override kwargs with options if user is overwriting
138 for key, value in options.__dict__.iteritems():
138 for key, value in options.__dict__.items():
139 139 if value is not None:
140 140 override_kwargs[key] = value
141 141
142 142 # arguments that function accepts without passed kwargs
143 143 f_required = list(f_args)
144 144 candidates = dict(kwargs)
145 145 candidates.update(override_kwargs)
146 for key, value in candidates.iteritems():
146 for key, value in candidates.items():
147 147 if key in f_args:
148 148 f_required.remove(key)
149 149
150 150 # map function arguments to parsed arguments
151 151 for arg in args:
152 152 try:
153 153 kw = f_required.pop(0)
154 154 except IndexError:
155 155 parser.error("Too many arguments for command %s: %s" % (command,
156 156 arg))
157 157 kwargs[kw] = arg
158 158
159 159 # apply overrides
160 160 kwargs.update(override_kwargs)
161 161
162 162 # configure options
163 for key, value in options.__dict__.iteritems():
163 for key, value in options.__dict__.items():
164 164 kwargs.setdefault(key, value)
165 165
166 166 # configure logging
167 167 if not asbool(kwargs.pop('disable_logging', False)):
168 168 # filter to log =< INFO into stdout and rest to stderr
169 169 class SingleLevelFilter(logging.Filter):
170 170 def __init__(self, min=None, max=None):
171 171 self.min = min or 0
172 172 self.max = max or 100
173 173
174 174 def filter(self, record):
175 175 return self.min <= record.levelno <= self.max
176 176
177 177 logger = logging.getLogger()
178 178 h1 = logging.StreamHandler(sys.stdout)
179 179 f1 = SingleLevelFilter(max=logging.INFO)
180 180 h1.addFilter(f1)
181 181 h2 = logging.StreamHandler(sys.stderr)
182 182 f2 = SingleLevelFilter(min=logging.WARN)
183 183 h2.addFilter(f2)
184 184 logger.addHandler(h1)
185 185 logger.addHandler(h2)
186 186
187 187 if options.debug:
188 188 logger.setLevel(logging.DEBUG)
189 189 else:
190 190 logger.setLevel(logging.INFO)
191 191
192 192 log = logging.getLogger(__name__)
193 193
194 194 # check if all args are given
195 195 try:
196 196 num_defaults = len(f_defaults)
197 197 except TypeError:
198 198 num_defaults = 0
199 199 f_args_default = f_args[len(f_args) - num_defaults:]
200 200 required = list(set(f_required) - set(f_args_default))
201 201 required.sort()
202 202 if required:
203 203 parser.error("Not enough arguments for command %s: %s not specified" \
204 204 % (command, ', '.join(required)))
205 205
206 206 # handle command
207 207 try:
208 208 ret = command_func(**kwargs)
209 209 if ret is not None:
210 210 log.info(ret)
211 211 except (exceptions.UsageError, exceptions.KnownError) as e:
212 212 parser.error(e.args[0])
213 213
214 214 if __name__ == "__main__":
215 215 main()
@@ -1,180 +1,180 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
3 3 """.. currentmodule:: migrate.versioning.util"""
4 4
5 5 import warnings
6 6 import logging
7 7 from decorator import decorator
8 8 from pkg_resources import EntryPoint
9 9
10 10 from sqlalchemy import create_engine
11 11 from sqlalchemy.engine import Engine
12 12 from sqlalchemy.pool import StaticPool
13 13
14 14 from rhodecode.lib.dbmigrate.migrate import exceptions
15 15 from rhodecode.lib.dbmigrate.migrate.versioning.util.keyedinstance import KeyedInstance
16 16 from rhodecode.lib.dbmigrate.migrate.versioning.util.importpath import import_path
17 17
18 18
19 19 log = logging.getLogger(__name__)
20 20
21 21
22 22 def load_model(dotted_name):
23 23 """Import module and use module-level variable".
24 24
25 25 :param dotted_name: path to model in form of string: ``some.python.module:Class``
26 26
27 27 .. versionchanged:: 0.5.4
28 28
29 29 """
30 30 if isinstance(dotted_name, str):
31 31 if ':' not in dotted_name:
32 32 # backwards compatibility
33 33 warnings.warn('model should be in form of module.model:User '
34 34 'and not module.model.User', exceptions.MigrateDeprecationWarning)
35 35 dotted_name = ':'.join(dotted_name.rsplit('.', 1))
36 36 return EntryPoint.parse('x=%s' % dotted_name).load(False)
37 37 else:
38 38 # Assume it's already loaded.
39 39 return dotted_name
40 40
41 41 def asbool(obj):
42 42 """Do everything to use object as bool"""
43 43 if isinstance(obj, str):
44 44 obj = obj.strip().lower()
45 45 if obj in ['true', 'yes', 'on', 'y', 't', '1']:
46 46 return True
47 47 elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
48 48 return False
49 49 else:
50 50 raise ValueError("String is not true/false: %r" % obj)
51 51 if obj in (True, False):
52 52 return bool(obj)
53 53 else:
54 54 raise ValueError("String is not true/false: %r" % obj)
55 55
56 56 def guess_obj_type(obj):
57 57 """Do everything to guess object type from string
58 58
59 59 Tries to convert to `int`, `bool` and finally returns if not succeded.
60 60
61 61 .. versionadded: 0.5.4
62 62 """
63 63
64 64 result = None
65 65
66 66 try:
67 67 result = int(obj)
68 68 except:
69 69 pass
70 70
71 71 if result is None:
72 72 try:
73 73 result = asbool(obj)
74 74 except:
75 75 pass
76 76
77 77 if result is not None:
78 78 return result
79 79 else:
80 80 return obj
81 81
82 82 @decorator
83 83 def catch_known_errors(f, *a, **kw):
84 84 """Decorator that catches known api errors
85 85
86 86 .. versionadded: 0.5.4
87 87 """
88 88
89 89 try:
90 90 return f(*a, **kw)
91 91 except exceptions.PathFoundError as e:
92 92 raise exceptions.KnownError("The path %s already exists" % e.args[0])
93 93
94 94 def construct_engine(engine, **opts):
95 95 """.. versionadded:: 0.5.4
96 96
97 97 Constructs and returns SQLAlchemy engine.
98 98
99 99 Currently, there are 2 ways to pass create_engine options to :mod:`migrate.versioning.api` functions:
100 100
101 101 :param engine: connection string or a existing engine
102 102 :param engine_dict: python dictionary of options to pass to `create_engine`
103 103 :param engine_arg_*: keyword parameters to pass to `create_engine` (evaluated with :func:`migrate.versioning.util.guess_obj_type`)
104 104 :type engine_dict: dict
105 105 :type engine: string or Engine instance
106 106 :type engine_arg_*: string
107 107 :returns: SQLAlchemy Engine
108 108
109 109 .. note::
110 110
111 111 keyword parameters override ``engine_dict`` values.
112 112
113 113 """
114 114 if isinstance(engine, Engine):
115 115 return engine
116 116 elif not isinstance(engine, str):
117 117 raise ValueError("you need to pass either an existing engine or a database uri")
118 118
119 119 # get options for create_engine
120 120 if opts.get('engine_dict') and isinstance(opts['engine_dict'], dict):
121 121 kwargs = opts['engine_dict']
122 122 else:
123 123 kwargs = {}
124 124
125 125 # DEPRECATED: handle echo the old way
126 126 echo = asbool(opts.get('echo', False))
127 127 if echo:
128 128 warnings.warn('echo=True parameter is deprecated, pass '
129 129 'engine_arg_echo=True or engine_dict={"echo": True}',
130 130 exceptions.MigrateDeprecationWarning)
131 131 kwargs['echo'] = echo
132 132
133 133 # parse keyword arguments
134 for key, value in opts.iteritems():
134 for key, value in opts.items():
135 135 if key.startswith('engine_arg_'):
136 136 kwargs[key[11:]] = guess_obj_type(value)
137 137
138 138 log.debug('Constructing engine')
139 139 # TODO: return create_engine(engine, poolclass=StaticPool, **kwargs)
140 140 # seems like 0.5.x branch does not work with engine.dispose and staticpool
141 141 return create_engine(engine, **kwargs)
142 142
143 143 @decorator
144 144 def with_engine(f, *a, **kw):
145 145 """Decorator for :mod:`migrate.versioning.api` functions
146 146 to safely close resources after function usage.
147 147
148 148 Passes engine parameters to :func:`construct_engine` and
149 149 resulting parameter is available as kw['engine'].
150 150
151 151 Engine is disposed after wrapped function is executed.
152 152
153 153 .. versionadded: 0.6.0
154 154 """
155 155 url = a[0]
156 156 engine = construct_engine(url, **kw)
157 157
158 158 try:
159 159 kw['engine'] = engine
160 160 return f(*a, **kw)
161 161 finally:
162 162 if isinstance(engine, Engine) and engine is not url:
163 163 log.debug('Disposing SQLAlchemy engine %s', engine)
164 164 engine.dispose()
165 165
166 166
167 167 class Memoize:
168 168 """Memoize(fn) - an instance which acts like fn but memoizes its arguments
169 169 Will only work on functions with non-mutable arguments
170 170
171 171 ActiveState Code 52201
172 172 """
173 173 def __init__(self, fn):
174 174 self.fn = fn
175 175 self.memo = {}
176 176
177 177 def __call__(self, *args):
178 178 if args not in self.memo:
179 179 self.memo[args] = self.fn(*args)
180 180 return self.memo[args]
@@ -1,1263 +1,1263 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 from collections import defaultdict
26 26
27 27 from sqlalchemy import *
28 28 from sqlalchemy.ext.hybrid import hybrid_property
29 29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 30 from beaker.cache import cache_region, region_invalidate
31 31
32 32 from rhodecode.lib.vcs import get_backend
33 33 from rhodecode.lib.vcs.utils.helpers import get_scm
34 34 from rhodecode.lib.vcs.exceptions import VCSError
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
38 38 safe_unicode
39 39 from rhodecode.lib.ext_json import json
40 40 from rhodecode.lib.caching_query import FromCache
41 41
42 42 from rhodecode.model.meta import Base, Session
43 43 import hashlib
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48 #==============================================================================
49 49 # BASE CLASSES
50 50 #==============================================================================
51 51
52 52 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
53 53
54 54
55 55 class ModelSerializer(json.JSONEncoder):
56 56 """
57 57 Simple Serializer for JSON,
58 58
59 59 usage::
60 60
61 61 to make object customized for serialization implement a __json__
62 62 method that will return a dict for serialization into json
63 63
64 64 example::
65 65
66 66 class Task(object):
67 67
68 68 def __init__(self, name, value):
69 69 self.name = name
70 70 self.value = value
71 71
72 72 def __json__(self):
73 73 return dict(name=self.name,
74 74 value=self.value)
75 75
76 76 """
77 77
78 78 def default(self, obj):
79 79
80 80 if hasattr(obj, '__json__'):
81 81 return obj.__json__()
82 82 else:
83 83 return json.JSONEncoder.default(self, obj)
84 84
85 85
86 86 class BaseModel(object):
87 87 """
88 88 Base Model for all classess
89 89 """
90 90
91 91 @classmethod
92 92 def _get_keys(cls):
93 93 """return column names for this model """
94 94 return class_mapper(cls).c.keys()
95 95
96 96 def get_dict(self):
97 97 """
98 98 return dict with keys and values corresponding
99 99 to this model data """
100 100
101 101 d = {}
102 102 for k in self._get_keys():
103 103 d[k] = getattr(self, k)
104 104
105 105 # also use __json__() if present to get additional fields
106 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
106 for k, val in getattr(self, '__json__', lambda: {})().items():
107 107 d[k] = val
108 108 return d
109 109
110 110 def get_appstruct(self):
111 111 """return list with keys and values tupples corresponding
112 112 to this model data """
113 113
114 114 l = []
115 115 for k in self._get_keys():
116 116 l.append((k, getattr(self, k),))
117 117 return l
118 118
119 119 def populate_obj(self, populate_dict):
120 120 """populate model with data from given populate_dict"""
121 121
122 122 for k in self._get_keys():
123 123 if k in populate_dict:
124 124 setattr(self, k, populate_dict[k])
125 125
126 126 @classmethod
127 127 def query(cls):
128 128 return Session.query(cls)
129 129
130 130 @classmethod
131 131 def get(cls, id_):
132 132 if id_:
133 133 return cls.query().get(id_)
134 134
135 135 @classmethod
136 136 def getAll(cls):
137 137 return cls.query().all()
138 138
139 139 @classmethod
140 140 def delete(cls, id_):
141 141 obj = cls.query().get(id_)
142 142 Session.delete(obj)
143 143
144 144 def __repr__(self):
145 145 if hasattr(self, '__unicode__'):
146 146 # python repr needs to return str
147 147 return safe_str(self.__unicode__())
148 148 return '<DB:%s>' % (self.__class__.__name__)
149 149
150 150
151 151 class RhodeCodeSetting(Base, BaseModel):
152 152 __tablename__ = 'rhodecode_settings'
153 153 __table_args__ = (
154 154 UniqueConstraint('app_settings_name'),
155 155 {'extend_existing': True, 'mysql_engine':'InnoDB',
156 156 'mysql_charset': 'utf8'}
157 157 )
158 158 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
159 159 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
160 160 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
161 161
162 162 def __init__(self, k='', v=''):
163 163 self.app_settings_name = k
164 164 self.app_settings_value = v
165 165
166 166 @validates('_app_settings_value')
167 167 def validate_settings_value(self, key, val):
168 168 assert type(val) == unicode
169 169 return val
170 170
171 171 @hybrid_property
172 172 def app_settings_value(self):
173 173 v = self._app_settings_value
174 174 if self.app_settings_name == 'ldap_active':
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, ldap_key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == ldap_key).scalar()
197 197
198 198 @classmethod
199 199 def get_app_settings(cls, cache=False):
200 200
201 201 ret = cls.query()
202 202
203 203 if cache:
204 204 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
205 205
206 206 if not ret:
207 207 raise Exception('Could not get application settings !')
208 208 settings = {}
209 209 for each in ret:
210 210 settings['rhodecode_' + each.app_settings_name] = \
211 211 each.app_settings_value
212 212
213 213 return settings
214 214
215 215 @classmethod
216 216 def get_ldap_settings(cls, cache=False):
217 217 ret = cls.query()\
218 218 .filter(cls.app_settings_name.startswith('ldap_')).all()
219 219 fd = {}
220 220 for row in ret:
221 221 fd.update({row.app_settings_name:row.app_settings_value})
222 222
223 223 return fd
224 224
225 225
226 226 class RhodeCodeUi(Base, BaseModel):
227 227 __tablename__ = 'rhodecode_ui'
228 228 __table_args__ = (
229 229 UniqueConstraint('ui_key'),
230 230 {'extend_existing': True, 'mysql_engine':'InnoDB',
231 231 'mysql_charset': 'utf8'}
232 232 )
233 233
234 234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 235 HOOK_PUSH = 'pretxnchangegroup.push_logger'
236 236 HOOK_PULL = 'preoutgoing.pull_logger'
237 237
238 238 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
239 239 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
240 240 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
241 241 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
242 242 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
243 243
244 244 @classmethod
245 245 def get_by_key(cls, key):
246 246 return cls.query().filter(cls.ui_key == key)
247 247
248 248 @classmethod
249 249 def get_builtin_hooks(cls):
250 250 q = cls.query()
251 251 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
252 252 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 253 return q.all()
254 254
255 255 @classmethod
256 256 def get_custom_hooks(cls):
257 257 q = cls.query()
258 258 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
259 259 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 260 q = q.filter(cls.ui_section == 'hooks')
261 261 return q.all()
262 262
263 263 @classmethod
264 264 def create_or_update_hook(cls, key, val):
265 265 new_ui = cls.get_by_key(key).scalar() or cls()
266 266 new_ui.ui_section = 'hooks'
267 267 new_ui.ui_active = True
268 268 new_ui.ui_key = key
269 269 new_ui.ui_value = val
270 270
271 271 Session.add(new_ui)
272 272
273 273
274 274 class User(Base, BaseModel):
275 275 __tablename__ = 'users'
276 276 __table_args__ = (
277 277 UniqueConstraint('username'), UniqueConstraint('email'),
278 278 {'extend_existing': True, 'mysql_engine':'InnoDB',
279 279 'mysql_charset': 'utf8'}
280 280 )
281 281 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 282 username = Column("username", String(255), nullable=True, unique=None, default=None)
283 283 password = Column("password", String(255), nullable=True, unique=None, default=None)
284 284 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
285 285 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
286 286 name = Column("name", String(255), nullable=True, unique=None, default=None)
287 287 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
288 288 _email = Column("email", String(255), nullable=True, unique=None, default=None)
289 289 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
290 290 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
291 291 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
292 292
293 293 user_log = relationship('UserLog', cascade='all')
294 294 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
295 295
296 296 repositories = relationship('Repository')
297 297 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
298 298 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
299 299 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
300 300
301 301 group_member = relationship('UserGroupMember', cascade='all')
302 302
303 303 notifications = relationship('UserNotification', cascade='all')
304 304 # notifications assigned to this user
305 305 user_created_notifications = relationship('Notification', cascade='all')
306 306 # comments created by this user
307 307 user_comments = relationship('ChangesetComment', cascade='all')
308 308
309 309 @hybrid_property
310 310 def email(self):
311 311 return self._email
312 312
313 313 @email.setter
314 314 def email(self, val):
315 315 self._email = val.lower() if val else None
316 316
317 317 @property
318 318 def full_name(self):
319 319 return '%s %s' % (self.name, self.lastname)
320 320
321 321 @property
322 322 def full_name_or_username(self):
323 323 return ('%s %s' % (self.name, self.lastname)
324 324 if (self.name and self.lastname) else self.username)
325 325
326 326 @property
327 327 def full_contact(self):
328 328 return '%s %s <%s>' % (self.name, self.lastname, self.email)
329 329
330 330 @property
331 331 def short_contact(self):
332 332 return '%s %s' % (self.name, self.lastname)
333 333
334 334 @property
335 335 def is_admin(self):
336 336 return self.admin
337 337
338 338 def __unicode__(self):
339 339 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
340 340 self.user_id, self.username)
341 341
342 342 @classmethod
343 343 def get_by_username(cls, username, case_insensitive=False, cache=False):
344 344 if case_insensitive:
345 345 q = cls.query().filter(cls.username.ilike(username))
346 346 else:
347 347 q = cls.query().filter(cls.username == username)
348 348
349 349 if cache:
350 350 q = q.options(FromCache(
351 351 "sql_cache_short",
352 352 "get_user_%s" % _hash_key(username)
353 353 )
354 354 )
355 355 return q.scalar()
356 356
357 357 @classmethod
358 358 def get_by_auth_token(cls, auth_token, cache=False):
359 359 q = cls.query().filter(cls.api_key == auth_token)
360 360
361 361 if cache:
362 362 q = q.options(FromCache("sql_cache_short",
363 363 "get_auth_token_%s" % auth_token))
364 364 return q.scalar()
365 365
366 366 @classmethod
367 367 def get_by_email(cls, email, case_insensitive=False, cache=False):
368 368 if case_insensitive:
369 369 q = cls.query().filter(cls.email.ilike(email))
370 370 else:
371 371 q = cls.query().filter(cls.email == email)
372 372
373 373 if cache:
374 374 q = q.options(FromCache("sql_cache_short",
375 375 "get_auth_token_%s" % email))
376 376 return q.scalar()
377 377
378 378 def update_lastlogin(self):
379 379 """Update user lastlogin"""
380 380 self.last_login = datetime.datetime.now()
381 381 Session.add(self)
382 382 log.debug('updated user %s lastlogin', self.username)
383 383
384 384 def __json__(self):
385 385 return dict(
386 386 user_id=self.user_id,
387 387 first_name=self.name,
388 388 last_name=self.lastname,
389 389 email=self.email,
390 390 full_name=self.full_name,
391 391 full_name_or_username=self.full_name_or_username,
392 392 short_contact=self.short_contact,
393 393 full_contact=self.full_contact
394 394 )
395 395
396 396
397 397 class UserLog(Base, BaseModel):
398 398 __tablename__ = 'user_logs'
399 399 __table_args__ = (
400 400 {'extend_existing': True, 'mysql_engine':'InnoDB',
401 401 'mysql_charset': 'utf8'},
402 402 )
403 403 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 404 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
405 405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 406 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 407 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 408 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 409 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 410
411 411 @property
412 412 def action_as_day(self):
413 413 return datetime.date(*self.action_date.timetuple()[:3])
414 414
415 415 user = relationship('User')
416 416 repository = relationship('Repository', cascade='')
417 417
418 418
419 419 class UserGroup(Base, BaseModel):
420 420 __tablename__ = 'users_groups'
421 421 __table_args__ = (
422 422 {'extend_existing': True, 'mysql_engine':'InnoDB',
423 423 'mysql_charset': 'utf8'},
424 424 )
425 425
426 426 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
427 427 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
428 428 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
429 429
430 430 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
431 431 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
432 432 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
433 433
434 434 def __unicode__(self):
435 435 return u'<userGroup(%s)>' % (self.users_group_name)
436 436
437 437 @classmethod
438 438 def get_by_group_name(cls, group_name, cache=False,
439 439 case_insensitive=False):
440 440 if case_insensitive:
441 441 q = cls.query().filter(cls.users_group_name.ilike(group_name))
442 442 else:
443 443 q = cls.query().filter(cls.users_group_name == group_name)
444 444 if cache:
445 445 q = q.options(FromCache(
446 446 "sql_cache_short",
447 447 "get_user_%s" % _hash_key(group_name)
448 448 )
449 449 )
450 450 return q.scalar()
451 451
452 452 @classmethod
453 453 def get(cls, users_group_id, cache=False):
454 454 users_group = cls.query()
455 455 if cache:
456 456 users_group = users_group.options(FromCache("sql_cache_short",
457 457 "get_users_group_%s" % users_group_id))
458 458 return users_group.get(users_group_id)
459 459
460 460
461 461 class UserGroupMember(Base, BaseModel):
462 462 __tablename__ = 'users_groups_members'
463 463 __table_args__ = (
464 464 {'extend_existing': True, 'mysql_engine':'InnoDB',
465 465 'mysql_charset': 'utf8'},
466 466 )
467 467
468 468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
469 469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
470 470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
471 471
472 472 user = relationship('User', lazy='joined')
473 473 users_group = relationship('UserGroup')
474 474
475 475 def __init__(self, gr_id='', u_id=''):
476 476 self.users_group_id = gr_id
477 477 self.user_id = u_id
478 478
479 479
480 480 class Repository(Base, BaseModel):
481 481 __tablename__ = 'repositories'
482 482 __table_args__ = (
483 483 UniqueConstraint('repo_name'),
484 484 {'extend_existing': True, 'mysql_engine':'InnoDB',
485 485 'mysql_charset': 'utf8'},
486 486 )
487 487
488 488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 489 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
490 490 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
491 491 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
492 492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 496 description = Column("description", String(10000), nullable=True, unique=None, default=None)
497 497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 498
499 499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501 501
502 502 user = relationship('User')
503 503 fork = relationship('Repository', remote_side=repo_id)
504 504 group = relationship('RepoGroup')
505 505 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
506 506 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
507 507 stats = relationship('Statistics', cascade='all', uselist=False)
508 508
509 509 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
510 510
511 511 logs = relationship('UserLog')
512 512
513 513 def __unicode__(self):
514 514 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
515 515 self.repo_name)
516 516
517 517 @classmethod
518 518 def url_sep(cls):
519 519 return '/'
520 520
521 521 @classmethod
522 522 def get_by_repo_name(cls, repo_name):
523 523 q = Session.query(cls).filter(cls.repo_name == repo_name)
524 524 q = q.options(joinedload(Repository.fork))\
525 525 .options(joinedload(Repository.user))\
526 526 .options(joinedload(Repository.group))
527 527 return q.scalar()
528 528
529 529 @classmethod
530 530 def get_repo_forks(cls, repo_id):
531 531 return cls.query().filter(Repository.fork_id == repo_id)
532 532
533 533 @classmethod
534 534 def base_path(cls):
535 535 """
536 536 Returns base path when all repos are stored
537 537
538 538 :param cls:
539 539 """
540 540 q = Session.query(RhodeCodeUi)\
541 541 .filter(RhodeCodeUi.ui_key == cls.url_sep())
542 542 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 543 return q.one().ui_value
544 544
545 545 @property
546 546 def just_name(self):
547 547 return self.repo_name.split(Repository.url_sep())[-1]
548 548
549 549 @property
550 550 def groups_with_parents(self):
551 551 groups = []
552 552 if self.group is None:
553 553 return groups
554 554
555 555 cur_gr = self.group
556 556 groups.insert(0, cur_gr)
557 557 while 1:
558 558 gr = getattr(cur_gr, 'parent_group', None)
559 559 cur_gr = cur_gr.parent_group
560 560 if gr is None:
561 561 break
562 562 groups.insert(0, gr)
563 563
564 564 return groups
565 565
566 566 @property
567 567 def groups_and_repo(self):
568 568 return self.groups_with_parents, self.just_name
569 569
570 570 @LazyProperty
571 571 def repo_path(self):
572 572 """
573 573 Returns base full path for that repository means where it actually
574 574 exists on a filesystem
575 575 """
576 576 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
577 577 Repository.url_sep())
578 578 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
579 579 return q.one().ui_value
580 580
581 581 @property
582 582 def repo_full_path(self):
583 583 p = [self.repo_path]
584 584 # we need to split the name by / since this is how we store the
585 585 # names in the database, but that eventually needs to be converted
586 586 # into a valid system path
587 587 p += self.repo_name.split(Repository.url_sep())
588 588 return os.path.join(*p)
589 589
590 590 def get_new_name(self, repo_name):
591 591 """
592 592 returns new full repository name based on assigned group and new new
593 593
594 594 :param group_name:
595 595 """
596 596 path_prefix = self.group.full_path_splitted if self.group else []
597 597 return Repository.url_sep().join(path_prefix + [repo_name])
598 598
599 599 @property
600 600 def _config(self):
601 601 """
602 602 Returns db based config object.
603 603 """
604 604 from rhodecode.lib.utils import make_db_config
605 605 return make_db_config(clear_session=False)
606 606
607 607 @classmethod
608 608 def is_valid(cls, repo_name):
609 609 """
610 610 returns True if given repo name is a valid filesystem repository
611 611
612 612 :param cls:
613 613 :param repo_name:
614 614 """
615 615 from rhodecode.lib.utils import is_valid_repo
616 616
617 617 return is_valid_repo(repo_name, cls.base_path())
618 618
619 619 #==========================================================================
620 620 # SCM PROPERTIES
621 621 #==========================================================================
622 622
623 623 def get_commit(self, rev):
624 624 return get_commit_safe(self.scm_instance, rev)
625 625
626 626 @property
627 627 def tip(self):
628 628 return self.get_commit('tip')
629 629
630 630 @property
631 631 def author(self):
632 632 return self.tip.author
633 633
634 634 @property
635 635 def last_change(self):
636 636 return self.scm_instance.last_change
637 637
638 638 def comments(self, revisions=None):
639 639 """
640 640 Returns comments for this repository grouped by revisions
641 641
642 642 :param revisions: filter query by revisions only
643 643 """
644 644 cmts = ChangesetComment.query()\
645 645 .filter(ChangesetComment.repo == self)
646 646 if revisions:
647 647 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
648 648 grouped = defaultdict(list)
649 649 for cmt in cmts.all():
650 650 grouped[cmt.revision].append(cmt)
651 651 return grouped
652 652
653 653 #==========================================================================
654 654 # SCM CACHE INSTANCE
655 655 #==========================================================================
656 656
657 657 @property
658 658 def invalidate(self):
659 659 return CacheInvalidation.invalidate(self.repo_name)
660 660
661 661 def set_invalidate(self):
662 662 """
663 663 set a cache for invalidation for this instance
664 664 """
665 665 CacheInvalidation.set_invalidate(self.repo_name)
666 666
667 667 @LazyProperty
668 668 def scm_instance(self):
669 669 return self.__get_instance()
670 670
671 671 @property
672 672 def scm_instance_cached(self):
673 673 return self.__get_instance()
674 674
675 675 def __get_instance(self):
676 676 repo_full_path = self.repo_full_path
677 677 try:
678 678 alias = get_scm(repo_full_path)[0]
679 679 log.debug('Creating instance of %s repository', alias)
680 680 backend = get_backend(alias)
681 681 except VCSError:
682 682 log.error(traceback.format_exc())
683 683 log.error('Perhaps this repository is in db and not in '
684 684 'filesystem run rescan repositories with '
685 685 '"destroy old data " option from admin panel')
686 686 return
687 687
688 688 if alias == 'hg':
689 689
690 690 repo = backend(safe_str(repo_full_path), create=False,
691 691 config=self._config)
692 692 else:
693 693 repo = backend(repo_full_path, create=False)
694 694
695 695 return repo
696 696
697 697
698 698 class RepoGroup(Base, BaseModel):
699 699 __tablename__ = 'groups'
700 700 __table_args__ = (
701 701 UniqueConstraint('group_name', 'group_parent_id'),
702 702 {'extend_existing': True, 'mysql_engine':'InnoDB',
703 703 'mysql_charset': 'utf8'},
704 704 )
705 705 __mapper_args__ = {'order_by': 'group_name'}
706 706
707 707 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
708 708 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
709 709 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
710 710 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
711 711
712 712 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 713 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
714 714
715 715 parent_group = relationship('RepoGroup', remote_side=group_id)
716 716
717 717 def __init__(self, group_name='', parent_group=None):
718 718 self.group_name = group_name
719 719 self.parent_group = parent_group
720 720
721 721 def __unicode__(self):
722 722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
723 723 self.group_name)
724 724
725 725 @classmethod
726 726 def url_sep(cls):
727 727 return '/'
728 728
729 729 @classmethod
730 730 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
731 731 if case_insensitive:
732 732 gr = cls.query()\
733 733 .filter(cls.group_name.ilike(group_name))
734 734 else:
735 735 gr = cls.query()\
736 736 .filter(cls.group_name == group_name)
737 737 if cache:
738 738 gr = gr.options(FromCache(
739 739 "sql_cache_short",
740 740 "get_group_%s" % _hash_key(group_name)
741 741 )
742 742 )
743 743 return gr.scalar()
744 744
745 745 @property
746 746 def parents(self):
747 747 parents_recursion_limit = 5
748 748 groups = []
749 749 if self.parent_group is None:
750 750 return groups
751 751 cur_gr = self.parent_group
752 752 groups.insert(0, cur_gr)
753 753 cnt = 0
754 754 while 1:
755 755 cnt += 1
756 756 gr = getattr(cur_gr, 'parent_group', None)
757 757 cur_gr = cur_gr.parent_group
758 758 if gr is None:
759 759 break
760 760 if cnt == parents_recursion_limit:
761 761 # this will prevent accidental infinit loops
762 762 log.error('group nested more than %s', parents_recursion_limit)
763 763 break
764 764
765 765 groups.insert(0, gr)
766 766 return groups
767 767
768 768 @property
769 769 def children(self):
770 770 return RepoGroup.query().filter(RepoGroup.parent_group == self)
771 771
772 772 @property
773 773 def name(self):
774 774 return self.group_name.split(RepoGroup.url_sep())[-1]
775 775
776 776 @property
777 777 def full_path(self):
778 778 return self.group_name
779 779
780 780 @property
781 781 def full_path_splitted(self):
782 782 return self.group_name.split(RepoGroup.url_sep())
783 783
784 784 @property
785 785 def repositories(self):
786 786 return Repository.query()\
787 787 .filter(Repository.group == self)\
788 788 .order_by(Repository.repo_name)
789 789
790 790 @property
791 791 def repositories_recursive_count(self):
792 792 cnt = self.repositories.count()
793 793
794 794 def children_count(group):
795 795 cnt = 0
796 796 for child in group.children:
797 797 cnt += child.repositories.count()
798 798 cnt += children_count(child)
799 799 return cnt
800 800
801 801 return cnt + children_count(self)
802 802
803 803 def get_new_name(self, group_name):
804 804 """
805 805 returns new full group name based on parent and new name
806 806
807 807 :param group_name:
808 808 """
809 809 path_prefix = (self.parent_group.full_path_splitted if
810 810 self.parent_group else [])
811 811 return RepoGroup.url_sep().join(path_prefix + [group_name])
812 812
813 813
814 814 class Permission(Base, BaseModel):
815 815 __tablename__ = 'permissions'
816 816 __table_args__ = (
817 817 {'extend_existing': True, 'mysql_engine':'InnoDB',
818 818 'mysql_charset': 'utf8'},
819 819 )
820 820 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
821 821 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
822 822 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
823 823
824 824 def __unicode__(self):
825 825 return u"<%s('%s:%s')>" % (
826 826 self.__class__.__name__, self.permission_id, self.permission_name
827 827 )
828 828
829 829 @classmethod
830 830 def get_by_key(cls, key):
831 831 return cls.query().filter(cls.permission_name == key).scalar()
832 832
833 833 @classmethod
834 834 def get_default_repo_perms(cls, default_user_id):
835 835 q = Session.query(UserRepoToPerm, Repository, cls)\
836 836 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
837 837 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
838 838 .filter(UserRepoToPerm.user_id == default_user_id)
839 839
840 840 return q.all()
841 841
842 842 @classmethod
843 843 def get_default_group_perms(cls, default_user_id):
844 844 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
845 845 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
846 846 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
847 847 .filter(UserRepoGroupToPerm.user_id == default_user_id)
848 848
849 849 return q.all()
850 850
851 851
852 852 class UserRepoToPerm(Base, BaseModel):
853 853 __tablename__ = 'repo_to_perm'
854 854 __table_args__ = (
855 855 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
856 856 {'extend_existing': True, 'mysql_engine':'InnoDB',
857 857 'mysql_charset': 'utf8'}
858 858 )
859 859 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
860 860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
861 861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
862 862 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
863 863
864 864 user = relationship('User')
865 865 repository = relationship('Repository')
866 866 permission = relationship('Permission')
867 867
868 868 @classmethod
869 869 def create(cls, user, repository, permission):
870 870 n = cls()
871 871 n.user = user
872 872 n.repository = repository
873 873 n.permission = permission
874 874 Session.add(n)
875 875 return n
876 876
877 877 def __unicode__(self):
878 878 return u'<user:%s => %s >' % (self.user, self.repository)
879 879
880 880
881 881 class UserToPerm(Base, BaseModel):
882 882 __tablename__ = 'user_to_perm'
883 883 __table_args__ = (
884 884 UniqueConstraint('user_id', 'permission_id'),
885 885 {'extend_existing': True, 'mysql_engine':'InnoDB',
886 886 'mysql_charset': 'utf8'}
887 887 )
888 888 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
889 889 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
890 890 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
891 891
892 892 user = relationship('User')
893 893 permission = relationship('Permission', lazy='joined')
894 894
895 895
896 896 class UserGroupRepoToPerm(Base, BaseModel):
897 897 __tablename__ = 'users_group_repo_to_perm'
898 898 __table_args__ = (
899 899 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
900 900 {'extend_existing': True, 'mysql_engine':'InnoDB',
901 901 'mysql_charset': 'utf8'}
902 902 )
903 903 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
904 904 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
905 905 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
906 906 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
907 907
908 908 users_group = relationship('UserGroup')
909 909 permission = relationship('Permission')
910 910 repository = relationship('Repository')
911 911
912 912 @classmethod
913 913 def create(cls, users_group, repository, permission):
914 914 n = cls()
915 915 n.users_group = users_group
916 916 n.repository = repository
917 917 n.permission = permission
918 918 Session.add(n)
919 919 return n
920 920
921 921 def __unicode__(self):
922 922 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
923 923
924 924
925 925 class UserGroupToPerm(Base, BaseModel):
926 926 __tablename__ = 'users_group_to_perm'
927 927 __table_args__ = (
928 928 UniqueConstraint('users_group_id', 'permission_id',),
929 929 {'extend_existing': True, 'mysql_engine':'InnoDB',
930 930 'mysql_charset': 'utf8'}
931 931 )
932 932 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
933 933 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
934 934 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
935 935
936 936 users_group = relationship('UserGroup')
937 937 permission = relationship('Permission')
938 938
939 939
940 940 class UserRepoGroupToPerm(Base, BaseModel):
941 941 __tablename__ = 'user_repo_group_to_perm'
942 942 __table_args__ = (
943 943 UniqueConstraint('user_id', 'group_id', 'permission_id'),
944 944 {'extend_existing': True, 'mysql_engine':'InnoDB',
945 945 'mysql_charset': 'utf8'}
946 946 )
947 947
948 948 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
950 950 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
951 951 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
952 952
953 953 user = relationship('User')
954 954 group = relationship('RepoGroup')
955 955 permission = relationship('Permission')
956 956
957 957
958 958 class UserGroupRepoGroupToPerm(Base, BaseModel):
959 959 __tablename__ = 'users_group_repo_group_to_perm'
960 960 __table_args__ = (
961 961 UniqueConstraint('users_group_id', 'group_id'),
962 962 {'extend_existing': True, 'mysql_engine':'InnoDB',
963 963 'mysql_charset': 'utf8'}
964 964 )
965 965
966 966 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)
967 967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
968 968 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
969 969 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
970 970
971 971 users_group = relationship('UserGroup')
972 972 permission = relationship('Permission')
973 973 group = relationship('RepoGroup')
974 974
975 975
976 976 class Statistics(Base, BaseModel):
977 977 __tablename__ = 'statistics'
978 978 __table_args__ = (
979 979 UniqueConstraint('repository_id'),
980 980 {'extend_existing': True, 'mysql_engine':'InnoDB',
981 981 'mysql_charset': 'utf8'}
982 982 )
983 983 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 984 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
985 985 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
986 986 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
987 987 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
988 988 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
989 989
990 990 repository = relationship('Repository', single_parent=True)
991 991
992 992
993 993 class UserFollowing(Base, BaseModel):
994 994 __tablename__ = 'user_followings'
995 995 __table_args__ = (
996 996 UniqueConstraint('user_id', 'follows_repository_id'),
997 997 UniqueConstraint('user_id', 'follows_user_id'),
998 998 {'extend_existing': True, 'mysql_engine':'InnoDB',
999 999 'mysql_charset': 'utf8'}
1000 1000 )
1001 1001
1002 1002 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1003 1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1004 1004 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1005 1005 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1006 1006 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1007 1007
1008 1008 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1009 1009
1010 1010 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1011 1011 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1012 1012
1013 1013 @classmethod
1014 1014 def get_repo_followers(cls, repo_id):
1015 1015 return cls.query().filter(cls.follows_repo_id == repo_id)
1016 1016
1017 1017
1018 1018 class CacheInvalidation(Base, BaseModel):
1019 1019 __tablename__ = 'cache_invalidation'
1020 1020 __table_args__ = (
1021 1021 UniqueConstraint('cache_key'),
1022 1022 {'extend_existing': True, 'mysql_engine':'InnoDB',
1023 1023 'mysql_charset': 'utf8'},
1024 1024 )
1025 1025 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1026 1026 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1027 1027 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1028 1028 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1029 1029
1030 1030 def __init__(self, cache_key, cache_args=''):
1031 1031 self.cache_key = cache_key
1032 1032 self.cache_args = cache_args
1033 1033 self.cache_active = False
1034 1034
1035 1035 def __unicode__(self):
1036 1036 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1037 1037 self.cache_id, self.cache_key)
1038 1038
1039 1039 @classmethod
1040 1040 def _get_key(cls, key):
1041 1041 """
1042 1042 Wrapper for generating a key, together with a prefix
1043 1043
1044 1044 :param key:
1045 1045 """
1046 1046 import rhodecode
1047 1047 prefix = ''
1048 1048 iid = rhodecode.CONFIG.get('instance_id')
1049 1049 if iid:
1050 1050 prefix = iid
1051 1051 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1052 1052
1053 1053 @classmethod
1054 1054 def get_by_key(cls, key):
1055 1055 return cls.query().filter(cls.cache_key == key).scalar()
1056 1056
1057 1057 @classmethod
1058 1058 def _get_or_create_key(cls, key, prefix, org_key):
1059 1059 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1060 1060 if not inv_obj:
1061 1061 try:
1062 1062 inv_obj = CacheInvalidation(key, org_key)
1063 1063 Session.add(inv_obj)
1064 1064 Session.commit()
1065 1065 except Exception:
1066 1066 log.error(traceback.format_exc())
1067 1067 Session.rollback()
1068 1068 return inv_obj
1069 1069
1070 1070 @classmethod
1071 1071 def invalidate(cls, key):
1072 1072 """
1073 1073 Returns Invalidation object if this given key should be invalidated
1074 1074 None otherwise. `cache_active = False` means that this cache
1075 1075 state is not valid and needs to be invalidated
1076 1076
1077 1077 :param key:
1078 1078 """
1079 1079
1080 1080 key, _prefix, _org_key = cls._get_key(key)
1081 1081 inv = cls._get_or_create_key(key, _prefix, _org_key)
1082 1082
1083 1083 if inv and inv.cache_active is False:
1084 1084 return inv
1085 1085
1086 1086 @classmethod
1087 1087 def set_invalidate(cls, key):
1088 1088 """
1089 1089 Mark this Cache key for invalidation
1090 1090
1091 1091 :param key:
1092 1092 """
1093 1093
1094 1094 key, _prefix, _org_key = cls._get_key(key)
1095 1095 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1096 1096 log.debug('marking %s key[s] %s for invalidation', len(inv_objs), _org_key)
1097 1097 try:
1098 1098 for inv_obj in inv_objs:
1099 1099 if inv_obj:
1100 1100 inv_obj.cache_active = False
1101 1101
1102 1102 Session.add(inv_obj)
1103 1103 Session.commit()
1104 1104 except Exception:
1105 1105 log.error(traceback.format_exc())
1106 1106 Session.rollback()
1107 1107
1108 1108 @classmethod
1109 1109 def set_valid(cls, key):
1110 1110 """
1111 1111 Mark this cache key as active and currently cached
1112 1112
1113 1113 :param key:
1114 1114 """
1115 1115 inv_obj = cls.get_by_key(key)
1116 1116 inv_obj.cache_active = True
1117 1117 Session.add(inv_obj)
1118 1118 Session.commit()
1119 1119
1120 1120
1121 1121 class ChangesetComment(Base, BaseModel):
1122 1122 __tablename__ = 'changeset_comments'
1123 1123 __table_args__ = (
1124 1124 {'extend_existing': True, 'mysql_engine':'InnoDB',
1125 1125 'mysql_charset': 'utf8'},
1126 1126 )
1127 1127 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1128 1128 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1129 1129 revision = Column('revision', String(40), nullable=False)
1130 1130 line_no = Column('line_no', Unicode(10), nullable=True)
1131 1131 f_path = Column('f_path', Unicode(1000), nullable=True)
1132 1132 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1133 1133 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1134 1134 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1135 1135
1136 1136 author = relationship('User', lazy='joined')
1137 1137 repo = relationship('Repository')
1138 1138
1139 1139 @classmethod
1140 1140 def get_users(cls, revision):
1141 1141 """
1142 1142 Returns user associated with this changesetComment. ie those
1143 1143 who actually commented
1144 1144
1145 1145 :param cls:
1146 1146 :param revision:
1147 1147 """
1148 1148 return Session.query(User)\
1149 1149 .filter(cls.revision == revision)\
1150 1150 .join(ChangesetComment.author).all()
1151 1151
1152 1152
1153 1153 class Notification(Base, BaseModel):
1154 1154 __tablename__ = 'notifications'
1155 1155 __table_args__ = (
1156 1156 {'extend_existing': True, 'mysql_engine':'InnoDB',
1157 1157 'mysql_charset': 'utf8'},
1158 1158 )
1159 1159
1160 1160 TYPE_CHANGESET_COMMENT = u'cs_comment'
1161 1161 TYPE_MESSAGE = u'message'
1162 1162 TYPE_MENTION = u'mention'
1163 1163 TYPE_REGISTRATION = u'registration'
1164 1164
1165 1165 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1166 1166 subject = Column('subject', Unicode(512), nullable=True)
1167 1167 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1168 1168 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1169 1169 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1170 1170 type_ = Column('type', Unicode(256))
1171 1171
1172 1172 created_by_user = relationship('User')
1173 1173 notifications_to_users = relationship('UserNotification', lazy='joined',
1174 1174 cascade="all, delete, delete-orphan")
1175 1175
1176 1176 @property
1177 1177 def recipients(self):
1178 1178 return [x.user for x in UserNotification.query()\
1179 1179 .filter(UserNotification.notification == self).all()]
1180 1180
1181 1181 @classmethod
1182 1182 def create(cls, created_by, subject, body, recipients, type_=None):
1183 1183 if type_ is None:
1184 1184 type_ = Notification.TYPE_MESSAGE
1185 1185
1186 1186 notification = cls()
1187 1187 notification.created_by_user = created_by
1188 1188 notification.subject = subject
1189 1189 notification.body = body
1190 1190 notification.type_ = type_
1191 1191 notification.created_on = datetime.datetime.now()
1192 1192
1193 1193 for u in recipients:
1194 1194 assoc = UserNotification()
1195 1195 assoc.notification = notification
1196 1196 u.notifications.append(assoc)
1197 1197 Session.add(notification)
1198 1198 return notification
1199 1199
1200 1200 @property
1201 1201 def description(self):
1202 1202 from rhodecode.model.notification import NotificationModel
1203 1203 return NotificationModel().make_description(self)
1204 1204
1205 1205
1206 1206 class UserNotification(Base, BaseModel):
1207 1207 __tablename__ = 'user_to_notification'
1208 1208 __table_args__ = (
1209 1209 UniqueConstraint('user_id', 'notification_id'),
1210 1210 {'extend_existing': True, 'mysql_engine':'InnoDB',
1211 1211 'mysql_charset': 'utf8'}
1212 1212 )
1213 1213 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1214 1214 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1215 1215 read = Column('read', Boolean, default=False)
1216 1216 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1217 1217
1218 1218 user = relationship('User', lazy="joined")
1219 1219 notification = relationship('Notification', lazy="joined",
1220 1220 order_by=lambda: Notification.created_on.desc(),)
1221 1221
1222 1222 def mark_as_read(self):
1223 1223 self.read = True
1224 1224 Session.add(self)
1225 1225
1226 1226
1227 1227 class DbMigrateVersion(Base, BaseModel):
1228 1228 __tablename__ = 'db_migrate_version'
1229 1229 __table_args__ = (
1230 1230 {'extend_existing': True, 'mysql_engine':'InnoDB',
1231 1231 'mysql_charset': 'utf8'},
1232 1232 )
1233 1233 repository_id = Column('repository_id', String(250), primary_key=True)
1234 1234 repository_path = Column('repository_path', Text)
1235 1235 version = Column('version', Integer)
1236 1236
1237 1237 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1238 1238 ## attaching a FK to this from 1_3_0 !
1239 1239
1240 1240
1241 1241 class PullRequest(Base, BaseModel):
1242 1242 __tablename__ = 'pull_requests'
1243 1243 __table_args__ = (
1244 1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 1245 'mysql_charset': 'utf8'},
1246 1246 )
1247 1247
1248 1248 STATUS_NEW = u'new'
1249 1249 STATUS_OPEN = u'open'
1250 1250 STATUS_CLOSED = u'closed'
1251 1251
1252 1252 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1253 1253 title = Column('title', Unicode(256), nullable=True)
1254 1254 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1255 1255 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1256 1256 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1257 1257 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1258 1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1259 1259 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) # 500 revisions max
1260 1260 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1261 1261 org_ref = Column('org_ref', Unicode(256), nullable=False)
1262 1262 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1263 1263 other_ref = Column('other_ref', Unicode(256), nullable=False)
@@ -1,971 +1,971 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import os
23 23 import time
24 24 import logging
25 25 import datetime
26 26 import traceback
27 27 import hashlib
28 28 import collections
29 29
30 30 from sqlalchemy import *
31 31 from sqlalchemy.ext.hybrid import hybrid_property
32 32 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 33 from sqlalchemy.exc import DatabaseError
34 34 from beaker.cache import cache_region, region_invalidate
35 35 from webob.exc import HTTPNotFound
36 36
37 37 from rhodecode.translation import _
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
44 44 safe_unicode, remove_suffix
45 45 from rhodecode.lib.ext_json import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47
48 48 from rhodecode.model.meta import Base, Session
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class BaseModel(object):
61 61 """
62 62 Base Model for all classes
63 63 """
64 64
65 65 @classmethod
66 66 def _get_keys(cls):
67 67 """return column names for this model """
68 68 return class_mapper(cls).c.keys()
69 69
70 70 def get_dict(self):
71 71 """
72 72 return dict with keys and values corresponding
73 73 to this model data """
74 74
75 75 d = {}
76 76 for k in self._get_keys():
77 77 d[k] = getattr(self, k)
78 78
79 79 # also use __json__() if present to get additional fields
80 80 _json_attr = getattr(self, '__json__', None)
81 81 if _json_attr:
82 82 # update with attributes from __json__
83 83 if callable(_json_attr):
84 84 _json_attr = _json_attr()
85 for k, val in _json_attr.iteritems():
85 for k, val in _json_attr.items():
86 86 d[k] = val
87 87 return d
88 88
89 89 def get_appstruct(self):
90 90 """return list with keys and values tupples corresponding
91 91 to this model data """
92 92
93 93 l = []
94 94 for k in self._get_keys():
95 95 l.append((k, getattr(self, k),))
96 96 return l
97 97
98 98 def populate_obj(self, populate_dict):
99 99 """populate model with data from given populate_dict"""
100 100
101 101 for k in self._get_keys():
102 102 if k in populate_dict:
103 103 setattr(self, k, populate_dict[k])
104 104
105 105 @classmethod
106 106 def query(cls):
107 107 return Session().query(cls)
108 108
109 109 @classmethod
110 110 def get(cls, id_):
111 111 if id_:
112 112 return cls.query().get(id_)
113 113
114 114 @classmethod
115 115 def get_or_404(cls, id_):
116 116 try:
117 117 id_ = int(id_)
118 118 except (TypeError, ValueError):
119 119 raise HTTPNotFound
120 120
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 return cls.query().all()
129 129
130 130 @classmethod
131 131 def delete(cls, id_):
132 132 obj = cls.query().get(id_)
133 133 Session().delete(obj)
134 134
135 135 def __repr__(self):
136 136 if hasattr(self, '__unicode__'):
137 137 # python repr needs to return str
138 138 return safe_str(self.__unicode__())
139 139 return '<DB:%s>' % (self.__class__.__name__)
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (
145 145 UniqueConstraint('app_settings_name'),
146 146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 147 'mysql_charset': 'utf8'}
148 148 )
149 149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 150 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
151 151 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
152 152
153 153 def __init__(self, k='', v=''):
154 154 self.app_settings_name = k
155 155 self.app_settings_value = v
156 156
157 157 @validates('_app_settings_value')
158 158 def validate_settings_value(self, key, val):
159 159 assert type(val) == unicode
160 160 return val
161 161
162 162 @hybrid_property
163 163 def app_settings_value(self):
164 164 v = self._app_settings_value
165 165 if self.app_settings_name == 'ldap_active':
166 166 v = str2bool(v)
167 167 return v
168 168
169 169 @app_settings_value.setter
170 170 def app_settings_value(self, val):
171 171 """
172 172 Setter that will always make sure we use unicode in app_settings_value
173 173
174 174 :param val:
175 175 """
176 176 self._app_settings_value = safe_unicode(val)
177 177
178 178 def __unicode__(self):
179 179 return u"<%s('%s:%s')>" % (
180 180 self.__class__.__name__,
181 181 self.app_settings_name, self.app_settings_value
182 182 )
183 183
184 184
185 185 class RhodeCodeUi(Base, BaseModel):
186 186 __tablename__ = 'rhodecode_ui'
187 187 __table_args__ = (
188 188 UniqueConstraint('ui_key'),
189 189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
190 190 'mysql_charset': 'utf8'}
191 191 )
192 192
193 193 HOOK_REPO_SIZE = 'changegroup.repo_size'
194 194 HOOK_PUSH = 'changegroup.push_logger'
195 195 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
196 196 HOOK_PULL = 'outgoing.pull_logger'
197 197 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
198 198
199 199 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 200 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
201 201 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
202 202 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
203 203 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
204 204
205 205
206 206
207 207 class User(Base, BaseModel):
208 208 __tablename__ = 'users'
209 209 __table_args__ = (
210 210 UniqueConstraint('username'), UniqueConstraint('email'),
211 211 Index('u_username_idx', 'username'),
212 212 Index('u_email_idx', 'email'),
213 213 {'extend_existing': True, 'mysql_engine': 'InnoDB',
214 214 'mysql_charset': 'utf8'}
215 215 )
216 216 DEFAULT_USER = 'default'
217 217 DEFAULT_PERMISSIONS = [
218 218 'hg.register.manual_activate', 'hg.create.repository',
219 219 'hg.fork.repository', 'repository.read', 'group.read'
220 220 ]
221 221 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 222 username = Column("username", String(255), nullable=True, unique=None, default=None)
223 223 password = Column("password", String(255), nullable=True, unique=None, default=None)
224 224 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
225 225 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
226 226 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
227 227 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
228 228 _email = Column("email", String(255), nullable=True, unique=None, default=None)
229 229 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
230 230 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
231 231 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
232 232 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
233 233
234 234 user_log = relationship('UserLog', cascade='all')
235 235 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
236 236
237 237 repositories = relationship('Repository')
238 238 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
239 239 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
240 240 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
241 241
242 242 group_member = relationship('UserGroupMember', cascade='all')
243 243
244 244 notifications = relationship('UserNotification', cascade='all')
245 245 # notifications assigned to this user
246 246 user_created_notifications = relationship('Notification', cascade='all')
247 247 # comments created by this user
248 248 user_comments = relationship('ChangesetComment', cascade='all')
249 249 user_emails = relationship('UserEmailMap', cascade='all')
250 250
251 251 @hybrid_property
252 252 def email(self):
253 253 return self._email
254 254
255 255 @email.setter
256 256 def email(self, val):
257 257 self._email = val.lower() if val else None
258 258
259 259 @property
260 260 def firstname(self):
261 261 # alias for future
262 262 return self.name
263 263
264 264 @property
265 265 def username_and_name(self):
266 266 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
267 267
268 268 @property
269 269 def full_name(self):
270 270 return '%s %s' % (self.firstname, self.lastname)
271 271
272 272 @property
273 273 def full_contact(self):
274 274 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
275 275
276 276 @property
277 277 def short_contact(self):
278 278 return '%s %s' % (self.firstname, self.lastname)
279 279
280 280 @property
281 281 def is_admin(self):
282 282 return self.admin
283 283
284 284 @classmethod
285 285 def get_by_username(cls, username, case_insensitive=False, cache=False):
286 286 if case_insensitive:
287 287 q = cls.query().filter(cls.username.ilike(username))
288 288 else:
289 289 q = cls.query().filter(cls.username == username)
290 290
291 291 if cache:
292 292 q = q.options(FromCache(
293 293 "sql_cache_short",
294 294 "get_user_%s" % _hash_key(username)
295 295 )
296 296 )
297 297 return q.scalar()
298 298
299 299 @classmethod
300 300 def get_by_auth_token(cls, auth_token, cache=False):
301 301 q = cls.query().filter(cls.api_key == auth_token)
302 302
303 303 if cache:
304 304 q = q.options(FromCache("sql_cache_short",
305 305 "get_auth_token_%s" % auth_token))
306 306 return q.scalar()
307 307
308 308 @classmethod
309 309 def get_by_email(cls, email, case_insensitive=False, cache=False):
310 310 if case_insensitive:
311 311 q = cls.query().filter(cls.email.ilike(email))
312 312 else:
313 313 q = cls.query().filter(cls.email == email)
314 314
315 315 if cache:
316 316 q = q.options(FromCache("sql_cache_short",
317 317 "get_email_key_%s" % email))
318 318
319 319 ret = q.scalar()
320 320 if ret is None:
321 321 q = UserEmailMap.query()
322 322 # try fetching in alternate email map
323 323 if case_insensitive:
324 324 q = q.filter(UserEmailMap.email.ilike(email))
325 325 else:
326 326 q = q.filter(UserEmailMap.email == email)
327 327 q = q.options(joinedload(UserEmailMap.user))
328 328 if cache:
329 329 q = q.options(FromCache("sql_cache_short",
330 330 "get_email_map_key_%s" % email))
331 331 ret = getattr(q.scalar(), 'user', None)
332 332
333 333 return ret
334 334
335 335
336 336 class UserEmailMap(Base, BaseModel):
337 337 __tablename__ = 'user_email_map'
338 338 __table_args__ = (
339 339 Index('uem_email_idx', 'email'),
340 340 UniqueConstraint('email'),
341 341 {'extend_existing': True, 'mysql_engine': 'InnoDB',
342 342 'mysql_charset': 'utf8'}
343 343 )
344 344 __mapper_args__ = {}
345 345
346 346 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 347 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
348 348 _email = Column("email", String(255), nullable=True, unique=False, default=None)
349 349 user = relationship('User', lazy='joined')
350 350
351 351 @validates('_email')
352 352 def validate_email(self, key, email):
353 353 # check if this email is not main one
354 354 main_email = Session().query(User).filter(User.email == email).scalar()
355 355 if main_email is not None:
356 356 raise AttributeError('email %s is present is user table' % email)
357 357 return email
358 358
359 359 @hybrid_property
360 360 def email(self):
361 361 return self._email
362 362
363 363 @email.setter
364 364 def email(self, val):
365 365 self._email = val.lower() if val else None
366 366
367 367
368 368 class UserLog(Base, BaseModel):
369 369 __tablename__ = 'user_logs'
370 370 __table_args__ = (
371 371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
372 372 'mysql_charset': 'utf8'},
373 373 )
374 374 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
375 375 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
376 376 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
377 377 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
378 378 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
379 379 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
380 380 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
381 381
382 382
383 383 user = relationship('User')
384 384 repository = relationship('Repository', cascade='')
385 385
386 386
387 387 class UserGroup(Base, BaseModel):
388 388 __tablename__ = 'users_groups'
389 389 __table_args__ = (
390 390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
391 391 'mysql_charset': 'utf8'},
392 392 )
393 393
394 394 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
395 395 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
396 396 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
397 397 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
398 398
399 399 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
400 400 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
401 401 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
402 402
403 403 def __unicode__(self):
404 404 return u'<userGroup(%s)>' % (self.users_group_name)
405 405
406 406 @classmethod
407 407 def get_by_group_name(cls, group_name, cache=False,
408 408 case_insensitive=False):
409 409 if case_insensitive:
410 410 q = cls.query().filter(cls.users_group_name.ilike(group_name))
411 411 else:
412 412 q = cls.query().filter(cls.users_group_name == group_name)
413 413 if cache:
414 414 q = q.options(FromCache(
415 415 "sql_cache_short",
416 416 "get_user_%s" % _hash_key(group_name)
417 417 )
418 418 )
419 419 return q.scalar()
420 420
421 421 @classmethod
422 422 def get(cls, users_group_id, cache=False):
423 423 user_group = cls.query()
424 424 if cache:
425 425 user_group = user_group.options(FromCache("sql_cache_short",
426 426 "get_users_group_%s" % users_group_id))
427 427 return user_group.get(users_group_id)
428 428
429 429
430 430 class UserGroupMember(Base, BaseModel):
431 431 __tablename__ = 'users_groups_members'
432 432 __table_args__ = (
433 433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 434 'mysql_charset': 'utf8'},
435 435 )
436 436
437 437 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 438 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
439 439 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
440 440
441 441 user = relationship('User', lazy='joined')
442 442 users_group = relationship('UserGroup')
443 443
444 444 def __init__(self, gr_id='', u_id=''):
445 445 self.users_group_id = gr_id
446 446 self.user_id = u_id
447 447
448 448
449 449 class Repository(Base, BaseModel):
450 450 __tablename__ = 'repositories'
451 451 __table_args__ = (
452 452 UniqueConstraint('repo_name'),
453 453 Index('r_repo_name_idx', 'repo_name'),
454 454 {'extend_existing': True, 'mysql_engine': 'InnoDB',
455 455 'mysql_charset': 'utf8'},
456 456 )
457 457
458 458 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
459 459 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
460 460 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
461 461 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
462 462 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
463 463 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
464 464 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
465 465 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
466 466 description = Column("description", String(10000), nullable=True, unique=None, default=None)
467 467 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
468 468 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
469 469 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
470 470 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
471 471 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
472 472
473 473 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
474 474 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
475 475
476 476 user = relationship('User')
477 477 fork = relationship('Repository', remote_side=repo_id)
478 478 group = relationship('RepoGroup')
479 479 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
480 480 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
481 481 stats = relationship('Statistics', cascade='all', uselist=False)
482 482
483 483 followers = relationship('UserFollowing',
484 484 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
485 485 cascade='all')
486 486
487 487 logs = relationship('UserLog')
488 488 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
489 489
490 490 pull_requests_org = relationship('PullRequest',
491 491 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
492 492 cascade="all, delete, delete-orphan")
493 493
494 494 pull_requests_other = relationship('PullRequest',
495 495 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
496 496 cascade="all, delete, delete-orphan")
497 497
498 498 def __unicode__(self):
499 499 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
500 500 self.repo_name)
501 501
502 502
503 503 @classmethod
504 504 def get_by_repo_name(cls, repo_name):
505 505 q = Session().query(cls).filter(cls.repo_name == repo_name)
506 506 q = q.options(joinedload(Repository.fork))\
507 507 .options(joinedload(Repository.user))\
508 508 .options(joinedload(Repository.group))
509 509 return q.scalar()
510 510
511 511
512 512 class RepoGroup(Base, BaseModel):
513 513 __tablename__ = 'groups'
514 514 __table_args__ = (
515 515 UniqueConstraint('group_name', 'group_parent_id'),
516 516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 517 'mysql_charset': 'utf8'},
518 518 )
519 519 __mapper_args__ = {'order_by': 'group_name'}
520 520
521 521 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
522 522 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
523 523 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
524 524 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
525 525 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
526 526
527 527 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
528 528 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
529 529 parent_group = relationship('RepoGroup', remote_side=group_id)
530 530
531 531 def __init__(self, group_name='', parent_group=None):
532 532 self.group_name = group_name
533 533 self.parent_group = parent_group
534 534
535 535 def __unicode__(self):
536 536 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
537 537 self.group_name)
538 538
539 539 @classmethod
540 540 def url_sep(cls):
541 541 return URL_SEP
542 542
543 543 @classmethod
544 544 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
545 545 if case_insensitive:
546 546 gr = cls.query()\
547 547 .filter(cls.group_name.ilike(group_name))
548 548 else:
549 549 gr = cls.query()\
550 550 .filter(cls.group_name == group_name)
551 551 if cache:
552 552 gr = gr.options(FromCache(
553 553 "sql_cache_short",
554 554 "get_group_%s" % _hash_key(group_name)
555 555 )
556 556 )
557 557 return gr.scalar()
558 558
559 559
560 560 class Permission(Base, BaseModel):
561 561 __tablename__ = 'permissions'
562 562 __table_args__ = (
563 563 Index('p_perm_name_idx', 'permission_name'),
564 564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
565 565 'mysql_charset': 'utf8'},
566 566 )
567 567 PERMS = [
568 568 ('repository.none', _('Repository no access')),
569 569 ('repository.read', _('Repository read access')),
570 570 ('repository.write', _('Repository write access')),
571 571 ('repository.admin', _('Repository admin access')),
572 572
573 573 ('group.none', _('Repositories Group no access')),
574 574 ('group.read', _('Repositories Group read access')),
575 575 ('group.write', _('Repositories Group write access')),
576 576 ('group.admin', _('Repositories Group admin access')),
577 577
578 578 ('hg.admin', _('RhodeCode Administrator')),
579 579 ('hg.create.none', _('Repository creation disabled')),
580 580 ('hg.create.repository', _('Repository creation enabled')),
581 581 ('hg.fork.none', _('Repository forking disabled')),
582 582 ('hg.fork.repository', _('Repository forking enabled')),
583 583 ('hg.register.none', _('Register disabled')),
584 584 ('hg.register.manual_activate', _('Register new user with RhodeCode '
585 585 'with manual activation')),
586 586
587 587 ('hg.register.auto_activate', _('Register new user with RhodeCode '
588 588 'with auto activation')),
589 589 ]
590 590
591 591 # defines which permissions are more important higher the more important
592 592 PERM_WEIGHTS = {
593 593 'repository.none': 0,
594 594 'repository.read': 1,
595 595 'repository.write': 3,
596 596 'repository.admin': 4,
597 597
598 598 'group.none': 0,
599 599 'group.read': 1,
600 600 'group.write': 3,
601 601 'group.admin': 4,
602 602
603 603 'hg.fork.none': 0,
604 604 'hg.fork.repository': 1,
605 605 'hg.create.none': 0,
606 606 'hg.create.repository':1
607 607 }
608 608
609 609 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
610 610 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
611 611 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
612 612
613 613 def __unicode__(self):
614 614 return u"<%s('%s:%s')>" % (
615 615 self.__class__.__name__, self.permission_id, self.permission_name
616 616 )
617 617
618 618 @classmethod
619 619 def get_by_key(cls, key):
620 620 return cls.query().filter(cls.permission_name == key).scalar()
621 621
622 622
623 623 class UserRepoToPerm(Base, BaseModel):
624 624 __tablename__ = 'repo_to_perm'
625 625 __table_args__ = (
626 626 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
627 627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 628 'mysql_charset': 'utf8'}
629 629 )
630 630 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
632 632 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
633 633 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
634 634
635 635 user = relationship('User')
636 636 repository = relationship('Repository')
637 637 permission = relationship('Permission')
638 638
639 639 def __unicode__(self):
640 640 return u'<user:%s => %s >' % (self.user, self.repository)
641 641
642 642
643 643 class UserToPerm(Base, BaseModel):
644 644 __tablename__ = 'user_to_perm'
645 645 __table_args__ = (
646 646 UniqueConstraint('user_id', 'permission_id'),
647 647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
648 648 'mysql_charset': 'utf8'}
649 649 )
650 650 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
651 651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
652 652 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
653 653
654 654 user = relationship('User')
655 655 permission = relationship('Permission', lazy='joined')
656 656
657 657
658 658 class UserGroupRepoToPerm(Base, BaseModel):
659 659 __tablename__ = 'users_group_repo_to_perm'
660 660 __table_args__ = (
661 661 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
662 662 {'extend_existing': True, 'mysql_engine': 'InnoDB',
663 663 'mysql_charset': 'utf8'}
664 664 )
665 665 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
666 666 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
667 667 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
668 668 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
669 669
670 670 users_group = relationship('UserGroup')
671 671 permission = relationship('Permission')
672 672 repository = relationship('Repository')
673 673
674 674 def __unicode__(self):
675 675 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
676 676
677 677
678 678 class UserGroupToPerm(Base, BaseModel):
679 679 __tablename__ = 'users_group_to_perm'
680 680 __table_args__ = (
681 681 UniqueConstraint('users_group_id', 'permission_id',),
682 682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
683 683 'mysql_charset': 'utf8'}
684 684 )
685 685 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
686 686 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
687 687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
688 688
689 689 users_group = relationship('UserGroup')
690 690 permission = relationship('Permission')
691 691
692 692
693 693 class UserRepoGroupToPerm(Base, BaseModel):
694 694 __tablename__ = 'user_repo_group_to_perm'
695 695 __table_args__ = (
696 696 UniqueConstraint('user_id', 'group_id', 'permission_id'),
697 697 {'extend_existing': True, 'mysql_engine': 'InnoDB',
698 698 'mysql_charset': 'utf8'}
699 699 )
700 700
701 701 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
702 702 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
703 703 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
704 704 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
705 705
706 706 user = relationship('User')
707 707 group = relationship('RepoGroup')
708 708 permission = relationship('Permission')
709 709
710 710
711 711 class UserGroupRepoGroupToPerm(Base, BaseModel):
712 712 __tablename__ = 'users_group_repo_group_to_perm'
713 713 __table_args__ = (
714 714 UniqueConstraint('users_group_id', 'group_id'),
715 715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
716 716 'mysql_charset': 'utf8'}
717 717 )
718 718
719 719 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)
720 720 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
721 721 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
722 722 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
723 723
724 724 users_group = relationship('UserGroup')
725 725 permission = relationship('Permission')
726 726 group = relationship('RepoGroup')
727 727
728 728
729 729 class Statistics(Base, BaseModel):
730 730 __tablename__ = 'statistics'
731 731 __table_args__ = (
732 732 UniqueConstraint('repository_id'),
733 733 {'extend_existing': True, 'mysql_engine': 'InnoDB',
734 734 'mysql_charset': 'utf8'}
735 735 )
736 736 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
737 737 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
738 738 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
739 739 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
740 740 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
741 741 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
742 742
743 743 repository = relationship('Repository', single_parent=True)
744 744
745 745
746 746 class UserFollowing(Base, BaseModel):
747 747 __tablename__ = 'user_followings'
748 748 __table_args__ = (
749 749 UniqueConstraint('user_id', 'follows_repository_id'),
750 750 UniqueConstraint('user_id', 'follows_user_id'),
751 751 {'extend_existing': True, 'mysql_engine': 'InnoDB',
752 752 'mysql_charset': 'utf8'}
753 753 )
754 754
755 755 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
756 756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
757 757 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
758 758 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
759 759 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
760 760
761 761 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
762 762
763 763 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
764 764 follows_repository = relationship('Repository', order_by='Repository.repo_name')
765 765
766 766
767 767 class CacheInvalidation(Base, BaseModel):
768 768 __tablename__ = 'cache_invalidation'
769 769 __table_args__ = (
770 770 UniqueConstraint('cache_key'),
771 771 Index('key_idx', 'cache_key'),
772 772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
773 773 'mysql_charset': 'utf8'},
774 774 )
775 775 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
776 776 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
777 777 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
778 778 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
779 779
780 780 def __init__(self, cache_key, cache_args=''):
781 781 self.cache_key = cache_key
782 782 self.cache_args = cache_args
783 783 self.cache_active = False
784 784
785 785
786 786 class ChangesetComment(Base, BaseModel):
787 787 __tablename__ = 'changeset_comments'
788 788 __table_args__ = (
789 789 Index('cc_revision_idx', 'revision'),
790 790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
791 791 'mysql_charset': 'utf8'},
792 792 )
793 793 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
794 794 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
795 795 revision = Column('revision', String(40), nullable=True)
796 796 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
797 797 line_no = Column('line_no', Unicode(10), nullable=True)
798 798 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
799 799 f_path = Column('f_path', Unicode(1000), nullable=True)
800 800 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
801 801 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
802 802 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
803 803 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
804 804
805 805 author = relationship('User', lazy='joined')
806 806 repo = relationship('Repository')
807 807 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
808 808 pull_request = relationship('PullRequest', lazy='joined')
809 809
810 810 @classmethod
811 811 def get_users(cls, revision=None, pull_request_id=None):
812 812 """
813 813 Returns user associated with this ChangesetComment. ie those
814 814 who actually commented
815 815
816 816 :param cls:
817 817 :param revision:
818 818 """
819 819 q = Session().query(User)\
820 820 .join(ChangesetComment.author)
821 821 if revision:
822 822 q = q.filter(cls.revision == revision)
823 823 elif pull_request_id:
824 824 q = q.filter(cls.pull_request_id == pull_request_id)
825 825 return q.all()
826 826
827 827
828 828 class ChangesetStatus(Base, BaseModel):
829 829 __tablename__ = 'changeset_statuses'
830 830 __table_args__ = (
831 831 Index('cs_revision_idx', 'revision'),
832 832 Index('cs_version_idx', 'version'),
833 833 UniqueConstraint('repo_id', 'revision', 'version'),
834 834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
835 835 'mysql_charset': 'utf8'}
836 836 )
837 837 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
838 838 STATUS_APPROVED = 'approved'
839 839 STATUS_REJECTED = 'rejected'
840 840 STATUS_UNDER_REVIEW = 'under_review'
841 841
842 842 STATUSES = [
843 843 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
844 844 (STATUS_APPROVED, _("Approved")),
845 845 (STATUS_REJECTED, _("Rejected")),
846 846 (STATUS_UNDER_REVIEW, _("Under Review")),
847 847 ]
848 848
849 849 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
850 850 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
851 851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
852 852 revision = Column('revision', String(40), nullable=False)
853 853 status = Column('status', String(128), nullable=False, default=DEFAULT)
854 854 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
855 855 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
856 856 version = Column('version', Integer(), nullable=False, default=0)
857 857 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
858 858
859 859 author = relationship('User', lazy='joined')
860 860 repo = relationship('Repository')
861 861 comment = relationship('ChangesetComment', lazy='joined')
862 862 pull_request = relationship('PullRequest', lazy='joined')
863 863
864 864
865 865
866 866 class PullRequest(Base, BaseModel):
867 867 __tablename__ = 'pull_requests'
868 868 __table_args__ = (
869 869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
870 870 'mysql_charset': 'utf8'},
871 871 )
872 872
873 873 STATUS_NEW = u'new'
874 874 STATUS_OPEN = u'open'
875 875 STATUS_CLOSED = u'closed'
876 876
877 877 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
878 878 title = Column('title', Unicode(256), nullable=True)
879 879 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
880 880 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
881 881 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
882 882 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
883 883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
884 884 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
885 885 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
886 886 org_ref = Column('org_ref', Unicode(256), nullable=False)
887 887 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
888 888 other_ref = Column('other_ref', Unicode(256), nullable=False)
889 889
890 890 author = relationship('User', lazy='joined')
891 891 reviewers = relationship('PullRequestReviewers',
892 892 cascade="all, delete, delete-orphan")
893 893 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
894 894 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
895 895 statuses = relationship('ChangesetStatus')
896 896 comments = relationship('ChangesetComment',
897 897 cascade="all, delete, delete-orphan")
898 898
899 899
900 900 class PullRequestReviewers(Base, BaseModel):
901 901 __tablename__ = 'pull_request_reviewers'
902 902 __table_args__ = (
903 903 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 904 'mysql_charset': 'utf8'},
905 905 )
906 906
907 907 def __init__(self, user=None, pull_request=None):
908 908 self.user = user
909 909 self.pull_request = pull_request
910 910
911 911 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
912 912 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
913 913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
914 914
915 915 user = relationship('User')
916 916 pull_request = relationship('PullRequest')
917 917
918 918
919 919 class Notification(Base, BaseModel):
920 920 __tablename__ = 'notifications'
921 921 __table_args__ = (
922 922 Index('notification_type_idx', 'type'),
923 923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
924 924 'mysql_charset': 'utf8'},
925 925 )
926 926
927 927 TYPE_CHANGESET_COMMENT = u'cs_comment'
928 928 TYPE_MESSAGE = u'message'
929 929 TYPE_MENTION = u'mention'
930 930 TYPE_REGISTRATION = u'registration'
931 931 TYPE_PULL_REQUEST = u'pull_request'
932 932 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
933 933
934 934 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
935 935 subject = Column('subject', Unicode(512), nullable=True)
936 936 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
937 937 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
938 938 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
939 939 type_ = Column('type', Unicode(256))
940 940
941 941 created_by_user = relationship('User')
942 942 notifications_to_users = relationship('UserNotification', lazy='joined',
943 943 cascade="all, delete, delete-orphan")
944 944
945 945
946 946 class UserNotification(Base, BaseModel):
947 947 __tablename__ = 'user_to_notification'
948 948 __table_args__ = (
949 949 UniqueConstraint('user_id', 'notification_id'),
950 950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
951 951 'mysql_charset': 'utf8'}
952 952 )
953 953 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
954 954 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
955 955 read = Column('read', Boolean, default=False)
956 956 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
957 957
958 958 user = relationship('User', lazy="joined")
959 959 notification = relationship('Notification', lazy="joined",
960 960 order_by=lambda: Notification.created_on.desc(),)
961 961
962 962
963 963 class DbMigrateVersion(Base, BaseModel):
964 964 __tablename__ = 'db_migrate_version'
965 965 __table_args__ = (
966 966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
967 967 'mysql_charset': 'utf8'},
968 968 )
969 969 repository_id = Column('repository_id', String(250), primary_key=True)
970 970 repository_path = Column('repository_path', Text)
971 971 version = Column('version', Integer)
@@ -1,992 +1,992 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import os
23 23 import time
24 24 import logging
25 25 import datetime
26 26 import traceback
27 27 import hashlib
28 28 import collections
29 29
30 30 from sqlalchemy import *
31 31 from sqlalchemy.ext.hybrid import hybrid_property
32 32 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 33 from sqlalchemy.exc import DatabaseError
34 34 from beaker.cache import cache_region, region_invalidate
35 35 from webob.exc import HTTPNotFound
36 36
37 37 from rhodecode.translation import _
38 38
39 39 from rhodecode.lib.vcs import get_backend
40 40 from rhodecode.lib.vcs.utils.helpers import get_scm
41 41 from rhodecode.lib.vcs.exceptions import VCSError
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 safe_unicode, remove_suffix, remove_prefix
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 for k, val in _json_attr.iteritems():
86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194
195 195 class RhodeCodeUi(Base, BaseModel):
196 196 __tablename__ = 'rhodecode_ui'
197 197 __table_args__ = (
198 198 UniqueConstraint('ui_key'),
199 199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 200 'mysql_charset': 'utf8'}
201 201 )
202 202
203 203 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 204 HOOK_PUSH = 'changegroup.push_logger'
205 205 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 206 HOOK_PULL = 'outgoing.pull_logger'
207 207 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 208
209 209 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 210 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 211 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 212 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 213 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 214
215 215
216 216
217 217 class User(Base, BaseModel):
218 218 __tablename__ = 'users'
219 219 __table_args__ = (
220 220 UniqueConstraint('username'), UniqueConstraint('email'),
221 221 Index('u_username_idx', 'username'),
222 222 Index('u_email_idx', 'email'),
223 223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 224 'mysql_charset': 'utf8'}
225 225 )
226 226 DEFAULT_USER = 'default'
227 227 DEFAULT_PERMISSIONS = [
228 228 'hg.register.manual_activate', 'hg.create.repository',
229 229 'hg.fork.repository', 'repository.read', 'group.read'
230 230 ]
231 231 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 232 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 233 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 234 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 235 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 236 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 237 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 238 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 239 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 240 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 241 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 242 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 243
244 244 user_log = relationship('UserLog')
245 245 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 246
247 247 repositories = relationship('Repository')
248 248 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 249 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 250
251 251 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 252 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 253
254 254 group_member = relationship('UserGroupMember', cascade='all')
255 255
256 256 notifications = relationship('UserNotification', cascade='all')
257 257 # notifications assigned to this user
258 258 user_created_notifications = relationship('Notification', cascade='all')
259 259 # comments created by this user
260 260 user_comments = relationship('ChangesetComment', cascade='all')
261 261 user_emails = relationship('UserEmailMap', cascade='all')
262 262
263 263 @hybrid_property
264 264 def email(self):
265 265 return self._email
266 266
267 267 @email.setter
268 268 def email(self, val):
269 269 self._email = val.lower() if val else None
270 270
271 271 @property
272 272 def firstname(self):
273 273 # alias for future
274 274 return self.name
275 275
276 276 @property
277 277 def username_and_name(self):
278 278 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 279
280 280 @property
281 281 def full_name(self):
282 282 return '%s %s' % (self.firstname, self.lastname)
283 283
284 284 @property
285 285 def full_contact(self):
286 286 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 287
288 288 @property
289 289 def short_contact(self):
290 290 return '%s %s' % (self.firstname, self.lastname)
291 291
292 292 @property
293 293 def is_admin(self):
294 294 return self.admin
295 295
296 296 @classmethod
297 297 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 298 if case_insensitive:
299 299 q = cls.query().filter(cls.username.ilike(username))
300 300 else:
301 301 q = cls.query().filter(cls.username == username)
302 302
303 303 if cache:
304 304 q = q.options(FromCache(
305 305 "sql_cache_short",
306 306 "get_user_%s" % _hash_key(username)
307 307 )
308 308 )
309 309 return q.scalar()
310 310
311 311 @classmethod
312 312 def get_by_auth_token(cls, auth_token, cache=False):
313 313 q = cls.query().filter(cls.api_key == auth_token)
314 314
315 315 if cache:
316 316 q = q.options(FromCache("sql_cache_short",
317 317 "get_auth_token_%s" % auth_token))
318 318 return q.scalar()
319 319
320 320 @classmethod
321 321 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 322 if case_insensitive:
323 323 q = cls.query().filter(cls.email.ilike(email))
324 324 else:
325 325 q = cls.query().filter(cls.email == email)
326 326
327 327 if cache:
328 328 q = q.options(FromCache("sql_cache_short",
329 329 "get_email_key_%s" % email))
330 330
331 331 ret = q.scalar()
332 332 if ret is None:
333 333 q = UserEmailMap.query()
334 334 # try fetching in alternate email map
335 335 if case_insensitive:
336 336 q = q.filter(UserEmailMap.email.ilike(email))
337 337 else:
338 338 q = q.filter(UserEmailMap.email == email)
339 339 q = q.options(joinedload(UserEmailMap.user))
340 340 if cache:
341 341 q = q.options(FromCache("sql_cache_short",
342 342 "get_email_map_key_%s" % email))
343 343 ret = getattr(q.scalar(), 'user', None)
344 344
345 345 return ret
346 346
347 347
348 348 class UserEmailMap(Base, BaseModel):
349 349 __tablename__ = 'user_email_map'
350 350 __table_args__ = (
351 351 Index('uem_email_idx', 'email'),
352 352 UniqueConstraint('email'),
353 353 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 354 'mysql_charset': 'utf8'}
355 355 )
356 356 __mapper_args__ = {}
357 357
358 358 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 360 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 361 user = relationship('User', lazy='joined')
362 362
363 363 @validates('_email')
364 364 def validate_email(self, key, email):
365 365 # check if this email is not main one
366 366 main_email = Session().query(User).filter(User.email == email).scalar()
367 367 if main_email is not None:
368 368 raise AttributeError('email %s is present is user table' % email)
369 369 return email
370 370
371 371 @hybrid_property
372 372 def email(self):
373 373 return self._email
374 374
375 375 @email.setter
376 376 def email(self, val):
377 377 self._email = val.lower() if val else None
378 378
379 379
380 380 class UserLog(Base, BaseModel):
381 381 __tablename__ = 'user_logs'
382 382 __table_args__ = (
383 383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
384 384 'mysql_charset': 'utf8'},
385 385 )
386 386 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
387 387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
388 388 username = Column("username", String(255), nullable=True, unique=None, default=None)
389 389 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
390 390 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
391 391 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
392 392 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
393 393 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
394 394
395 395
396 396 user = relationship('User')
397 397 repository = relationship('Repository', cascade='')
398 398
399 399
400 400 class UserGroup(Base, BaseModel):
401 401 __tablename__ = 'users_groups'
402 402 __table_args__ = (
403 403 {'extend_existing': True, 'mysql_engine': 'InnoDB',
404 404 'mysql_charset': 'utf8'},
405 405 )
406 406
407 407 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
408 408 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
409 409 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
410 410 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
411 411
412 412 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
413 413 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
414 414 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
415 415
416 416 def __unicode__(self):
417 417 return u'<userGroup(%s)>' % (self.users_group_name)
418 418
419 419 @classmethod
420 420 def get_by_group_name(cls, group_name, cache=False,
421 421 case_insensitive=False):
422 422 if case_insensitive:
423 423 q = cls.query().filter(cls.users_group_name.ilike(group_name))
424 424 else:
425 425 q = cls.query().filter(cls.users_group_name == group_name)
426 426 if cache:
427 427 q = q.options(FromCache(
428 428 "sql_cache_short",
429 429 "get_user_%s" % _hash_key(group_name)
430 430 )
431 431 )
432 432 return q.scalar()
433 433
434 434 @classmethod
435 435 def get(cls, users_group_id, cache=False):
436 436 user_group = cls.query()
437 437 if cache:
438 438 user_group = user_group.options(FromCache("sql_cache_short",
439 439 "get_users_group_%s" % users_group_id))
440 440 return user_group.get(users_group_id)
441 441
442 442
443 443 class UserGroupMember(Base, BaseModel):
444 444 __tablename__ = 'users_groups_members'
445 445 __table_args__ = (
446 446 {'extend_existing': True, 'mysql_engine': 'InnoDB',
447 447 'mysql_charset': 'utf8'},
448 448 )
449 449
450 450 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
451 451 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
452 452 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
453 453
454 454 user = relationship('User', lazy='joined')
455 455 users_group = relationship('UserGroup')
456 456
457 457 def __init__(self, gr_id='', u_id=''):
458 458 self.users_group_id = gr_id
459 459 self.user_id = u_id
460 460
461 461
462 462 class Repository(Base, BaseModel):
463 463 __tablename__ = 'repositories'
464 464 __table_args__ = (
465 465 UniqueConstraint('repo_name'),
466 466 Index('r_repo_name_idx', 'repo_name'),
467 467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 468 'mysql_charset': 'utf8'},
469 469 )
470 470
471 471 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 472 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
473 473 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
474 474 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
475 475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
476 476 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
477 477 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
478 478 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
479 479 description = Column("description", String(10000), nullable=True, unique=None, default=None)
480 480 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
481 481 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
482 482 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
483 483 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
484 484 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
485 485
486 486 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
487 487 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
488 488
489 489 user = relationship('User')
490 490 fork = relationship('Repository', remote_side=repo_id)
491 491 group = relationship('RepoGroup')
492 492 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
493 493 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
494 494 stats = relationship('Statistics', cascade='all', uselist=False)
495 495
496 496 followers = relationship('UserFollowing',
497 497 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
498 498 cascade='all')
499 499
500 500 logs = relationship('UserLog')
501 501 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
502 502
503 503 pull_requests_org = relationship('PullRequest',
504 504 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
505 505 cascade="all, delete, delete-orphan")
506 506
507 507 pull_requests_other = relationship('PullRequest',
508 508 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
509 509 cascade="all, delete, delete-orphan")
510 510
511 511 def __unicode__(self):
512 512 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
513 513 self.repo_name)
514 514
515 515
516 516 @classmethod
517 517 def get_by_repo_name(cls, repo_name):
518 518 q = Session().query(cls).filter(cls.repo_name == repo_name)
519 519 q = q.options(joinedload(Repository.fork))\
520 520 .options(joinedload(Repository.user))\
521 521 .options(joinedload(Repository.group))
522 522 return q.scalar()
523 523
524 524
525 525 class RepoGroup(Base, BaseModel):
526 526 __tablename__ = 'groups'
527 527 __table_args__ = (
528 528 UniqueConstraint('group_name', 'group_parent_id'),
529 529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 530 'mysql_charset': 'utf8'},
531 531 )
532 532 __mapper_args__ = {'order_by': 'group_name'}
533 533
534 534 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 535 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
536 536 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
537 537 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
538 538 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
539 539
540 540 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
541 541 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
542 542 parent_group = relationship('RepoGroup', remote_side=group_id)
543 543
544 544 def __init__(self, group_name='', parent_group=None):
545 545 self.group_name = group_name
546 546 self.parent_group = parent_group
547 547
548 548 def __unicode__(self):
549 549 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
550 550 self.group_name)
551 551
552 552 @classmethod
553 553 def url_sep(cls):
554 554 return URL_SEP
555 555
556 556 @classmethod
557 557 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
558 558 if case_insensitive:
559 559 gr = cls.query()\
560 560 .filter(cls.group_name.ilike(group_name))
561 561 else:
562 562 gr = cls.query()\
563 563 .filter(cls.group_name == group_name)
564 564 if cache:
565 565 gr = gr.options(FromCache(
566 566 "sql_cache_short",
567 567 "get_group_%s" % _hash_key(group_name)
568 568 )
569 569 )
570 570 return gr.scalar()
571 571
572 572
573 573 class Permission(Base, BaseModel):
574 574 __tablename__ = 'permissions'
575 575 __table_args__ = (
576 576 Index('p_perm_name_idx', 'permission_name'),
577 577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 578 'mysql_charset': 'utf8'},
579 579 )
580 580 PERMS = [
581 581 ('repository.none', _('Repository no access')),
582 582 ('repository.read', _('Repository read access')),
583 583 ('repository.write', _('Repository write access')),
584 584 ('repository.admin', _('Repository admin access')),
585 585
586 586 ('group.none', _('Repositories Group no access')),
587 587 ('group.read', _('Repositories Group read access')),
588 588 ('group.write', _('Repositories Group write access')),
589 589 ('group.admin', _('Repositories Group admin access')),
590 590
591 591 ('hg.admin', _('RhodeCode Administrator')),
592 592 ('hg.create.none', _('Repository creation disabled')),
593 593 ('hg.create.repository', _('Repository creation enabled')),
594 594 ('hg.fork.none', _('Repository forking disabled')),
595 595 ('hg.fork.repository', _('Repository forking enabled')),
596 596 ('hg.register.none', _('Register disabled')),
597 597 ('hg.register.manual_activate', _('Register new user with RhodeCode '
598 598 'with manual activation')),
599 599
600 600 ('hg.register.auto_activate', _('Register new user with RhodeCode '
601 601 'with auto activation')),
602 602 ]
603 603
604 604 # defines which permissions are more important higher the more important
605 605 PERM_WEIGHTS = {
606 606 'repository.none': 0,
607 607 'repository.read': 1,
608 608 'repository.write': 3,
609 609 'repository.admin': 4,
610 610
611 611 'group.none': 0,
612 612 'group.read': 1,
613 613 'group.write': 3,
614 614 'group.admin': 4,
615 615
616 616 'hg.fork.none': 0,
617 617 'hg.fork.repository': 1,
618 618 'hg.create.none': 0,
619 619 'hg.create.repository':1
620 620 }
621 621
622 622 DEFAULT_USER_PERMISSIONS = [
623 623 'repository.read',
624 624 'group.read',
625 625 'hg.create.repository',
626 626 'hg.fork.repository',
627 627 'hg.register.manual_activate',
628 628 ]
629 629
630 630 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 631 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
632 632 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
633 633
634 634 def __unicode__(self):
635 635 return u"<%s('%s:%s')>" % (
636 636 self.__class__.__name__, self.permission_id, self.permission_name
637 637 )
638 638
639 639 @classmethod
640 640 def get_by_key(cls, key):
641 641 return cls.query().filter(cls.permission_name == key).scalar()
642 642
643 643
644 644 class UserRepoToPerm(Base, BaseModel):
645 645 __tablename__ = 'repo_to_perm'
646 646 __table_args__ = (
647 647 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
648 648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
649 649 'mysql_charset': 'utf8'}
650 650 )
651 651 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
652 652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
653 653 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
654 654 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
655 655
656 656 user = relationship('User')
657 657 repository = relationship('Repository')
658 658 permission = relationship('Permission')
659 659
660 660 def __unicode__(self):
661 661 return u'<user:%s => %s >' % (self.user, self.repository)
662 662
663 663
664 664 class UserToPerm(Base, BaseModel):
665 665 __tablename__ = 'user_to_perm'
666 666 __table_args__ = (
667 667 UniqueConstraint('user_id', 'permission_id'),
668 668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
669 669 'mysql_charset': 'utf8'}
670 670 )
671 671 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
672 672 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
673 673 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
674 674
675 675 user = relationship('User')
676 676 permission = relationship('Permission', lazy='joined')
677 677
678 678
679 679 class UserGroupRepoToPerm(Base, BaseModel):
680 680 __tablename__ = 'users_group_repo_to_perm'
681 681 __table_args__ = (
682 682 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
683 683 {'extend_existing': True, 'mysql_engine': 'InnoDB',
684 684 'mysql_charset': 'utf8'}
685 685 )
686 686 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
687 687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
688 688 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
689 689 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
690 690
691 691 users_group = relationship('UserGroup')
692 692 permission = relationship('Permission')
693 693 repository = relationship('Repository')
694 694
695 695 def __unicode__(self):
696 696 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
697 697
698 698
699 699 class UserGroupToPerm(Base, BaseModel):
700 700 __tablename__ = 'users_group_to_perm'
701 701 __table_args__ = (
702 702 UniqueConstraint('users_group_id', 'permission_id',),
703 703 {'extend_existing': True, 'mysql_engine': 'InnoDB',
704 704 'mysql_charset': 'utf8'}
705 705 )
706 706 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
707 707 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
708 708 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
709 709
710 710 users_group = relationship('UserGroup')
711 711 permission = relationship('Permission')
712 712
713 713
714 714 class UserRepoGroupToPerm(Base, BaseModel):
715 715 __tablename__ = 'user_repo_group_to_perm'
716 716 __table_args__ = (
717 717 UniqueConstraint('user_id', 'group_id', 'permission_id'),
718 718 {'extend_existing': True, 'mysql_engine': 'InnoDB',
719 719 'mysql_charset': 'utf8'}
720 720 )
721 721
722 722 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 723 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
724 724 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
725 725 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
726 726
727 727 user = relationship('User')
728 728 group = relationship('RepoGroup')
729 729 permission = relationship('Permission')
730 730
731 731
732 732 class UserGroupRepoGroupToPerm(Base, BaseModel):
733 733 __tablename__ = 'users_group_repo_group_to_perm'
734 734 __table_args__ = (
735 735 UniqueConstraint('users_group_id', 'group_id'),
736 736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
737 737 'mysql_charset': 'utf8'}
738 738 )
739 739
740 740 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)
741 741 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
742 742 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
743 743 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
744 744
745 745 users_group = relationship('UserGroup')
746 746 permission = relationship('Permission')
747 747 group = relationship('RepoGroup')
748 748
749 749
750 750 class Statistics(Base, BaseModel):
751 751 __tablename__ = 'statistics'
752 752 __table_args__ = (
753 753 UniqueConstraint('repository_id'),
754 754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
755 755 'mysql_charset': 'utf8'}
756 756 )
757 757 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
758 758 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
759 759 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
760 760 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
761 761 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
762 762 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
763 763
764 764 repository = relationship('Repository', single_parent=True)
765 765
766 766
767 767 class UserFollowing(Base, BaseModel):
768 768 __tablename__ = 'user_followings'
769 769 __table_args__ = (
770 770 UniqueConstraint('user_id', 'follows_repository_id'),
771 771 UniqueConstraint('user_id', 'follows_user_id'),
772 772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
773 773 'mysql_charset': 'utf8'}
774 774 )
775 775
776 776 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
777 777 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
778 778 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
779 779 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
780 780 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
781 781
782 782 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
783 783
784 784 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
785 785 follows_repository = relationship('Repository', order_by='Repository.repo_name')
786 786
787 787
788 788 class CacheInvalidation(Base, BaseModel):
789 789 __tablename__ = 'cache_invalidation'
790 790 __table_args__ = (
791 791 UniqueConstraint('cache_key'),
792 792 Index('key_idx', 'cache_key'),
793 793 {'extend_existing': True, 'mysql_engine': 'InnoDB',
794 794 'mysql_charset': 'utf8'},
795 795 )
796 796 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
797 797 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
798 798 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
799 799 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
800 800
801 801 def __init__(self, cache_key, cache_args=''):
802 802 self.cache_key = cache_key
803 803 self.cache_args = cache_args
804 804 self.cache_active = False
805 805
806 806
807 807 class ChangesetComment(Base, BaseModel):
808 808 __tablename__ = 'changeset_comments'
809 809 __table_args__ = (
810 810 Index('cc_revision_idx', 'revision'),
811 811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
812 812 'mysql_charset': 'utf8'},
813 813 )
814 814 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
815 815 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
816 816 revision = Column('revision', String(40), nullable=True)
817 817 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
818 818 line_no = Column('line_no', Unicode(10), nullable=True)
819 819 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
820 820 f_path = Column('f_path', Unicode(1000), nullable=True)
821 821 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
822 822 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
823 823 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
824 824 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
825 825
826 826 author = relationship('User', lazy='joined')
827 827 repo = relationship('Repository')
828 828 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
829 829 pull_request = relationship('PullRequest', lazy='joined')
830 830
831 831 @classmethod
832 832 def get_users(cls, revision=None, pull_request_id=None):
833 833 """
834 834 Returns user associated with this ChangesetComment. ie those
835 835 who actually commented
836 836
837 837 :param cls:
838 838 :param revision:
839 839 """
840 840 q = Session().query(User)\
841 841 .join(ChangesetComment.author)
842 842 if revision:
843 843 q = q.filter(cls.revision == revision)
844 844 elif pull_request_id:
845 845 q = q.filter(cls.pull_request_id == pull_request_id)
846 846 return q.all()
847 847
848 848
849 849 class ChangesetStatus(Base, BaseModel):
850 850 __tablename__ = 'changeset_statuses'
851 851 __table_args__ = (
852 852 Index('cs_revision_idx', 'revision'),
853 853 Index('cs_version_idx', 'version'),
854 854 UniqueConstraint('repo_id', 'revision', 'version'),
855 855 {'extend_existing': True, 'mysql_engine': 'InnoDB',
856 856 'mysql_charset': 'utf8'}
857 857 )
858 858 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
859 859 STATUS_APPROVED = 'approved'
860 860 STATUS_REJECTED = 'rejected'
861 861 STATUS_UNDER_REVIEW = 'under_review'
862 862
863 863 STATUSES = [
864 864 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
865 865 (STATUS_APPROVED, _("Approved")),
866 866 (STATUS_REJECTED, _("Rejected")),
867 867 (STATUS_UNDER_REVIEW, _("Under Review")),
868 868 ]
869 869
870 870 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
871 871 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
872 872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
873 873 revision = Column('revision', String(40), nullable=False)
874 874 status = Column('status', String(128), nullable=False, default=DEFAULT)
875 875 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
876 876 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
877 877 version = Column('version', Integer(), nullable=False, default=0)
878 878 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
879 879
880 880 author = relationship('User', lazy='joined')
881 881 repo = relationship('Repository')
882 882 comment = relationship('ChangesetComment', lazy='joined')
883 883 pull_request = relationship('PullRequest', lazy='joined')
884 884
885 885
886 886
887 887 class PullRequest(Base, BaseModel):
888 888 __tablename__ = 'pull_requests'
889 889 __table_args__ = (
890 890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
891 891 'mysql_charset': 'utf8'},
892 892 )
893 893
894 894 STATUS_NEW = u'new'
895 895 STATUS_OPEN = u'open'
896 896 STATUS_CLOSED = u'closed'
897 897
898 898 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
899 899 title = Column('title', Unicode(256), nullable=True)
900 900 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
901 901 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
902 902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
903 903 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
904 904 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
905 905 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
906 906 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
907 907 org_ref = Column('org_ref', Unicode(256), nullable=False)
908 908 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
909 909 other_ref = Column('other_ref', Unicode(256), nullable=False)
910 910
911 911 author = relationship('User', lazy='joined')
912 912 reviewers = relationship('PullRequestReviewers',
913 913 cascade="all, delete, delete-orphan")
914 914 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
915 915 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
916 916 statuses = relationship('ChangesetStatus')
917 917 comments = relationship('ChangesetComment',
918 918 cascade="all, delete, delete-orphan")
919 919
920 920
921 921 class PullRequestReviewers(Base, BaseModel):
922 922 __tablename__ = 'pull_request_reviewers'
923 923 __table_args__ = (
924 924 {'extend_existing': True, 'mysql_engine': 'InnoDB',
925 925 'mysql_charset': 'utf8'},
926 926 )
927 927
928 928 def __init__(self, user=None, pull_request=None):
929 929 self.user = user
930 930 self.pull_request = pull_request
931 931
932 932 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
933 933 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
934 934 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
935 935
936 936 user = relationship('User')
937 937 pull_request = relationship('PullRequest')
938 938
939 939
940 940 class Notification(Base, BaseModel):
941 941 __tablename__ = 'notifications'
942 942 __table_args__ = (
943 943 Index('notification_type_idx', 'type'),
944 944 {'extend_existing': True, 'mysql_engine': 'InnoDB',
945 945 'mysql_charset': 'utf8'},
946 946 )
947 947
948 948 TYPE_CHANGESET_COMMENT = u'cs_comment'
949 949 TYPE_MESSAGE = u'message'
950 950 TYPE_MENTION = u'mention'
951 951 TYPE_REGISTRATION = u'registration'
952 952 TYPE_PULL_REQUEST = u'pull_request'
953 953 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
954 954
955 955 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
956 956 subject = Column('subject', Unicode(512), nullable=True)
957 957 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
958 958 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
959 959 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
960 960 type_ = Column('type', Unicode(256))
961 961
962 962 created_by_user = relationship('User')
963 963 notifications_to_users = relationship('UserNotification', lazy='joined',
964 964 cascade="all, delete, delete-orphan")
965 965
966 966
967 967 class UserNotification(Base, BaseModel):
968 968 __tablename__ = 'user_to_notification'
969 969 __table_args__ = (
970 970 UniqueConstraint('user_id', 'notification_id'),
971 971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
972 972 'mysql_charset': 'utf8'}
973 973 )
974 974 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
975 975 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
976 976 read = Column('read', Boolean, default=False)
977 977 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
978 978
979 979 user = relationship('User', lazy="joined")
980 980 notification = relationship('Notification', lazy="joined",
981 981 order_by=lambda: Notification.created_on.desc(),)
982 982
983 983
984 984 class DbMigrateVersion(Base, BaseModel):
985 985 __tablename__ = 'db_migrate_version'
986 986 __table_args__ = (
987 987 {'extend_existing': True, 'mysql_engine': 'InnoDB',
988 988 'mysql_charset': 'utf8'},
989 989 )
990 990 repository_id = Column('repository_id', String(250), primary_key=True)
991 991 repository_path = Column('repository_path', Text)
992 992 version = Column('version', Integer)
@@ -1,1001 +1,1001 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 safe_unicode, remove_suffix, remove_prefix
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 for k, val in _json_attr.iteritems():
86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194
195 195 class RhodeCodeUi(Base, BaseModel):
196 196 __tablename__ = 'rhodecode_ui'
197 197 __table_args__ = (
198 198 UniqueConstraint('ui_key'),
199 199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 200 'mysql_charset': 'utf8'}
201 201 )
202 202
203 203 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 204 HOOK_PUSH = 'changegroup.push_logger'
205 205 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 206 HOOK_PULL = 'outgoing.pull_logger'
207 207 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 208
209 209 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 210 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 211 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 212 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 213 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 214
215 215
216 216
217 217 class User(Base, BaseModel):
218 218 __tablename__ = 'users'
219 219 __table_args__ = (
220 220 UniqueConstraint('username'), UniqueConstraint('email'),
221 221 Index('u_username_idx', 'username'),
222 222 Index('u_email_idx', 'email'),
223 223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 224 'mysql_charset': 'utf8'}
225 225 )
226 226 DEFAULT_USER = 'default'
227 227 DEFAULT_PERMISSIONS = [
228 228 'hg.register.manual_activate', 'hg.create.repository',
229 229 'hg.fork.repository', 'repository.read', 'group.read'
230 230 ]
231 231 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 232 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 233 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 234 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 235 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 236 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 237 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 238 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 239 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 240 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 241 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 242 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 243
244 244 user_log = relationship('UserLog')
245 245 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 246
247 247 repositories = relationship('Repository')
248 248 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 249 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 250
251 251 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 252 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 253
254 254 group_member = relationship('UserGroupMember', cascade='all')
255 255
256 256 notifications = relationship('UserNotification', cascade='all')
257 257 # notifications assigned to this user
258 258 user_created_notifications = relationship('Notification', cascade='all')
259 259 # comments created by this user
260 260 user_comments = relationship('ChangesetComment', cascade='all')
261 261 user_emails = relationship('UserEmailMap', cascade='all')
262 262
263 263 @hybrid_property
264 264 def email(self):
265 265 return self._email
266 266
267 267 @email.setter
268 268 def email(self, val):
269 269 self._email = val.lower() if val else None
270 270
271 271 @property
272 272 def firstname(self):
273 273 # alias for future
274 274 return self.name
275 275
276 276 @property
277 277 def username_and_name(self):
278 278 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 279
280 280 @property
281 281 def full_name(self):
282 282 return '%s %s' % (self.firstname, self.lastname)
283 283
284 284 @property
285 285 def full_contact(self):
286 286 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 287
288 288 @property
289 289 def short_contact(self):
290 290 return '%s %s' % (self.firstname, self.lastname)
291 291
292 292 @property
293 293 def is_admin(self):
294 294 return self.admin
295 295
296 296 @classmethod
297 297 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 298 if case_insensitive:
299 299 q = cls.query().filter(cls.username.ilike(username))
300 300 else:
301 301 q = cls.query().filter(cls.username == username)
302 302
303 303 if cache:
304 304 q = q.options(FromCache(
305 305 "sql_cache_short",
306 306 "get_user_%s" % _hash_key(username)
307 307 )
308 308 )
309 309 return q.scalar()
310 310
311 311 @classmethod
312 312 def get_by_auth_token(cls, auth_token, cache=False):
313 313 q = cls.query().filter(cls.api_key == auth_token)
314 314
315 315 if cache:
316 316 q = q.options(FromCache("sql_cache_short",
317 317 "get_auth_token_%s" % auth_token))
318 318 return q.scalar()
319 319
320 320 @classmethod
321 321 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 322 if case_insensitive:
323 323 q = cls.query().filter(cls.email.ilike(email))
324 324 else:
325 325 q = cls.query().filter(cls.email == email)
326 326
327 327 if cache:
328 328 q = q.options(FromCache("sql_cache_short",
329 329 "get_email_key_%s" % email))
330 330
331 331 ret = q.scalar()
332 332 if ret is None:
333 333 q = UserEmailMap.query()
334 334 # try fetching in alternate email map
335 335 if case_insensitive:
336 336 q = q.filter(UserEmailMap.email.ilike(email))
337 337 else:
338 338 q = q.filter(UserEmailMap.email == email)
339 339 q = q.options(joinedload(UserEmailMap.user))
340 340 if cache:
341 341 q = q.options(FromCache("sql_cache_short",
342 342 "get_email_map_key_%s" % email))
343 343 ret = getattr(q.scalar(), 'user', None)
344 344
345 345 return ret
346 346
347 347
348 348 class UserEmailMap(Base, BaseModel):
349 349 __tablename__ = 'user_email_map'
350 350 __table_args__ = (
351 351 Index('uem_email_idx', 'email'),
352 352 UniqueConstraint('email'),
353 353 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 354 'mysql_charset': 'utf8'}
355 355 )
356 356 __mapper_args__ = {}
357 357
358 358 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 360 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 361 user = relationship('User', lazy='joined')
362 362
363 363 @validates('_email')
364 364 def validate_email(self, key, email):
365 365 # check if this email is not main one
366 366 main_email = Session().query(User).filter(User.email == email).scalar()
367 367 if main_email is not None:
368 368 raise AttributeError('email %s is present is user table' % email)
369 369 return email
370 370
371 371 @hybrid_property
372 372 def email(self):
373 373 return self._email
374 374
375 375 @email.setter
376 376 def email(self, val):
377 377 self._email = val.lower() if val else None
378 378
379 379
380 380 class UserIpMap(Base, BaseModel):
381 381 __tablename__ = 'user_ip_map'
382 382 __table_args__ = (
383 383 UniqueConstraint('user_id', 'ip_addr'),
384 384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 385 'mysql_charset': 'utf8'}
386 386 )
387 387 __mapper_args__ = {}
388 388
389 389 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
390 390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
391 391 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
392 392 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
393 393 user = relationship('User', lazy='joined')
394 394
395 395
396 396 class UserLog(Base, BaseModel):
397 397 __tablename__ = 'user_logs'
398 398 __table_args__ = (
399 399 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 400 'mysql_charset': 'utf8'},
401 401 )
402 402 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 404 username = Column("username", String(255), nullable=True, unique=None, default=None)
405 405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 406 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 407 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 408 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 409 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 410
411 411
412 412 user = relationship('User')
413 413 repository = relationship('Repository', cascade='')
414 414
415 415
416 416 class UserGroup(Base, BaseModel):
417 417 __tablename__ = 'users_groups'
418 418 __table_args__ = (
419 419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
420 420 'mysql_charset': 'utf8'},
421 421 )
422 422
423 423 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 424 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
425 425 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
426 426 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
427 427
428 428 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
429 429 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
430 430 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
431 431
432 432 def __unicode__(self):
433 433 return u'<userGroup(%s)>' % (self.users_group_name)
434 434
435 435 @classmethod
436 436 def get_by_group_name(cls, group_name, cache=False,
437 437 case_insensitive=False):
438 438 if case_insensitive:
439 439 q = cls.query().filter(cls.users_group_name.ilike(group_name))
440 440 else:
441 441 q = cls.query().filter(cls.users_group_name == group_name)
442 442 if cache:
443 443 q = q.options(FromCache(
444 444 "sql_cache_short",
445 445 "get_user_%s" % _hash_key(group_name)
446 446 )
447 447 )
448 448 return q.scalar()
449 449
450 450 @classmethod
451 451 def get(cls, users_group_id, cache=False):
452 452 user_group = cls.query()
453 453 if cache:
454 454 user_group = user_group.options(FromCache("sql_cache_short",
455 455 "get_users_group_%s" % users_group_id))
456 456 return user_group.get(users_group_id)
457 457
458 458
459 459 class UserGroupMember(Base, BaseModel):
460 460 __tablename__ = 'users_groups_members'
461 461 __table_args__ = (
462 462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 463 'mysql_charset': 'utf8'},
464 464 )
465 465
466 466 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 467 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
468 468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 469
470 470 user = relationship('User', lazy='joined')
471 471 users_group = relationship('UserGroup')
472 472
473 473 def __init__(self, gr_id='', u_id=''):
474 474 self.users_group_id = gr_id
475 475 self.user_id = u_id
476 476
477 477
478 478 class Repository(Base, BaseModel):
479 479 __tablename__ = 'repositories'
480 480 __table_args__ = (
481 481 UniqueConstraint('repo_name'),
482 482 Index('r_repo_name_idx', 'repo_name'),
483 483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
484 484 'mysql_charset': 'utf8'},
485 485 )
486 486
487 487 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 488 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
489 489 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
490 490 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
491 491 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
492 492 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
493 493 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
494 494 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
495 495 description = Column("description", String(10000), nullable=True, unique=None, default=None)
496 496 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
497 497 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 498 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
499 499 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
500 500 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
501 501 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
502 502
503 503 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
504 504 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
505 505
506 506 user = relationship('User')
507 507 fork = relationship('Repository', remote_side=repo_id)
508 508 group = relationship('RepoGroup')
509 509 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
510 510 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
511 511 stats = relationship('Statistics', cascade='all', uselist=False)
512 512
513 513 followers = relationship('UserFollowing',
514 514 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
515 515 cascade='all')
516 516
517 517 logs = relationship('UserLog')
518 518 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
519 519
520 520 pull_requests_org = relationship('PullRequest',
521 521 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
522 522 cascade="all, delete, delete-orphan")
523 523
524 524 pull_requests_other = relationship('PullRequest',
525 525 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
526 526 cascade="all, delete, delete-orphan")
527 527
528 528 def __unicode__(self):
529 529 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
530 530 safe_unicode(self.repo_name))
531 531
532 532
533 533 @classmethod
534 534 def get_by_repo_name(cls, repo_name):
535 535 q = Session().query(cls).filter(cls.repo_name == repo_name)
536 536 q = q.options(joinedload(Repository.fork))\
537 537 .options(joinedload(Repository.user))\
538 538 .options(joinedload(Repository.group))
539 539 return q.scalar()
540 540
541 541
542 542 class RepoGroup(Base, BaseModel):
543 543 __tablename__ = 'groups'
544 544 __table_args__ = (
545 545 UniqueConstraint('group_name', 'group_parent_id'),
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'},
548 548 )
549 549 __mapper_args__ = {'order_by': 'group_name'}
550 550
551 551 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 552 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
553 553 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
554 554 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
555 555 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
556 556
557 557 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
558 558 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
559 559 parent_group = relationship('RepoGroup', remote_side=group_id)
560 560
561 561 def __init__(self, group_name='', parent_group=None):
562 562 self.group_name = group_name
563 563 self.parent_group = parent_group
564 564
565 565 def __unicode__(self):
566 566 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
567 567 self.group_name)
568 568
569 569 @classmethod
570 570 def url_sep(cls):
571 571 return URL_SEP
572 572
573 573 @classmethod
574 574 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
575 575 if case_insensitive:
576 576 gr = cls.query()\
577 577 .filter(cls.group_name.ilike(group_name))
578 578 else:
579 579 gr = cls.query()\
580 580 .filter(cls.group_name == group_name)
581 581 if cache:
582 582 gr = gr.options(FromCache(
583 583 "sql_cache_short",
584 584 "get_group_%s" % _hash_key(group_name)
585 585 )
586 586 )
587 587 return gr.scalar()
588 588
589 589
590 590 class Permission(Base, BaseModel):
591 591 __tablename__ = 'permissions'
592 592 __table_args__ = (
593 593 Index('p_perm_name_idx', 'permission_name'),
594 594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
595 595 'mysql_charset': 'utf8'},
596 596 )
597 597 PERMS = [
598 598 ('repository.none', _('Repository no access')),
599 599 ('repository.read', _('Repository read access')),
600 600 ('repository.write', _('Repository write access')),
601 601 ('repository.admin', _('Repository admin access')),
602 602
603 603 ('group.none', _('Repositories Group no access')),
604 604 ('group.read', _('Repositories Group read access')),
605 605 ('group.write', _('Repositories Group write access')),
606 606 ('group.admin', _('Repositories Group admin access')),
607 607
608 608 ('hg.admin', _('RhodeCode Administrator')),
609 609 ('hg.create.none', _('Repository creation disabled')),
610 610 ('hg.create.repository', _('Repository creation enabled')),
611 611 ('hg.fork.none', _('Repository forking disabled')),
612 612 ('hg.fork.repository', _('Repository forking enabled')),
613 613 ('hg.register.none', _('Register disabled')),
614 614 ('hg.register.manual_activate', _('Register new user with RhodeCode '
615 615 'with manual activation')),
616 616
617 617 ('hg.register.auto_activate', _('Register new user with RhodeCode '
618 618 'with auto activation')),
619 619 ]
620 620
621 621 # defines which permissions are more important higher the more important
622 622 PERM_WEIGHTS = {
623 623 'repository.none': 0,
624 624 'repository.read': 1,
625 625 'repository.write': 3,
626 626 'repository.admin': 4,
627 627
628 628 'group.none': 0,
629 629 'group.read': 1,
630 630 'group.write': 3,
631 631 'group.admin': 4,
632 632
633 633 'hg.fork.none': 0,
634 634 'hg.fork.repository': 1,
635 635 'hg.create.none': 0,
636 636 'hg.create.repository':1
637 637 }
638 638
639 639 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
640 640 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
641 641 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
642 642
643 643 def __unicode__(self):
644 644 return u"<%s('%s:%s')>" % (
645 645 self.__class__.__name__, self.permission_id, self.permission_name
646 646 )
647 647
648 648 @classmethod
649 649 def get_by_key(cls, key):
650 650 return cls.query().filter(cls.permission_name == key).scalar()
651 651
652 652
653 653 class UserRepoToPerm(Base, BaseModel):
654 654 __tablename__ = 'repo_to_perm'
655 655 __table_args__ = (
656 656 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
657 657 {'extend_existing': True, 'mysql_engine': 'InnoDB',
658 658 'mysql_charset': 'utf8'}
659 659 )
660 660 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
661 661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662 662 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
663 663 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
664 664
665 665 user = relationship('User')
666 666 repository = relationship('Repository')
667 667 permission = relationship('Permission')
668 668
669 669 def __unicode__(self):
670 670 return u'<user:%s => %s >' % (self.user, self.repository)
671 671
672 672
673 673 class UserToPerm(Base, BaseModel):
674 674 __tablename__ = 'user_to_perm'
675 675 __table_args__ = (
676 676 UniqueConstraint('user_id', 'permission_id'),
677 677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
678 678 'mysql_charset': 'utf8'}
679 679 )
680 680 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 681 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
682 682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
683 683
684 684 user = relationship('User')
685 685 permission = relationship('Permission', lazy='joined')
686 686
687 687
688 688 class UserGroupRepoToPerm(Base, BaseModel):
689 689 __tablename__ = 'users_group_repo_to_perm'
690 690 __table_args__ = (
691 691 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
692 692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
693 693 'mysql_charset': 'utf8'}
694 694 )
695 695 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
696 696 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
697 697 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
698 698 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
699 699
700 700 users_group = relationship('UserGroup')
701 701 permission = relationship('Permission')
702 702 repository = relationship('Repository')
703 703
704 704 def __unicode__(self):
705 705 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
706 706
707 707
708 708 class UserGroupToPerm(Base, BaseModel):
709 709 __tablename__ = 'users_group_to_perm'
710 710 __table_args__ = (
711 711 UniqueConstraint('users_group_id', 'permission_id',),
712 712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
713 713 'mysql_charset': 'utf8'}
714 714 )
715 715 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
716 716 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
717 717 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
718 718
719 719 users_group = relationship('UserGroup')
720 720 permission = relationship('Permission')
721 721
722 722
723 723 class UserRepoGroupToPerm(Base, BaseModel):
724 724 __tablename__ = 'user_repo_group_to_perm'
725 725 __table_args__ = (
726 726 UniqueConstraint('user_id', 'group_id', 'permission_id'),
727 727 {'extend_existing': True, 'mysql_engine': 'InnoDB',
728 728 'mysql_charset': 'utf8'}
729 729 )
730 730
731 731 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
732 732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
733 733 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
734 734 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
735 735
736 736 user = relationship('User')
737 737 group = relationship('RepoGroup')
738 738 permission = relationship('Permission')
739 739
740 740
741 741 class UserGroupRepoGroupToPerm(Base, BaseModel):
742 742 __tablename__ = 'users_group_repo_group_to_perm'
743 743 __table_args__ = (
744 744 UniqueConstraint('users_group_id', 'group_id'),
745 745 {'extend_existing': True, 'mysql_engine': 'InnoDB',
746 746 'mysql_charset': 'utf8'}
747 747 )
748 748
749 749 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)
750 750 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
751 751 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
752 752 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
753 753
754 754 users_group = relationship('UserGroup')
755 755 permission = relationship('Permission')
756 756 group = relationship('RepoGroup')
757 757
758 758
759 759 class Statistics(Base, BaseModel):
760 760 __tablename__ = 'statistics'
761 761 __table_args__ = (
762 762 UniqueConstraint('repository_id'),
763 763 {'extend_existing': True, 'mysql_engine': 'InnoDB',
764 764 'mysql_charset': 'utf8'}
765 765 )
766 766 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
767 767 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
768 768 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
769 769 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
770 770 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
771 771 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
772 772
773 773 repository = relationship('Repository', single_parent=True)
774 774
775 775
776 776 class UserFollowing(Base, BaseModel):
777 777 __tablename__ = 'user_followings'
778 778 __table_args__ = (
779 779 UniqueConstraint('user_id', 'follows_repository_id'),
780 780 UniqueConstraint('user_id', 'follows_user_id'),
781 781 {'extend_existing': True, 'mysql_engine': 'InnoDB',
782 782 'mysql_charset': 'utf8'}
783 783 )
784 784
785 785 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
786 786 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
787 787 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
788 788 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
789 789 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
790 790
791 791 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
792 792
793 793 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
794 794 follows_repository = relationship('Repository', order_by='Repository.repo_name')
795 795
796 796
797 797 class CacheInvalidation(Base, BaseModel):
798 798 __tablename__ = 'cache_invalidation'
799 799 __table_args__ = (
800 800 UniqueConstraint('cache_key'),
801 801 Index('key_idx', 'cache_key'),
802 802 {'extend_existing': True, 'mysql_engine': 'InnoDB',
803 803 'mysql_charset': 'utf8'},
804 804 )
805 805 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
806 806 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
807 807 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
808 808 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
809 809
810 810 def __init__(self, cache_key, cache_args=''):
811 811 self.cache_key = cache_key
812 812 self.cache_args = cache_args
813 813 self.cache_active = False
814 814
815 815
816 816 class ChangesetComment(Base, BaseModel):
817 817 __tablename__ = 'changeset_comments'
818 818 __table_args__ = (
819 819 Index('cc_revision_idx', 'revision'),
820 820 {'extend_existing': True, 'mysql_engine': 'InnoDB',
821 821 'mysql_charset': 'utf8'},
822 822 )
823 823 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
824 824 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
825 825 revision = Column('revision', String(40), nullable=True)
826 826 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
827 827 line_no = Column('line_no', Unicode(10), nullable=True)
828 828 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
829 829 f_path = Column('f_path', Unicode(1000), nullable=True)
830 830 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
831 831 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
832 832 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
833 833 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
834 834
835 835 author = relationship('User', lazy='joined')
836 836 repo = relationship('Repository')
837 837 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
838 838 pull_request = relationship('PullRequest', lazy='joined')
839 839
840 840 @classmethod
841 841 def get_users(cls, revision=None, pull_request_id=None):
842 842 """
843 843 Returns user associated with this ChangesetComment. ie those
844 844 who actually commented
845 845
846 846 :param cls:
847 847 :param revision:
848 848 """
849 849 q = Session().query(User)\
850 850 .join(ChangesetComment.author)
851 851 if revision:
852 852 q = q.filter(cls.revision == revision)
853 853 elif pull_request_id:
854 854 q = q.filter(cls.pull_request_id == pull_request_id)
855 855 return q.all()
856 856
857 857
858 858 class ChangesetStatus(Base, BaseModel):
859 859 __tablename__ = 'changeset_statuses'
860 860 __table_args__ = (
861 861 Index('cs_revision_idx', 'revision'),
862 862 Index('cs_version_idx', 'version'),
863 863 UniqueConstraint('repo_id', 'revision', 'version'),
864 864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
865 865 'mysql_charset': 'utf8'}
866 866 )
867 867 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
868 868 STATUS_APPROVED = 'approved'
869 869 STATUS_REJECTED = 'rejected'
870 870 STATUS_UNDER_REVIEW = 'under_review'
871 871
872 872 STATUSES = [
873 873 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
874 874 (STATUS_APPROVED, _("Approved")),
875 875 (STATUS_REJECTED, _("Rejected")),
876 876 (STATUS_UNDER_REVIEW, _("Under Review")),
877 877 ]
878 878
879 879 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
880 880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
881 881 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
882 882 revision = Column('revision', String(40), nullable=False)
883 883 status = Column('status', String(128), nullable=False, default=DEFAULT)
884 884 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
885 885 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
886 886 version = Column('version', Integer(), nullable=False, default=0)
887 887 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
888 888
889 889 author = relationship('User', lazy='joined')
890 890 repo = relationship('Repository')
891 891 comment = relationship('ChangesetComment', lazy='joined')
892 892 pull_request = relationship('PullRequest', lazy='joined')
893 893
894 894
895 895
896 896 class PullRequest(Base, BaseModel):
897 897 __tablename__ = 'pull_requests'
898 898 __table_args__ = (
899 899 {'extend_existing': True, 'mysql_engine': 'InnoDB',
900 900 'mysql_charset': 'utf8'},
901 901 )
902 902
903 903 STATUS_NEW = u'new'
904 904 STATUS_OPEN = u'open'
905 905 STATUS_CLOSED = u'closed'
906 906
907 907 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
908 908 title = Column('title', Unicode(256), nullable=True)
909 909 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
910 910 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
911 911 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
912 912 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
913 913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
914 914 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
915 915 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
916 916 org_ref = Column('org_ref', Unicode(256), nullable=False)
917 917 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
918 918 other_ref = Column('other_ref', Unicode(256), nullable=False)
919 919
920 920 author = relationship('User', lazy='joined')
921 921 reviewers = relationship('PullRequestReviewers',
922 922 cascade="all, delete, delete-orphan")
923 923 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
924 924 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
925 925 statuses = relationship('ChangesetStatus')
926 926 comments = relationship('ChangesetComment',
927 927 cascade="all, delete, delete-orphan")
928 928
929 929
930 930 class PullRequestReviewers(Base, BaseModel):
931 931 __tablename__ = 'pull_request_reviewers'
932 932 __table_args__ = (
933 933 {'extend_existing': True, 'mysql_engine': 'InnoDB',
934 934 'mysql_charset': 'utf8'},
935 935 )
936 936
937 937 def __init__(self, user=None, pull_request=None):
938 938 self.user = user
939 939 self.pull_request = pull_request
940 940
941 941 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
942 942 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
943 943 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
944 944
945 945 user = relationship('User')
946 946 pull_request = relationship('PullRequest')
947 947
948 948
949 949 class Notification(Base, BaseModel):
950 950 __tablename__ = 'notifications'
951 951 __table_args__ = (
952 952 Index('notification_type_idx', 'type'),
953 953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
954 954 'mysql_charset': 'utf8'},
955 955 )
956 956
957 957 TYPE_CHANGESET_COMMENT = u'cs_comment'
958 958 TYPE_MESSAGE = u'message'
959 959 TYPE_MENTION = u'mention'
960 960 TYPE_REGISTRATION = u'registration'
961 961 TYPE_PULL_REQUEST = u'pull_request'
962 962 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
963 963
964 964 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
965 965 subject = Column('subject', Unicode(512), nullable=True)
966 966 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
967 967 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
968 968 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
969 969 type_ = Column('type', Unicode(256))
970 970
971 971 created_by_user = relationship('User')
972 972 notifications_to_users = relationship('UserNotification', lazy='joined',
973 973 cascade="all, delete, delete-orphan")
974 974
975 975
976 976 class UserNotification(Base, BaseModel):
977 977 __tablename__ = 'user_to_notification'
978 978 __table_args__ = (
979 979 UniqueConstraint('user_id', 'notification_id'),
980 980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 981 'mysql_charset': 'utf8'}
982 982 )
983 983 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
984 984 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
985 985 read = Column('read', Boolean, default=False)
986 986 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
987 987
988 988 user = relationship('User', lazy="joined")
989 989 notification = relationship('Notification', lazy="joined",
990 990 order_by=lambda: Notification.created_on.desc(),)
991 991
992 992
993 993 class DbMigrateVersion(Base, BaseModel):
994 994 __tablename__ = 'db_migrate_version'
995 995 __table_args__ = (
996 996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 997 'mysql_charset': 'utf8'},
998 998 )
999 999 repository_id = Column('repository_id', String(250), primary_key=True)
1000 1000 repository_path = Column('repository_path', Text)
1001 1001 version = Column('version', Integer)
@@ -1,1084 +1,1084 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 for k, val in _json_attr.iteritems():
86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194
195 195 class RhodeCodeUi(Base, BaseModel):
196 196 __tablename__ = 'rhodecode_ui'
197 197 __table_args__ = (
198 198 UniqueConstraint('ui_key'),
199 199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 200 'mysql_charset': 'utf8'}
201 201 )
202 202
203 203 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 204 HOOK_PUSH = 'changegroup.push_logger'
205 205 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 206 HOOK_PULL = 'outgoing.pull_logger'
207 207 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 208
209 209 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 210 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 211 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 212 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 213 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 214
215 215
216 216
217 217 class User(Base, BaseModel):
218 218 __tablename__ = 'users'
219 219 __table_args__ = (
220 220 UniqueConstraint('username'), UniqueConstraint('email'),
221 221 Index('u_username_idx', 'username'),
222 222 Index('u_email_idx', 'email'),
223 223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 224 'mysql_charset': 'utf8'}
225 225 )
226 226 DEFAULT_USER = 'default'
227 227 DEFAULT_PERMISSIONS = [
228 228 'hg.register.manual_activate', 'hg.create.repository',
229 229 'hg.fork.repository', 'repository.read', 'group.read'
230 230 ]
231 231 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 232 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 233 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 234 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 235 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 236 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 237 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 238 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 239 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 240 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 241 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 242 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 243
244 244 user_log = relationship('UserLog')
245 245 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 246
247 247 repositories = relationship('Repository')
248 248 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 249 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 250
251 251 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 252 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 253
254 254 group_member = relationship('UserGroupMember', cascade='all')
255 255
256 256 notifications = relationship('UserNotification', cascade='all')
257 257 # notifications assigned to this user
258 258 user_created_notifications = relationship('Notification', cascade='all')
259 259 # comments created by this user
260 260 user_comments = relationship('ChangesetComment', cascade='all')
261 261 user_emails = relationship('UserEmailMap', cascade='all')
262 262
263 263 @hybrid_property
264 264 def email(self):
265 265 return self._email
266 266
267 267 @email.setter
268 268 def email(self, val):
269 269 self._email = val.lower() if val else None
270 270
271 271 @property
272 272 def firstname(self):
273 273 # alias for future
274 274 return self.name
275 275
276 276 @property
277 277 def username_and_name(self):
278 278 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 279
280 280 @property
281 281 def full_name(self):
282 282 return '%s %s' % (self.firstname, self.lastname)
283 283
284 284 @property
285 285 def full_contact(self):
286 286 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 287
288 288 @property
289 289 def short_contact(self):
290 290 return '%s %s' % (self.firstname, self.lastname)
291 291
292 292 @property
293 293 def is_admin(self):
294 294 return self.admin
295 295
296 296 @classmethod
297 297 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 298 if case_insensitive:
299 299 q = cls.query().filter(cls.username.ilike(username))
300 300 else:
301 301 q = cls.query().filter(cls.username == username)
302 302
303 303 if cache:
304 304 q = q.options(FromCache(
305 305 "sql_cache_short",
306 306 "get_user_%s" % _hash_key(username)
307 307 )
308 308 )
309 309 return q.scalar()
310 310
311 311 @classmethod
312 312 def get_by_auth_token(cls, auth_token, cache=False):
313 313 q = cls.query().filter(cls.api_key == auth_token)
314 314
315 315 if cache:
316 316 q = q.options(FromCache("sql_cache_short",
317 317 "get_auth_token_%s" % auth_token))
318 318 return q.scalar()
319 319
320 320 @classmethod
321 321 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 322 if case_insensitive:
323 323 q = cls.query().filter(cls.email.ilike(email))
324 324 else:
325 325 q = cls.query().filter(cls.email == email)
326 326
327 327 if cache:
328 328 q = q.options(FromCache("sql_cache_short",
329 329 "get_email_key_%s" % email))
330 330
331 331 ret = q.scalar()
332 332 if ret is None:
333 333 q = UserEmailMap.query()
334 334 # try fetching in alternate email map
335 335 if case_insensitive:
336 336 q = q.filter(UserEmailMap.email.ilike(email))
337 337 else:
338 338 q = q.filter(UserEmailMap.email == email)
339 339 q = q.options(joinedload(UserEmailMap.user))
340 340 if cache:
341 341 q = q.options(FromCache("sql_cache_short",
342 342 "get_email_map_key_%s" % email))
343 343 ret = getattr(q.scalar(), 'user', None)
344 344
345 345 return ret
346 346
347 347
348 348 class UserEmailMap(Base, BaseModel):
349 349 __tablename__ = 'user_email_map'
350 350 __table_args__ = (
351 351 Index('uem_email_idx', 'email'),
352 352 UniqueConstraint('email'),
353 353 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 354 'mysql_charset': 'utf8'}
355 355 )
356 356 __mapper_args__ = {}
357 357
358 358 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 360 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 361 user = relationship('User', lazy='joined')
362 362
363 363 @validates('_email')
364 364 def validate_email(self, key, email):
365 365 # check if this email is not main one
366 366 main_email = Session().query(User).filter(User.email == email).scalar()
367 367 if main_email is not None:
368 368 raise AttributeError('email %s is present is user table' % email)
369 369 return email
370 370
371 371 @hybrid_property
372 372 def email(self):
373 373 return self._email
374 374
375 375 @email.setter
376 376 def email(self, val):
377 377 self._email = val.lower() if val else None
378 378
379 379
380 380 class UserIpMap(Base, BaseModel):
381 381 __tablename__ = 'user_ip_map'
382 382 __table_args__ = (
383 383 UniqueConstraint('user_id', 'ip_addr'),
384 384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 385 'mysql_charset': 'utf8'}
386 386 )
387 387 __mapper_args__ = {}
388 388
389 389 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
390 390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
391 391 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
392 392 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
393 393 user = relationship('User', lazy='joined')
394 394
395 395
396 396 class UserLog(Base, BaseModel):
397 397 __tablename__ = 'user_logs'
398 398 __table_args__ = (
399 399 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 400 'mysql_charset': 'utf8'},
401 401 )
402 402 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 404 username = Column("username", String(255), nullable=True, unique=None, default=None)
405 405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 406 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 407 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 408 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 409 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 410
411 411
412 412 user = relationship('User')
413 413 repository = relationship('Repository', cascade='')
414 414
415 415
416 416 class UserGroup(Base, BaseModel):
417 417 __tablename__ = 'users_groups'
418 418 __table_args__ = (
419 419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
420 420 'mysql_charset': 'utf8'},
421 421 )
422 422
423 423 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 424 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
425 425 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
426 426 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
427 427
428 428 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
429 429 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
430 430 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
431 431
432 432 def __unicode__(self):
433 433 return u'<userGroup(%s)>' % (self.users_group_name)
434 434
435 435 @classmethod
436 436 def get_by_group_name(cls, group_name, cache=False,
437 437 case_insensitive=False):
438 438 if case_insensitive:
439 439 q = cls.query().filter(cls.users_group_name.ilike(group_name))
440 440 else:
441 441 q = cls.query().filter(cls.users_group_name == group_name)
442 442 if cache:
443 443 q = q.options(FromCache(
444 444 "sql_cache_short",
445 445 "get_user_%s" % _hash_key(group_name)
446 446 )
447 447 )
448 448 return q.scalar()
449 449
450 450 @classmethod
451 451 def get(cls, users_group_id, cache=False):
452 452 user_group = cls.query()
453 453 if cache:
454 454 user_group = user_group.options(FromCache("sql_cache_short",
455 455 "get_users_group_%s" % users_group_id))
456 456 return user_group.get(users_group_id)
457 457
458 458
459 459 class UserGroupMember(Base, BaseModel):
460 460 __tablename__ = 'users_groups_members'
461 461 __table_args__ = (
462 462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 463 'mysql_charset': 'utf8'},
464 464 )
465 465
466 466 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 467 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
468 468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 469
470 470 user = relationship('User', lazy='joined')
471 471 users_group = relationship('UserGroup')
472 472
473 473 def __init__(self, gr_id='', u_id=''):
474 474 self.users_group_id = gr_id
475 475 self.user_id = u_id
476 476
477 477
478 478 class RepositoryField(Base, BaseModel):
479 479 __tablename__ = 'repositories_fields'
480 480 __table_args__ = (
481 481 UniqueConstraint('repository_id', 'field_key'), # no-multi field
482 482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
483 483 'mysql_charset': 'utf8'},
484 484 )
485 485 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
486 486
487 487 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 488 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
489 489 field_key = Column("field_key", String(250))
490 490 field_label = Column("field_label", String(1024), nullable=False)
491 491 field_value = Column("field_value", String(10000), nullable=False)
492 492 field_desc = Column("field_desc", String(1024), nullable=False)
493 493 field_type = Column("field_type", String(256), nullable=False, unique=None)
494 494 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
495 495
496 496 repository = relationship('Repository')
497 497
498 498 @classmethod
499 499 def get_by_key_name(cls, key, repo):
500 500 row = cls.query()\
501 501 .filter(cls.repository == repo)\
502 502 .filter(cls.field_key == key).scalar()
503 503 return row
504 504
505 505
506 506 class Repository(Base, BaseModel):
507 507 __tablename__ = 'repositories'
508 508 __table_args__ = (
509 509 UniqueConstraint('repo_name'),
510 510 Index('r_repo_name_idx', 'repo_name'),
511 511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
512 512 'mysql_charset': 'utf8'},
513 513 )
514 514
515 515 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
516 516 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
517 517 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
518 518 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
519 519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
520 520 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
521 521 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
522 522 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
523 523 description = Column("description", String(10000), nullable=True, unique=None, default=None)
524 524 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
525 525 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
526 526 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
527 527 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
528 528 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
529 529 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
530 530
531 531 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
532 532 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
533 533
534 534 user = relationship('User')
535 535 fork = relationship('Repository', remote_side=repo_id)
536 536 group = relationship('RepoGroup')
537 537 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
538 538 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
539 539 stats = relationship('Statistics', cascade='all', uselist=False)
540 540
541 541 followers = relationship('UserFollowing',
542 542 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
543 543 cascade='all')
544 544 extra_fields = relationship('RepositoryField',
545 545 cascade="all, delete, delete-orphan")
546 546
547 547 logs = relationship('UserLog')
548 548 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
549 549
550 550 pull_requests_org = relationship('PullRequest',
551 551 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
552 552 cascade="all, delete, delete-orphan")
553 553
554 554 pull_requests_other = relationship('PullRequest',
555 555 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
556 556 cascade="all, delete, delete-orphan")
557 557
558 558 def __unicode__(self):
559 559 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
560 560 safe_unicode(self.repo_name))
561 561
562 562 #NOTE for this migration we are required tio have it
563 563 @hybrid_property
564 564 def changeset_cache(self):
565 565 from rhodecode.lib.vcs.backends.base import EmptyCommit
566 566 dummy = EmptyCommit().__json__()
567 567 if not self._changeset_cache:
568 568 return dummy
569 569 try:
570 570 return json.loads(self._changeset_cache)
571 571 except TypeError:
572 572 return dummy
573 573
574 574 @changeset_cache.setter
575 575 def changeset_cache(self, val):
576 576 try:
577 577 self._changeset_cache = json.dumps(val)
578 578 except Exception:
579 579 log.error(traceback.format_exc())
580 580
581 581 @classmethod
582 582 def get_by_repo_name(cls, repo_name):
583 583 q = Session().query(cls).filter(cls.repo_name == repo_name)
584 584 q = q.options(joinedload(Repository.fork))\
585 585 .options(joinedload(Repository.user))\
586 586 .options(joinedload(Repository.group))
587 587 return q.scalar()
588 588
589 589 #NOTE this is required for this migration to work
590 590 def update_commit_cache(self, cs_cache=None):
591 591 """
592 592 Update cache of last changeset for repository, keys should be::
593 593
594 594 short_id
595 595 raw_id
596 596 revision
597 597 message
598 598 date
599 599 author
600 600
601 601 :param cs_cache:
602 602 """
603 603 from rhodecode.lib.vcs.backends.base import BaseChangeset
604 604 if cs_cache is None:
605 605 cs_cache = EmptyCommit()
606 606 # Note: Using always the empty commit here in case we are
607 607 # upgrading towards version 3.0 and above. Reason is that in this
608 608 # case the vcsclient connection is not available and things
609 609 # would explode here.
610 610
611 611 if isinstance(cs_cache, BaseChangeset):
612 612 cs_cache = cs_cache.__json__()
613 613
614 614 if (cs_cache != self.changeset_cache or not self.changeset_cache):
615 615 _default = datetime.datetime.fromtimestamp(0)
616 616 last_change = cs_cache.get('date') or _default
617 617 log.debug('updated repo %s with new commit cache %s', self.repo_name, cs_cache)
618 618 self.updated_on = last_change
619 619 self.changeset_cache = cs_cache
620 620 Session().add(self)
621 621 Session().commit()
622 622 else:
623 623 log.debug('Skipping repo:%s already with latest changes', self.repo_name)
624 624
625 625 class RepoGroup(Base, BaseModel):
626 626 __tablename__ = 'groups'
627 627 __table_args__ = (
628 628 UniqueConstraint('group_name', 'group_parent_id'),
629 629 {'extend_existing': True, 'mysql_engine': 'InnoDB',
630 630 'mysql_charset': 'utf8'},
631 631 )
632 632 __mapper_args__ = {'order_by': 'group_name'}
633 633
634 634 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
635 635 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
636 636 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
637 637 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
638 638 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
639 639
640 640 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
641 641 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
642 642 parent_group = relationship('RepoGroup', remote_side=group_id)
643 643
644 644 def __init__(self, group_name='', parent_group=None):
645 645 self.group_name = group_name
646 646 self.parent_group = parent_group
647 647
648 648 def __unicode__(self):
649 649 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
650 650 self.group_name)
651 651
652 652 @classmethod
653 653 def url_sep(cls):
654 654 return URL_SEP
655 655
656 656 @classmethod
657 657 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
658 658 if case_insensitive:
659 659 gr = cls.query()\
660 660 .filter(cls.group_name.ilike(group_name))
661 661 else:
662 662 gr = cls.query()\
663 663 .filter(cls.group_name == group_name)
664 664 if cache:
665 665 gr = gr.options(FromCache(
666 666 "sql_cache_short",
667 667 "get_group_%s" % _hash_key(group_name)
668 668 )
669 669 )
670 670 return gr.scalar()
671 671
672 672
673 673 class Permission(Base, BaseModel):
674 674 __tablename__ = 'permissions'
675 675 __table_args__ = (
676 676 Index('p_perm_name_idx', 'permission_name'),
677 677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
678 678 'mysql_charset': 'utf8'},
679 679 )
680 680 PERMS = [
681 681 ('repository.none', _('Repository no access')),
682 682 ('repository.read', _('Repository read access')),
683 683 ('repository.write', _('Repository write access')),
684 684 ('repository.admin', _('Repository admin access')),
685 685
686 686 ('group.none', _('Repository group no access')),
687 687 ('group.read', _('Repository group read access')),
688 688 ('group.write', _('Repository group write access')),
689 689 ('group.admin', _('Repository group admin access')),
690 690
691 691 ('hg.admin', _('RhodeCode Administrator')),
692 692 ('hg.create.none', _('Repository creation disabled')),
693 693 ('hg.create.repository', _('Repository creation enabled')),
694 694 ('hg.fork.none', _('Repository forking disabled')),
695 695 ('hg.fork.repository', _('Repository forking enabled')),
696 696 ('hg.register.none', _('Register disabled')),
697 697 ('hg.register.manual_activate', _('Register new user with RhodeCode '
698 698 'with manual activation')),
699 699
700 700 ('hg.register.auto_activate', _('Register new user with RhodeCode '
701 701 'with auto activation')),
702 702 ]
703 703
704 704 # defines which permissions are more important higher the more important
705 705 PERM_WEIGHTS = {
706 706 'repository.none': 0,
707 707 'repository.read': 1,
708 708 'repository.write': 3,
709 709 'repository.admin': 4,
710 710
711 711 'group.none': 0,
712 712 'group.read': 1,
713 713 'group.write': 3,
714 714 'group.admin': 4,
715 715
716 716 'hg.fork.none': 0,
717 717 'hg.fork.repository': 1,
718 718 'hg.create.none': 0,
719 719 'hg.create.repository':1
720 720 }
721 721
722 722 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 723 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
724 724 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
725 725
726 726 def __unicode__(self):
727 727 return u"<%s('%s:%s')>" % (
728 728 self.__class__.__name__, self.permission_id, self.permission_name
729 729 )
730 730
731 731 @classmethod
732 732 def get_by_key(cls, key):
733 733 return cls.query().filter(cls.permission_name == key).scalar()
734 734
735 735
736 736 class UserRepoToPerm(Base, BaseModel):
737 737 __tablename__ = 'repo_to_perm'
738 738 __table_args__ = (
739 739 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
740 740 {'extend_existing': True, 'mysql_engine': 'InnoDB',
741 741 'mysql_charset': 'utf8'}
742 742 )
743 743 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
744 744 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
745 745 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
746 746 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
747 747
748 748 user = relationship('User')
749 749 repository = relationship('Repository')
750 750 permission = relationship('Permission')
751 751
752 752 def __unicode__(self):
753 753 return u'<user:%s => %s >' % (self.user, self.repository)
754 754
755 755
756 756 class UserToPerm(Base, BaseModel):
757 757 __tablename__ = 'user_to_perm'
758 758 __table_args__ = (
759 759 UniqueConstraint('user_id', 'permission_id'),
760 760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 761 'mysql_charset': 'utf8'}
762 762 )
763 763 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
764 764 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
765 765 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
766 766
767 767 user = relationship('User')
768 768 permission = relationship('Permission', lazy='joined')
769 769
770 770
771 771 class UserGroupRepoToPerm(Base, BaseModel):
772 772 __tablename__ = 'users_group_repo_to_perm'
773 773 __table_args__ = (
774 774 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
775 775 {'extend_existing': True, 'mysql_engine': 'InnoDB',
776 776 'mysql_charset': 'utf8'}
777 777 )
778 778 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
779 779 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
780 780 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
781 781 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
782 782
783 783 users_group = relationship('UserGroup')
784 784 permission = relationship('Permission')
785 785 repository = relationship('Repository')
786 786
787 787 def __unicode__(self):
788 788 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
789 789
790 790
791 791 class UserGroupToPerm(Base, BaseModel):
792 792 __tablename__ = 'users_group_to_perm'
793 793 __table_args__ = (
794 794 UniqueConstraint('users_group_id', 'permission_id',),
795 795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
796 796 'mysql_charset': 'utf8'}
797 797 )
798 798 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
799 799 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
800 800 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
801 801
802 802 users_group = relationship('UserGroup')
803 803 permission = relationship('Permission')
804 804
805 805
806 806 class UserRepoGroupToPerm(Base, BaseModel):
807 807 __tablename__ = 'user_repo_group_to_perm'
808 808 __table_args__ = (
809 809 UniqueConstraint('user_id', 'group_id', 'permission_id'),
810 810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
811 811 'mysql_charset': 'utf8'}
812 812 )
813 813
814 814 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
815 815 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
816 816 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
817 817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 818
819 819 user = relationship('User')
820 820 group = relationship('RepoGroup')
821 821 permission = relationship('Permission')
822 822
823 823
824 824 class UserGroupRepoGroupToPerm(Base, BaseModel):
825 825 __tablename__ = 'users_group_repo_group_to_perm'
826 826 __table_args__ = (
827 827 UniqueConstraint('users_group_id', 'group_id'),
828 828 {'extend_existing': True, 'mysql_engine': 'InnoDB',
829 829 'mysql_charset': 'utf8'}
830 830 )
831 831
832 832 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)
833 833 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
834 834 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
835 835 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
836 836
837 837 users_group = relationship('UserGroup')
838 838 permission = relationship('Permission')
839 839 group = relationship('RepoGroup')
840 840
841 841
842 842 class Statistics(Base, BaseModel):
843 843 __tablename__ = 'statistics'
844 844 __table_args__ = (
845 845 UniqueConstraint('repository_id'),
846 846 {'extend_existing': True, 'mysql_engine': 'InnoDB',
847 847 'mysql_charset': 'utf8'}
848 848 )
849 849 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 850 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
851 851 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
852 852 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
853 853 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
854 854 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
855 855
856 856 repository = relationship('Repository', single_parent=True)
857 857
858 858
859 859 class UserFollowing(Base, BaseModel):
860 860 __tablename__ = 'user_followings'
861 861 __table_args__ = (
862 862 UniqueConstraint('user_id', 'follows_repository_id'),
863 863 UniqueConstraint('user_id', 'follows_user_id'),
864 864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
865 865 'mysql_charset': 'utf8'}
866 866 )
867 867
868 868 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
870 870 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
871 871 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
872 872 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
873 873
874 874 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
875 875
876 876 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
877 877 follows_repository = relationship('Repository', order_by='Repository.repo_name')
878 878
879 879
880 880 class CacheInvalidation(Base, BaseModel):
881 881 __tablename__ = 'cache_invalidation'
882 882 __table_args__ = (
883 883 UniqueConstraint('cache_key'),
884 884 Index('key_idx', 'cache_key'),
885 885 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 886 'mysql_charset': 'utf8'},
887 887 )
888 888 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
889 889 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
890 890 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
891 891 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
892 892
893 893 def __init__(self, cache_key, cache_args=''):
894 894 self.cache_key = cache_key
895 895 self.cache_args = cache_args
896 896 self.cache_active = False
897 897
898 898
899 899 class ChangesetComment(Base, BaseModel):
900 900 __tablename__ = 'changeset_comments'
901 901 __table_args__ = (
902 902 Index('cc_revision_idx', 'revision'),
903 903 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 904 'mysql_charset': 'utf8'},
905 905 )
906 906 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
907 907 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
908 908 revision = Column('revision', String(40), nullable=True)
909 909 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
910 910 line_no = Column('line_no', Unicode(10), nullable=True)
911 911 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
912 912 f_path = Column('f_path', Unicode(1000), nullable=True)
913 913 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
914 914 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
915 915 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
916 916 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
917 917
918 918 author = relationship('User', lazy='joined')
919 919 repo = relationship('Repository')
920 920 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
921 921 pull_request = relationship('PullRequest', lazy='joined')
922 922
923 923 @classmethod
924 924 def get_users(cls, revision=None, pull_request_id=None):
925 925 """
926 926 Returns user associated with this ChangesetComment. ie those
927 927 who actually commented
928 928
929 929 :param cls:
930 930 :param revision:
931 931 """
932 932 q = Session().query(User)\
933 933 .join(ChangesetComment.author)
934 934 if revision:
935 935 q = q.filter(cls.revision == revision)
936 936 elif pull_request_id:
937 937 q = q.filter(cls.pull_request_id == pull_request_id)
938 938 return q.all()
939 939
940 940
941 941 class ChangesetStatus(Base, BaseModel):
942 942 __tablename__ = 'changeset_statuses'
943 943 __table_args__ = (
944 944 Index('cs_revision_idx', 'revision'),
945 945 Index('cs_version_idx', 'version'),
946 946 UniqueConstraint('repo_id', 'revision', 'version'),
947 947 {'extend_existing': True, 'mysql_engine': 'InnoDB',
948 948 'mysql_charset': 'utf8'}
949 949 )
950 950 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
951 951 STATUS_APPROVED = 'approved'
952 952 STATUS_REJECTED = 'rejected'
953 953 STATUS_UNDER_REVIEW = 'under_review'
954 954
955 955 STATUSES = [
956 956 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
957 957 (STATUS_APPROVED, _("Approved")),
958 958 (STATUS_REJECTED, _("Rejected")),
959 959 (STATUS_UNDER_REVIEW, _("Under Review")),
960 960 ]
961 961
962 962 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
963 963 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
964 964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
965 965 revision = Column('revision', String(40), nullable=False)
966 966 status = Column('status', String(128), nullable=False, default=DEFAULT)
967 967 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
968 968 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
969 969 version = Column('version', Integer(), nullable=False, default=0)
970 970 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
971 971
972 972 author = relationship('User', lazy='joined')
973 973 repo = relationship('Repository')
974 974 comment = relationship('ChangesetComment', lazy='joined')
975 975 pull_request = relationship('PullRequest', lazy='joined')
976 976
977 977
978 978
979 979 class PullRequest(Base, BaseModel):
980 980 __tablename__ = 'pull_requests'
981 981 __table_args__ = (
982 982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 983 'mysql_charset': 'utf8'},
984 984 )
985 985
986 986 STATUS_NEW = u'new'
987 987 STATUS_OPEN = u'open'
988 988 STATUS_CLOSED = u'closed'
989 989
990 990 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
991 991 title = Column('title', Unicode(256), nullable=True)
992 992 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
993 993 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
994 994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
995 995 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
996 996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
997 997 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
998 998 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
999 999 org_ref = Column('org_ref', Unicode(256), nullable=False)
1000 1000 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1001 1001 other_ref = Column('other_ref', Unicode(256), nullable=False)
1002 1002
1003 1003 author = relationship('User', lazy='joined')
1004 1004 reviewers = relationship('PullRequestReviewers',
1005 1005 cascade="all, delete, delete-orphan")
1006 1006 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1007 1007 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1008 1008 statuses = relationship('ChangesetStatus')
1009 1009 comments = relationship('ChangesetComment',
1010 1010 cascade="all, delete, delete-orphan")
1011 1011
1012 1012
1013 1013 class PullRequestReviewers(Base, BaseModel):
1014 1014 __tablename__ = 'pull_request_reviewers'
1015 1015 __table_args__ = (
1016 1016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 1017 'mysql_charset': 'utf8'},
1018 1018 )
1019 1019
1020 1020 def __init__(self, user=None, pull_request=None):
1021 1021 self.user = user
1022 1022 self.pull_request = pull_request
1023 1023
1024 1024 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1025 1025 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1026 1026 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1027 1027
1028 1028 user = relationship('User')
1029 1029 pull_request = relationship('PullRequest')
1030 1030
1031 1031
1032 1032 class Notification(Base, BaseModel):
1033 1033 __tablename__ = 'notifications'
1034 1034 __table_args__ = (
1035 1035 Index('notification_type_idx', 'type'),
1036 1036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 1037 'mysql_charset': 'utf8'},
1038 1038 )
1039 1039
1040 1040 TYPE_CHANGESET_COMMENT = u'cs_comment'
1041 1041 TYPE_MESSAGE = u'message'
1042 1042 TYPE_MENTION = u'mention'
1043 1043 TYPE_REGISTRATION = u'registration'
1044 1044 TYPE_PULL_REQUEST = u'pull_request'
1045 1045 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1046 1046
1047 1047 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1048 1048 subject = Column('subject', Unicode(512), nullable=True)
1049 1049 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1050 1050 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1051 1051 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1052 1052 type_ = Column('type', Unicode(256))
1053 1053
1054 1054 created_by_user = relationship('User')
1055 1055 notifications_to_users = relationship('UserNotification', lazy='joined',
1056 1056 cascade="all, delete, delete-orphan")
1057 1057
1058 1058
1059 1059 class UserNotification(Base, BaseModel):
1060 1060 __tablename__ = 'user_to_notification'
1061 1061 __table_args__ = (
1062 1062 UniqueConstraint('user_id', 'notification_id'),
1063 1063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1064 1064 'mysql_charset': 'utf8'}
1065 1065 )
1066 1066 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1067 1067 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1068 1068 read = Column('read', Boolean, default=False)
1069 1069 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1070 1070
1071 1071 user = relationship('User', lazy="joined")
1072 1072 notification = relationship('Notification', lazy="joined",
1073 1073 order_by=lambda: Notification.created_on.desc(),)
1074 1074
1075 1075
1076 1076 class DbMigrateVersion(Base, BaseModel):
1077 1077 __tablename__ = 'db_migrate_version'
1078 1078 __table_args__ = (
1079 1079 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1080 1080 'mysql_charset': 'utf8'},
1081 1081 )
1082 1082 repository_id = Column('repository_id', String(250), primary_key=True)
1083 1083 repository_path = Column('repository_path', Text)
1084 1084 version = Column('version', Integer)
@@ -1,1145 +1,1145 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 for k, val in _json_attr.iteritems():
86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194
195 195 class RhodeCodeUi(Base, BaseModel):
196 196 __tablename__ = 'rhodecode_ui'
197 197 __table_args__ = (
198 198 UniqueConstraint('ui_key'),
199 199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 200 'mysql_charset': 'utf8'}
201 201 )
202 202
203 203 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 204 HOOK_PUSH = 'changegroup.push_logger'
205 205 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 206 HOOK_PULL = 'outgoing.pull_logger'
207 207 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 208
209 209 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 210 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 211 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 212 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 213 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 214
215 215
216 216
217 217 class User(Base, BaseModel):
218 218 __tablename__ = 'users'
219 219 __table_args__ = (
220 220 UniqueConstraint('username'), UniqueConstraint('email'),
221 221 Index('u_username_idx', 'username'),
222 222 Index('u_email_idx', 'email'),
223 223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 224 'mysql_charset': 'utf8'}
225 225 )
226 226 DEFAULT_USER = 'default'
227 227
228 228 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
229 229 username = Column("username", String(255), nullable=True, unique=None, default=None)
230 230 password = Column("password", String(255), nullable=True, unique=None, default=None)
231 231 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
232 232 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
233 233 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
234 234 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
235 235 _email = Column("email", String(255), nullable=True, unique=None, default=None)
236 236 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
237 237 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
238 238 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
239 239 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
240 240
241 241 user_log = relationship('UserLog')
242 242 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
243 243
244 244 repositories = relationship('Repository')
245 245 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
246 246 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
247 247
248 248 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
249 249 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
250 250
251 251 group_member = relationship('UserGroupMember', cascade='all')
252 252
253 253 notifications = relationship('UserNotification', cascade='all')
254 254 # notifications assigned to this user
255 255 user_created_notifications = relationship('Notification', cascade='all')
256 256 # comments created by this user
257 257 user_comments = relationship('ChangesetComment', cascade='all')
258 258 user_emails = relationship('UserEmailMap', cascade='all')
259 259
260 260 @hybrid_property
261 261 def email(self):
262 262 return self._email
263 263
264 264 @email.setter
265 265 def email(self, val):
266 266 self._email = val.lower() if val else None
267 267
268 268 @property
269 269 def firstname(self):
270 270 # alias for future
271 271 return self.name
272 272
273 273 @property
274 274 def username_and_name(self):
275 275 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
276 276
277 277 @property
278 278 def full_name(self):
279 279 return '%s %s' % (self.firstname, self.lastname)
280 280
281 281 @property
282 282 def full_contact(self):
283 283 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
284 284
285 285 @property
286 286 def short_contact(self):
287 287 return '%s %s' % (self.firstname, self.lastname)
288 288
289 289 @property
290 290 def is_admin(self):
291 291 return self.admin
292 292
293 293 @classmethod
294 294 def get_by_username(cls, username, case_insensitive=False, cache=False):
295 295 if case_insensitive:
296 296 q = cls.query().filter(cls.username.ilike(username))
297 297 else:
298 298 q = cls.query().filter(cls.username == username)
299 299
300 300 if cache:
301 301 q = q.options(FromCache(
302 302 "sql_cache_short",
303 303 "get_user_%s" % _hash_key(username)
304 304 )
305 305 )
306 306 return q.scalar()
307 307
308 308 @classmethod
309 309 def get_by_auth_token(cls, auth_token, cache=False):
310 310 q = cls.query().filter(cls.api_key == auth_token)
311 311
312 312 if cache:
313 313 q = q.options(FromCache("sql_cache_short",
314 314 "get_auth_token_%s" % auth_token))
315 315 return q.scalar()
316 316
317 317 @classmethod
318 318 def get_by_email(cls, email, case_insensitive=False, cache=False):
319 319 if case_insensitive:
320 320 q = cls.query().filter(cls.email.ilike(email))
321 321 else:
322 322 q = cls.query().filter(cls.email == email)
323 323
324 324 if cache:
325 325 q = q.options(FromCache("sql_cache_short",
326 326 "get_email_key_%s" % email))
327 327
328 328 ret = q.scalar()
329 329 if ret is None:
330 330 q = UserEmailMap.query()
331 331 # try fetching in alternate email map
332 332 if case_insensitive:
333 333 q = q.filter(UserEmailMap.email.ilike(email))
334 334 else:
335 335 q = q.filter(UserEmailMap.email == email)
336 336 q = q.options(joinedload(UserEmailMap.user))
337 337 if cache:
338 338 q = q.options(FromCache("sql_cache_short",
339 339 "get_email_map_key_%s" % email))
340 340 ret = getattr(q.scalar(), 'user', None)
341 341
342 342 return ret
343 343
344 344 @classmethod
345 345 def get_first_admin(cls):
346 346 user = User.query().filter(User.admin == True).first()
347 347 if user is None:
348 348 raise Exception('Missing administrative account!')
349 349 return user
350 350
351 351 @classmethod
352 352 def get_default_user(cls, cache=False):
353 353 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
354 354 if user is None:
355 355 raise Exception('Missing default account!')
356 356 return user
357 357
358 358
359 359
360 360
361 361 class UserEmailMap(Base, BaseModel):
362 362 __tablename__ = 'user_email_map'
363 363 __table_args__ = (
364 364 Index('uem_email_idx', 'email'),
365 365 UniqueConstraint('email'),
366 366 {'extend_existing': True, 'mysql_engine': 'InnoDB',
367 367 'mysql_charset': 'utf8'}
368 368 )
369 369 __mapper_args__ = {}
370 370
371 371 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
372 372 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
373 373 _email = Column("email", String(255), nullable=True, unique=False, default=None)
374 374 user = relationship('User', lazy='joined')
375 375
376 376 @validates('_email')
377 377 def validate_email(self, key, email):
378 378 # check if this email is not main one
379 379 main_email = Session().query(User).filter(User.email == email).scalar()
380 380 if main_email is not None:
381 381 raise AttributeError('email %s is present is user table' % email)
382 382 return email
383 383
384 384 @hybrid_property
385 385 def email(self):
386 386 return self._email
387 387
388 388 @email.setter
389 389 def email(self, val):
390 390 self._email = val.lower() if val else None
391 391
392 392
393 393 class UserIpMap(Base, BaseModel):
394 394 __tablename__ = 'user_ip_map'
395 395 __table_args__ = (
396 396 UniqueConstraint('user_id', 'ip_addr'),
397 397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
398 398 'mysql_charset': 'utf8'}
399 399 )
400 400 __mapper_args__ = {}
401 401
402 402 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 404 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
405 405 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
406 406 user = relationship('User', lazy='joined')
407 407
408 408
409 409 class UserLog(Base, BaseModel):
410 410 __tablename__ = 'user_logs'
411 411 __table_args__ = (
412 412 {'extend_existing': True, 'mysql_engine': 'InnoDB',
413 413 'mysql_charset': 'utf8'},
414 414 )
415 415 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
416 416 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
417 417 username = Column("username", String(255), nullable=True, unique=None, default=None)
418 418 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
419 419 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
420 420 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
421 421 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
422 422 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
423 423
424 424 def __unicode__(self):
425 425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
426 426 self.repository_name,
427 427 self.action)
428 428
429 429 user = relationship('User')
430 430 repository = relationship('Repository', cascade='')
431 431
432 432
433 433 class UserGroup(Base, BaseModel):
434 434 __tablename__ = 'users_groups'
435 435 __table_args__ = (
436 436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
437 437 'mysql_charset': 'utf8'},
438 438 )
439 439
440 440 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
441 441 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
442 442 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
443 443 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
444 444 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
445 445
446 446 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
447 447 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
448 448 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
449 449 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
450 450 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
451 451 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
452 452
453 453 user = relationship('User')
454 454
455 455 def __unicode__(self):
456 456 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
457 457 self.users_group_id,
458 458 self.users_group_name)
459 459
460 460 @classmethod
461 461 def get_by_group_name(cls, group_name, cache=False,
462 462 case_insensitive=False):
463 463 if case_insensitive:
464 464 q = cls.query().filter(cls.users_group_name.ilike(group_name))
465 465 else:
466 466 q = cls.query().filter(cls.users_group_name == group_name)
467 467 if cache:
468 468 q = q.options(FromCache(
469 469 "sql_cache_short",
470 470 "get_user_%s" % _hash_key(group_name)
471 471 )
472 472 )
473 473 return q.scalar()
474 474
475 475 @classmethod
476 476 def get(cls, users_group_id, cache=False):
477 477 user_group = cls.query()
478 478 if cache:
479 479 user_group = user_group.options(FromCache("sql_cache_short",
480 480 "get_users_group_%s" % users_group_id))
481 481 return user_group.get(users_group_id)
482 482
483 483
484 484 class UserGroupMember(Base, BaseModel):
485 485 __tablename__ = 'users_groups_members'
486 486 __table_args__ = (
487 487 {'extend_existing': True, 'mysql_engine': 'InnoDB',
488 488 'mysql_charset': 'utf8'},
489 489 )
490 490
491 491 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
492 492 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
493 493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
494 494
495 495 user = relationship('User', lazy='joined')
496 496 users_group = relationship('UserGroup')
497 497
498 498 def __init__(self, gr_id='', u_id=''):
499 499 self.users_group_id = gr_id
500 500 self.user_id = u_id
501 501
502 502
503 503 class RepositoryField(Base, BaseModel):
504 504 __tablename__ = 'repositories_fields'
505 505 __table_args__ = (
506 506 UniqueConstraint('repository_id', 'field_key'), # no-multi field
507 507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
508 508 'mysql_charset': 'utf8'},
509 509 )
510 510 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
511 511
512 512 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
513 513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
514 514 field_key = Column("field_key", String(250))
515 515 field_label = Column("field_label", String(1024), nullable=False)
516 516 field_value = Column("field_value", String(10000), nullable=False)
517 517 field_desc = Column("field_desc", String(1024), nullable=False)
518 518 field_type = Column("field_type", String(256), nullable=False, unique=None)
519 519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 520
521 521 repository = relationship('Repository')
522 522
523 523 @classmethod
524 524 def get_by_key_name(cls, key, repo):
525 525 row = cls.query()\
526 526 .filter(cls.repository == repo)\
527 527 .filter(cls.field_key == key).scalar()
528 528 return row
529 529
530 530
531 531 class Repository(Base, BaseModel):
532 532 __tablename__ = 'repositories'
533 533 __table_args__ = (
534 534 UniqueConstraint('repo_name'),
535 535 Index('r_repo_name_idx', 'repo_name'),
536 536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
537 537 'mysql_charset': 'utf8'},
538 538 )
539 539
540 540 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
541 541 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
542 542 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
543 543 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
544 544 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
545 545 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
546 546 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
547 547 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
548 548 description = Column("description", String(10000), nullable=True, unique=None, default=None)
549 549 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
550 550 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
551 551 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
552 552 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
553 553 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
554 554 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
555 555
556 556 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
557 557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
558 558
559 559 user = relationship('User')
560 560 fork = relationship('Repository', remote_side=repo_id)
561 561 group = relationship('RepoGroup')
562 562 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
563 563 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
564 564 stats = relationship('Statistics', cascade='all', uselist=False)
565 565
566 566 followers = relationship('UserFollowing',
567 567 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
568 568 cascade='all')
569 569 extra_fields = relationship('RepositoryField',
570 570 cascade="all, delete, delete-orphan")
571 571
572 572 logs = relationship('UserLog')
573 573 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
574 574
575 575 pull_requests_org = relationship('PullRequest',
576 576 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
577 577 cascade="all, delete, delete-orphan")
578 578
579 579 pull_requests_other = relationship('PullRequest',
580 580 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
581 581 cascade="all, delete, delete-orphan")
582 582
583 583 def __unicode__(self):
584 584 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
585 585 safe_unicode(self.repo_name))
586 586
587 587 @classmethod
588 588 def get_by_repo_name(cls, repo_name):
589 589 q = Session().query(cls).filter(cls.repo_name == repo_name)
590 590 q = q.options(joinedload(Repository.fork))\
591 591 .options(joinedload(Repository.user))\
592 592 .options(joinedload(Repository.group))
593 593 return q.scalar()
594 594
595 595
596 596 class RepoGroup(Base, BaseModel):
597 597 __tablename__ = 'groups'
598 598 __table_args__ = (
599 599 UniqueConstraint('group_name', 'group_parent_id'),
600 600 {'extend_existing': True, 'mysql_engine': 'InnoDB',
601 601 'mysql_charset': 'utf8'},
602 602 )
603 603 __mapper_args__ = {'order_by': 'group_name'}
604 604
605 605 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 606 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
607 607 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
608 608 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
609 609 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
610 610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 611
612 612 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
613 613 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
614 614 parent_group = relationship('RepoGroup', remote_side=group_id)
615 615 user = relationship('User')
616 616
617 617 def __init__(self, group_name='', parent_group=None):
618 618 self.group_name = group_name
619 619 self.parent_group = parent_group
620 620
621 621 def __unicode__(self):
622 622 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
623 623 self.group_name)
624 624
625 625 @classmethod
626 626 def url_sep(cls):
627 627 return URL_SEP
628 628
629 629 @classmethod
630 630 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
631 631 if case_insensitive:
632 632 gr = cls.query()\
633 633 .filter(cls.group_name.ilike(group_name))
634 634 else:
635 635 gr = cls.query()\
636 636 .filter(cls.group_name == group_name)
637 637 if cache:
638 638 gr = gr.options(FromCache(
639 639 "sql_cache_short",
640 640 "get_group_%s" % _hash_key(group_name)
641 641 )
642 642 )
643 643 return gr.scalar()
644 644
645 645
646 646 class Permission(Base, BaseModel):
647 647 __tablename__ = 'permissions'
648 648 __table_args__ = (
649 649 Index('p_perm_name_idx', 'permission_name'),
650 650 {'extend_existing': True, 'mysql_engine': 'InnoDB',
651 651 'mysql_charset': 'utf8'},
652 652 )
653 653 PERMS = [
654 654 ('hg.admin', _('RhodeCode Administrator')),
655 655
656 656 ('repository.none', _('Repository no access')),
657 657 ('repository.read', _('Repository read access')),
658 658 ('repository.write', _('Repository write access')),
659 659 ('repository.admin', _('Repository admin access')),
660 660
661 661 ('group.none', _('Repository group no access')),
662 662 ('group.read', _('Repository group read access')),
663 663 ('group.write', _('Repository group write access')),
664 664 ('group.admin', _('Repository group admin access')),
665 665
666 666 ('usergroup.none', _('User group no access')),
667 667 ('usergroup.read', _('User group read access')),
668 668 ('usergroup.write', _('User group write access')),
669 669 ('usergroup.admin', _('User group admin access')),
670 670
671 671 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
672 672 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
673 673
674 674 ('hg.usergroup.create.false', _('User Group creation disabled')),
675 675 ('hg.usergroup.create.true', _('User Group creation enabled')),
676 676
677 677 ('hg.create.none', _('Repository creation disabled')),
678 678 ('hg.create.repository', _('Repository creation enabled')),
679 679
680 680 ('hg.fork.none', _('Repository forking disabled')),
681 681 ('hg.fork.repository', _('Repository forking enabled')),
682 682
683 683 ('hg.register.none', _('Registration disabled')),
684 684 ('hg.register.manual_activate', _('User Registration with manual account activation')),
685 685 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
686 686
687 687 ('hg.extern_activate.manual', _('Manual activation of external account')),
688 688 ('hg.extern_activate.auto', _('Automatic activation of external account')),
689 689
690 690 ]
691 691
692 692 #definition of system default permissions for DEFAULT user
693 693 DEFAULT_USER_PERMISSIONS = [
694 694 'repository.read',
695 695 'group.read',
696 696 'usergroup.read',
697 697 'hg.create.repository',
698 698 'hg.fork.repository',
699 699 'hg.register.manual_activate',
700 700 'hg.extern_activate.auto',
701 701 ]
702 702
703 703 # defines which permissions are more important higher the more important
704 704 # Weight defines which permissions are more important.
705 705 # The higher number the more important.
706 706 PERM_WEIGHTS = {
707 707 'repository.none': 0,
708 708 'repository.read': 1,
709 709 'repository.write': 3,
710 710 'repository.admin': 4,
711 711
712 712 'group.none': 0,
713 713 'group.read': 1,
714 714 'group.write': 3,
715 715 'group.admin': 4,
716 716
717 717 'usergroup.none': 0,
718 718 'usergroup.read': 1,
719 719 'usergroup.write': 3,
720 720 'usergroup.admin': 4,
721 721 'hg.repogroup.create.false': 0,
722 722 'hg.repogroup.create.true': 1,
723 723
724 724 'hg.usergroup.create.false': 0,
725 725 'hg.usergroup.create.true': 1,
726 726
727 727 'hg.fork.none': 0,
728 728 'hg.fork.repository': 1,
729 729 'hg.create.none': 0,
730 730 'hg.create.repository': 1
731 731 }
732 732
733 733 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
734 734 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
735 735 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
736 736
737 737 def __unicode__(self):
738 738 return u"<%s('%s:%s')>" % (
739 739 self.__class__.__name__, self.permission_id, self.permission_name
740 740 )
741 741
742 742 @classmethod
743 743 def get_by_key(cls, key):
744 744 return cls.query().filter(cls.permission_name == key).scalar()
745 745
746 746
747 747 class UserRepoToPerm(Base, BaseModel):
748 748 __tablename__ = 'repo_to_perm'
749 749 __table_args__ = (
750 750 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
751 751 {'extend_existing': True, 'mysql_engine': 'InnoDB',
752 752 'mysql_charset': 'utf8'}
753 753 )
754 754 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
755 755 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
756 756 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
757 757 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
758 758
759 759 user = relationship('User')
760 760 repository = relationship('Repository')
761 761 permission = relationship('Permission')
762 762
763 763 def __unicode__(self):
764 764 return u'<%s => %s >' % (self.user, self.repository)
765 765
766 766
767 767 class UserUserGroupToPerm(Base, BaseModel):
768 768 __tablename__ = 'user_user_group_to_perm'
769 769 __table_args__ = (
770 770 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
771 771 {'extend_existing': True, 'mysql_engine': 'InnoDB',
772 772 'mysql_charset': 'utf8'}
773 773 )
774 774 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
775 775 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
776 776 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
777 777 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
778 778
779 779 user = relationship('User')
780 780 user_group = relationship('UserGroup')
781 781 permission = relationship('Permission')
782 782
783 783 def __unicode__(self):
784 784 return u'<%s => %s >' % (self.user, self.user_group)
785 785
786 786
787 787 class UserToPerm(Base, BaseModel):
788 788 __tablename__ = 'user_to_perm'
789 789 __table_args__ = (
790 790 UniqueConstraint('user_id', 'permission_id'),
791 791 {'extend_existing': True, 'mysql_engine': 'InnoDB',
792 792 'mysql_charset': 'utf8'}
793 793 )
794 794 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
795 795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
796 796 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
797 797
798 798 user = relationship('User')
799 799 permission = relationship('Permission', lazy='joined')
800 800
801 801 def __unicode__(self):
802 802 return u'<%s => %s >' % (self.user, self.permission)
803 803
804 804
805 805 class UserGroupRepoToPerm(Base, BaseModel):
806 806 __tablename__ = 'users_group_repo_to_perm'
807 807 __table_args__ = (
808 808 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
809 809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
810 810 'mysql_charset': 'utf8'}
811 811 )
812 812 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
813 813 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
814 814 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
815 815 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
816 816
817 817 users_group = relationship('UserGroup')
818 818 permission = relationship('Permission')
819 819 repository = relationship('Repository')
820 820
821 821 def __unicode__(self):
822 822 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
823 823
824 824
825 825 class UserGroupUserGroupToPerm(Base, BaseModel):
826 826 __tablename__ = 'user_group_user_group_to_perm'
827 827 __table_args__ = (
828 828 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
829 829 CheckConstraint('target_user_group_id != user_group_id'),
830 830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
831 831 'mysql_charset': 'utf8'}
832 832 )
833 833 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)
834 834 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
835 835 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
836 836 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
837 837
838 838 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
839 839 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
840 840 permission = relationship('Permission')
841 841
842 842 def __unicode__(self):
843 843 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
844 844
845 845
846 846 class UserGroupToPerm(Base, BaseModel):
847 847 __tablename__ = 'users_group_to_perm'
848 848 __table_args__ = (
849 849 UniqueConstraint('users_group_id', 'permission_id',),
850 850 {'extend_existing': True, 'mysql_engine': 'InnoDB',
851 851 'mysql_charset': 'utf8'}
852 852 )
853 853 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
854 854 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
855 855 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
856 856
857 857 users_group = relationship('UserGroup')
858 858 permission = relationship('Permission')
859 859
860 860
861 861 class UserRepoGroupToPerm(Base, BaseModel):
862 862 __tablename__ = 'user_repo_group_to_perm'
863 863 __table_args__ = (
864 864 UniqueConstraint('user_id', 'group_id', 'permission_id'),
865 865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
866 866 'mysql_charset': 'utf8'}
867 867 )
868 868
869 869 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
870 870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
871 871 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
872 872 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 873
874 874 user = relationship('User')
875 875 group = relationship('RepoGroup')
876 876 permission = relationship('Permission')
877 877
878 878
879 879 class UserGroupRepoGroupToPerm(Base, BaseModel):
880 880 __tablename__ = 'users_group_repo_group_to_perm'
881 881 __table_args__ = (
882 882 UniqueConstraint('users_group_id', 'group_id'),
883 883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
884 884 'mysql_charset': 'utf8'}
885 885 )
886 886
887 887 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)
888 888 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
889 889 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
890 890 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
891 891
892 892 users_group = relationship('UserGroup')
893 893 permission = relationship('Permission')
894 894 group = relationship('RepoGroup')
895 895
896 896
897 897 class Statistics(Base, BaseModel):
898 898 __tablename__ = 'statistics'
899 899 __table_args__ = (
900 900 UniqueConstraint('repository_id'),
901 901 {'extend_existing': True, 'mysql_engine': 'InnoDB',
902 902 'mysql_charset': 'utf8'}
903 903 )
904 904 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
905 905 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
906 906 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
907 907 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
908 908 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
909 909 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
910 910
911 911 repository = relationship('Repository', single_parent=True)
912 912
913 913
914 914 class UserFollowing(Base, BaseModel):
915 915 __tablename__ = 'user_followings'
916 916 __table_args__ = (
917 917 UniqueConstraint('user_id', 'follows_repository_id'),
918 918 UniqueConstraint('user_id', 'follows_user_id'),
919 919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
920 920 'mysql_charset': 'utf8'}
921 921 )
922 922
923 923 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 924 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
925 925 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
926 926 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
927 927 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
928 928
929 929 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
930 930
931 931 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
932 932 follows_repository = relationship('Repository', order_by='Repository.repo_name')
933 933
934 934
935 935 class CacheInvalidation(Base, BaseModel):
936 936 __tablename__ = 'cache_invalidation'
937 937 __table_args__ = (
938 938 UniqueConstraint('cache_key'),
939 939 Index('key_idx', 'cache_key'),
940 940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
941 941 'mysql_charset': 'utf8'},
942 942 )
943 943 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
944 944 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
945 945 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
946 946 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
947 947
948 948 def __init__(self, cache_key, cache_args=''):
949 949 self.cache_key = cache_key
950 950 self.cache_args = cache_args
951 951 self.cache_active = False
952 952
953 953
954 954 class ChangesetComment(Base, BaseModel):
955 955 __tablename__ = 'changeset_comments'
956 956 __table_args__ = (
957 957 Index('cc_revision_idx', 'revision'),
958 958 {'extend_existing': True, 'mysql_engine': 'InnoDB',
959 959 'mysql_charset': 'utf8'},
960 960 )
961 961 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
962 962 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
963 963 revision = Column('revision', String(40), nullable=True)
964 964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
965 965 line_no = Column('line_no', Unicode(10), nullable=True)
966 966 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
967 967 f_path = Column('f_path', Unicode(1000), nullable=True)
968 968 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
969 969 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
970 970 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
971 971 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
972 972
973 973 author = relationship('User', lazy='joined')
974 974 repo = relationship('Repository')
975 975 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
976 976 pull_request = relationship('PullRequest', lazy='joined')
977 977
978 978
979 979 class ChangesetStatus(Base, BaseModel):
980 980 __tablename__ = 'changeset_statuses'
981 981 __table_args__ = (
982 982 Index('cs_revision_idx', 'revision'),
983 983 Index('cs_version_idx', 'version'),
984 984 UniqueConstraint('repo_id', 'revision', 'version'),
985 985 {'extend_existing': True, 'mysql_engine': 'InnoDB',
986 986 'mysql_charset': 'utf8'}
987 987 )
988 988 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
989 989 STATUS_APPROVED = 'approved'
990 990 STATUS_REJECTED = 'rejected'
991 991 STATUS_UNDER_REVIEW = 'under_review'
992 992
993 993 STATUSES = [
994 994 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
995 995 (STATUS_APPROVED, _("Approved")),
996 996 (STATUS_REJECTED, _("Rejected")),
997 997 (STATUS_UNDER_REVIEW, _("Under Review")),
998 998 ]
999 999
1000 1000 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1001 1001 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1002 1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1003 1003 revision = Column('revision', String(40), nullable=False)
1004 1004 status = Column('status', String(128), nullable=False, default=DEFAULT)
1005 1005 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1006 1006 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1007 1007 version = Column('version', Integer(), nullable=False, default=0)
1008 1008 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1009 1009
1010 1010 author = relationship('User', lazy='joined')
1011 1011 repo = relationship('Repository')
1012 1012 comment = relationship('ChangesetComment', lazy='joined')
1013 1013 pull_request = relationship('PullRequest', lazy='joined')
1014 1014
1015 1015
1016 1016
1017 1017 class PullRequest(Base, BaseModel):
1018 1018 __tablename__ = 'pull_requests'
1019 1019 __table_args__ = (
1020 1020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 1021 'mysql_charset': 'utf8'},
1022 1022 )
1023 1023
1024 1024 STATUS_NEW = u'new'
1025 1025 STATUS_OPEN = u'open'
1026 1026 STATUS_CLOSED = u'closed'
1027 1027
1028 1028 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1029 1029 title = Column('title', Unicode(256), nullable=True)
1030 1030 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1031 1031 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1032 1032 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1033 1033 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1034 1034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1035 1035 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1036 1036 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1037 1037 org_ref = Column('org_ref', Unicode(256), nullable=False)
1038 1038 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1039 1039 other_ref = Column('other_ref', Unicode(256), nullable=False)
1040 1040
1041 1041 author = relationship('User', lazy='joined')
1042 1042 reviewers = relationship('PullRequestReviewers',
1043 1043 cascade="all, delete, delete-orphan")
1044 1044 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1045 1045 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1046 1046 statuses = relationship('ChangesetStatus')
1047 1047 comments = relationship('ChangesetComment',
1048 1048 cascade="all, delete, delete-orphan")
1049 1049
1050 1050
1051 1051 class PullRequestReviewers(Base, BaseModel):
1052 1052 __tablename__ = 'pull_request_reviewers'
1053 1053 __table_args__ = (
1054 1054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1055 1055 'mysql_charset': 'utf8'},
1056 1056 )
1057 1057
1058 1058 def __init__(self, user=None, pull_request=None):
1059 1059 self.user = user
1060 1060 self.pull_request = pull_request
1061 1061
1062 1062 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1063 1063 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1064 1064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1065 1065
1066 1066 user = relationship('User')
1067 1067 pull_request = relationship('PullRequest')
1068 1068
1069 1069
1070 1070 class Notification(Base, BaseModel):
1071 1071 __tablename__ = 'notifications'
1072 1072 __table_args__ = (
1073 1073 Index('notification_type_idx', 'type'),
1074 1074 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1075 1075 'mysql_charset': 'utf8'},
1076 1076 )
1077 1077
1078 1078 TYPE_CHANGESET_COMMENT = u'cs_comment'
1079 1079 TYPE_MESSAGE = u'message'
1080 1080 TYPE_MENTION = u'mention'
1081 1081 TYPE_REGISTRATION = u'registration'
1082 1082 TYPE_PULL_REQUEST = u'pull_request'
1083 1083 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1084 1084
1085 1085 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1086 1086 subject = Column('subject', Unicode(512), nullable=True)
1087 1087 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1088 1088 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1089 1089 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1090 1090 type_ = Column('type', Unicode(256))
1091 1091
1092 1092 created_by_user = relationship('User')
1093 1093 notifications_to_users = relationship('UserNotification', lazy='joined',
1094 1094 cascade="all, delete, delete-orphan")
1095 1095
1096 1096
1097 1097 class UserNotification(Base, BaseModel):
1098 1098 __tablename__ = 'user_to_notification'
1099 1099 __table_args__ = (
1100 1100 UniqueConstraint('user_id', 'notification_id'),
1101 1101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1102 1102 'mysql_charset': 'utf8'}
1103 1103 )
1104 1104 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1105 1105 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1106 1106 read = Column('read', Boolean, default=False)
1107 1107 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1108 1108
1109 1109 user = relationship('User', lazy="joined")
1110 1110 notification = relationship('Notification', lazy="joined",
1111 1111 order_by=lambda: Notification.created_on.desc(),)
1112 1112
1113 1113
1114 1114 class Gist(Base, BaseModel):
1115 1115 __tablename__ = 'gists'
1116 1116 __table_args__ = (
1117 1117 Index('g_gist_access_id_idx', 'gist_access_id'),
1118 1118 Index('g_created_on_idx', 'created_on'),
1119 1119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1120 1120 'mysql_charset': 'utf8'}
1121 1121 )
1122 1122 GIST_PUBLIC = u'public'
1123 1123 GIST_PRIVATE = u'private'
1124 1124
1125 1125 gist_id = Column('gist_id', Integer(), primary_key=True)
1126 1126 gist_access_id = Column('gist_access_id', Unicode(250))
1127 1127 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1128 1128 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1129 1129 gist_expires = Column('gist_expires', Float(), nullable=False)
1130 1130 gist_type = Column('gist_type', Unicode(128), nullable=False)
1131 1131 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1132 1132 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1133 1133
1134 1134 owner = relationship('User')
1135 1135
1136 1136
1137 1137 class DbMigrateVersion(Base, BaseModel):
1138 1138 __tablename__ = 'db_migrate_version'
1139 1139 __table_args__ = (
1140 1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 1141 'mysql_charset': 'utf8'},
1142 1142 )
1143 1143 repository_id = Column('repository_id', String(250), primary_key=True)
1144 1144 repository_path = Column('repository_path', Text)
1145 1145 version = Column('version', Integer)
@@ -1,1147 +1,1147 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 for k, val in _json_attr.iteritems():
86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 158 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
159 159
160 160 def __init__(self, key='', val='', type='unicode'):
161 161 self.app_settings_name = key
162 162 self.app_settings_value = val
163 163 self.app_settings_type = type
164 164
165 165 @validates('_app_settings_value')
166 166 def validate_settings_value(self, key, val):
167 167 assert type(val) == unicode
168 168 return val
169 169
170 170 @hybrid_property
171 171 def app_settings_value(self):
172 172 v = self._app_settings_value
173 173 if self.app_settings_name in ["ldap_active",
174 174 "default_repo_enable_statistics",
175 175 "default_repo_enable_locking",
176 176 "default_repo_private",
177 177 "default_repo_enable_downloads"]:
178 178 v = str2bool(v)
179 179 return v
180 180
181 181 @app_settings_value.setter
182 182 def app_settings_value(self, val):
183 183 """
184 184 Setter that will always make sure we use unicode in app_settings_value
185 185
186 186 :param val:
187 187 """
188 188 self._app_settings_value = safe_unicode(val)
189 189
190 190 def __unicode__(self):
191 191 return u"<%s('%s:%s')>" % (
192 192 self.__class__.__name__,
193 193 self.app_settings_name, self.app_settings_value
194 194 )
195 195
196 196
197 197 class RhodeCodeUi(Base, BaseModel):
198 198 __tablename__ = 'rhodecode_ui'
199 199 __table_args__ = (
200 200 UniqueConstraint('ui_key'),
201 201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
202 202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
203 203 )
204 204
205 205 HOOK_REPO_SIZE = 'changegroup.repo_size'
206 206 HOOK_PUSH = 'changegroup.push_logger'
207 207 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
208 208 HOOK_PULL = 'outgoing.pull_logger'
209 209 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
210 210
211 211 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
212 212 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
213 213 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
214 214 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
215 215 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
216 216
217 217
218 218
219 219 class User(Base, BaseModel):
220 220 __tablename__ = 'users'
221 221 __table_args__ = (
222 222 UniqueConstraint('username'), UniqueConstraint('email'),
223 223 Index('u_username_idx', 'username'),
224 224 Index('u_email_idx', 'email'),
225 225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
226 226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
227 227 )
228 228 DEFAULT_USER = 'default'
229 229
230 230 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 231 username = Column("username", String(255), nullable=True, unique=None, default=None)
232 232 password = Column("password", String(255), nullable=True, unique=None, default=None)
233 233 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
234 234 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
235 235 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
236 236 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
237 237 _email = Column("email", String(255), nullable=True, unique=None, default=None)
238 238 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
239 239 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
240 240 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
241 241 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
242 242
243 243 user_log = relationship('UserLog')
244 244 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
245 245
246 246 repositories = relationship('Repository')
247 247 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
248 248 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
249 249
250 250 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
251 251 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
252 252
253 253 group_member = relationship('UserGroupMember', cascade='all')
254 254
255 255 notifications = relationship('UserNotification', cascade='all')
256 256 # notifications assigned to this user
257 257 user_created_notifications = relationship('Notification', cascade='all')
258 258 # comments created by this user
259 259 user_comments = relationship('ChangesetComment', cascade='all')
260 260 user_emails = relationship('UserEmailMap', cascade='all')
261 261
262 262 @hybrid_property
263 263 def email(self):
264 264 return self._email
265 265
266 266 @email.setter
267 267 def email(self, val):
268 268 self._email = val.lower() if val else None
269 269
270 270 @property
271 271 def firstname(self):
272 272 # alias for future
273 273 return self.name
274 274
275 275 @property
276 276 def username_and_name(self):
277 277 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
278 278
279 279 @property
280 280 def full_name(self):
281 281 return '%s %s' % (self.firstname, self.lastname)
282 282
283 283 @property
284 284 def full_contact(self):
285 285 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
286 286
287 287 @property
288 288 def short_contact(self):
289 289 return '%s %s' % (self.firstname, self.lastname)
290 290
291 291 @property
292 292 def is_admin(self):
293 293 return self.admin
294 294
295 295 @classmethod
296 296 def get_by_username(cls, username, case_insensitive=False, cache=False):
297 297 if case_insensitive:
298 298 q = cls.query().filter(cls.username.ilike(username))
299 299 else:
300 300 q = cls.query().filter(cls.username == username)
301 301
302 302 if cache:
303 303 q = q.options(FromCache(
304 304 "sql_cache_short",
305 305 "get_user_%s" % _hash_key(username)
306 306 )
307 307 )
308 308 return q.scalar()
309 309
310 310 @classmethod
311 311 def get_by_auth_token(cls, auth_token, cache=False):
312 312 q = cls.query().filter(cls.api_key == auth_token)
313 313
314 314 if cache:
315 315 q = q.options(FromCache("sql_cache_short",
316 316 "get_auth_token_%s" % auth_token))
317 317 return q.scalar()
318 318
319 319 @classmethod
320 320 def get_by_email(cls, email, case_insensitive=False, cache=False):
321 321 if case_insensitive:
322 322 q = cls.query().filter(cls.email.ilike(email))
323 323 else:
324 324 q = cls.query().filter(cls.email == email)
325 325
326 326 if cache:
327 327 q = q.options(FromCache("sql_cache_short",
328 328 "get_email_key_%s" % email))
329 329
330 330 ret = q.scalar()
331 331 if ret is None:
332 332 q = UserEmailMap.query()
333 333 # try fetching in alternate email map
334 334 if case_insensitive:
335 335 q = q.filter(UserEmailMap.email.ilike(email))
336 336 else:
337 337 q = q.filter(UserEmailMap.email == email)
338 338 q = q.options(joinedload(UserEmailMap.user))
339 339 if cache:
340 340 q = q.options(FromCache("sql_cache_short",
341 341 "get_email_map_key_%s" % email))
342 342 ret = getattr(q.scalar(), 'user', None)
343 343
344 344 return ret
345 345
346 346 @classmethod
347 347 def get_first_admin(cls):
348 348 user = User.query().filter(User.admin == True).first()
349 349 if user is None:
350 350 raise Exception('Missing administrative account!')
351 351 return user
352 352
353 353 @classmethod
354 354 def get_default_user(cls, cache=False):
355 355 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
356 356 if user is None:
357 357 raise Exception('Missing default account!')
358 358 return user
359 359
360 360
361 361
362 362
363 363 class UserEmailMap(Base, BaseModel):
364 364 __tablename__ = 'user_email_map'
365 365 __table_args__ = (
366 366 Index('uem_email_idx', 'email'),
367 367 UniqueConstraint('email'),
368 368 {'extend_existing': True, 'mysql_engine': 'InnoDB',
369 369 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
370 370 )
371 371 __mapper_args__ = {}
372 372
373 373 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
374 374 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
375 375 _email = Column("email", String(255), nullable=True, unique=False, default=None)
376 376 user = relationship('User', lazy='joined')
377 377
378 378 @validates('_email')
379 379 def validate_email(self, key, email):
380 380 # check if this email is not main one
381 381 main_email = Session().query(User).filter(User.email == email).scalar()
382 382 if main_email is not None:
383 383 raise AttributeError('email %s is present is user table' % email)
384 384 return email
385 385
386 386 @hybrid_property
387 387 def email(self):
388 388 return self._email
389 389
390 390 @email.setter
391 391 def email(self, val):
392 392 self._email = val.lower() if val else None
393 393
394 394
395 395 class UserIpMap(Base, BaseModel):
396 396 __tablename__ = 'user_ip_map'
397 397 __table_args__ = (
398 398 UniqueConstraint('user_id', 'ip_addr'),
399 399 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 400 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
401 401 )
402 402 __mapper_args__ = {}
403 403
404 404 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 405 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
406 406 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
407 407 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
408 408 user = relationship('User', lazy='joined')
409 409
410 410
411 411 class UserLog(Base, BaseModel):
412 412 __tablename__ = 'user_logs'
413 413 __table_args__ = (
414 414 {'extend_existing': True, 'mysql_engine': 'InnoDB',
415 415 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
416 416 )
417 417 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
418 418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
419 419 username = Column("username", String(255), nullable=True, unique=None, default=None)
420 420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
421 421 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
422 422 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
423 423 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
424 424 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
425 425
426 426 def __unicode__(self):
427 427 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
428 428 self.repository_name,
429 429 self.action)
430 430
431 431 user = relationship('User')
432 432 repository = relationship('Repository', cascade='')
433 433
434 434
435 435 class UserGroup(Base, BaseModel):
436 436 __tablename__ = 'users_groups'
437 437 __table_args__ = (
438 438 {'extend_existing': True, 'mysql_engine': 'InnoDB',
439 439 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
440 440 )
441 441
442 442 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
443 443 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
444 444 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
445 445 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
446 446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
447 447
448 448 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
449 449 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
450 450 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
451 451 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
452 452 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
453 453 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
454 454
455 455 user = relationship('User')
456 456
457 457 def __unicode__(self):
458 458 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
459 459 self.users_group_id,
460 460 self.users_group_name)
461 461
462 462 @classmethod
463 463 def get_by_group_name(cls, group_name, cache=False,
464 464 case_insensitive=False):
465 465 if case_insensitive:
466 466 q = cls.query().filter(cls.users_group_name.ilike(group_name))
467 467 else:
468 468 q = cls.query().filter(cls.users_group_name == group_name)
469 469 if cache:
470 470 q = q.options(FromCache(
471 471 "sql_cache_short",
472 472 "get_user_%s" % _hash_key(group_name)
473 473 )
474 474 )
475 475 return q.scalar()
476 476
477 477 @classmethod
478 478 def get(cls, user_group_id, cache=False):
479 479 user_group = cls.query()
480 480 if cache:
481 481 user_group = user_group.options(FromCache("sql_cache_short",
482 482 "get_users_group_%s" % user_group_id))
483 483 return user_group.get(user_group_id)
484 484
485 485
486 486 class UserGroupMember(Base, BaseModel):
487 487 __tablename__ = 'users_groups_members'
488 488 __table_args__ = (
489 489 {'extend_existing': True, 'mysql_engine': 'InnoDB',
490 490 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
491 491 )
492 492
493 493 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
494 494 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
495 495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
496 496
497 497 user = relationship('User', lazy='joined')
498 498 users_group = relationship('UserGroup')
499 499
500 500 def __init__(self, gr_id='', u_id=''):
501 501 self.users_group_id = gr_id
502 502 self.user_id = u_id
503 503
504 504
505 505 class RepositoryField(Base, BaseModel):
506 506 __tablename__ = 'repositories_fields'
507 507 __table_args__ = (
508 508 UniqueConstraint('repository_id', 'field_key'), # no-multi field
509 509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
510 510 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
511 511 )
512 512 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
513 513
514 514 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
515 515 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
516 516 field_key = Column("field_key", String(250))
517 517 field_label = Column("field_label", String(1024), nullable=False)
518 518 field_value = Column("field_value", String(10000), nullable=False)
519 519 field_desc = Column("field_desc", String(1024), nullable=False)
520 520 field_type = Column("field_type", String(256), nullable=False, unique=None)
521 521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
522 522
523 523 repository = relationship('Repository')
524 524
525 525 @classmethod
526 526 def get_by_key_name(cls, key, repo):
527 527 row = cls.query()\
528 528 .filter(cls.repository == repo)\
529 529 .filter(cls.field_key == key).scalar()
530 530 return row
531 531
532 532
533 533 class Repository(Base, BaseModel):
534 534 __tablename__ = 'repositories'
535 535 __table_args__ = (
536 536 UniqueConstraint('repo_name'),
537 537 Index('r_repo_name_idx', 'repo_name'),
538 538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
540 540 )
541 541
542 542 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
543 543 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
544 544 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
545 545 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
546 546 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
547 547 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
548 548 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
549 549 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
550 550 description = Column("description", String(10000), nullable=True, unique=None, default=None)
551 551 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
552 552 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
553 553 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
554 554 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
555 555 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
556 556 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
557 557
558 558 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
559 559 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
560 560
561 561 user = relationship('User')
562 562 fork = relationship('Repository', remote_side=repo_id)
563 563 group = relationship('RepoGroup')
564 564 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
565 565 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
566 566 stats = relationship('Statistics', cascade='all', uselist=False)
567 567
568 568 followers = relationship('UserFollowing',
569 569 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
570 570 cascade='all')
571 571 extra_fields = relationship('RepositoryField',
572 572 cascade="all, delete, delete-orphan")
573 573
574 574 logs = relationship('UserLog')
575 575 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
576 576
577 577 pull_requests_org = relationship('PullRequest',
578 578 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
579 579 cascade="all, delete, delete-orphan")
580 580
581 581 pull_requests_other = relationship('PullRequest',
582 582 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
583 583 cascade="all, delete, delete-orphan")
584 584
585 585 def __unicode__(self):
586 586 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
587 587 safe_unicode(self.repo_name))
588 588
589 589 @classmethod
590 590 def get_by_repo_name(cls, repo_name):
591 591 q = Session().query(cls).filter(cls.repo_name == repo_name)
592 592 q = q.options(joinedload(Repository.fork))\
593 593 .options(joinedload(Repository.user))\
594 594 .options(joinedload(Repository.group))
595 595 return q.scalar()
596 596
597 597
598 598 class RepoGroup(Base, BaseModel):
599 599 __tablename__ = 'groups'
600 600 __table_args__ = (
601 601 UniqueConstraint('group_name', 'group_parent_id'),
602 602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 603 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
604 604 )
605 605 __mapper_args__ = {'order_by': 'group_name'}
606 606
607 607 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 608 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
609 609 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
610 610 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
611 611 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
612 612 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
613 613
614 614 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
615 615 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
616 616 parent_group = relationship('RepoGroup', remote_side=group_id)
617 617 user = relationship('User')
618 618
619 619 def __init__(self, group_name='', parent_group=None):
620 620 self.group_name = group_name
621 621 self.parent_group = parent_group
622 622
623 623 def __unicode__(self):
624 624 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
625 625 self.group_name)
626 626
627 627 @classmethod
628 628 def url_sep(cls):
629 629 return URL_SEP
630 630
631 631 @classmethod
632 632 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
633 633 if case_insensitive:
634 634 gr = cls.query()\
635 635 .filter(cls.group_name.ilike(group_name))
636 636 else:
637 637 gr = cls.query()\
638 638 .filter(cls.group_name == group_name)
639 639 if cache:
640 640 gr = gr.options(FromCache(
641 641 "sql_cache_short",
642 642 "get_group_%s" % _hash_key(group_name)
643 643 )
644 644 )
645 645 return gr.scalar()
646 646
647 647
648 648 class Permission(Base, BaseModel):
649 649 __tablename__ = 'permissions'
650 650 __table_args__ = (
651 651 Index('p_perm_name_idx', 'permission_name'),
652 652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
653 653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
654 654 )
655 655 PERMS = [
656 656 ('hg.admin', _('RhodeCode Administrator')),
657 657
658 658 ('repository.none', _('Repository no access')),
659 659 ('repository.read', _('Repository read access')),
660 660 ('repository.write', _('Repository write access')),
661 661 ('repository.admin', _('Repository admin access')),
662 662
663 663 ('group.none', _('Repository group no access')),
664 664 ('group.read', _('Repository group read access')),
665 665 ('group.write', _('Repository group write access')),
666 666 ('group.admin', _('Repository group admin access')),
667 667
668 668 ('usergroup.none', _('User group no access')),
669 669 ('usergroup.read', _('User group read access')),
670 670 ('usergroup.write', _('User group write access')),
671 671 ('usergroup.admin', _('User group admin access')),
672 672
673 673 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
674 674 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
675 675
676 676 ('hg.usergroup.create.false', _('User Group creation disabled')),
677 677 ('hg.usergroup.create.true', _('User Group creation enabled')),
678 678
679 679 ('hg.create.none', _('Repository creation disabled')),
680 680 ('hg.create.repository', _('Repository creation enabled')),
681 681
682 682 ('hg.fork.none', _('Repository forking disabled')),
683 683 ('hg.fork.repository', _('Repository forking enabled')),
684 684
685 685 ('hg.register.none', _('Registration disabled')),
686 686 ('hg.register.manual_activate', _('User Registration with manual account activation')),
687 687 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
688 688
689 689 ('hg.extern_activate.manual', _('Manual activation of external account')),
690 690 ('hg.extern_activate.auto', _('Automatic activation of external account')),
691 691
692 692 ]
693 693
694 694 #definition of system default permissions for DEFAULT user
695 695 DEFAULT_USER_PERMISSIONS = [
696 696 'repository.read',
697 697 'group.read',
698 698 'usergroup.read',
699 699 'hg.create.repository',
700 700 'hg.fork.repository',
701 701 'hg.register.manual_activate',
702 702 'hg.extern_activate.auto',
703 703 ]
704 704
705 705 # defines which permissions are more important higher the more important
706 706 # Weight defines which permissions are more important.
707 707 # The higher number the more important.
708 708 PERM_WEIGHTS = {
709 709 'repository.none': 0,
710 710 'repository.read': 1,
711 711 'repository.write': 3,
712 712 'repository.admin': 4,
713 713
714 714 'group.none': 0,
715 715 'group.read': 1,
716 716 'group.write': 3,
717 717 'group.admin': 4,
718 718
719 719 'usergroup.none': 0,
720 720 'usergroup.read': 1,
721 721 'usergroup.write': 3,
722 722 'usergroup.admin': 4,
723 723 'hg.repogroup.create.false': 0,
724 724 'hg.repogroup.create.true': 1,
725 725
726 726 'hg.usergroup.create.false': 0,
727 727 'hg.usergroup.create.true': 1,
728 728
729 729 'hg.fork.none': 0,
730 730 'hg.fork.repository': 1,
731 731 'hg.create.none': 0,
732 732 'hg.create.repository': 1
733 733 }
734 734
735 735 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
736 736 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
737 737 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
738 738
739 739 def __unicode__(self):
740 740 return u"<%s('%s:%s')>" % (
741 741 self.__class__.__name__, self.permission_id, self.permission_name
742 742 )
743 743
744 744 @classmethod
745 745 def get_by_key(cls, key):
746 746 return cls.query().filter(cls.permission_name == key).scalar()
747 747
748 748
749 749 class UserRepoToPerm(Base, BaseModel):
750 750 __tablename__ = 'repo_to_perm'
751 751 __table_args__ = (
752 752 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
753 753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
754 754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
755 755 )
756 756 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
757 757 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
758 758 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
759 759 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
760 760
761 761 user = relationship('User')
762 762 repository = relationship('Repository')
763 763 permission = relationship('Permission')
764 764
765 765 def __unicode__(self):
766 766 return u'<%s => %s >' % (self.user, self.repository)
767 767
768 768
769 769 class UserUserGroupToPerm(Base, BaseModel):
770 770 __tablename__ = 'user_user_group_to_perm'
771 771 __table_args__ = (
772 772 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
773 773 {'extend_existing': True, 'mysql_engine': 'InnoDB',
774 774 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
775 775 )
776 776 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
777 777 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
778 778 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
779 779 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
780 780
781 781 user = relationship('User')
782 782 user_group = relationship('UserGroup')
783 783 permission = relationship('Permission')
784 784
785 785 def __unicode__(self):
786 786 return u'<%s => %s >' % (self.user, self.user_group)
787 787
788 788
789 789 class UserToPerm(Base, BaseModel):
790 790 __tablename__ = 'user_to_perm'
791 791 __table_args__ = (
792 792 UniqueConstraint('user_id', 'permission_id'),
793 793 {'extend_existing': True, 'mysql_engine': 'InnoDB',
794 794 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
795 795 )
796 796 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
797 797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
798 798 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
799 799
800 800 user = relationship('User')
801 801 permission = relationship('Permission', lazy='joined')
802 802
803 803 def __unicode__(self):
804 804 return u'<%s => %s >' % (self.user, self.permission)
805 805
806 806
807 807 class UserGroupRepoToPerm(Base, BaseModel):
808 808 __tablename__ = 'users_group_repo_to_perm'
809 809 __table_args__ = (
810 810 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
811 811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
812 812 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
813 813 )
814 814 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
815 815 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
816 816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
817 817 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
818 818
819 819 users_group = relationship('UserGroup')
820 820 permission = relationship('Permission')
821 821 repository = relationship('Repository')
822 822
823 823 def __unicode__(self):
824 824 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
825 825
826 826
827 827 class UserGroupUserGroupToPerm(Base, BaseModel):
828 828 __tablename__ = 'user_group_user_group_to_perm'
829 829 __table_args__ = (
830 830 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
831 831 CheckConstraint('target_user_group_id != user_group_id'),
832 832 {'extend_existing': True, 'mysql_engine': 'InnoDB',
833 833 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
834 834 )
835 835 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)
836 836 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
837 837 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
838 838 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
839 839
840 840 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
841 841 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
842 842 permission = relationship('Permission')
843 843
844 844 def __unicode__(self):
845 845 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
846 846
847 847
848 848 class UserGroupToPerm(Base, BaseModel):
849 849 __tablename__ = 'users_group_to_perm'
850 850 __table_args__ = (
851 851 UniqueConstraint('users_group_id', 'permission_id',),
852 852 {'extend_existing': True, 'mysql_engine': 'InnoDB',
853 853 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
854 854 )
855 855 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
856 856 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
857 857 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
858 858
859 859 users_group = relationship('UserGroup')
860 860 permission = relationship('Permission')
861 861
862 862
863 863 class UserRepoGroupToPerm(Base, BaseModel):
864 864 __tablename__ = 'user_repo_group_to_perm'
865 865 __table_args__ = (
866 866 UniqueConstraint('user_id', 'group_id', 'permission_id'),
867 867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
868 868 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
869 869 )
870 870
871 871 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 873 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
874 874 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
875 875
876 876 user = relationship('User')
877 877 group = relationship('RepoGroup')
878 878 permission = relationship('Permission')
879 879
880 880
881 881 class UserGroupRepoGroupToPerm(Base, BaseModel):
882 882 __tablename__ = 'users_group_repo_group_to_perm'
883 883 __table_args__ = (
884 884 UniqueConstraint('users_group_id', 'group_id'),
885 885 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 886 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
887 887 )
888 888
889 889 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)
890 890 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
891 891 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
892 892 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
893 893
894 894 users_group = relationship('UserGroup')
895 895 permission = relationship('Permission')
896 896 group = relationship('RepoGroup')
897 897
898 898
899 899 class Statistics(Base, BaseModel):
900 900 __tablename__ = 'statistics'
901 901 __table_args__ = (
902 902 UniqueConstraint('repository_id'),
903 903 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 904 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
905 905 )
906 906 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
907 907 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
908 908 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
909 909 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
910 910 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
911 911 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
912 912
913 913 repository = relationship('Repository', single_parent=True)
914 914
915 915
916 916 class UserFollowing(Base, BaseModel):
917 917 __tablename__ = 'user_followings'
918 918 __table_args__ = (
919 919 UniqueConstraint('user_id', 'follows_repository_id'),
920 920 UniqueConstraint('user_id', 'follows_user_id'),
921 921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
922 922 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
923 923 )
924 924
925 925 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
926 926 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
927 927 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
928 928 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
929 929 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
930 930
931 931 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
932 932
933 933 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
934 934 follows_repository = relationship('Repository', order_by='Repository.repo_name')
935 935
936 936
937 937 class CacheInvalidation(Base, BaseModel):
938 938 __tablename__ = 'cache_invalidation'
939 939 __table_args__ = (
940 940 UniqueConstraint('cache_key'),
941 941 Index('key_idx', 'cache_key'),
942 942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
943 943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
944 944 )
945 945 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
946 946 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
947 947 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
948 948 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
949 949
950 950 def __init__(self, cache_key, cache_args=''):
951 951 self.cache_key = cache_key
952 952 self.cache_args = cache_args
953 953 self.cache_active = False
954 954
955 955
956 956 class ChangesetComment(Base, BaseModel):
957 957 __tablename__ = 'changeset_comments'
958 958 __table_args__ = (
959 959 Index('cc_revision_idx', 'revision'),
960 960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
961 961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
962 962 )
963 963 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
964 964 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
965 965 revision = Column('revision', String(40), nullable=True)
966 966 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
967 967 line_no = Column('line_no', Unicode(10), nullable=True)
968 968 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
969 969 f_path = Column('f_path', Unicode(1000), nullable=True)
970 970 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
971 971 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
972 972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
973 973 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
974 974
975 975 author = relationship('User', lazy='joined')
976 976 repo = relationship('Repository')
977 977 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
978 978 pull_request = relationship('PullRequest', lazy='joined')
979 979
980 980
981 981 class ChangesetStatus(Base, BaseModel):
982 982 __tablename__ = 'changeset_statuses'
983 983 __table_args__ = (
984 984 Index('cs_revision_idx', 'revision'),
985 985 Index('cs_version_idx', 'version'),
986 986 UniqueConstraint('repo_id', 'revision', 'version'),
987 987 {'extend_existing': True, 'mysql_engine': 'InnoDB',
988 988 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
989 989 )
990 990 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
991 991 STATUS_APPROVED = 'approved'
992 992 STATUS_REJECTED = 'rejected'
993 993 STATUS_UNDER_REVIEW = 'under_review'
994 994
995 995 STATUSES = [
996 996 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
997 997 (STATUS_APPROVED, _("Approved")),
998 998 (STATUS_REJECTED, _("Rejected")),
999 999 (STATUS_UNDER_REVIEW, _("Under Review")),
1000 1000 ]
1001 1001
1002 1002 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1003 1003 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1004 1004 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1005 1005 revision = Column('revision', String(40), nullable=False)
1006 1006 status = Column('status', String(128), nullable=False, default=DEFAULT)
1007 1007 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1008 1008 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1009 1009 version = Column('version', Integer(), nullable=False, default=0)
1010 1010 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1011 1011
1012 1012 author = relationship('User', lazy='joined')
1013 1013 repo = relationship('Repository')
1014 1014 comment = relationship('ChangesetComment', lazy='joined')
1015 1015 pull_request = relationship('PullRequest', lazy='joined')
1016 1016
1017 1017
1018 1018
1019 1019 class PullRequest(Base, BaseModel):
1020 1020 __tablename__ = 'pull_requests'
1021 1021 __table_args__ = (
1022 1022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1023 1023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1024 1024 )
1025 1025
1026 1026 STATUS_NEW = u'new'
1027 1027 STATUS_OPEN = u'open'
1028 1028 STATUS_CLOSED = u'closed'
1029 1029
1030 1030 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1031 1031 title = Column('title', Unicode(256), nullable=True)
1032 1032 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1033 1033 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1034 1034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 1035 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1036 1036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1037 1037 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1038 1038 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1039 1039 org_ref = Column('org_ref', Unicode(256), nullable=False)
1040 1040 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1041 1041 other_ref = Column('other_ref', Unicode(256), nullable=False)
1042 1042
1043 1043 author = relationship('User', lazy='joined')
1044 1044 reviewers = relationship('PullRequestReviewers',
1045 1045 cascade="all, delete, delete-orphan")
1046 1046 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1047 1047 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1048 1048 statuses = relationship('ChangesetStatus')
1049 1049 comments = relationship('ChangesetComment',
1050 1050 cascade="all, delete, delete-orphan")
1051 1051
1052 1052
1053 1053 class PullRequestReviewers(Base, BaseModel):
1054 1054 __tablename__ = 'pull_request_reviewers'
1055 1055 __table_args__ = (
1056 1056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1057 1057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1058 1058 )
1059 1059
1060 1060 def __init__(self, user=None, pull_request=None):
1061 1061 self.user = user
1062 1062 self.pull_request = pull_request
1063 1063
1064 1064 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1065 1065 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1066 1066 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1067 1067
1068 1068 user = relationship('User')
1069 1069 pull_request = relationship('PullRequest')
1070 1070
1071 1071
1072 1072 class Notification(Base, BaseModel):
1073 1073 __tablename__ = 'notifications'
1074 1074 __table_args__ = (
1075 1075 Index('notification_type_idx', 'type'),
1076 1076 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1077 1077 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1078 1078 )
1079 1079
1080 1080 TYPE_CHANGESET_COMMENT = u'cs_comment'
1081 1081 TYPE_MESSAGE = u'message'
1082 1082 TYPE_MENTION = u'mention'
1083 1083 TYPE_REGISTRATION = u'registration'
1084 1084 TYPE_PULL_REQUEST = u'pull_request'
1085 1085 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1086 1086
1087 1087 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1088 1088 subject = Column('subject', Unicode(512), nullable=True)
1089 1089 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1090 1090 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1091 1091 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1092 1092 type_ = Column('type', Unicode(256))
1093 1093
1094 1094 created_by_user = relationship('User')
1095 1095 notifications_to_users = relationship('UserNotification', lazy='joined',
1096 1096 cascade="all, delete, delete-orphan")
1097 1097
1098 1098
1099 1099 class UserNotification(Base, BaseModel):
1100 1100 __tablename__ = 'user_to_notification'
1101 1101 __table_args__ = (
1102 1102 UniqueConstraint('user_id', 'notification_id'),
1103 1103 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1104 1104 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1105 1105 )
1106 1106 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1107 1107 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1108 1108 read = Column('read', Boolean, default=False)
1109 1109 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1110 1110
1111 1111 user = relationship('User', lazy="joined")
1112 1112 notification = relationship('Notification', lazy="joined",
1113 1113 order_by=lambda: Notification.created_on.desc(),)
1114 1114
1115 1115
1116 1116 class Gist(Base, BaseModel):
1117 1117 __tablename__ = 'gists'
1118 1118 __table_args__ = (
1119 1119 Index('g_gist_access_id_idx', 'gist_access_id'),
1120 1120 Index('g_created_on_idx', 'created_on'),
1121 1121 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1122 1122 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1123 1123 )
1124 1124 GIST_PUBLIC = u'public'
1125 1125 GIST_PRIVATE = u'private'
1126 1126
1127 1127 gist_id = Column('gist_id', Integer(), primary_key=True)
1128 1128 gist_access_id = Column('gist_access_id', Unicode(250))
1129 1129 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1130 1130 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1131 1131 gist_expires = Column('gist_expires', Float(53), nullable=False)
1132 1132 gist_type = Column('gist_type', Unicode(128), nullable=False)
1133 1133 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1134 1134 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1135 1135
1136 1136 owner = relationship('User')
1137 1137
1138 1138
1139 1139 class DbMigrateVersion(Base, BaseModel):
1140 1140 __tablename__ = 'db_migrate_version'
1141 1141 __table_args__ = (
1142 1142 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 1143 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 1144 )
1145 1145 repository_id = Column('repository_id', String(250), primary_key=True)
1146 1146 repository_path = Column('repository_path', Text)
1147 1147 version = Column('version', Integer)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now